Files
myeasycms-v2/packages/supabase/src/require-user.ts
Giancarlo Buomprisco 2c0d0bf7a1 Next.js 16, React 19.2, Identities page, Invitations identities step, PNPM Catalogs (#381)
* Upgraded to Next.js 16
* Refactored code to comply with React 19.2 ESLint rules
* Refactored some useEffect usages with the new useEffectEvent
* Added Identities page and added second step to set up an identity after accepting an invitation
* Updated all dependencies
* Introduced PNPM catalogs for some frequently updated dependencies
* Bugs fixing and improvements
2025-10-22 11:47:47 +09:00

121 lines
2.7 KiB
TypeScript

import type { AMREntry, SupabaseClient } from '@supabase/supabase-js';
import { checkRequiresMultiFactorAuthentication } from './check-requires-mfa';
import { JWTUserData } from './types';
const MULTI_FACTOR_AUTH_VERIFY_PATH = '/auth/verify';
const SIGN_IN_PATH = '/auth/sign-in';
/**
* @name UserClaims
* @description The user claims returned from the Supabase auth API.
*/
type UserClaims = {
aud: string;
exp: number;
iat: number;
iss: string;
sub: string;
email: string;
phone: string;
app_metadata: Record<string, unknown>;
user_metadata: Record<string, unknown>;
role: string;
aal: `aal1` | `aal2`;
session_id: string;
is_anonymous: boolean;
amr: AMREntry[];
};
/**
* @name requireUser
* @description Require a session to be present in the request
* @param client
* @param options
* @param options.verifyMfa
* @param options.next
*/
export async function requireUser(
client: SupabaseClient,
options?: {
verifyMfa?: boolean;
next?: string;
},
): Promise<
| {
error: null;
data: JWTUserData;
}
| (
| {
error: AuthenticationError;
data: null;
redirectTo: string;
}
| {
error: MultiFactorAuthError;
data: null;
redirectTo: string;
}
)
> {
const { data, error } = await client.auth.getClaims();
if (!data?.claims || error) {
return {
data: null,
error: new AuthenticationError(),
redirectTo: getRedirectTo(SIGN_IN_PATH, options?.next),
};
}
const { verifyMfa = true } = options ?? {};
if (verifyMfa) {
const requiresMfa = await checkRequiresMultiFactorAuthentication(client);
// If the user requires multi-factor authentication,
// redirect them to the page where they can verify their identity.
if (requiresMfa) {
return {
data: null,
error: new MultiFactorAuthError(),
redirectTo: getRedirectTo(MULTI_FACTOR_AUTH_VERIFY_PATH, options?.next),
};
}
}
// the client doesn't type the claims, so we need to cast it to the User type
const user = data.claims as UserClaims;
return {
error: null,
data: {
is_anonymous: user.is_anonymous,
aal: user.aal,
email: user.email,
phone: user.phone,
app_metadata: user.app_metadata,
user_metadata: user.user_metadata,
id: user.sub,
amr: user.amr,
},
};
}
class AuthenticationError extends Error {
constructor() {
super(`Authentication required`);
}
}
export class MultiFactorAuthError extends Error {
constructor() {
super(`Multi-factor authentication required`);
}
}
function getRedirectTo(path: string, next?: string) {
return path + (next ? `?next=${next}` : '');
}