Improve app startup and route fallbacks

This commit is contained in:
shuaiplus
2026-05-04 04:19:02 +08:00
parent 45f0387526
commit 75a6a593dc
14 changed files with 858 additions and 87 deletions
+21 -8
View File
@@ -141,6 +141,26 @@ function normalizeIconHost(rawHost: string): string | null {
}
}
const ICON_UPSTREAM_TIMEOUT_MS = 2500;
async function fetchIconSource(source: { url: string; headers?: HeadersInit }): Promise<Response> {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), ICON_UPSTREAM_TIMEOUT_MS);
try {
return await fetch(source.url, {
headers: source.headers,
redirect: 'follow',
signal: controller.signal,
cf: {
cacheEverything: true,
cacheTtl: LIMITS.cache.iconTtlSeconds,
},
} as RequestInit & { cf: { cacheEverything: boolean; cacheTtl: number } });
} finally {
clearTimeout(timeout);
}
}
async function handleWebsiteIcon(host: string, fallbackMode: 'default' | 'not-found' = 'default'): Promise<Response> {
const normalizedHost = normalizeIconHost(host);
if (!normalizedHost) return fallbackMode === 'not-found' ? handleMissingWebsiteIcon() : handleNwFavicon();
@@ -164,14 +184,7 @@ async function handleWebsiteIcon(host: string, fallbackMode: 'default' | 'not-fo
try {
for (const source of upstreamSources) {
const resp = await fetch(source.url, {
headers: source.headers,
redirect: 'follow',
cf: {
cacheEverything: true,
cacheTtl: LIMITS.cache.iconTtlSeconds,
},
} as RequestInit & { cf: { cacheEverything: boolean; cacheTtl: number } });
const resp = await fetchIconSource(source);
if (!resp.ok) continue;
const contentType = String(resp.headers.get('Content-Type') || '').toLowerCase();
+20 -2
View File
@@ -57,6 +57,12 @@ export class AuthService {
return user;
}
private async getFreshUser(userId: string): Promise<User | null> {
const user = await this.storage.getUserById(userId);
this.writeCachedUser(userId, user);
return user;
}
private readCachedDevice(userId: string, deviceId: string) {
const cacheKey = `${userId}:${deviceId}`;
const cached = AuthService.deviceCache.get(cacheKey);
@@ -84,6 +90,12 @@ export class AuthService {
return device;
}
private async getFreshDevice(userId: string, deviceId: string) {
const device = await this.storage.getDevice(userId, deviceId);
this.writeCachedDevice(userId, deviceId, device);
return device;
}
// Second-layer hash: PBKDF2-SHA256(clientHash, email-salt, iterations).
// Ensures database contents alone cannot be used to authenticate (pass-the-hash defense).
// Result is prefixed with "$s$" to distinguish from legacy raw client hashes.
@@ -162,7 +174,10 @@ export class AuthService {
const payload = await verifyJWT(parts[1], this.env.JWT_SECRET);
if (!payload) return null;
const user = await this.getCachedUser(payload.sub);
let user = await this.getCachedUser(payload.sub);
if (!user || user.status !== 'active' || payload.sstamp !== user.securityStamp) {
user = await this.getFreshUser(payload.sub);
}
if (!user) return null;
if (user.status !== 'active') return null;
@@ -171,7 +186,10 @@ export class AuthService {
}
if (payload.did) {
const device = await this.getCachedDevice(user.id, payload.did);
let device = await this.getCachedDevice(user.id, payload.did);
if (!device || !payload.dstamp || payload.dstamp !== device.sessionStamp) {
device = await this.getFreshDevice(user.id, payload.did);
}
if (!device) return null;
if (!payload.dstamp || payload.dstamp !== device.sessionStamp) return null;
}