feat: add app-to-app authentication
This commit is contained in:
parent
4f0b4fecaf
commit
35b4ce412c
@ -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[]
|
||||
AccountPushToken AccountPushToken[]
|
||||
TerminalAuthRequest TerminalAuthRequest[]
|
||||
AccountAuthRequest AccountAuthRequest[]
|
||||
UsageReport UsageReport[]
|
||||
}
|
||||
|
||||
@ -42,6 +43,16 @@ model TerminalAuthRequest {
|
||||
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 {
|
||||
id String @id @default(cuid())
|
||||
accountId String
|
||||
|
@ -240,6 +240,80 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
|
||||
return reply.send({ success: true });
|
||||
});
|
||||
|
||||
// Account auth request
|
||||
typed.post('/v1/auth/account/request', {
|
||||
schema: {
|
||||
body: z.object({
|
||||
publicKey: z.string(),
|
||||
}),
|
||||
response: {
|
||||
200: z.union([z.object({
|
||||
state: z.literal('requested'),
|
||||
}), z.object({
|
||||
state: z.literal('authorized'),
|
||||
token: z.string(),
|
||||
response: z.string()
|
||||
})]),
|
||||
401: z.object({
|
||||
error: z.literal('Invalid public key')
|
||||
})
|
||||
}
|
||||
}
|
||||
}, 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 answer = await db.accountAuthRequest.upsert({
|
||||
where: { publicKey: privacyKit.encodeHex(publicKey) },
|
||||
update: {},
|
||||
create: { publicKey: privacyKit.encodeHex(publicKey) }
|
||||
});
|
||||
|
||||
if (answer.response && answer.responseAccountId) {
|
||||
const token = await tokenGenerator.new({ user: answer.responseAccountId! });
|
||||
return reply.send({
|
||||
state: 'authorized',
|
||||
token: token,
|
||||
response: answer.response
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
typed.get('/v1/sessions', {
|
||||
preHandler: app.authenticate,
|
||||
|
Loading…
Reference in New Issue
Block a user