/** * Authentication utilities for postgres.do packages * * This module provides shared authentication utilities including: * - Bearer token extraction from HTTP headers * - User-scoped database ID generation * - OAuth token validation * - Type definitions for authenticated users * * @module @dotdo/postgres-shared/auth */ /** * Authenticated user information */ export interface AuthenticatedUser { /** Unique user identifier */ id: string /** User's email address */ email: string /** User's display name */ name?: string /** Additional user metadata */ metadata?: Record } /** * Token validation result */ export interface AuthTokenValidationResult { valid: boolean user?: AuthenticatedUser error?: string expiresAt?: Date } /** * Options for token validation */ export interface ValidateTokenOptions { /** OAuth provider URL (default: https://oauth.do) */ oauthUrl?: string } /** * Extract bearer token from Authorization header * * Parses the Authorization header and extracts the token if it uses * the Bearer scheme. * * @param request - The HTTP request to extract the token from * @returns The bearer token string or null if not found * * @example * ```typescript * import { extractBearerToken } from '@dotdo/postgres-shared' * * const token = extractBearerToken(request) * if (token) { * // Validate the token * const result = await validateToken(token) * } * ``` */ export function extractBearerToken(request: Request): string | null { const authHeader = request.headers.get('Authorization') if (!authHeader?.startsWith('Bearer ')) { return null } return authHeader.slice(7).trim() } /** * Generate a database ID from user ID * * Creates a deterministic, URL-safe identifier for user-scoped databases. * The generated ID is prefixed with 'user_' and has non-alphanumeric * characters replaced with underscores. * * @param userId - The user's unique identifier * @returns A URL-safe database identifier * * @example * ```typescript * import { generateDatabaseId } from '@dotdo/postgres-shared' * * const dbId = generateDatabaseId('user-123') * // Returns: 'user_user_123' * * const dbId2 = generateDatabaseId('abc@def.com') * // Returns: 'user_abc_def_com' * ``` */ export function generateDatabaseId(userId: string): string { return `user_${userId.replace(/[^a-zA-Z0-9_-]/g, '_')}` } /** * Validate a token against oauth.do * * Makes an HTTP request to the OAuth provider to validate the token * and retrieve user information. * * @param token - The bearer token to validate * @param options - Validation options * @returns The validation result with user info if valid * * @example * ```typescript * import { validateToken } from '@dotdo/postgres-shared' * * const result = await validateToken(token) * if (result.valid && result.user) { * console.log(`Authenticated as: ${result.user.email}`) * } else { * console.error(`Validation failed: ${result.error}`) * } * ``` */ export async function validateToken( token: string, options: ValidateTokenOptions = {} ): Promise { const oauthUrl = options.oauthUrl || 'https://oauth.do' try { const response = await fetch(`${oauthUrl}/api/auth/session`, { headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', }, }) if (!response.ok) { return { valid: false, error: `Token validation failed: ${response.status}`, } } const data = await response.json() as { user?: AuthenticatedUser; expires?: string } if (!data.user) { return { valid: false, error: 'No user in session response', } } const result: AuthTokenValidationResult = { valid: true, user: data.user, } if (data.expires) { result.expiresAt = new Date(data.expires) } return result } catch (error) { return { valid: false, error: `Token validation error: ${error instanceof Error ? error.message : 'Unknown error'}`, } } } /** * Default OAuth URL */ export const DEFAULT_OAUTH_URL = 'https://oauth.do' /** * Default token cache TTL in milliseconds (1 minute) */ export const DEFAULT_TOKEN_CACHE_TTL = 60000