Initial boilerplate scaffold for continuum-common
Adds ts-core (logger, Supabase JWT verification, Elysia error transformers) and rust-core (thiserror error type, tracing init, G-code checksum/header parsing) packages. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import { createRemoteJWKSet, jwtVerify, type JWTPayload } from "jose";
|
||||
|
||||
export interface SupabaseClaims extends JWTPayload {
|
||||
sub: string;
|
||||
email?: string;
|
||||
role?: string;
|
||||
aud: string | string[];
|
||||
app_metadata?: Record<string, unknown>;
|
||||
user_metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export class JwtVerificationError extends Error {
|
||||
constructor(message: string, readonly cause?: unknown) {
|
||||
super(message);
|
||||
this.name = "JwtVerificationError";
|
||||
}
|
||||
}
|
||||
|
||||
export interface VerifierOptions {
|
||||
/** Supabase project URL, e.g. https://xyzcompany.supabase.co */
|
||||
supabaseUrl: string;
|
||||
/**
|
||||
* Legacy HS256 project JWT secret. When provided, verification uses this
|
||||
* shared secret instead of fetching the project's JWKS. Prefer leaving
|
||||
* this unset for projects on Supabase's newer asymmetric (ES256/RS256)
|
||||
* signing keys.
|
||||
*/
|
||||
jwtSecret?: string;
|
||||
audience?: string;
|
||||
}
|
||||
|
||||
export interface SupabaseJwtVerifier {
|
||||
verify(token: string): Promise<SupabaseClaims>;
|
||||
}
|
||||
|
||||
export function createSupabaseJwtVerifier(options: VerifierOptions): SupabaseJwtVerifier {
|
||||
const audience = options.audience ?? "authenticated";
|
||||
|
||||
if (options.jwtSecret) {
|
||||
const key = new TextEncoder().encode(options.jwtSecret);
|
||||
return {
|
||||
async verify(token: string): Promise<SupabaseClaims> {
|
||||
try {
|
||||
const { payload } = await jwtVerify(token, key, {
|
||||
algorithms: ["HS256"],
|
||||
audience,
|
||||
});
|
||||
return payload as SupabaseClaims;
|
||||
} catch (err) {
|
||||
throw new JwtVerificationError("failed to verify Supabase JWT (HS256)", err);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const jwksUrl = new URL("/auth/v1/.well-known/jwks.json", options.supabaseUrl);
|
||||
const jwks = createRemoteJWKSet(jwksUrl);
|
||||
|
||||
return {
|
||||
async verify(token: string): Promise<SupabaseClaims> {
|
||||
try {
|
||||
const { payload } = await jwtVerify(token, jwks, { audience });
|
||||
return payload as SupabaseClaims;
|
||||
} catch (err) {
|
||||
throw new JwtVerificationError("failed to verify Supabase JWT (JWKS)", err);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function extractBearerToken(authorizationHeader: string | null | undefined): string {
|
||||
if (!authorizationHeader) {
|
||||
throw new JwtVerificationError("missing Authorization header");
|
||||
}
|
||||
const [scheme, token] = authorizationHeader.split(" ");
|
||||
if (scheme !== "Bearer" || !token) {
|
||||
throw new JwtVerificationError("Authorization header is not a Bearer token");
|
||||
}
|
||||
return token;
|
||||
}
|
||||
Reference in New Issue
Block a user