mirror of
https://github.com/versia-pub/server.git
synced 2026-03-13 13:59:16 +01:00
refactor(api): ♻️ Move from @hono/zod-openapi to hono-openapi
hono-openapi is easier to work with and generates better OpenAPI definitions
This commit is contained in:
parent
0576aff972
commit
58342e86e1
240 changed files with 9494 additions and 9575 deletions
|
|
@ -1,46 +1,28 @@
|
|||
import { handleZodError } from "@/api";
|
||||
import { randomString } from "@/math.ts";
|
||||
import { createRoute, z } from "@hono/zod-openapi";
|
||||
import { Account as AccountSchema } from "@versia/client/schemas";
|
||||
import { RolePermission } from "@versia/client/schemas";
|
||||
import { Media, Token, User, db } from "@versia/kit/db";
|
||||
import { type SQL, and, eq, isNull } from "@versia/kit/drizzle";
|
||||
import { OpenIdAccounts, Users } from "@versia/kit/tables";
|
||||
import { describeRoute } from "hono-openapi";
|
||||
import { validator } from "hono-openapi/zod";
|
||||
import { setCookie } from "hono/cookie";
|
||||
import { SignJWT } from "jose";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error.ts";
|
||||
import type { PluginType } from "../../index.ts";
|
||||
import { automaticOidcFlow } from "../../utils.ts";
|
||||
|
||||
const schemas = {
|
||||
query: z.object({
|
||||
client_id: z.string().optional(),
|
||||
flow: z.string(),
|
||||
link: z
|
||||
.string()
|
||||
.transform((v) => ["true", "1", "on"].includes(v.toLowerCase()))
|
||||
.optional(),
|
||||
user_id: z.string().uuid().optional(),
|
||||
}),
|
||||
param: z.object({
|
||||
issuer: z.string(),
|
||||
}),
|
||||
};
|
||||
|
||||
export default (plugin: PluginType): void => {
|
||||
plugin.registerRoute("/oauth/sso/{issuer}/callback", (app) => {
|
||||
app.openapi(
|
||||
createRoute({
|
||||
method: "get",
|
||||
path: "/oauth/sso/{issuer}/callback",
|
||||
app.get(
|
||||
"/oauth/sso/:issuer/callback",
|
||||
describeRoute({
|
||||
summary: "SSO callback",
|
||||
tags: ["OpenID"],
|
||||
description:
|
||||
"After the user has authenticated to an external OpenID provider, they are redirected here to complete the OAuth flow and get a code",
|
||||
middleware: [plugin.middleware] as const,
|
||||
request: {
|
||||
query: schemas.query,
|
||||
params: schemas.param,
|
||||
},
|
||||
responses: {
|
||||
302: {
|
||||
description:
|
||||
|
|
@ -48,6 +30,29 @@ export default (plugin: PluginType): void => {
|
|||
},
|
||||
},
|
||||
}),
|
||||
plugin.middleware,
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
issuer: z.string(),
|
||||
}),
|
||||
handleZodError,
|
||||
),
|
||||
validator(
|
||||
"query",
|
||||
z.object({
|
||||
client_id: z.string().optional(),
|
||||
flow: z.string(),
|
||||
link: z
|
||||
.string()
|
||||
.transform((v) =>
|
||||
["true", "1", "on"].includes(v.toLowerCase()),
|
||||
)
|
||||
.optional(),
|
||||
user_id: z.string().uuid().optional(),
|
||||
}),
|
||||
handleZodError,
|
||||
),
|
||||
async (context) => {
|
||||
const currentUrl = new URL(context.req.url);
|
||||
const redirectUrl = new URL(context.req.url);
|
||||
|
|
|
|||
|
|
@ -1,48 +1,25 @@
|
|||
import { jsonOrForm } from "@/api";
|
||||
import { createRoute, z } from "@hono/zod-openapi";
|
||||
import { handleZodError, jsonOrForm } from "@/api";
|
||||
import { Token, db } from "@versia/kit/db";
|
||||
import { and, eq } from "@versia/kit/drizzle";
|
||||
import { Tokens } from "@versia/kit/tables";
|
||||
import { describeRoute } from "hono-openapi";
|
||||
import { resolver, validator } from "hono-openapi/zod";
|
||||
import { z } from "zod";
|
||||
import type { PluginType } from "../../index.ts";
|
||||
|
||||
const schemas = {
|
||||
json: z.object({
|
||||
client_id: z.string(),
|
||||
client_secret: z.string(),
|
||||
token: z.string().optional(),
|
||||
}),
|
||||
};
|
||||
|
||||
export default (plugin: PluginType): void => {
|
||||
plugin.registerRoute("/oauth/revoke", (app) => {
|
||||
app.openapi(
|
||||
createRoute({
|
||||
method: "post",
|
||||
path: "/oauth/revoke",
|
||||
app.post(
|
||||
"/oauth/revoke",
|
||||
describeRoute({
|
||||
summary: "Revoke token",
|
||||
tags: ["OpenID"],
|
||||
middleware: [jsonOrForm(), plugin.middleware],
|
||||
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 deleted",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({}),
|
||||
schema: resolver(z.object({})),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -50,15 +27,28 @@ export default (plugin: PluginType): void => {
|
|||
description: "Authorization error",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
error: z.string(),
|
||||
error_description: z.string(),
|
||||
}),
|
||||
schema: resolver(
|
||||
z.object({
|
||||
error: z.string(),
|
||||
error_description: z.string(),
|
||||
}),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
jsonOrForm(),
|
||||
plugin.middleware,
|
||||
validator(
|
||||
"json",
|
||||
z.object({
|
||||
client_id: z.string(),
|
||||
client_secret: z.string(),
|
||||
token: z.string().optional(),
|
||||
}),
|
||||
handleZodError,
|
||||
),
|
||||
async (context) => {
|
||||
const { client_id, client_secret, token } =
|
||||
context.req.valid("json");
|
||||
|
|
|
|||
|
|
@ -1,37 +1,25 @@
|
|||
import { createRoute, z } from "@hono/zod-openapi";
|
||||
import { handleZodError } from "@/api.ts";
|
||||
import { Application, db } from "@versia/kit/db";
|
||||
import { OpenIdLoginFlows } from "@versia/kit/tables";
|
||||
import { describeRoute } from "hono-openapi";
|
||||
import { validator } from "hono-openapi/zod";
|
||||
import {
|
||||
calculatePKCECodeChallenge,
|
||||
discoveryRequest,
|
||||
generateRandomCodeVerifier,
|
||||
processDiscoveryResponse,
|
||||
} from "oauth4webapi";
|
||||
import { z } from "zod";
|
||||
import type { PluginType } from "../../index.ts";
|
||||
import { oauthRedirectUri } from "../../utils.ts";
|
||||
|
||||
const schemas = {
|
||||
query: z.object({
|
||||
issuer: z.string(),
|
||||
client_id: z.string().optional(),
|
||||
redirect_uri: z.string().url().optional(),
|
||||
scope: z.string().optional(),
|
||||
response_type: z.enum(["code"]).optional(),
|
||||
}),
|
||||
};
|
||||
|
||||
export default (plugin: PluginType): void => {
|
||||
plugin.registerRoute("/oauth/sso", (app) => {
|
||||
app.openapi(
|
||||
createRoute({
|
||||
method: "get",
|
||||
path: "/oauth/sso",
|
||||
app.get(
|
||||
"/oauth/sso",
|
||||
describeRoute({
|
||||
summary: "Initiate SSO login flow",
|
||||
tags: ["OpenID"],
|
||||
request: {
|
||||
query: schemas.query,
|
||||
},
|
||||
middleware: [plugin.middleware] as const,
|
||||
responses: {
|
||||
302: {
|
||||
description:
|
||||
|
|
@ -39,6 +27,18 @@ export default (plugin: PluginType): void => {
|
|||
},
|
||||
},
|
||||
}),
|
||||
plugin.middleware,
|
||||
validator(
|
||||
"query",
|
||||
z.object({
|
||||
issuer: z.string(),
|
||||
client_id: z.string().optional(),
|
||||
redirect_uri: z.string().url().optional(),
|
||||
scope: z.string().optional(),
|
||||
response_type: z.enum(["code"]).optional(),
|
||||
}),
|
||||
handleZodError,
|
||||
),
|
||||
async (context) => {
|
||||
// This is the Versia client's client_id, not the external OAuth provider's client_id
|
||||
const { issuer: issuerId, client_id } =
|
||||
|
|
|
|||
|
|
@ -1,87 +1,44 @@
|
|||
import { jsonOrForm } from "@/api";
|
||||
import { createRoute, z } from "@hono/zod-openapi";
|
||||
import { handleZodError, jsonOrForm } from "@/api";
|
||||
import { Application, Token } from "@versia/kit/db";
|
||||
import { and, eq } from "@versia/kit/drizzle";
|
||||
import { Tokens } from "@versia/kit/tables";
|
||||
import { describeRoute } from "hono-openapi";
|
||||
import { resolver, validator } from "hono-openapi/zod";
|
||||
import { z } from "zod";
|
||||
import type { PluginType } from "../../index.ts";
|
||||
|
||||
const schemas = {
|
||||
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(),
|
||||
}),
|
||||
};
|
||||
|
||||
export default (plugin: PluginType): void => {
|
||||
plugin.registerRoute("/oauth/token", (app) => {
|
||||
app.openapi(
|
||||
createRoute({
|
||||
method: "post",
|
||||
path: "/oauth/token",
|
||||
app.post(
|
||||
"/oauth/token",
|
||||
describeRoute({
|
||||
summary: "Get token",
|
||||
tags: ["OpenID"],
|
||||
middleware: [jsonOrForm(), plugin.middleware],
|
||||
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(),
|
||||
}),
|
||||
schema: resolver(
|
||||
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(),
|
||||
}),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -89,15 +46,53 @@ export default (plugin: PluginType): void => {
|
|||
description: "Authorization error",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
error: z.string(),
|
||||
error_description: z.string(),
|
||||
}),
|
||||
schema: resolver(
|
||||
z.object({
|
||||
error: z.string(),
|
||||
error_description: z.string(),
|
||||
}),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
jsonOrForm(),
|
||||
plugin.middleware,
|
||||
validator(
|
||||
"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(),
|
||||
}),
|
||||
handleZodError,
|
||||
),
|
||||
async (context) => {
|
||||
const {
|
||||
grant_type,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue