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:
82
server/utils/auth0.ts
Normal file
82
server/utils/auth0.ts
Normal 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." });
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user