xtablo-source/apps/api/src/helpers/auth.ts
2025-11-13 09:24:23 +01:00

116 lines
2.9 KiB
TypeScript

import type { SupabaseClient, User } from "@supabase/supabase-js";
/**
* Result of auth header validation
*/
export type AuthHeaderValidationResult =
| { success: true; token: string }
| { success: false; error: string; statusCode: number };
/**
* Result of user authentication
*/
export type AuthResult =
| { success: true; user: User }
| { success: false; error: string; statusCode: number };
/**
* Validates the Authorization header format and extracts the Bearer token
* @param authHeader - The Authorization header value
* @returns Validation result with token or error
*/
export function validateAuthHeader(authHeader: string | undefined): AuthHeaderValidationResult {
// Handle non-string inputs (runtime type safety)
if (typeof authHeader !== "string" || !authHeader) {
return {
success: false,
error: "Missing or invalid authorization header",
statusCode: 401,
};
}
if (!authHeader.startsWith("Bearer ")) {
return {
success: false,
error: "Missing or invalid authorization header",
statusCode: 401,
};
}
const token = authHeader.substring(7); // Remove "Bearer " prefix
if (!token || token.trim() === "") {
return {
success: false,
error: "Missing or invalid authorization header",
statusCode: 401,
};
}
return {
success: true,
token,
};
}
/**
* Authenticates a user with Supabase using a Bearer token
* @param supabase - Supabase client instance
* @param token - The Bearer token to validate
* @returns Auth result with user or error
*/
export async function authenticateUser(
supabase: SupabaseClient,
token: string
): Promise<AuthResult> {
try {
const {
data: { user },
error,
} = await supabase.auth.getUser(token);
if (error || !user) {
return {
success: false,
error: "Invalid or expired token",
statusCode: 401,
};
}
return {
success: true,
user,
};
} catch {
return {
success: false,
error: "Invalid or expired token",
statusCode: 401,
};
}
}
/**
* Complete authentication flow: validates header and authenticates user
* @param authHeader - The Authorization header value
* @param supabase - Supabase client instance
* @returns Auth result with user or error
*/
export async function authenticateFromHeader(
authHeader: string | undefined,
supabase: SupabaseClient
): Promise<AuthResult> {
const headerValidation = validateAuthHeader(authHeader);
if (headerValidation.success) {
return authenticateUser(supabase, headerValidation.token);
}
// headerValidation.success is false, so error and statusCode exist
return {
success: false,
error: (headerValidation as { success: false; error: string; statusCode: number }).error,
statusCode: (headerValidation as { success: false; error: string; statusCode: number })
.statusCode,
};
}