116 lines
2.9 KiB
TypeScript
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,
|
|
};
|
|
}
|