// Two-factor authentication with an authenticator app (TOTP). See lib/two-factor.ts. The fields // are hidden, so only the server (a superuser) reads them, never a user's own token. // // totp_secret: the shared secret once 2FA is on; totp_pending: one being set up, until the first // code confirms it; totp_last_step: the time step of the last code used, so no code works twice; // recovery_codes: hashes of the unused one-time recovery codes. migrate( (app) => { const users = app.findCollectionByNameOrId("users"); users.fields.add(new TextField({ name: "totp_secret", max: 64, hidden: true })); users.fields.add(new TextField({ name: "totp_pending", max: 64, hidden: true })); users.fields.add(new NumberField({ name: "totp_enabled_at", onlyInt: true, min: 0, hidden: true })); users.fields.add(new NumberField({ name: "totp_last_step", onlyInt: true, min: 0, hidden: true })); users.fields.add(new JSONField({ name: "recovery_codes", maxSize: 4000, hidden: true })); app.save(users); }, (app) => { const users = app.findCollectionByNameOrId("users"); for (const name of ["totp_secret", "totp_pending", "totp_enabled_at", "totp_last_step", "recovery_codes"]) users.fields.removeByName(name); app.save(users); }, );