Kyoo/api/src/auth.ts
2025-04-06 19:52:54 +02:00

72 lines
1.7 KiB
TypeScript

import { TypeCompiler } from "@sinclair/typebox/compiler";
import Elysia, { t } from "elysia";
import { createRemoteJWKSet, jwtVerify } from "jose";
const jwtSecret = process.env.JWT_SECRET
? new TextEncoder().encode(process.env.JWT_SECRET)
: null;
const jwks = createRemoteJWKSet(
new URL(
".well-known/jwks.json",
process.env.AUTH_SERVER ?? "http://auth:4568",
),
);
const Jwt = t.Object({
sub: t.String({ description: "User id" }),
sid: t.String({ description: "Session id" }),
username: t.String(),
permissions: t.Array(t.String()),
});
const validator = TypeCompiler.Compile(Jwt);
export const auth = new Elysia({ name: "auth" })
.guard({
headers: t.Object({
authorization: t.TemplateLiteral("Bearer ${string}"),
}),
})
.resolve(async ({ headers: { authorization }, error }) => {
const bearer = authorization?.slice(7);
if (!bearer) {
return error(500, {
status: 500,
message: "No jwt, auth server configuration error.",
});
}
try {
// @ts-expect-error ts can't understand that there's two overload idk why
const { payload } = await jwtVerify(bearer, jwtSecret ?? jwks, {
issuer: process.env.JWT_ISSUER,
});
const jwt = validator.Decode(payload);
return { jwt };
} catch (err) {
return error(403, {
status: 403,
message: "Invalid jwt. Verification vailed",
details: err,
});
}
})
.macro({
permissions(perms: string[]) {
return {
beforeHandle: ({ jwt, error }) => {
for (const perm of perms) {
if (!jwt!.permissions.includes(perm)) {
return error(403, {
status: 403,
message: `Missing permission: '${perm}'.`,
details: { current: jwt!.permissions, required: perms },
});
}
}
},
};
},
})
.as("plugin");