feat: add design persistence functionality with Auth0 and Supabase integration

- Implemented `useDesignPersistence` composable for managing design records.
- Enhanced `useSlipmatDesigner` to support loading designs from JSON.
- Created global authentication middleware for route protection.
- Added Supabase client plugin for database interactions.
- Developed API endpoints for fetching, saving, and retrieving designs.
- Introduced utility functions for Auth0 token verification and Supabase client retrieval.
- Updated Nuxt configuration to include Auth0 and Supabase environment variables.
- Added necessary dependencies for Auth0 and Supabase.
- Enhanced TypeScript configuration for improved type support.
This commit is contained in:
Frank John Begornia
2025-11-07 00:01:52 +08:00
parent e2955debb7
commit 4d91925fad
20 changed files with 1242 additions and 19 deletions

82
server/utils/auth0.ts Normal file
View File

@@ -0,0 +1,82 @@
import { createError, getHeader, type H3Event } from "h3";
import { useRuntimeConfig } from "#imports";
import {
createRemoteJWKSet,
jwtVerify,
type JWTPayload,
type JWTVerifyOptions,
} from "jose";
const globalKey = Symbol.for("slipmatz.auth0.jwks");
type GlobalWithJwks = typeof globalThis & {
[globalKey]?: ReturnType<typeof createRemoteJWKSet>;
};
const getJwks = (issuer: string) => {
const globalScope = globalThis as GlobalWithJwks;
if (!globalScope[globalKey]) {
globalScope[globalKey] = createRemoteJWKSet(
new URL(`${issuer}.well-known/jwks.json`)
);
}
return globalScope[globalKey]!;
};
export type Auth0TokenPayload = JWTPayload & {
sub: string;
scope?: string;
permissions?: string[];
};
export const verifyAccessToken = async (token: string): Promise<Auth0TokenPayload> => {
const config = useRuntimeConfig();
const domain = config.public.auth0?.domain;
if (!domain) {
throw createError({
statusCode: 500,
statusMessage: "Auth0 domain is not configured.",
});
}
const issuer = `https://${domain}/`;
const jwks = getJwks(issuer);
const options: JWTVerifyOptions = {
issuer,
};
if (config.public.auth0?.audience) {
options.audience = config.public.auth0.audience;
}
const { payload } = await jwtVerify(token, jwks, options);
if (!payload.sub) {
throw createError({
statusCode: 401,
statusMessage: "Invalid access token payload.",
});
}
return payload as Auth0TokenPayload;
};
export const requireAuth0User = async (event: H3Event): Promise<Auth0TokenPayload> => {
const header = getHeader(event, "authorization");
if (!header) {
throw createError({ statusCode: 401, statusMessage: "Authorization header missing." });
}
const match = header.match(/^Bearer\s+(.+)$/i);
if (!match) {
throw createError({ statusCode: 401, statusMessage: "Invalid authorization format." });
}
const token = match[1];
try {
return await verifyAccessToken(token);
} catch (error) {
throw createError({ statusCode: 401, statusMessage: "Access token verification failed." });
}
};