You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
184 lines
5.1 KiB
184 lines
5.1 KiB
/** |
|
* API Context Utilities |
|
* Extract and validate common request context from SvelteKit requests |
|
*/ |
|
|
|
import type { RequestEvent } from '@sveltejs/kit'; |
|
import { requireNpubHex, decodeNpubToHex } from './npub-utils.js'; |
|
|
|
// Re-export RequestEvent for convenience |
|
export type { RequestEvent }; |
|
|
|
/** |
|
* Extracted request context |
|
*/ |
|
export interface RequestContext { |
|
userPubkey: string | null; |
|
userPubkeyHex: string | null; |
|
clientIp: string; |
|
ref?: string; |
|
path?: string; |
|
branch?: string; |
|
limit?: number; |
|
[key: string]: unknown; |
|
} |
|
|
|
/** |
|
* Repository context with validated parameters |
|
*/ |
|
export interface RepoContext { |
|
npub: string; |
|
repo: string; |
|
repoOwnerPubkey: string; |
|
} |
|
|
|
/** |
|
* Combined context for repository operations |
|
*/ |
|
export interface RepoRequestContext extends RequestContext, RepoContext {} |
|
|
|
/** |
|
* Extract common request context from a SvelteKit request |
|
* |
|
* @param event - SvelteKit request event |
|
* @param url - URL object (can be extracted from event.url) |
|
* @returns Extracted request context |
|
*/ |
|
export function extractRequestContext( |
|
event: RequestEvent, |
|
url?: URL |
|
): RequestContext { |
|
const requestUrl = url || event.url; |
|
|
|
// Extract user pubkey from query params or headers (support both lowercase and capitalized) |
|
const userPubkey = requestUrl.searchParams.get('userPubkey') || |
|
event.request.headers.get('X-User-Pubkey') || |
|
event.request.headers.get('x-user-pubkey') || |
|
null; |
|
|
|
// Debug logging |
|
if (userPubkey) { |
|
console.debug('[API Context] Extracted userPubkey from request:', userPubkey.substring(0, 16) + '...'); |
|
} else { |
|
console.debug('[API Context] No userPubkey found in request headers or query params'); |
|
// Log all headers for debugging |
|
const allHeaders: Record<string, string> = {}; |
|
event.request.headers.forEach((value, key) => { |
|
allHeaders[key] = value; |
|
}); |
|
console.debug('[API Context] Request headers:', allHeaders); |
|
} |
|
|
|
// Convert to hex if needed |
|
const userPubkeyHex = userPubkey ? (decodeNpubToHex(userPubkey) || userPubkey) : null; |
|
|
|
if (userPubkeyHex) { |
|
console.debug('[API Context] Converted to hex:', userPubkeyHex.substring(0, 16) + '...'); |
|
} |
|
|
|
// Extract client IP |
|
let clientIp: string; |
|
try { |
|
clientIp = event.getClientAddress(); |
|
} catch { |
|
// Fallback for internal Vite dev server requests or when client address can't be determined |
|
clientIp = event.request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() || |
|
event.request.headers.get('x-real-ip') || |
|
'127.0.0.1'; |
|
} |
|
|
|
// Extract common query parameters |
|
// Normalize 'null' string to undefined (query params are always strings) |
|
const refParam = requestUrl.searchParams.get('ref'); |
|
const ref = (refParam && refParam !== 'null') ? refParam : undefined; |
|
const pathParam = requestUrl.searchParams.get('path'); |
|
const path = (pathParam && pathParam !== 'null') ? pathParam : undefined; |
|
const branchParam = requestUrl.searchParams.get('branch'); |
|
const branch = (branchParam && branchParam !== 'null') ? branchParam : undefined; |
|
const limit = requestUrl.searchParams.get('limit') |
|
? parseInt(requestUrl.searchParams.get('limit')!, 10) |
|
: undefined; |
|
|
|
return { |
|
userPubkey, |
|
userPubkeyHex, |
|
clientIp, |
|
ref, |
|
path, |
|
branch, |
|
limit |
|
}; |
|
} |
|
|
|
/** |
|
* Validate and extract repository context from route parameters |
|
* |
|
* @param params - Route parameters (from SvelteKit) |
|
* @returns Validated repository context |
|
* @throws Error if validation fails |
|
*/ |
|
export function validateRepoParams(params: { npub?: string; repo?: string }): RepoContext { |
|
const { npub, repo } = params; |
|
|
|
if (!npub || !repo) { |
|
throw new Error('Missing npub or repo parameter'); |
|
} |
|
|
|
// Validate and convert npub to pubkey |
|
let repoOwnerPubkey: string; |
|
try { |
|
repoOwnerPubkey = requireNpubHex(npub); |
|
} catch { |
|
throw new Error(`Invalid npub format: ${npub}`); |
|
} |
|
|
|
return { |
|
npub, |
|
repo, |
|
repoOwnerPubkey |
|
}; |
|
} |
|
|
|
/** |
|
* Get combined repository and request context |
|
* Combines parameter validation with request context extraction |
|
* |
|
* @param event - SvelteKit request event |
|
* @param params - Route parameters |
|
* @returns Combined repository and request context |
|
*/ |
|
export function getRepoContext( |
|
event: RequestEvent, |
|
params: { npub?: string; repo?: string } |
|
): RepoRequestContext { |
|
const requestContext = extractRequestContext(event); |
|
const repoContext = validateRepoParams(params); |
|
|
|
return { |
|
...requestContext, |
|
...repoContext |
|
}; |
|
} |
|
|
|
/** |
|
* Extract user pubkey from request (convenience function) |
|
*/ |
|
export function getUserPubkey(event: RequestEvent): string | null { |
|
const url = event.url; |
|
return url.searchParams.get('userPubkey') || |
|
event.request.headers.get('x-user-pubkey') || |
|
null; |
|
} |
|
|
|
/** |
|
* Extract client IP from request (convenience function) |
|
*/ |
|
export function getClientIp(event: RequestEvent): string { |
|
try { |
|
return event.getClientAddress(); |
|
} catch { |
|
return event.request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() || |
|
event.request.headers.get('x-real-ip') || |
|
'127.0.0.1'; |
|
} |
|
}
|
|
|