2024-09-30 13:42:12 +02:00
|
|
|
import { jsonOrForm } from "@/api";
|
|
|
|
|
import { createRoute, z } from "@hono/zod-openapi";
|
2024-11-03 17:45:21 +01:00
|
|
|
import { Application, Token } from "@versia/kit/db";
|
|
|
|
|
import { and, eq } from "@versia/kit/drizzle";
|
2024-09-30 14:34:43 +02:00
|
|
|
import { Tokens } from "@versia/kit/tables";
|
2024-10-04 15:22:48 +02:00
|
|
|
import type { PluginType } from "../../index.ts";
|
2024-09-30 13:42:12 +02:00
|
|
|
|
2024-12-30 20:26:56 +01:00
|
|
|
const schemas = {
|
2024-09-30 13:42:12 +02:00
|
|
|
json: z.object({
|
|
|
|
|
code: z.string().optional(),
|
|
|
|
|
code_verifier: z.string().optional(),
|
|
|
|
|
grant_type: z
|
|
|
|
|
.enum([
|
|
|
|
|
"authorization_code",
|
|
|
|
|
"refresh_token",
|
|
|
|
|
"client_credentials",
|
|
|
|
|
"password",
|
|
|
|
|
"urn:ietf:params:oauth:grant-type:device_code",
|
|
|
|
|
"urn:ietf:params:oauth:grant-type:token-exchange",
|
|
|
|
|
"urn:ietf:params:oauth:grant-type:saml2-bearer",
|
|
|
|
|
"urn:openid:params:grant-type:ciba",
|
|
|
|
|
])
|
|
|
|
|
.default("authorization_code"),
|
|
|
|
|
client_id: z.string().optional(),
|
|
|
|
|
client_secret: z.string().optional(),
|
|
|
|
|
username: z.string().trim().optional(),
|
|
|
|
|
password: z.string().trim().optional(),
|
|
|
|
|
redirect_uri: z.string().url().optional(),
|
|
|
|
|
refresh_token: z.string().optional(),
|
|
|
|
|
scope: z.string().optional(),
|
|
|
|
|
assertion: z.string().optional(),
|
|
|
|
|
audience: z.string().optional(),
|
|
|
|
|
subject_token_type: z.string().optional(),
|
|
|
|
|
subject_token: z.string().optional(),
|
|
|
|
|
actor_token_type: z.string().optional(),
|
|
|
|
|
actor_token: z.string().optional(),
|
|
|
|
|
auth_req_id: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
};
|
|
|
|
|
|
2024-11-02 00:43:33 +01:00
|
|
|
export default (plugin: PluginType): void => {
|
2024-09-30 13:42:12 +02:00
|
|
|
plugin.registerRoute("/oauth/token", (app) => {
|
|
|
|
|
app.openapi(
|
|
|
|
|
createRoute({
|
|
|
|
|
method: "post",
|
|
|
|
|
path: "/oauth/token",
|
|
|
|
|
summary: "Get token",
|
2025-03-28 22:12:07 +01:00
|
|
|
tags: ["OpenID"],
|
2024-10-11 17:16:03 +02:00
|
|
|
middleware: [jsonOrForm(), plugin.middleware],
|
2024-09-30 13:42:12 +02:00
|
|
|
request: {
|
|
|
|
|
body: {
|
|
|
|
|
content: {
|
|
|
|
|
"application/json": {
|
|
|
|
|
schema: schemas.json,
|
|
|
|
|
},
|
|
|
|
|
"application/x-www-form-urlencoded": {
|
|
|
|
|
schema: schemas.json,
|
|
|
|
|
},
|
|
|
|
|
"multipart/form-data": {
|
|
|
|
|
schema: schemas.json,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
responses: {
|
|
|
|
|
200: {
|
|
|
|
|
description: "Token",
|
|
|
|
|
content: {
|
|
|
|
|
"application/json": {
|
|
|
|
|
schema: z.object({
|
|
|
|
|
access_token: z.string(),
|
|
|
|
|
token_type: z.string(),
|
|
|
|
|
expires_in: z
|
|
|
|
|
.number()
|
|
|
|
|
.optional()
|
|
|
|
|
.nullable(),
|
|
|
|
|
id_token: z.string().optional().nullable(),
|
|
|
|
|
refresh_token: z
|
|
|
|
|
.string()
|
|
|
|
|
.optional()
|
|
|
|
|
.nullable(),
|
|
|
|
|
scope: z.string().optional(),
|
|
|
|
|
created_at: z.number(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
401: {
|
|
|
|
|
description: "Authorization error",
|
|
|
|
|
content: {
|
|
|
|
|
"application/json": {
|
|
|
|
|
schema: z.object({
|
|
|
|
|
error: z.string(),
|
|
|
|
|
error_description: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
async (context) => {
|
|
|
|
|
const {
|
|
|
|
|
grant_type,
|
|
|
|
|
code,
|
|
|
|
|
redirect_uri,
|
|
|
|
|
client_id,
|
|
|
|
|
client_secret,
|
|
|
|
|
} = context.req.valid("json");
|
|
|
|
|
|
|
|
|
|
switch (grant_type) {
|
|
|
|
|
case "authorization_code": {
|
|
|
|
|
if (!code) {
|
|
|
|
|
return context.json(
|
|
|
|
|
{
|
|
|
|
|
error: "invalid_request",
|
|
|
|
|
error_description: "Code is required",
|
|
|
|
|
},
|
|
|
|
|
401,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!redirect_uri) {
|
|
|
|
|
return context.json(
|
|
|
|
|
{
|
|
|
|
|
error: "invalid_request",
|
|
|
|
|
error_description:
|
|
|
|
|
"Redirect URI is required",
|
|
|
|
|
},
|
|
|
|
|
401,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!client_id) {
|
|
|
|
|
return context.json(
|
|
|
|
|
{
|
|
|
|
|
error: "invalid_request",
|
|
|
|
|
error_description: "Client ID is required",
|
|
|
|
|
},
|
|
|
|
|
401,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify the client_secret
|
2024-10-23 17:56:47 +02:00
|
|
|
const client =
|
|
|
|
|
await Application.fromClientId(client_id);
|
2024-09-30 13:42:12 +02:00
|
|
|
|
2024-10-23 17:56:47 +02:00
|
|
|
if (!client || client.data.secret !== client_secret) {
|
2024-09-30 13:42:12 +02:00
|
|
|
return context.json(
|
|
|
|
|
{
|
|
|
|
|
error: "invalid_client",
|
|
|
|
|
error_description:
|
|
|
|
|
"Invalid client credentials",
|
|
|
|
|
},
|
|
|
|
|
401,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-03 17:45:21 +01:00
|
|
|
const token = await Token.fromSql(
|
|
|
|
|
and(
|
|
|
|
|
eq(Tokens.code, code),
|
|
|
|
|
eq(Tokens.redirectUri, decodeURI(redirect_uri)),
|
|
|
|
|
eq(Tokens.clientId, client_id),
|
|
|
|
|
),
|
|
|
|
|
);
|
2024-09-30 13:42:12 +02:00
|
|
|
|
|
|
|
|
if (!token) {
|
|
|
|
|
return context.json(
|
|
|
|
|
{
|
|
|
|
|
error: "invalid_grant",
|
|
|
|
|
error_description: "Code not found",
|
|
|
|
|
},
|
|
|
|
|
401,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Invalidate the code
|
2024-11-03 17:45:21 +01:00
|
|
|
await token.update({ code: null });
|
2024-09-30 13:42:12 +02:00
|
|
|
|
|
|
|
|
return context.json(
|
|
|
|
|
{
|
2024-11-03 17:45:21 +01:00
|
|
|
...token.toApi(),
|
|
|
|
|
expires_in: token.data.expiresAt
|
2024-09-30 13:42:12 +02:00
|
|
|
? Math.floor(
|
2024-11-03 17:45:21 +01:00
|
|
|
(new Date(
|
|
|
|
|
token.data.expiresAt,
|
|
|
|
|
).getTime() -
|
2024-09-30 13:42:12 +02:00
|
|
|
Date.now()) /
|
|
|
|
|
1000,
|
|
|
|
|
)
|
|
|
|
|
: null,
|
2024-11-03 17:45:21 +01:00
|
|
|
id_token: token.data.idToken,
|
2024-09-30 13:42:12 +02:00
|
|
|
refresh_token: null,
|
|
|
|
|
},
|
|
|
|
|
200,
|
|
|
|
|
);
|
|
|
|
|
}
|
2024-10-03 19:06:24 +02:00
|
|
|
|
|
|
|
|
default:
|
2024-09-30 13:42:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return context.json(
|
|
|
|
|
{
|
|
|
|
|
error: "unsupported_grant_type",
|
|
|
|
|
error_description: "Unsupported grant type",
|
|
|
|
|
},
|
|
|
|
|
401,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
};
|