fix:Harden authentication and sensitive file handling

This commit is contained in:
shuaiplus
2026-06-24 01:44:50 +08:00
committed by Shuai
parent 850fe0f044
commit a2a8f1c7b6
6 changed files with 100 additions and 12 deletions
+26 -2
View File
@@ -123,6 +123,9 @@ import {
ensureUsedAttachmentDownloadTokenTable as ensureStoredAttachmentTokenTable,
consumeAttachmentDownloadToken as consumeStoredAttachmentDownloadToken,
} from './storage-attachment-token-repo';
import {
consumeTotpLoginCounter as consumeStoredTotpLoginCounter,
} from './storage-totp-replay-repo';
import {
getRevisionDate as getStoredRevisionDate,
updateRevisionDate as updateStoredRevisionDate,
@@ -150,8 +153,8 @@ const STORAGE_SCHEMA_VERSION_KEY = 'schema.version';
// Bump this whenever src/services/storage-schema.ts or migrations/0001_init.sql
// changes. Existing D1 installs only rerun ensureStorageSchema() when this value
// differs from config.schema.version.
const STORAGE_SCHEMA_VERSION = '2026-06-23-invite-used-by';
const REQUIRED_SCHEMA_TABLES = ['webauthn_credentials', 'webauthn_challenges', 'auth_requests'] as const;
const STORAGE_SCHEMA_VERSION = '2026-06-23-totp-login-replay';
const REQUIRED_SCHEMA_TABLES = ['webauthn_credentials', 'webauthn_challenges', 'auth_requests', 'totp_login_replays'] as const;
// D1-backed storage.
// Contract:
@@ -164,10 +167,13 @@ export class StorageService {
private static schemaVerified = false;
private static lastRefreshTokenCleanupAt = 0;
private static lastAttachmentTokenCleanupAt = 0;
private static lastTotpReplayCleanupAt = 0;
private static readonly MAX_D1_SQL_VARIABLES = 100;
private static readonly REFRESH_TOKEN_CLEANUP_INTERVAL_MS = LIMITS.cleanup.refreshTokenCleanupIntervalMs;
private static readonly ATTACHMENT_TOKEN_CLEANUP_INTERVAL_MS = LIMITS.cleanup.attachmentTokenCleanupIntervalMs;
private static readonly TOTP_REPLAY_CLEANUP_INTERVAL_MS = 10 * 60 * 1000;
private static readonly TOTP_REPLAY_MARKER_TTL_MS = 5 * 60 * 1000;
private static readonly PERIODIC_CLEANUP_PROBABILITY = LIMITS.cleanup.cleanupProbability;
constructor(private db: D1Database) {}
@@ -833,6 +839,24 @@ export class StorageService {
return findStoredTrustedTokenUserId(this.db, this.trustedTwoFactorTokenKey.bind(this), token, deviceIdentifier);
}
async consumeTotpLoginCounter(userId: string, timeCounter: number, consumedAtMs: number = Date.now()): Promise<boolean> {
if (!Number.isSafeInteger(timeCounter) || timeCounter < 0) return false;
const result = await consumeStoredTotpLoginCounter(
this.db,
this.shouldRunPeriodicCleanup.bind(this),
StorageService.lastTotpReplayCleanupAt,
StorageService.TOTP_REPLAY_CLEANUP_INTERVAL_MS,
userId,
timeCounter,
consumedAtMs,
StorageService.TOTP_REPLAY_MARKER_TTL_MS
);
if (result.cleanedUpAt !== null) {
StorageService.lastTotpReplayCleanupAt = result.cleanedUpAt;
}
return result.consumed;
}
// --- Revision dates ---
async getRevisionDate(userId: string): Promise<string> {