@nacre.work/core 0.17.5 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.d.ts +33 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +114 -0
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/mail.d.ts +43 -0
- package/dist/mail.d.ts.map +1 -0
- package/dist/mail.js +92 -0
- package/dist/mail.js.map +1 -0
- package/dist/migrations/0029_second_factor.sql +111 -0
- package/dist/migrations/0030_password_reset.sql +59 -0
- package/dist/passwords.d.ts.map +1 -1
- package/dist/passwords.js +8 -0
- package/dist/passwords.js.map +1 -1
- package/dist/totp.d.ts +79 -0
- package/dist/totp.d.ts.map +1 -0
- package/dist/totp.js +206 -0
- package/dist/totp.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -1
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
-- 0030 — a password somebody can recover, without a `psql` session.
|
|
2
|
+
--
|
|
3
|
+
-- `POST /v1/users/{id}/password` has existed since there were users, and it is
|
|
4
|
+
-- an *administrator* setting somebody else's. The person who forgot theirs at
|
|
5
|
+
-- the weekend had no route at all, and on a single-administrator installation
|
|
6
|
+
-- — which the open core mostly is — the administrator who forgets theirs had
|
|
7
|
+
-- no route that did not go through the database.
|
|
8
|
+
--
|
|
9
|
+
-- The specification is docs/api.md, "Recovering a password".
|
|
10
|
+
|
|
11
|
+
-- ─────────── the token ───────────
|
|
12
|
+
--
|
|
13
|
+
-- One row per issued link, spent once.
|
|
14
|
+
--
|
|
15
|
+
-- **No cross-tenant read, and that is why the token carries its organization.**
|
|
16
|
+
-- Resolving a credential outside `withOrg` has exactly two mechanisms here and
|
|
17
|
+
-- both are narrow on purpose — `0008`'s own words are that `users` gets neither
|
|
18
|
+
-- "as a decision rather than an omission". A token shaped `<org_id>.<secret>`
|
|
19
|
+
-- means redemption knows the organization before it reads anything, so this
|
|
20
|
+
-- table is read through `withOrg` like every other, and nothing here opens a
|
|
21
|
+
-- second door into a table that a stranger can reach unauthenticated.
|
|
22
|
+
--
|
|
23
|
+
-- The organization id is not a secret from the person holding the link: it is
|
|
24
|
+
-- in their own `/v1/me`. What is secret is the half beside it.
|
|
25
|
+
CREATE TABLE password_reset_tokens (
|
|
26
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
27
|
+
org_id uuid NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
|
28
|
+
user_id uuid NOT NULL,
|
|
29
|
+
|
|
30
|
+
-- SHA-256 of the whole token. Fast on purpose: the secret half is 32 bytes
|
|
31
|
+
-- from the CSPRNG, so there is no dictionary to slow down — the same
|
|
32
|
+
-- reasoning the recovery codes in 0029 carry, and the opposite of a password.
|
|
33
|
+
token_hash text NOT NULL,
|
|
34
|
+
|
|
35
|
+
expires_at timestamptz NOT NULL,
|
|
36
|
+
-- Spent by the UPDATE that finds it, so two requests cannot spend one.
|
|
37
|
+
used_at timestamptz,
|
|
38
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
39
|
+
|
|
40
|
+
FOREIGN KEY (user_id, org_id) REFERENCES users (id, org_id) ON DELETE CASCADE,
|
|
41
|
+
|
|
42
|
+
-- Unique across the installation, so redemption is one statement with no read
|
|
43
|
+
-- in front of it that another request could race.
|
|
44
|
+
UNIQUE (token_hash)
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
-- The sweep reads by expiry; redemption reads by hash, which the constraint
|
|
48
|
+
-- above already indexes.
|
|
49
|
+
CREATE INDEX ON password_reset_tokens (expires_at);
|
|
50
|
+
|
|
51
|
+
ALTER TABLE password_reset_tokens ENABLE ROW LEVEL SECURITY;
|
|
52
|
+
ALTER TABLE password_reset_tokens FORCE ROW LEVEL SECURITY;
|
|
53
|
+
CREATE POLICY org_isolation ON password_reset_tokens
|
|
54
|
+
USING (org_id = current_setting('app.current_org')::uuid);
|
|
55
|
+
|
|
56
|
+
-- The application issues, spends and prunes them. Not granted to
|
|
57
|
+
-- `nacre_worker`: the worker has no question about who is signing in, and a
|
|
58
|
+
-- privilege nothing reads is the shape this repository keeps removing.
|
|
59
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON password_reset_tokens TO nacre_app;
|
package/dist/passwords.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passwords.d.ts","sourceRoot":"","sources":["../passwords.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"passwords.d.ts","sourceRoot":"","sources":["../passwords.ts"],"names":[],"mappings":"AA4GA,iFAAiF;AACjF,qBAAa,OAAQ,SAAQ,KAAK;;CAKjC;AAyCD,iEAAiE;AACjE,eAAO,MAAM,WAAW,QAAO;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAI5E,CAAA;AAEF;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAWpE;AAmCD;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAexF;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAIpD;AAgBD,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAI5E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,cAAc,qsBAUjB,CAAA;AAEV,qCAAqC;AACrC,eAAO,MAAM,mBAAmB,IAAI,CAAA;AAKpC;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,QACgD,CAAA;AAElF,wBAAgB,gBAAgB,IAAI,MAAM,CAQzC"}
|
package/dist/passwords.js
CHANGED
|
@@ -76,6 +76,14 @@ const MAX_MEM = 256 * 1024 * 1024;
|
|
|
76
76
|
* Refusing is not an oracle. It depends on how loaded the process is and not at
|
|
77
77
|
* all on whether the account exists, and it is returned identically to the
|
|
78
78
|
* caller whether the address matched a user or not.
|
|
79
|
+
*
|
|
80
|
+
* The 503 is `errors.ts`'s `tooBusy`, sent from the two error boundaries in
|
|
81
|
+
* `server.ts` — sign-in is reached without a credential and everything else
|
|
82
|
+
* with one, so a single catch cannot cover both. That is worth naming here
|
|
83
|
+
* because this paragraph claimed "the caller answers 503" while exactly one of
|
|
84
|
+
* the four routes that reach this gate did: the other three — creating a user,
|
|
85
|
+
* an administrator resetting a password, and redeeming a recovery link — turned
|
|
86
|
+
* a loaded process into a 500.
|
|
79
87
|
*/
|
|
80
88
|
const poolSize = Number(process.env.UV_THREADPOOL_SIZE ?? 4);
|
|
81
89
|
const MAX_CONCURRENT = Math.max(1, Math.floor((Number.isFinite(poolSize) ? poolSize : 4) / 2));
|
package/dist/passwords.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passwords.js","sourceRoot":"","sources":["../passwords.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,SAAS,EACT,MAAM,IAAI,cAAc,EACxB,eAAe,GAEhB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAErC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,8EAA8E;AAC9E,iDAAiD;AACjD,MAAM,MAAM,GAAG,SAAS,CAAC,cAAc,CAKnB,CAAA;AAEpB,oDAAoD;AACpD,MAAM,CAAC,GAAG,OAAO,CAAA;AACjB,MAAM,CAAC,GAAG,CAAC,CAAA;AACX,MAAM,CAAC,GAAG,CAAC,CAAA;AACX,MAAM,SAAS,GAAG,EAAE,CAAA;AACpB,MAAM,UAAU,GAAG,EAAE,CAAA;AAErB;;;;;GAKG;AACH,MAAM,OAAO,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAA;AAEjC
|
|
1
|
+
{"version":3,"file":"passwords.js","sourceRoot":"","sources":["../passwords.ts"],"names":[],"mappings":"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"}
|
package/dist/totp.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/** RFC 6238's default and what every authenticator assumes. */
|
|
2
|
+
export declare const TOTP_PERIOD_SECONDS = 30;
|
|
3
|
+
/** Six, for the same reason: it is what the apps show. */
|
|
4
|
+
export declare const TOTP_DIGITS = 6;
|
|
5
|
+
/**
|
|
6
|
+
* How far either side of now a code is accepted.
|
|
7
|
+
*
|
|
8
|
+
* One step, which is up to sixty seconds of tolerance in the worst alignment.
|
|
9
|
+
* Phones drift and people type slowly; two steps is the number that starts
|
|
10
|
+
* making a shoulder-surfed code useful for longer than it should be.
|
|
11
|
+
*/
|
|
12
|
+
export declare const TOTP_SKEW_STEPS = 1;
|
|
13
|
+
export declare function base32Encode(bytes: Uint8Array): string;
|
|
14
|
+
export declare function base32Decode(text: string): Buffer;
|
|
15
|
+
/**
|
|
16
|
+
* 160 bits, which is what RFC 4226 recommends and what the apps expect.
|
|
17
|
+
*
|
|
18
|
+
* Shorter secrets are accepted by every authenticator and are the reason a
|
|
19
|
+
* generator is here rather than left to a call site: 80 bits is legal, common
|
|
20
|
+
* in examples, and half the strength of the thing it is protecting.
|
|
21
|
+
*/
|
|
22
|
+
export declare function generateTotpSecret(): string;
|
|
23
|
+
/** The step a moment falls in. Exported because the replay bound is a step. */
|
|
24
|
+
export declare const totpStep: (at?: Date) => number;
|
|
25
|
+
/** RFC 6238 over RFC 4226: HMAC the counter, take the dynamic offset, mod 10^d. */
|
|
26
|
+
export declare function totpCode(secret: string, step: number): string;
|
|
27
|
+
export interface TotpVerification {
|
|
28
|
+
/** The step the code belonged to, to be stored so it cannot be spent twice. */
|
|
29
|
+
readonly step: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Check a code, and say which step it was.
|
|
33
|
+
*
|
|
34
|
+
* `after` is the last step this factor already accepted, and a code at or
|
|
35
|
+
* before it is refused however correct it is. Without that, a code is good for
|
|
36
|
+
* the whole window it was shown in — so somebody who reads it over a shoulder,
|
|
37
|
+
* or a phishing page that relays it, gets a second use out of it. The caller
|
|
38
|
+
* stores the returned step.
|
|
39
|
+
*/
|
|
40
|
+
export declare function verifyTotp(secret: string, code: string, options?: {
|
|
41
|
+
readonly at?: Date;
|
|
42
|
+
readonly after?: number | null;
|
|
43
|
+
}): TotpVerification | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* The URL an authenticator reads out of a QR code.
|
|
46
|
+
*
|
|
47
|
+
* The label carries the issuer as well as the account, which is what makes two
|
|
48
|
+
* installations distinguishable in a list of thirty entries — an app shows the
|
|
49
|
+
* label, and "dana@example.com" alone is four identical rows.
|
|
50
|
+
*/
|
|
51
|
+
export declare function otpauthUrl(options: {
|
|
52
|
+
readonly issuer: string;
|
|
53
|
+
readonly account: string;
|
|
54
|
+
readonly secret: string;
|
|
55
|
+
}): string;
|
|
56
|
+
export declare function sealTotpSecret(secret: string, key: Buffer): string;
|
|
57
|
+
export declare function openTotpSecret(sealed: string, key: Buffer): string;
|
|
58
|
+
/** Ten, which is what a person can print on one line each and lose half of. */
|
|
59
|
+
export declare const RECOVERY_CODE_COUNT = 10;
|
|
60
|
+
/**
|
|
61
|
+
* A recovery code, in the shape people retype correctly.
|
|
62
|
+
*
|
|
63
|
+
* Four groups of five from a 32-character alphabet is 100 bits — far past
|
|
64
|
+
* anything guessable — and the groups exist because a twenty-character run
|
|
65
|
+
* gets transcribed wrongly and blamed on the product.
|
|
66
|
+
*/
|
|
67
|
+
export declare function generateRecoveryCode(): string;
|
|
68
|
+
export declare const normalizeRecoveryCode: (code: string) => string;
|
|
69
|
+
/**
|
|
70
|
+
* Hashed with SHA-256 and deliberately not with scrypt.
|
|
71
|
+
*
|
|
72
|
+
* A recovery code is 100 bits from the CSPRNG, so there is no dictionary to
|
|
73
|
+
* slow down and no human-chosen password to protect. A cost parameter would buy
|
|
74
|
+
* nothing against an attacker and would make issuing ten of them a second of
|
|
75
|
+
* CPU on a request — on the same libuv pool the sign-in path already has to be
|
|
76
|
+
* careful about.
|
|
77
|
+
*/
|
|
78
|
+
export declare function hashRecoveryCode(code: string): string;
|
|
79
|
+
//# sourceMappingURL=totp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"totp.d.ts","sourceRoot":"","sources":["../totp.ts"],"names":[],"mappings":"AA8BA,+DAA+D;AAC/D,eAAO,MAAM,mBAAmB,KAAK,CAAA;AACrC,0DAA0D;AAC1D,eAAO,MAAM,WAAW,IAAI,CAAA;AAE5B;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,IAAI,CAAA;AAKhC,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CActD;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAoBjD;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED,+EAA+E;AAC/E,eAAO,MAAM,QAAQ,GAAI,KAAI,IAAiB,KAAG,MACM,CAAA;AAEvD,mFAAmF;AACnF,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAQ7D;AAED,MAAM,WAAW,gBAAgB;IAC/B,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAO,GACnE,gBAAgB,GAAG,SAAS,CAe9B;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB,GAAG,MAAM,CAUT;AAiBD,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAMlE;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAQlE;AAID,+EAA+E;AAC/E,eAAO,MAAM,mBAAmB,KAAK,CAAA;AAErC;;;;;;GAMG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAI7C;AAED,eAAO,MAAM,qBAAqB,GAAI,MAAM,MAAM,KAAG,MACT,CAAA;AAE5C;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAErD"}
|
package/dist/totp.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A second factor, and the codes that get you back in without one.
|
|
3
|
+
*
|
|
4
|
+
* ## What this decides, and what it does not
|
|
5
|
+
*
|
|
6
|
+
* A second factor decides whether a **session starts**. It grants nothing: the
|
|
7
|
+
* permitted set is still computed per request from `grants`, and a token minted
|
|
8
|
+
* after a correct code reaches exactly what the same token reaches without one.
|
|
9
|
+
* Nothing in `authz/` reads anything here, deliberately — a factor that could
|
|
10
|
+
* widen access would be a second answer to the question this product exists to
|
|
11
|
+
* answer once.
|
|
12
|
+
*
|
|
13
|
+
* ## Why SHA-1
|
|
14
|
+
*
|
|
15
|
+
* RFC 6238 leaves the hash open and every authenticator on a phone implements
|
|
16
|
+
* SHA-1; several implement nothing else. The weakness SHA-1 is retired for is
|
|
17
|
+
* collision resistance, which HMAC does not rest on — and the key here is 160
|
|
18
|
+
* bits from the CSPRNG while the message is a counter and the answer lives
|
|
19
|
+
* thirty seconds. Choosing SHA-256 would buy nothing measurable and would meet
|
|
20
|
+
* a person whose authenticator shows six digits that never work.
|
|
21
|
+
*
|
|
22
|
+
* ## Standard library only
|
|
23
|
+
*
|
|
24
|
+
* The same argument the parser makes: this module is on the authentication
|
|
25
|
+
* path, and a dependency here is a dependency inside every sign-in. The whole
|
|
26
|
+
* of it is HMAC, a counter, a base32 alphabet and AES-GCM, all of which
|
|
27
|
+
* `node:crypto` already has.
|
|
28
|
+
*/
|
|
29
|
+
import { createCipheriv, createDecipheriv, createHash, createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
30
|
+
/** RFC 6238's default and what every authenticator assumes. */
|
|
31
|
+
export const TOTP_PERIOD_SECONDS = 30;
|
|
32
|
+
/** Six, for the same reason: it is what the apps show. */
|
|
33
|
+
export const TOTP_DIGITS = 6;
|
|
34
|
+
/**
|
|
35
|
+
* How far either side of now a code is accepted.
|
|
36
|
+
*
|
|
37
|
+
* One step, which is up to sixty seconds of tolerance in the worst alignment.
|
|
38
|
+
* Phones drift and people type slowly; two steps is the number that starts
|
|
39
|
+
* making a shoulder-surfed code useful for longer than it should be.
|
|
40
|
+
*/
|
|
41
|
+
export const TOTP_SKEW_STEPS = 1;
|
|
42
|
+
/** RFC 4648 base32, which is the only encoding an authenticator will read. */
|
|
43
|
+
const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
|
44
|
+
export function base32Encode(bytes) {
|
|
45
|
+
let bits = 0;
|
|
46
|
+
let value = 0;
|
|
47
|
+
let out = '';
|
|
48
|
+
for (const byte of bytes) {
|
|
49
|
+
value = (value << 8) | byte;
|
|
50
|
+
bits += 8;
|
|
51
|
+
while (bits >= 5) {
|
|
52
|
+
out += ALPHABET[(value >>> (bits - 5)) & 31];
|
|
53
|
+
bits -= 5;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (bits > 0)
|
|
57
|
+
out += ALPHABET[(value << (5 - bits)) & 31];
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
export function base32Decode(text) {
|
|
61
|
+
// Padding and case are both things a person retypes wrongly, and neither
|
|
62
|
+
// carries information. Whitespace goes first: with the order reversed, a
|
|
63
|
+
// value ending in `== ` keeps its padding, because it is no longer at the
|
|
64
|
+
// end — which a test caught on the first run.
|
|
65
|
+
const clean = text.replace(/\s+/gu, '').replace(/=+$/u, '').toUpperCase();
|
|
66
|
+
let bits = 0;
|
|
67
|
+
let value = 0;
|
|
68
|
+
const out = [];
|
|
69
|
+
for (const character of clean) {
|
|
70
|
+
const index = ALPHABET.indexOf(character);
|
|
71
|
+
if (index === -1)
|
|
72
|
+
throw new Error(`not base32: ${character}`);
|
|
73
|
+
value = (value << 5) | index;
|
|
74
|
+
bits += 5;
|
|
75
|
+
if (bits >= 8) {
|
|
76
|
+
out.push((value >>> (bits - 8)) & 0xff);
|
|
77
|
+
bits -= 8;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return Buffer.from(out);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* 160 bits, which is what RFC 4226 recommends and what the apps expect.
|
|
84
|
+
*
|
|
85
|
+
* Shorter secrets are accepted by every authenticator and are the reason a
|
|
86
|
+
* generator is here rather than left to a call site: 80 bits is legal, common
|
|
87
|
+
* in examples, and half the strength of the thing it is protecting.
|
|
88
|
+
*/
|
|
89
|
+
export function generateTotpSecret() {
|
|
90
|
+
return base32Encode(randomBytes(20));
|
|
91
|
+
}
|
|
92
|
+
/** The step a moment falls in. Exported because the replay bound is a step. */
|
|
93
|
+
export const totpStep = (at = new Date()) => Math.floor(at.getTime() / 1000 / TOTP_PERIOD_SECONDS);
|
|
94
|
+
/** RFC 6238 over RFC 4226: HMAC the counter, take the dynamic offset, mod 10^d. */
|
|
95
|
+
export function totpCode(secret, step) {
|
|
96
|
+
const counter = Buffer.alloc(8);
|
|
97
|
+
counter.writeBigUInt64BE(BigInt(step));
|
|
98
|
+
const mac = createHmac('sha1', base32Decode(secret)).update(counter).digest();
|
|
99
|
+
const offset = mac[mac.length - 1] & 0x0f;
|
|
100
|
+
const binary = ((mac[offset] & 0x7f) << 24) | (mac[offset + 1] << 16) | (mac[offset + 2] << 8) | mac[offset + 3];
|
|
101
|
+
return String(binary % 10 ** TOTP_DIGITS).padStart(TOTP_DIGITS, '0');
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Check a code, and say which step it was.
|
|
105
|
+
*
|
|
106
|
+
* `after` is the last step this factor already accepted, and a code at or
|
|
107
|
+
* before it is refused however correct it is. Without that, a code is good for
|
|
108
|
+
* the whole window it was shown in — so somebody who reads it over a shoulder,
|
|
109
|
+
* or a phishing page that relays it, gets a second use out of it. The caller
|
|
110
|
+
* stores the returned step.
|
|
111
|
+
*/
|
|
112
|
+
export function verifyTotp(secret, code, options = {}) {
|
|
113
|
+
const typed = code.replace(/\s+/gu, '');
|
|
114
|
+
if (!new RegExp(`^[0-9]{${String(TOTP_DIGITS)}}$`, 'u').test(typed))
|
|
115
|
+
return undefined;
|
|
116
|
+
const current = totpStep(options.at ?? new Date());
|
|
117
|
+
for (let offset = -TOTP_SKEW_STEPS; offset <= TOTP_SKEW_STEPS; offset += 1) {
|
|
118
|
+
const step = current + offset;
|
|
119
|
+
if (options.after !== undefined && options.after !== null && step <= options.after)
|
|
120
|
+
continue;
|
|
121
|
+
const expected = Buffer.from(totpCode(secret, step));
|
|
122
|
+
const given = Buffer.from(typed);
|
|
123
|
+
// Constant time, because the comparison is over a secret-derived value and
|
|
124
|
+
// an attacker controls one side of it.
|
|
125
|
+
if (expected.length === given.length && timingSafeEqual(expected, given))
|
|
126
|
+
return { step };
|
|
127
|
+
}
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* The URL an authenticator reads out of a QR code.
|
|
132
|
+
*
|
|
133
|
+
* The label carries the issuer as well as the account, which is what makes two
|
|
134
|
+
* installations distinguishable in a list of thirty entries — an app shows the
|
|
135
|
+
* label, and "dana@example.com" alone is four identical rows.
|
|
136
|
+
*/
|
|
137
|
+
export function otpauthUrl(options) {
|
|
138
|
+
const label = `${encodeURIComponent(options.issuer)}:${encodeURIComponent(options.account)}`;
|
|
139
|
+
const query = new URLSearchParams({
|
|
140
|
+
secret: options.secret,
|
|
141
|
+
issuer: options.issuer,
|
|
142
|
+
algorithm: 'SHA1',
|
|
143
|
+
digits: String(TOTP_DIGITS),
|
|
144
|
+
period: String(TOTP_PERIOD_SECONDS),
|
|
145
|
+
});
|
|
146
|
+
return `otpauth://totp/${label}?${query.toString()}`;
|
|
147
|
+
}
|
|
148
|
+
// ── the secret at rest ───────────────────────────────────────────────────────
|
|
149
|
+
/**
|
|
150
|
+
* Sealed with AES-256-GCM under a key this process is given.
|
|
151
|
+
*
|
|
152
|
+
* A TOTP secret in the clear is a second factor that a database dump defeats,
|
|
153
|
+
* which is the same blast-radius argument that made the token signing key
|
|
154
|
+
* asymmetric: the point of the factor is that stealing one thing is not enough.
|
|
155
|
+
*
|
|
156
|
+
* The nonce and the tag travel inside the stored value the way `password_hash`
|
|
157
|
+
* carries its scrypt parameters — a column that needs a second column to be
|
|
158
|
+
* readable is two things that can come apart.
|
|
159
|
+
*/
|
|
160
|
+
const SEALED = 'v1';
|
|
161
|
+
export function sealTotpSecret(secret, key) {
|
|
162
|
+
if (key.length !== 32)
|
|
163
|
+
throw new Error('a 2FA key is 32 bytes');
|
|
164
|
+
const nonce = randomBytes(12);
|
|
165
|
+
const cipher = createCipheriv('aes-256-gcm', key, nonce);
|
|
166
|
+
const body = Buffer.concat([cipher.update(secret, 'utf8'), cipher.final()]);
|
|
167
|
+
return [SEALED, nonce.toString('base64url'), body.toString('base64url'), cipher.getAuthTag().toString('base64url')].join('.');
|
|
168
|
+
}
|
|
169
|
+
export function openTotpSecret(sealed, key) {
|
|
170
|
+
const [version, nonce, body, tag] = sealed.split('.');
|
|
171
|
+
if (version !== SEALED || nonce === undefined || body === undefined || tag === undefined) {
|
|
172
|
+
throw new Error('not a sealed TOTP secret');
|
|
173
|
+
}
|
|
174
|
+
const decipher = createDecipheriv('aes-256-gcm', key, Buffer.from(nonce, 'base64url'));
|
|
175
|
+
decipher.setAuthTag(Buffer.from(tag, 'base64url'));
|
|
176
|
+
return Buffer.concat([decipher.update(Buffer.from(body, 'base64url')), decipher.final()]).toString('utf8');
|
|
177
|
+
}
|
|
178
|
+
// ── recovery codes ───────────────────────────────────────────────────────────
|
|
179
|
+
/** Ten, which is what a person can print on one line each and lose half of. */
|
|
180
|
+
export const RECOVERY_CODE_COUNT = 10;
|
|
181
|
+
/**
|
|
182
|
+
* A recovery code, in the shape people retype correctly.
|
|
183
|
+
*
|
|
184
|
+
* Four groups of five from a 32-character alphabet is 100 bits — far past
|
|
185
|
+
* anything guessable — and the groups exist because a twenty-character run
|
|
186
|
+
* gets transcribed wrongly and blamed on the product.
|
|
187
|
+
*/
|
|
188
|
+
export function generateRecoveryCode() {
|
|
189
|
+
const bytes = randomBytes(20);
|
|
190
|
+
const text = base32Encode(bytes).slice(0, 20);
|
|
191
|
+
return [text.slice(0, 5), text.slice(5, 10), text.slice(10, 15), text.slice(15, 20)].join('-');
|
|
192
|
+
}
|
|
193
|
+
export const normalizeRecoveryCode = (code) => code.replace(/[\s-]+/gu, '').toUpperCase();
|
|
194
|
+
/**
|
|
195
|
+
* Hashed with SHA-256 and deliberately not with scrypt.
|
|
196
|
+
*
|
|
197
|
+
* A recovery code is 100 bits from the CSPRNG, so there is no dictionary to
|
|
198
|
+
* slow down and no human-chosen password to protect. A cost parameter would buy
|
|
199
|
+
* nothing against an attacker and would make issuing ten of them a second of
|
|
200
|
+
* CPU on a request — on the same libuv pool the sign-in path already has to be
|
|
201
|
+
* careful about.
|
|
202
|
+
*/
|
|
203
|
+
export function hashRecoveryCode(code) {
|
|
204
|
+
return createHash('sha256').update(normalizeRecoveryCode(code)).digest('hex');
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=totp.js.map
|
package/dist/totp.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"totp.js","sourceRoot":"","sources":["../totp.ts"],"names":[],"mappings":"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"}
|