2025-03-22 18:04:47 +01:00
|
|
|
import { RolePermission } from "@versia/client/schemas";
|
2024-09-25 12:31:35 +02:00
|
|
|
import { Hooks, Plugin } from "@versia/kit";
|
2024-10-24 18:48:11 +02:00
|
|
|
import { User } from "@versia/kit/db";
|
2024-12-18 20:42:40 +01:00
|
|
|
import { getCookie } from "hono/cookie";
|
2024-11-25 13:09:28 +01:00
|
|
|
import { jwtVerify } from "jose";
|
|
|
|
|
import { JOSEError, JWTExpired } from "jose/errors";
|
2025-03-29 03:30:06 +01:00
|
|
|
import { z } from "zod";
|
2025-04-10 19:15:31 +02:00
|
|
|
import { keyPair, sensitiveString, url } from "~/classes/config/schema.ts";
|
2024-12-30 18:00:23 +01:00
|
|
|
import { ApiError } from "~/classes/errors/api-error.ts";
|
2024-10-04 15:22:48 +02:00
|
|
|
import authorizeRoute from "./routes/authorize.ts";
|
2024-10-07 12:52:22 +02:00
|
|
|
import jwksRoute from "./routes/jwks.ts";
|
2024-10-11 15:15:06 +02:00
|
|
|
import ssoLoginCallbackRoute from "./routes/oauth/callback.ts";
|
2024-10-04 15:22:48 +02:00
|
|
|
import tokenRevokeRoute from "./routes/oauth/revoke.ts";
|
2024-10-11 14:39:25 +02:00
|
|
|
import ssoLoginRoute from "./routes/oauth/sso.ts";
|
2024-10-04 15:22:48 +02:00
|
|
|
import tokenRoute from "./routes/oauth/token.ts";
|
|
|
|
|
import ssoIdRoute from "./routes/sso/:id/index.ts";
|
|
|
|
|
import ssoRoute from "./routes/sso/index.ts";
|
2024-08-29 20:32:04 +02:00
|
|
|
|
2024-11-25 13:09:28 +01:00
|
|
|
const configSchema = z.object({
|
|
|
|
|
forced: z.boolean().default(false),
|
|
|
|
|
allow_registration: z.boolean().default(true),
|
|
|
|
|
providers: z
|
|
|
|
|
.array(
|
|
|
|
|
z.object({
|
|
|
|
|
name: z.string().min(1),
|
|
|
|
|
id: z.string().min(1),
|
|
|
|
|
url: z.string().min(1),
|
|
|
|
|
client_id: z.string().min(1),
|
2025-02-15 02:47:29 +01:00
|
|
|
client_secret: sensitiveString,
|
2025-03-30 23:44:50 +02:00
|
|
|
icon: url.optional(),
|
2024-10-24 18:18:39 +02:00
|
|
|
}),
|
2024-11-25 13:09:28 +01:00
|
|
|
)
|
|
|
|
|
.default([]),
|
2025-02-15 02:47:29 +01:00
|
|
|
keys: keyPair,
|
2024-11-25 13:09:28 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const plugin = new Plugin(configSchema);
|
2024-08-29 20:32:04 +02:00
|
|
|
|
2024-09-30 13:42:12 +02:00
|
|
|
// Test hook for screenshots
|
2024-08-29 20:32:04 +02:00
|
|
|
plugin.registerHandler(Hooks.Response, (req) => {
|
|
|
|
|
console.info("Request received:", req);
|
|
|
|
|
return req;
|
|
|
|
|
});
|
2024-09-30 13:42:12 +02:00
|
|
|
|
2024-08-29 20:32:04 +02:00
|
|
|
authorizeRoute(plugin);
|
2024-09-24 14:42:39 +02:00
|
|
|
ssoRoute(plugin);
|
2024-09-25 12:31:35 +02:00
|
|
|
ssoIdRoute(plugin);
|
2024-09-30 13:42:12 +02:00
|
|
|
tokenRoute(plugin);
|
|
|
|
|
tokenRevokeRoute(plugin);
|
2024-10-07 12:52:22 +02:00
|
|
|
jwksRoute(plugin);
|
2024-10-11 14:39:25 +02:00
|
|
|
ssoLoginRoute(plugin);
|
2024-10-11 15:15:06 +02:00
|
|
|
ssoLoginCallbackRoute(plugin);
|
2024-08-29 20:32:04 +02:00
|
|
|
|
2025-04-18 14:38:00 +02:00
|
|
|
plugin.registerRoute("/admin/queues/api/*", (app) => {
|
2024-11-25 13:09:28 +01:00
|
|
|
// Check for JWT when accessing the admin panel
|
2025-04-18 14:38:00 +02:00
|
|
|
app.use("/admin/queues/api/*", async (context, next) => {
|
2024-11-25 13:09:28 +01:00
|
|
|
const jwtCookie = getCookie(context, "jwt");
|
|
|
|
|
|
|
|
|
|
if (!jwtCookie) {
|
2024-12-30 18:00:23 +01:00
|
|
|
throw new ApiError(401, "Missing JWT cookie");
|
2024-11-25 13:09:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { keys } = context.get("pluginConfig");
|
|
|
|
|
|
|
|
|
|
const result = await jwtVerify(jwtCookie, keys.public, {
|
|
|
|
|
algorithms: ["EdDSA"],
|
|
|
|
|
issuer: new URL(context.get("config").http.base_url).origin,
|
|
|
|
|
}).catch((error) => {
|
|
|
|
|
if (error instanceof JOSEError) {
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
throw error;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (result instanceof JOSEError) {
|
|
|
|
|
if (result instanceof JWTExpired) {
|
2024-12-30 18:00:23 +01:00
|
|
|
throw new ApiError(401, "JWT has expired");
|
2024-11-25 13:09:28 +01:00
|
|
|
}
|
|
|
|
|
|
2024-12-30 18:00:23 +01:00
|
|
|
throw new ApiError(401, "Invalid JWT");
|
2024-11-25 13:09:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
payload: { sub },
|
|
|
|
|
} = result;
|
|
|
|
|
|
|
|
|
|
if (!sub) {
|
2024-12-30 18:00:23 +01:00
|
|
|
throw new ApiError(401, "Invalid JWT (no sub)");
|
2024-11-25 13:09:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const user = await User.fromId(sub);
|
|
|
|
|
|
2025-03-22 18:04:47 +01:00
|
|
|
if (!user?.hasPermission(RolePermission.ManageInstanceFederation)) {
|
2024-12-30 18:00:23 +01:00
|
|
|
throw new ApiError(
|
2024-11-25 13:09:28 +01:00
|
|
|
403,
|
2025-03-22 18:04:47 +01:00
|
|
|
`Missing '${RolePermission.ManageInstanceFederation}' permission`,
|
2024-11-25 13:09:28 +01:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await next();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2024-08-29 20:32:04 +02:00
|
|
|
export type PluginType = typeof plugin;
|
|
|
|
|
export default plugin;
|