Files
blogflare/src/hooks.server.ts
T

134 lines
3.7 KiB
TypeScript

import { applyCachePolicy, isContentMutation } from '$lib/server/cache';
import { purgePublicCache } from '$lib/server/cloudflare';
import { dev } from '$app/environment';
import { verifyAccessJwt } from '$lib/server/access';
import type { Handle } from '@sveltejs/kit';
function unauthorized(): Response {
return new Response('身份验证失败:Access 凭证校验未通过', {
status: 401,
headers: {
'Cache-Control': 'no-store',
'Content-Type': 'text/plain; charset=utf-8'
}
});
}
function accessNotConfigured(): Response {
return new Response('后台认证尚未配置', {
status: 503,
headers: {
'Cache-Control': 'no-store',
'Content-Type': 'text/plain; charset=utf-8'
}
});
}
const authenticate: Handle = async ({ event, resolve }) => {
if (event.url.pathname.startsWith('/api/v1/')) {
const authHeader = event.request.headers.get('Authorization');
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return new Response(JSON.stringify({ error: 'Unauthorized: Missing or invalid token' }), {
status: 401,
headers: { 'Content-Type': 'application/json' }
});
}
const token = authHeader.slice('Bearer '.length).trim();
const db = event.platform?.env.DB;
if (!db) {
return new Response(JSON.stringify({ error: 'Database not available' }), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
try {
const result = await db
.prepare("SELECT value FROM settings WHERE key = 'agent.api_token'")
.first<{ value: string }>();
const validToken = result?.value;
if (!validToken || token !== validToken) {
return new Response(JSON.stringify({ error: 'Unauthorized: Invalid token' }), {
status: 401,
headers: { 'Content-Type': 'application/json' }
});
}
} catch (err) {
console.error('API token verification failed', err);
return new Response(JSON.stringify({ error: 'Internal Server Error' }), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
return resolve(event);
}
if (!event.url.pathname.startsWith('/admin')) {
return resolve(event);
}
if (dev) {
event.locals.user = {
email: event.request.headers.get('cf-access-authenticated-user-email') || 'dev@local.host'
};
return resolve(event);
}
const token = event.request.headers.get('cf-access-jwt-assertion');
const teamDomain = event.platform?.env.CF_ACCESS_TEAM_DOMAIN;
const audience = event.platform?.env.CF_ACCESS_AUD;
if (!teamDomain || !audience) {
console.error(
JSON.stringify({
message: 'Cloudflare Access configuration is missing',
path: event.url.pathname,
hasTeamDomain: Boolean(teamDomain),
hasAudience: Boolean(audience)
})
);
return accessNotConfigured();
}
if (!token) {
console.error(
JSON.stringify({
message: 'Cloudflare Access JWT is missing',
path: event.url.pathname
})
);
return unauthorized();
}
try {
event.locals.user = {
email: await verifyAccessJwt(token, teamDomain, audience)
};
} catch (cause) {
console.error(
JSON.stringify({
message: 'Cloudflare Access JWT verification failed',
path: event.url.pathname,
error: cause instanceof Error ? cause.message : String(cause)
})
);
return unauthorized();
}
return resolve(event);
};
export const handle: Handle = async (input) => {
const response = await authenticate(input);
const result = applyCachePolicy(input.event, response);
if (!dev && isContentMutation(input.event) && response.status < 400) {
// Await invalidation before completing the save. Failed purges do not undo saved content.
const error = await purgePublicCache(input.event.platform);
result.headers.set('X-Blogflare-Cache-Purge', error ? 'failed' : 'accepted');
}
return result;
};