feat: add app-to-app authentication
This commit is contained in:
parent
62a2280268
commit
6f1aefc056
@ -0,0 +1,17 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "AccountAuthRequest" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"publicKey" TEXT NOT NULL,
|
||||||
|
"response" TEXT,
|
||||||
|
"responseAccountId" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "AccountAuthRequest_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "AccountAuthRequest_publicKey_key" ON "AccountAuthRequest"("publicKey");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "AccountAuthRequest" ADD CONSTRAINT "AccountAuthRequest_responseAccountId_fkey" FOREIGN KEY ("responseAccountId") REFERENCES "Account"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
@ -29,6 +29,7 @@ model Account {
|
|||||||
Session Session[]
|
Session Session[]
|
||||||
AccountPushToken AccountPushToken[]
|
AccountPushToken AccountPushToken[]
|
||||||
TerminalAuthRequest TerminalAuthRequest[]
|
TerminalAuthRequest TerminalAuthRequest[]
|
||||||
|
AccountAuthRequest AccountAuthRequest[]
|
||||||
UsageReport UsageReport[]
|
UsageReport UsageReport[]
|
||||||
Machine Machine[]
|
Machine Machine[]
|
||||||
}
|
}
|
||||||
@ -43,6 +44,16 @@ model TerminalAuthRequest {
|
|||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model AccountAuthRequest {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
publicKey String @unique
|
||||||
|
response String?
|
||||||
|
responseAccountId String?
|
||||||
|
responseAccount Account? @relation(fields: [responseAccountId], references: [id])
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
model AccountPushToken {
|
model AccountPushToken {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
accountId String
|
accountId String
|
||||||
|
@ -292,63 +292,78 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
|
|||||||
return reply.send({ success: true });
|
return reply.send({ success: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
// OpenAI Realtime ephemeral token generation
|
// Account auth request
|
||||||
typed.post('/v1/openai/realtime-token', {
|
typed.post('/v1/auth/account/request', {
|
||||||
preHandler: app.authenticate,
|
|
||||||
schema: {
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
publicKey: z.string(),
|
||||||
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.union([z.object({
|
||||||
token: z.string()
|
state: z.literal('requested'),
|
||||||
}),
|
}), z.object({
|
||||||
500: z.object({
|
state: z.literal('authorized'),
|
||||||
error: z.string()
|
token: z.string(),
|
||||||
|
response: z.string()
|
||||||
|
})]),
|
||||||
|
401: z.object({
|
||||||
|
error: z.literal('Invalid public key')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, async (request, reply) => {
|
}, async (request, reply) => {
|
||||||
try {
|
const publicKey = privacyKit.decodeBase64(request.body.publicKey);
|
||||||
// Check if OpenAI API key is configured on server
|
const isValid = tweetnacl.box.publicKeyLength === publicKey.length;
|
||||||
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
|
if (!isValid) {
|
||||||
if (!OPENAI_API_KEY) {
|
return reply.code(401).send({ error: 'Invalid public key' });
|
||||||
return reply.code(500).send({
|
}
|
||||||
error: 'OpenAI API key not configured on server'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate ephemeral token from OpenAI
|
const answer = await db.accountAuthRequest.upsert({
|
||||||
const response = await fetch('https://api.openai.com/v1/realtime/sessions', {
|
where: { publicKey: privacyKit.encodeHex(publicKey) },
|
||||||
method: 'POST',
|
update: {},
|
||||||
headers: {
|
create: { publicKey: privacyKit.encodeHex(publicKey) }
|
||||||
'Authorization': `Bearer ${OPENAI_API_KEY}`,
|
});
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
model: 'gpt-4o-realtime-preview-2024-12-17',
|
|
||||||
voice: 'verse',
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`OpenAI API error: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json() as {
|
|
||||||
client_secret: {
|
|
||||||
value: string;
|
|
||||||
expires_at: number;
|
|
||||||
};
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
if (answer.response && answer.responseAccountId) {
|
||||||
|
const token = await tokenGenerator.new({ user: answer.responseAccountId! });
|
||||||
return reply.send({
|
return reply.send({
|
||||||
token: data.client_secret.value
|
state: 'authorized',
|
||||||
});
|
token: token,
|
||||||
} catch (error) {
|
response: answer.response
|
||||||
log({ module: 'openai', level: 'error' }, 'Failed to generate ephemeral token', error);
|
|
||||||
return reply.code(500).send({
|
|
||||||
error: 'Failed to generate ephemeral token'
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return reply.send({ state: 'requested' });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Approve account auth request
|
||||||
|
typed.post('/v1/auth/account/response', {
|
||||||
|
preHandler: app.authenticate,
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
response: z.string(),
|
||||||
|
publicKey: z.string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, async (request, reply) => {
|
||||||
|
const publicKey = privacyKit.decodeBase64(request.body.publicKey);
|
||||||
|
const isValid = tweetnacl.box.publicKeyLength === publicKey.length;
|
||||||
|
if (!isValid) {
|
||||||
|
return reply.code(401).send({ error: 'Invalid public key' });
|
||||||
|
}
|
||||||
|
const authRequest = await db.accountAuthRequest.findUnique({
|
||||||
|
where: { publicKey: privacyKit.encodeHex(publicKey) }
|
||||||
|
});
|
||||||
|
if (!authRequest) {
|
||||||
|
return reply.code(404).send({ error: 'Request not found' });
|
||||||
|
}
|
||||||
|
if (!authRequest.response) {
|
||||||
|
await db.accountAuthRequest.update({
|
||||||
|
where: { id: authRequest.id },
|
||||||
|
data: { response: request.body.response, responseAccountId: request.user.id }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return reply.send({ success: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sessions API
|
// Sessions API
|
||||||
|
Loading…
Reference in New Issue
Block a user