mirror of
https://github.com/versia-pub/server.git
synced 2026-03-13 05:49: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,10 +1,10 @@
|
|||
import { z } from "@hono/zod-openapi";
|
||||
import { RolePermission } from "@versia/client/schemas";
|
||||
import { Hooks, Plugin } from "@versia/kit";
|
||||
import { User } from "@versia/kit/db";
|
||||
import { getCookie } from "hono/cookie";
|
||||
import { jwtVerify } from "jose";
|
||||
import { JOSEError, JWTExpired } from "jose/errors";
|
||||
import { z } from "zod";
|
||||
import { keyPair, sensitiveString } from "~/classes/config/schema.ts";
|
||||
import { ApiError } from "~/classes/errors/api-error.ts";
|
||||
import authorizeRoute from "./routes/authorize.ts";
|
||||
|
|
|
|||
|
|
@ -1,105 +1,101 @@
|
|||
import { auth, jsonOrForm } from "@/api";
|
||||
import { auth, handleZodError, jsonOrForm } from "@/api";
|
||||
import { randomString } from "@/math";
|
||||
import { z } from "@hono/zod-openapi";
|
||||
import { RolePermission } from "@versia/client/schemas";
|
||||
import { Application, Token, User } from "@versia/kit/db";
|
||||
import { describeRoute } from "hono-openapi";
|
||||
import { validator } from "hono-openapi/zod";
|
||||
import { type JWTPayload, SignJWT, jwtVerify } from "jose";
|
||||
import { JOSEError } from "jose/errors";
|
||||
import { z } from "zod";
|
||||
import { errorRedirect, errors } from "../errors.ts";
|
||||
import type { PluginType } from "../index.ts";
|
||||
|
||||
const schemas = {
|
||||
query: z.object({
|
||||
prompt: z
|
||||
.enum(["none", "login", "consent", "select_account"])
|
||||
.optional()
|
||||
.default("none"),
|
||||
max_age: z.coerce
|
||||
.number()
|
||||
.int()
|
||||
.optional()
|
||||
.default(60 * 60 * 24 * 7),
|
||||
}),
|
||||
json: z
|
||||
.object({
|
||||
scope: z.string().optional(),
|
||||
redirect_uri: z
|
||||
.string()
|
||||
.url()
|
||||
.optional()
|
||||
.or(z.literal("urn:ietf:wg:oauth:2.0:oob")),
|
||||
response_type: z.enum([
|
||||
"code",
|
||||
"token",
|
||||
"none",
|
||||
"id_token",
|
||||
"code id_token",
|
||||
"code token",
|
||||
"token id_token",
|
||||
"code token id_token",
|
||||
]),
|
||||
client_id: z.string(),
|
||||
state: z.string().optional(),
|
||||
code_challenge: z.string().optional(),
|
||||
code_challenge_method: z.enum(["plain", "S256"]).optional(),
|
||||
})
|
||||
.refine(
|
||||
// Check if redirect_uri is valid for code flow
|
||||
(data) =>
|
||||
data.response_type.includes("code") ? data.redirect_uri : true,
|
||||
"redirect_uri is required for code flow",
|
||||
),
|
||||
// Disable for Mastodon API compatibility
|
||||
/* .refine(
|
||||
// Check if code_challenge is valid for code flow
|
||||
(data) =>
|
||||
data.response_type.includes("code")
|
||||
? data.code_challenge
|
||||
: true,
|
||||
"code_challenge is required for code flow",
|
||||
), */
|
||||
cookies: z.object({
|
||||
jwt: z.string(),
|
||||
}),
|
||||
};
|
||||
|
||||
export default (plugin: PluginType): void =>
|
||||
plugin.registerRoute("/oauth/authorize", (app) =>
|
||||
app.openapi(
|
||||
{
|
||||
method: "post",
|
||||
path: "/oauth/authorize",
|
||||
app.post(
|
||||
"/oauth/authorize",
|
||||
describeRoute({
|
||||
summary: "Main OpenID authorization endpoint",
|
||||
tags: ["OpenID"],
|
||||
middleware: [
|
||||
auth({
|
||||
auth: false,
|
||||
}),
|
||||
jsonOrForm(),
|
||||
plugin.middleware,
|
||||
] as const,
|
||||
responses: {
|
||||
302: {
|
||||
description: "Redirect to the application",
|
||||
},
|
||||
},
|
||||
request: {
|
||||
query: schemas.query,
|
||||
body: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: schemas.json,
|
||||
},
|
||||
"application/x-www-form-urlencoded": {
|
||||
schema: schemas.json,
|
||||
},
|
||||
"multipart/form-data": {
|
||||
schema: schemas.json,
|
||||
},
|
||||
},
|
||||
},
|
||||
cookies: schemas.cookies,
|
||||
},
|
||||
},
|
||||
}),
|
||||
plugin.middleware,
|
||||
auth({
|
||||
auth: false,
|
||||
}),
|
||||
jsonOrForm(),
|
||||
validator(
|
||||
"query",
|
||||
z.object({
|
||||
prompt: z
|
||||
.enum(["none", "login", "consent", "select_account"])
|
||||
.optional()
|
||||
.default("none"),
|
||||
max_age: z.coerce
|
||||
.number()
|
||||
.int()
|
||||
.optional()
|
||||
.default(60 * 60 * 24 * 7),
|
||||
}),
|
||||
handleZodError,
|
||||
),
|
||||
validator(
|
||||
"json",
|
||||
z
|
||||
.object({
|
||||
scope: z.string().optional(),
|
||||
redirect_uri: z
|
||||
.string()
|
||||
.url()
|
||||
.optional()
|
||||
.or(z.literal("urn:ietf:wg:oauth:2.0:oob")),
|
||||
response_type: z.enum([
|
||||
"code",
|
||||
"token",
|
||||
"none",
|
||||
"id_token",
|
||||
"code id_token",
|
||||
"code token",
|
||||
"token id_token",
|
||||
"code token id_token",
|
||||
]),
|
||||
client_id: z.string(),
|
||||
state: z.string().optional(),
|
||||
code_challenge: z.string().optional(),
|
||||
code_challenge_method: z
|
||||
.enum(["plain", "S256"])
|
||||
.optional(),
|
||||
})
|
||||
.refine(
|
||||
// Check if redirect_uri is valid for code flow
|
||||
(data) =>
|
||||
data.response_type.includes("code")
|
||||
? data.redirect_uri
|
||||
: true,
|
||||
"redirect_uri is required for code flow",
|
||||
),
|
||||
// Disable for Mastodon API compatibility
|
||||
/* .refine(
|
||||
// Check if code_challenge is valid for code flow
|
||||
(data) =>
|
||||
data.response_type.includes("code")
|
||||
? data.code_challenge
|
||||
: true,
|
||||
"code_challenge is required for code flow",
|
||||
), */
|
||||
handleZodError,
|
||||
),
|
||||
validator(
|
||||
"cookie",
|
||||
z.object({
|
||||
jwt: z.string(),
|
||||
}),
|
||||
handleZodError,
|
||||
),
|
||||
async (context) => {
|
||||
const { scope, redirect_uri, client_id, state } =
|
||||
context.req.valid("json");
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
import { auth } from "@/api";
|
||||
import { createRoute, z } from "@hono/zod-openapi";
|
||||
import { describeRoute } from "hono-openapi";
|
||||
import { resolver } from "hono-openapi/zod";
|
||||
import { exportJWK } from "jose";
|
||||
import { z } from "zod";
|
||||
import type { PluginType } from "../index.ts";
|
||||
|
||||
export default (plugin: PluginType): void => {
|
||||
plugin.registerRoute("/.well-known/jwks", (app) =>
|
||||
app.openapi(
|
||||
createRoute({
|
||||
method: "get",
|
||||
path: "/.well-known/jwks",
|
||||
app.get(
|
||||
"/.well-known/jwks",
|
||||
describeRoute({
|
||||
summary: "JWK Set",
|
||||
tags: ["OpenID"],
|
||||
responses: {
|
||||
|
|
@ -16,30 +17,30 @@ export default (plugin: PluginType): void => {
|
|||
description: "JWK Set",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
keys: z.array(
|
||||
z.object({
|
||||
kty: z.string().optional(),
|
||||
use: z.string(),
|
||||
alg: z.string(),
|
||||
kid: z.string(),
|
||||
crv: z.string().optional(),
|
||||
x: z.string().optional(),
|
||||
y: z.string().optional(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
schema: resolver(
|
||||
z.object({
|
||||
keys: z.array(
|
||||
z.object({
|
||||
kty: z.string().optional(),
|
||||
use: z.string(),
|
||||
alg: z.string(),
|
||||
kid: z.string(),
|
||||
crv: z.string().optional(),
|
||||
x: z.string().optional(),
|
||||
y: z.string().optional(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
middleware: [
|
||||
auth({
|
||||
auth: false,
|
||||
}),
|
||||
plugin.middleware,
|
||||
] as const,
|
||||
}),
|
||||
auth({
|
||||
auth: false,
|
||||
}),
|
||||
plugin.middleware,
|
||||
async (context) => {
|
||||
const jwk = await exportJWK(
|
||||
context.get("pluginConfig").keys?.public,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -1,49 +1,46 @@
|
|||
import { auth } from "@/api";
|
||||
import { auth, handleZodError } from "@/api";
|
||||
import { proxyUrl } from "@/response";
|
||||
import { createRoute, z } from "@hono/zod-openapi";
|
||||
import { RolePermission } from "@versia/client/schemas";
|
||||
import { db } from "@versia/kit/db";
|
||||
import { type SQL, eq } from "@versia/kit/drizzle";
|
||||
import { OpenIdAccounts } from "@versia/kit/tables";
|
||||
import { describeRoute } from "hono-openapi";
|
||||
import { resolver, validator } from "hono-openapi/zod";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error";
|
||||
import type { PluginType } from "~/plugins/openid";
|
||||
|
||||
export default (plugin: PluginType): void => {
|
||||
plugin.registerRoute("/api/v1/sso", (app) => {
|
||||
app.openapi(
|
||||
createRoute({
|
||||
method: "get",
|
||||
path: "/api/v1/sso/{id}",
|
||||
plugin.registerRoute("/api/v1/sso/{id}", (app) => {
|
||||
app.get(
|
||||
"/api/v1/sso/:id",
|
||||
describeRoute({
|
||||
summary: "Get linked account",
|
||||
tags: ["SSO"],
|
||||
middleware: [
|
||||
auth({
|
||||
auth: true,
|
||||
permissions: [RolePermission.OAuth],
|
||||
}),
|
||||
plugin.middleware,
|
||||
] as const,
|
||||
request: {
|
||||
params: z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: "Linked account",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
icon: z.string().optional(),
|
||||
}),
|
||||
schema: resolver(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
icon: z.string().optional(),
|
||||
}),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
404: ApiError.accountNotFound().schema,
|
||||
},
|
||||
}),
|
||||
auth({
|
||||
auth: true,
|
||||
permissions: [RolePermission.OAuth],
|
||||
}),
|
||||
plugin.middleware,
|
||||
validator("param", z.object({ id: z.string() }), handleZodError),
|
||||
async (context) => {
|
||||
const { id: issuerId } = context.req.valid("param");
|
||||
const { user } = context.get("auth");
|
||||
|
|
@ -89,24 +86,11 @@ export default (plugin: PluginType): void => {
|
|||
},
|
||||
);
|
||||
|
||||
app.openapi(
|
||||
createRoute({
|
||||
method: "delete",
|
||||
path: "/api/v1/sso/{id}",
|
||||
app.delete(
|
||||
"/api/v1/sso/:id",
|
||||
describeRoute({
|
||||
summary: "Unlink account",
|
||||
tags: ["SSO"],
|
||||
middleware: [
|
||||
auth({
|
||||
auth: true,
|
||||
permissions: [RolePermission.OAuth],
|
||||
}),
|
||||
plugin.middleware,
|
||||
] as const,
|
||||
request: {
|
||||
params: z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
},
|
||||
responses: {
|
||||
204: {
|
||||
description: "Account unlinked",
|
||||
|
|
@ -115,12 +99,18 @@ export default (plugin: PluginType): void => {
|
|||
description: "Account not found",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: ApiError.zodSchema,
|
||||
schema: resolver(ApiError.zodSchema),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
auth({
|
||||
auth: true,
|
||||
permissions: [RolePermission.OAuth],
|
||||
}),
|
||||
plugin.middleware,
|
||||
validator("param", z.object({ id: z.string() }), handleZodError),
|
||||
async (context) => {
|
||||
const { id: issuerId } = context.req.valid("param");
|
||||
const { user } = context.get("auth");
|
||||
|
|
|
|||
|
|
@ -1,48 +1,49 @@
|
|||
import { auth } from "@/api";
|
||||
import { z } from "@hono/zod-openapi";
|
||||
import { auth, handleZodError } from "@/api";
|
||||
import { RolePermission } from "@versia/client/schemas";
|
||||
import { Application, db } from "@versia/kit/db";
|
||||
import { OpenIdLoginFlows } from "@versia/kit/tables";
|
||||
import { describeRoute } from "hono-openapi";
|
||||
import { resolver, validator } from "hono-openapi/zod";
|
||||
import {
|
||||
calculatePKCECodeChallenge,
|
||||
generateRandomCodeVerifier,
|
||||
} from "oauth4webapi";
|
||||
import { z } from "zod";
|
||||
import { ApiError } from "~/classes/errors/api-error.ts";
|
||||
import type { PluginType } from "../../index.ts";
|
||||
import { oauthDiscoveryRequest, oauthRedirectUri } from "../../utils.ts";
|
||||
|
||||
export default (plugin: PluginType): void => {
|
||||
plugin.registerRoute("/api/v1/sso", (app) => {
|
||||
app.openapi(
|
||||
{
|
||||
method: "get",
|
||||
path: "/api/v1/sso",
|
||||
app.get(
|
||||
"/api/v1/sso",
|
||||
describeRoute({
|
||||
summary: "Get linked accounts",
|
||||
tags: ["SSO"],
|
||||
middleware: [
|
||||
auth({
|
||||
auth: true,
|
||||
permissions: [RolePermission.OAuth],
|
||||
}),
|
||||
plugin.middleware,
|
||||
] as const,
|
||||
responses: {
|
||||
200: {
|
||||
description: "Linked accounts",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
icon: z.string().optional(),
|
||||
}),
|
||||
schema: resolver(
|
||||
z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
icon: z.string().optional(),
|
||||
}),
|
||||
),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
auth({
|
||||
auth: true,
|
||||
permissions: [RolePermission.OAuth],
|
||||
}),
|
||||
plugin.middleware,
|
||||
async (context) => {
|
||||
const { user } = context.get("auth");
|
||||
|
||||
|
|
@ -61,30 +62,11 @@ export default (plugin: PluginType): void => {
|
|||
},
|
||||
);
|
||||
|
||||
app.openapi(
|
||||
{
|
||||
method: "post",
|
||||
path: "/api/v1/sso",
|
||||
app.post(
|
||||
"/api/v1/sso",
|
||||
describeRoute({
|
||||
summary: "Link account",
|
||||
tags: ["SSO"],
|
||||
middleware: [
|
||||
auth({
|
||||
auth: true,
|
||||
permissions: [RolePermission.OAuth],
|
||||
}),
|
||||
plugin.middleware,
|
||||
] as const,
|
||||
request: {
|
||||
body: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
issuer: z.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
302: {
|
||||
description: "Redirect to OpenID provider",
|
||||
|
|
@ -93,12 +75,18 @@ export default (plugin: PluginType): void => {
|
|||
description: "Issuer not found",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: ApiError.zodSchema,
|
||||
schema: resolver(ApiError.zodSchema),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
auth({
|
||||
auth: true,
|
||||
permissions: [RolePermission.OAuth],
|
||||
}),
|
||||
plugin.middleware,
|
||||
validator("json", z.object({ issuer: z.string() }), handleZodError),
|
||||
async (context) => {
|
||||
const { user } = context.get("auth");
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue