`validateAuthEnv()` was running at module-load time, which fired during `astro build` while constructing the route manifest — before any actual request needed the secrets. CI build failed even though build-time code doesn't use JWT_SECRET / OAuth secrets. - src/lib/auth/env.ts: wrap authEnv in a Proxy that runs validation on first property read; cache the validated object after. - src/lib/auth/jwt.ts: defer `new TextEncoder().encode(...)` of the secret behind a memoised getSecret() helper. - src/lib/auth/oauth.ts: same for the JWT secret; vkOAuthConfig and yandexOAuthConfig switched to getter-based properties so credential access is also lazy. - src/lib/auth/auth.test.ts: 3 tests previously asserted "throws at module load"; updated to assert "throws on first access" — semantic guarantee (invalid env throws) is preserved. Runtime fail-fast is intact: any auth route reading authEnv.X will throw with the same descriptive Zod error if a var is missing. Build just no longer crashes when secrets aren't in env (e.g. CI deploy pipeline running tsc/lint/build without secrets). Verified: npm run build succeeds with no auth env vars set; tsc 0, lint 0, vitest 339/339. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
46 lines
1.2 KiB
TypeScript
46 lines
1.2 KiB
TypeScript
import { SignJWT, jwtVerify } from 'jose';
|
|
import type { APIContext } from 'astro';
|
|
import { authEnv } from './env';
|
|
|
|
let secretCache: Uint8Array | null = null;
|
|
function getSecret(): Uint8Array {
|
|
if (!secretCache) {
|
|
secretCache = new TextEncoder().encode(authEnv.JWT_SECRET);
|
|
}
|
|
return secretCache;
|
|
}
|
|
|
|
export async function createToken(userId: string): Promise<string> {
|
|
return new SignJWT({ sub: userId })
|
|
.setProtectedHeader({ alg: 'HS256' })
|
|
.setIssuedAt()
|
|
.setExpirationTime('7d')
|
|
.sign(getSecret());
|
|
}
|
|
|
|
export async function verifyToken(token: string): Promise<{ sub: string }> {
|
|
const { payload } = await jwtVerify(token, getSecret(), {
|
|
clockTolerance: 60,
|
|
});
|
|
if (!payload.sub || typeof payload.sub !== 'string') {
|
|
throw new Error('Invalid token payload');
|
|
}
|
|
return { sub: payload.sub };
|
|
}
|
|
|
|
export function setAuthCookie(token: string, context: APIContext): void {
|
|
context.cookies.set('auth_token', token, {
|
|
httpOnly: true,
|
|
secure: true,
|
|
sameSite: 'lax',
|
|
maxAge: 60 * 60 * 24 * 7,
|
|
path: '/',
|
|
});
|
|
}
|
|
|
|
export function clearAuthCookie(context: APIContext): void {
|
|
context.cookies.delete('auth_token', {
|
|
path: '/',
|
|
});
|
|
}
|