mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 08:28:19 +01:00
refactor(plugin): 🚚 Move JWKS well-known endpoint to OpenID plugin
This commit is contained in:
parent
2e827814de
commit
0557d52afe
|
|
@ -1,74 +0,0 @@
|
||||||
import { apiRoute, applyConfig } from "@/api";
|
|
||||||
import { createRoute, z } from "@hono/zod-openapi";
|
|
||||||
import { exportJWK } from "jose";
|
|
||||||
import { config } from "~/packages/config-manager";
|
|
||||||
|
|
||||||
export const meta = applyConfig({
|
|
||||||
auth: {
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
ratelimits: {
|
|
||||||
duration: 30,
|
|
||||||
max: 60,
|
|
||||||
},
|
|
||||||
route: "/.well-known/jwks",
|
|
||||||
});
|
|
||||||
|
|
||||||
const route = createRoute({
|
|
||||||
method: "get",
|
|
||||||
path: "/.well-known/jwks",
|
|
||||||
summary: "JWK Set",
|
|
||||||
responses: {
|
|
||||||
200: {
|
|
||||||
description: "JWK Set",
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: z.object({
|
|
||||||
keys: z.array(
|
|
||||||
z.object({
|
|
||||||
kty: z.string(),
|
|
||||||
use: z.string(),
|
|
||||||
alg: z.string(),
|
|
||||||
kid: z.string(),
|
|
||||||
crv: z.string().optional(),
|
|
||||||
x: z.string().optional(),
|
|
||||||
y: z.string().optional(),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default apiRoute((app) =>
|
|
||||||
app.openapi(route, async (context) => {
|
|
||||||
const publicKey = await crypto.subtle.importKey(
|
|
||||||
"spki",
|
|
||||||
Buffer.from(config.oidc.keys?.public ?? "", "base64"),
|
|
||||||
"Ed25519",
|
|
||||||
true,
|
|
||||||
["verify"],
|
|
||||||
);
|
|
||||||
|
|
||||||
const jwk = await exportJWK(publicKey);
|
|
||||||
|
|
||||||
// Remove the private key
|
|
||||||
jwk.d = undefined;
|
|
||||||
|
|
||||||
return context.json(
|
|
||||||
{
|
|
||||||
keys: [
|
|
||||||
{
|
|
||||||
...jwk,
|
|
||||||
use: "sig",
|
|
||||||
alg: "EdDSA",
|
|
||||||
kid: "1",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
200,
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { Hooks, Plugin } from "@versia/kit";
|
import { Hooks, Plugin } from "@versia/kit";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import authorizeRoute from "./routes/authorize.ts";
|
import authorizeRoute from "./routes/authorize.ts";
|
||||||
|
import jwksRoute from "./routes/jwks.ts";
|
||||||
import tokenRevokeRoute from "./routes/oauth/revoke.ts";
|
import tokenRevokeRoute from "./routes/oauth/revoke.ts";
|
||||||
import tokenRoute from "./routes/oauth/token.ts";
|
import tokenRoute from "./routes/oauth/token.ts";
|
||||||
import ssoIdRoute from "./routes/sso/:id/index.ts";
|
import ssoIdRoute from "./routes/sso/:id/index.ts";
|
||||||
|
|
@ -74,6 +75,7 @@ ssoRoute(plugin);
|
||||||
ssoIdRoute(plugin);
|
ssoIdRoute(plugin);
|
||||||
tokenRoute(plugin);
|
tokenRoute(plugin);
|
||||||
tokenRevokeRoute(plugin);
|
tokenRevokeRoute(plugin);
|
||||||
|
jwksRoute(plugin);
|
||||||
|
|
||||||
export type PluginType = typeof plugin;
|
export type PluginType = typeof plugin;
|
||||||
export default plugin;
|
export default plugin;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||||
import { randomString } from "@/math";
|
import { randomString } from "@/math";
|
||||||
import { eq } from "drizzle-orm";
|
import { db } from "@versia/kit/db";
|
||||||
|
import { eq } from "@versia/kit/drizzle";
|
||||||
|
import { Applications, RolePermissions } from "@versia/kit/tables";
|
||||||
import { SignJWT } from "jose";
|
import { SignJWT } from "jose";
|
||||||
import { db } from "~/drizzle/db";
|
|
||||||
import { Applications, RolePermissions } from "~/drizzle/schema";
|
|
||||||
import { config } from "~/packages/config-manager";
|
import { config } from "~/packages/config-manager";
|
||||||
import { fakeRequest, getTestUsers } from "~/tests/utils";
|
import { fakeRequest, getTestUsers } from "~/tests/utils";
|
||||||
|
|
||||||
|
|
|
||||||
42
plugins/openid/routes/jwks.test.ts
Normal file
42
plugins/openid/routes/jwks.test.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||||
|
import { db } from "@versia/kit/db";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { Applications } from "~/drizzle/schema";
|
||||||
|
import { fakeRequest } from "~/tests/utils";
|
||||||
|
|
||||||
|
const clientId = "test-client-id";
|
||||||
|
const redirectUri = "https://example.com/callback";
|
||||||
|
const scope = "openid profile email";
|
||||||
|
const secret = "test-secret";
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await db.insert(Applications).values({
|
||||||
|
clientId,
|
||||||
|
redirectUri,
|
||||||
|
scopes: scope,
|
||||||
|
name: "Test Application",
|
||||||
|
secret,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await db.delete(Applications).where(eq(Applications.clientId, clientId));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("/.well-known/jwks", () => {
|
||||||
|
test("should return JWK set with valid inputs", async () => {
|
||||||
|
const response = await fakeRequest("/.well-known/jwks", {
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
const body = await response.json();
|
||||||
|
expect(body.keys).toHaveLength(1);
|
||||||
|
expect(body.keys[0].kty).toBe("OKP");
|
||||||
|
expect(body.keys[0].use).toBe("sig");
|
||||||
|
expect(body.keys[0].alg).toBe("EdDSA");
|
||||||
|
expect(body.keys[0].kid).toBe("1");
|
||||||
|
expect(body.keys[0].crv).toBe("Ed25519");
|
||||||
|
expect(body.keys[0].x).toBeString();
|
||||||
|
});
|
||||||
|
});
|
||||||
65
plugins/openid/routes/jwks.ts
Normal file
65
plugins/openid/routes/jwks.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
import { auth } from "@/api";
|
||||||
|
import { createRoute, z } from "@hono/zod-openapi";
|
||||||
|
import { exportJWK } from "jose";
|
||||||
|
import type { PluginType } from "../index.ts";
|
||||||
|
|
||||||
|
export default (plugin: PluginType) => {
|
||||||
|
plugin.registerRoute("/.well-known/jwks", (app) =>
|
||||||
|
app.openapi(
|
||||||
|
createRoute({
|
||||||
|
method: "get",
|
||||||
|
path: "/.well-known/jwks",
|
||||||
|
summary: "JWK Set",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "JWK Set",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
keys: z.array(
|
||||||
|
z.object({
|
||||||
|
kty: z.string(),
|
||||||
|
use: z.string(),
|
||||||
|
alg: z.string(),
|
||||||
|
kid: z.string(),
|
||||||
|
crv: z.string().optional(),
|
||||||
|
x: z.string().optional(),
|
||||||
|
y: z.string().optional(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
middleware: [
|
||||||
|
auth({
|
||||||
|
required: false,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
async (context) => {
|
||||||
|
const jwk = await exportJWK(
|
||||||
|
context.get("pluginConfig").keys?.public,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Remove the private key 💀
|
||||||
|
jwk.d = undefined;
|
||||||
|
|
||||||
|
return context.json(
|
||||||
|
{
|
||||||
|
keys: [
|
||||||
|
{
|
||||||
|
...jwk,
|
||||||
|
use: "sig",
|
||||||
|
alg: "EdDSA",
|
||||||
|
kid: "1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
200,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||||
import { eq } from "drizzle-orm";
|
import { db } from "@versia/kit/db";
|
||||||
import { db } from "~/drizzle/db";
|
import { eq } from "@versia/kit/drizzle";
|
||||||
import { Applications, Tokens } from "~/drizzle/schema";
|
import { Applications, Tokens } from "@versia/kit/tables";
|
||||||
import { fakeRequest, getTestUsers } from "~/tests/utils";
|
import { fakeRequest, getTestUsers } from "~/tests/utils";
|
||||||
|
|
||||||
const { deleteUsers, users } = await getTestUsers(1);
|
const { deleteUsers, users } = await getTestUsers(1);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||||
import { eq } from "drizzle-orm";
|
import { db } from "@versia/kit/db";
|
||||||
import { db } from "~/drizzle/db";
|
import { eq } from "@versia/kit/drizzle";
|
||||||
import { Applications, Tokens } from "~/drizzle/schema";
|
import { Applications, Tokens } from "@versia/kit/tables";
|
||||||
import { fakeRequest, getTestUsers } from "~/tests/utils";
|
import { fakeRequest, getTestUsers } from "~/tests/utils";
|
||||||
|
|
||||||
const { deleteUsers, users } = await getTestUsers(1);
|
const { deleteUsers, users } = await getTestUsers(1);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue