Platform backbone (plan 2026-10-13-plattform-rueckgrat), reviewed tasks: - run roles api/worker/job/all with shared startup checks (Task 1) - schema and migration for DomainEvent, deliveries, JobRun, ThrottlerHit (Task 2) - audit, notifications and domain events written in the same transaction (Task 3) - dispatcher with SKIP LOCKED claims, retries and stale release (Task 4) - worker building blocks: tick, LISTEN/NOTIFY wake-up, health (Task 5) - worker process and entry points (Task 6) - throttler counters in Postgres (Task 19) - separate keys per purpose with key ring (Task 20) - public origin page without exact location (Task 22) - shop keys split into read and write (Task 23) - Kustomize base, components, overlay encry, CI validation (Task 26) Field capture T3 (plan 2026-10-13-t3-erfassung-am-stand), reviewed tasks: - brood frames, read endpoints, apiary overview and offline pack - network basics, offline queue v3, navigation sheets, form building blocks - new inspection with last-time hints and weather, varroa per day, treatment prefill, apiary page with round - inspection photo (Task 10) is implemented, review pending Not rolled out. Remaining tasks follow in later commits. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
32 lines
1.1 KiB
TypeScript
32 lines
1.1 KiB
TypeScript
import {
|
|
KEY_ID_PATTERN,
|
|
isSealedWithCurrentKey,
|
|
loadKeyRing,
|
|
openLegacyCiphertext,
|
|
openWithRing,
|
|
sealWithRing,
|
|
} from '../../common/crypto/key-ring';
|
|
|
|
/**
|
|
* TOTP secrets must be reversible (unlike passwords/refresh tokens, which
|
|
* are one-way hashed) so the server can compute the expected code on every
|
|
* login. They are sealed with the MFA key ring (MFA_ENCRYPTION_KEY, see
|
|
* common/crypto/key-ring.ts). Secrets stored before the key ring have no key
|
|
* id and were encrypted with sha256(JWT_ACCESS_SECRET); they stay readable
|
|
* until the rekey job reseals them.
|
|
*/
|
|
export function encryptMfaSecret(plaintext: string): string {
|
|
return sealWithRing(loadKeyRing('mfa'), plaintext);
|
|
}
|
|
|
|
export function decryptMfaSecret(stored: string): string {
|
|
return KEY_ID_PATTERN.test(stored)
|
|
? openWithRing(loadKeyRing('mfa'), stored).plaintext
|
|
: openLegacyCiphertext('mfa', stored);
|
|
}
|
|
|
|
/** True for secrets the rekey job should reseal with the current key. */
|
|
export function mfaSecretNeedsRekey(stored: string): boolean {
|
|
return !isSealedWithCurrentKey(loadKeyRing('mfa'), stored);
|
|
}
|