2026-06-04 04:10:14 -05:00
|
|
|
'use strict';
|
|
|
|
|
|
refactor(server): migrate 10 services to TypeScript (.cts)
Continues the incremental server TS migration (after utils/*.mts and
paymentValidation.cts): converts 10 services to .cts (CommonJS TypeScript,
run natively via Node type-stripping — no build step), type-checked by
tsconfig.server.json.
Migrated: totpService, amountSuggestionService, encryptionService,
paymentAccountingService, statusService, aprService, driftService,
analyticsService, spendingService, billMerchantRuleService.
- types/db.d.ts: shared minimal better-sqlite3 Db/Statement types (the lib
ships none), reused across the migrated modules.
- Every require of a migrated service updated to the explicit .cts extension
(extensionless require does not resolve .cts) across routes / services /
db migrations / server.js / workers / tests — require-only changes.
- package.json: check:server find pattern now includes *.cts (it silently
skipped .cts before, so paymentValidation.cts had no node --check gate).
Behavior-preserving (types only). Verified: typecheck:server 0,
check:server 0, full server suite 226/226, real boot → /api/health 200.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:35:29 -05:00
|
|
|
import type { Db } from '../types/db';
|
|
|
|
|
|
2026-06-04 04:10:14 -05:00
|
|
|
const crypto = require('crypto');
|
refactor(server): migrate 10 services to TypeScript (.cts)
Continues the incremental server TS migration (after utils/*.mts and
paymentValidation.cts): converts 10 services to .cts (CommonJS TypeScript,
run natively via Node type-stripping — no build step), type-checked by
tsconfig.server.json.
Migrated: totpService, amountSuggestionService, encryptionService,
paymentAccountingService, statusService, aprService, driftService,
analyticsService, spendingService, billMerchantRuleService.
- types/db.d.ts: shared minimal better-sqlite3 Db/Statement types (the lib
ships none), reused across the migrated modules.
- Every require of a migrated service updated to the explicit .cts extension
(extensionless require does not resolve .cts) across routes / services /
db migrations / server.js / workers / tests — require-only changes.
- package.json: check:server find pattern now includes *.cts (it silently
skipped .cts before, so paymentValidation.cts had no node --check gate).
Behavior-preserving (types only). Verified: typecheck:server 0,
check:server 0, full server suite 226/226, real boot → /api/health 200.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:35:29 -05:00
|
|
|
const { generateSecret, generateURI, verifySync } = require('otplib');
|
2026-06-04 04:10:14 -05:00
|
|
|
const QRCode = require('qrcode');
|
refactor(server): migrate 10 services to TypeScript (.cts)
Continues the incremental server TS migration (after utils/*.mts and
paymentValidation.cts): converts 10 services to .cts (CommonJS TypeScript,
run natively via Node type-stripping — no build step), type-checked by
tsconfig.server.json.
Migrated: totpService, amountSuggestionService, encryptionService,
paymentAccountingService, statusService, aprService, driftService,
analyticsService, spendingService, billMerchantRuleService.
- types/db.d.ts: shared minimal better-sqlite3 Db/Statement types (the lib
ships none), reused across the migrated modules.
- Every require of a migrated service updated to the explicit .cts extension
(extensionless require does not resolve .cts) across routes / services /
db migrations / server.js / workers / tests — require-only changes.
- package.json: check:server find pattern now includes *.cts (it silently
skipped .cts before, so paymentValidation.cts had no node --check gate).
Behavior-preserving (types only). Verified: typecheck:server 0,
check:server 0, full server suite 226/226, real boot → /api/health 200.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:35:29 -05:00
|
|
|
const { encryptSecret, decryptSecret } = require('./encryptionService.cts');
|
2026-06-04 04:10:14 -05:00
|
|
|
|
|
|
|
|
const APP_NAME = 'Bill Tracker';
|
|
|
|
|
const RECOVERY_CODE_COUNT = 8;
|
2026-06-04 04:16:20 -05:00
|
|
|
const CHALLENGE_TTL_MS = 5 * 60 * 1000;
|
2026-06-04 04:10:14 -05:00
|
|
|
|
refactor(server): migrate 10 services to TypeScript (.cts)
Continues the incremental server TS migration (after utils/*.mts and
paymentValidation.cts): converts 10 services to .cts (CommonJS TypeScript,
run natively via Node type-stripping — no build step), type-checked by
tsconfig.server.json.
Migrated: totpService, amountSuggestionService, encryptionService,
paymentAccountingService, statusService, aprService, driftService,
analyticsService, spendingService, billMerchantRuleService.
- types/db.d.ts: shared minimal better-sqlite3 Db/Statement types (the lib
ships none), reused across the migrated modules.
- Every require of a migrated service updated to the explicit .cts extension
(extensionless require does not resolve .cts) across routes / services /
db migrations / server.js / workers / tests — require-only changes.
- package.json: check:server find pattern now includes *.cts (it silently
skipped .cts before, so paymentValidation.cts had no node --check gate).
Behavior-preserving (types only). Verified: typecheck:server 0,
check:server 0, full server suite 226/226, real boot → /api/health 200.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:35:29 -05:00
|
|
|
function newSecret(): string {
|
2026-06-04 04:16:20 -05:00
|
|
|
return generateSecret(20);
|
2026-06-04 04:10:14 -05:00
|
|
|
}
|
|
|
|
|
|
2026-07-06 14:23:53 -05:00
|
|
|
async function generateQrCode(
|
|
|
|
|
secret: string,
|
|
|
|
|
username: string,
|
|
|
|
|
): Promise<{ uri: string; qr_data_url: string }> {
|
2026-06-04 04:16:20 -05:00
|
|
|
const uri = generateURI({ secret, account: username, issuer: APP_NAME, type: 'totp' });
|
|
|
|
|
const qr_data_url = await QRCode.toDataURL(uri, { width: 200, margin: 2 });
|
|
|
|
|
return { uri, qr_data_url };
|
2026-06-04 04:10:14 -05:00
|
|
|
}
|
|
|
|
|
|
2026-07-06 14:23:53 -05:00
|
|
|
function verifyToken(
|
|
|
|
|
encryptedSecret: string | null | undefined,
|
|
|
|
|
token: string | number | null | undefined,
|
|
|
|
|
): boolean {
|
2026-06-04 04:10:14 -05:00
|
|
|
if (!encryptedSecret || !token) return false;
|
|
|
|
|
try {
|
|
|
|
|
const secret = decryptSecret(encryptedSecret);
|
2026-06-04 04:16:20 -05:00
|
|
|
const result = verifySync({ secret, token: String(token).replace(/\s/g, ''), type: 'totp' });
|
|
|
|
|
return result?.valid === true;
|
2026-06-04 04:10:14 -05:00
|
|
|
} catch {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-06 14:23:53 -05:00
|
|
|
function verifyTokenRaw(
|
|
|
|
|
secret: string | null | undefined,
|
|
|
|
|
token: string | number | null | undefined,
|
|
|
|
|
): boolean {
|
2026-06-04 04:10:14 -05:00
|
|
|
if (!secret || !token) return false;
|
|
|
|
|
try {
|
2026-06-04 04:16:20 -05:00
|
|
|
const result = verifySync({ secret, token: String(token).replace(/\s/g, ''), type: 'totp' });
|
|
|
|
|
return result?.valid === true;
|
2026-06-04 04:10:14 -05:00
|
|
|
} catch {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
refactor(server): migrate 10 services to TypeScript (.cts)
Continues the incremental server TS migration (after utils/*.mts and
paymentValidation.cts): converts 10 services to .cts (CommonJS TypeScript,
run natively via Node type-stripping — no build step), type-checked by
tsconfig.server.json.
Migrated: totpService, amountSuggestionService, encryptionService,
paymentAccountingService, statusService, aprService, driftService,
analyticsService, spendingService, billMerchantRuleService.
- types/db.d.ts: shared minimal better-sqlite3 Db/Statement types (the lib
ships none), reused across the migrated modules.
- Every require of a migrated service updated to the explicit .cts extension
(extensionless require does not resolve .cts) across routes / services /
db migrations / server.js / workers / tests — require-only changes.
- package.json: check:server find pattern now includes *.cts (it silently
skipped .cts before, so paymentValidation.cts had no node --check gate).
Behavior-preserving (types only). Verified: typecheck:server 0,
check:server 0, full server suite 226/226, real boot → /api/health 200.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:35:29 -05:00
|
|
|
function makeRecoveryCodes(): string[] {
|
2026-06-04 04:10:14 -05:00
|
|
|
return Array.from({ length: RECOVERY_CODE_COUNT }, () => {
|
|
|
|
|
const bytes = crypto.randomBytes(5);
|
|
|
|
|
const hex = bytes.toString('hex').toUpperCase();
|
|
|
|
|
return `${hex.slice(0, 5)}-${hex.slice(5)}`;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
refactor(server): migrate 10 services to TypeScript (.cts)
Continues the incremental server TS migration (after utils/*.mts and
paymentValidation.cts): converts 10 services to .cts (CommonJS TypeScript,
run natively via Node type-stripping — no build step), type-checked by
tsconfig.server.json.
Migrated: totpService, amountSuggestionService, encryptionService,
paymentAccountingService, statusService, aprService, driftService,
analyticsService, spendingService, billMerchantRuleService.
- types/db.d.ts: shared minimal better-sqlite3 Db/Statement types (the lib
ships none), reused across the migrated modules.
- Every require of a migrated service updated to the explicit .cts extension
(extensionless require does not resolve .cts) across routes / services /
db migrations / server.js / workers / tests — require-only changes.
- package.json: check:server find pattern now includes *.cts (it silently
skipped .cts before, so paymentValidation.cts had no node --check gate).
Behavior-preserving (types only). Verified: typecheck:server 0,
check:server 0, full server suite 226/226, real boot → /api/health 200.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:35:29 -05:00
|
|
|
function hashRecoveryCode(code: string): string {
|
2026-06-04 04:10:14 -05:00
|
|
|
return crypto.createHash('sha256').update(code.replace(/-/g, '').toUpperCase()).digest('hex');
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-06 14:23:53 -05:00
|
|
|
function consumeRecoveryCode(
|
|
|
|
|
db: Db,
|
|
|
|
|
userId: number,
|
|
|
|
|
code: string,
|
|
|
|
|
): { used: boolean; remaining?: number } {
|
2026-06-04 04:10:14 -05:00
|
|
|
const normalized = code.replace(/[-\s]/g, '').toUpperCase();
|
|
|
|
|
const user = db.prepare('SELECT totp_recovery_codes FROM users WHERE id = ?').get(userId);
|
|
|
|
|
if (!user?.totp_recovery_codes) return { used: false };
|
|
|
|
|
|
refactor(server): migrate 10 services to TypeScript (.cts)
Continues the incremental server TS migration (after utils/*.mts and
paymentValidation.cts): converts 10 services to .cts (CommonJS TypeScript,
run natively via Node type-stripping — no build step), type-checked by
tsconfig.server.json.
Migrated: totpService, amountSuggestionService, encryptionService,
paymentAccountingService, statusService, aprService, driftService,
analyticsService, spendingService, billMerchantRuleService.
- types/db.d.ts: shared minimal better-sqlite3 Db/Statement types (the lib
ships none), reused across the migrated modules.
- Every require of a migrated service updated to the explicit .cts extension
(extensionless require does not resolve .cts) across routes / services /
db migrations / server.js / workers / tests — require-only changes.
- package.json: check:server find pattern now includes *.cts (it silently
skipped .cts before, so paymentValidation.cts had no node --check gate).
Behavior-preserving (types only). Verified: typecheck:server 0,
check:server 0, full server suite 226/226, real boot → /api/health 200.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:35:29 -05:00
|
|
|
let stored: string[];
|
2026-06-04 04:10:14 -05:00
|
|
|
try {
|
|
|
|
|
stored = JSON.parse(decryptSecret(user.totp_recovery_codes));
|
|
|
|
|
} catch {
|
|
|
|
|
return { used: false };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const incomingHash = crypto.createHash('sha256').update(normalized).digest('hex');
|
2026-07-06 14:23:53 -05:00
|
|
|
const idx = stored.findIndex((h) => h === incomingHash);
|
2026-06-04 04:10:14 -05:00
|
|
|
if (idx === -1) return { used: false };
|
|
|
|
|
|
|
|
|
|
stored.splice(idx, 1);
|
2026-07-06 14:23:53 -05:00
|
|
|
db.prepare('UPDATE users SET totp_recovery_codes = ? WHERE id = ?').run(
|
|
|
|
|
encryptSecret(JSON.stringify(stored)),
|
|
|
|
|
userId,
|
|
|
|
|
);
|
2026-06-04 04:10:14 -05:00
|
|
|
|
|
|
|
|
return { used: true, remaining: stored.length };
|
|
|
|
|
}
|
|
|
|
|
|
refactor(server): migrate 10 services to TypeScript (.cts)
Continues the incremental server TS migration (after utils/*.mts and
paymentValidation.cts): converts 10 services to .cts (CommonJS TypeScript,
run natively via Node type-stripping — no build step), type-checked by
tsconfig.server.json.
Migrated: totpService, amountSuggestionService, encryptionService,
paymentAccountingService, statusService, aprService, driftService,
analyticsService, spendingService, billMerchantRuleService.
- types/db.d.ts: shared minimal better-sqlite3 Db/Statement types (the lib
ships none), reused across the migrated modules.
- Every require of a migrated service updated to the explicit .cts extension
(extensionless require does not resolve .cts) across routes / services /
db migrations / server.js / workers / tests — require-only changes.
- package.json: check:server find pattern now includes *.cts (it silently
skipped .cts before, so paymentValidation.cts had no node --check gate).
Behavior-preserving (types only). Verified: typecheck:server 0,
check:server 0, full server suite 226/226, real boot → /api/health 200.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:35:29 -05:00
|
|
|
function createChallenge(db: Db, userId: number): string {
|
2026-06-04 04:10:14 -05:00
|
|
|
const id = crypto.randomUUID();
|
|
|
|
|
const expiresAt = new Date(Date.now() + CHALLENGE_TTL_MS)
|
2026-07-06 14:23:53 -05:00
|
|
|
.toISOString()
|
|
|
|
|
.slice(0, 19)
|
|
|
|
|
.replace('T', ' ');
|
2026-06-04 04:10:14 -05:00
|
|
|
db.prepare('DELETE FROM totp_challenges WHERE user_id = ?').run(userId);
|
2026-07-06 14:23:53 -05:00
|
|
|
db.prepare('INSERT INTO totp_challenges (id, user_id, expires_at) VALUES (?, ?, ?)').run(
|
|
|
|
|
id,
|
|
|
|
|
userId,
|
|
|
|
|
expiresAt,
|
|
|
|
|
);
|
2026-06-04 04:10:14 -05:00
|
|
|
return id;
|
|
|
|
|
}
|
|
|
|
|
|
refactor(server): migrate 10 services to TypeScript (.cts)
Continues the incremental server TS migration (after utils/*.mts and
paymentValidation.cts): converts 10 services to .cts (CommonJS TypeScript,
run natively via Node type-stripping — no build step), type-checked by
tsconfig.server.json.
Migrated: totpService, amountSuggestionService, encryptionService,
paymentAccountingService, statusService, aprService, driftService,
analyticsService, spendingService, billMerchantRuleService.
- types/db.d.ts: shared minimal better-sqlite3 Db/Statement types (the lib
ships none), reused across the migrated modules.
- Every require of a migrated service updated to the explicit .cts extension
(extensionless require does not resolve .cts) across routes / services /
db migrations / server.js / workers / tests — require-only changes.
- package.json: check:server find pattern now includes *.cts (it silently
skipped .cts before, so paymentValidation.cts had no node --check gate).
Behavior-preserving (types only). Verified: typecheck:server 0,
check:server 0, full server suite 226/226, real boot → /api/health 200.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:35:29 -05:00
|
|
|
function consumeChallenge(db: Db, challengeId: string): number | null {
|
2026-07-06 14:23:53 -05:00
|
|
|
const row = db
|
|
|
|
|
.prepare("SELECT user_id FROM totp_challenges WHERE id = ? AND expires_at > datetime('now')")
|
|
|
|
|
.get(challengeId);
|
2026-06-04 04:10:14 -05:00
|
|
|
if (!row) return null;
|
|
|
|
|
db.prepare('DELETE FROM totp_challenges WHERE id = ?').run(challengeId);
|
|
|
|
|
return row.user_id;
|
|
|
|
|
}
|
|
|
|
|
|
refactor(server): migrate 10 services to TypeScript (.cts)
Continues the incremental server TS migration (after utils/*.mts and
paymentValidation.cts): converts 10 services to .cts (CommonJS TypeScript,
run natively via Node type-stripping — no build step), type-checked by
tsconfig.server.json.
Migrated: totpService, amountSuggestionService, encryptionService,
paymentAccountingService, statusService, aprService, driftService,
analyticsService, spendingService, billMerchantRuleService.
- types/db.d.ts: shared minimal better-sqlite3 Db/Statement types (the lib
ships none), reused across the migrated modules.
- Every require of a migrated service updated to the explicit .cts extension
(extensionless require does not resolve .cts) across routes / services /
db migrations / server.js / workers / tests — require-only changes.
- package.json: check:server find pattern now includes *.cts (it silently
skipped .cts before, so paymentValidation.cts had no node --check gate).
Behavior-preserving (types only). Verified: typecheck:server 0,
check:server 0, full server suite 226/226, real boot → /api/health 200.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:35:29 -05:00
|
|
|
function pruneExpiredChallenges(db: Db): void {
|
2026-06-04 04:10:14 -05:00
|
|
|
db.prepare("DELETE FROM totp_challenges WHERE expires_at <= datetime('now')").run();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
module.exports = {
|
2026-06-04 04:16:20 -05:00
|
|
|
generateSecret: newSecret,
|
2026-06-04 04:10:14 -05:00
|
|
|
generateQrCode,
|
|
|
|
|
verifyToken,
|
|
|
|
|
verifyTokenRaw,
|
2026-06-04 04:16:20 -05:00
|
|
|
generateRecoveryCodes: makeRecoveryCodes,
|
2026-06-04 04:10:14 -05:00
|
|
|
hashRecoveryCode,
|
|
|
|
|
consumeRecoveryCode,
|
|
|
|
|
createChallenge,
|
|
|
|
|
consumeChallenge,
|
|
|
|
|
pruneExpiredChallenges,
|
|
|
|
|
};
|