@owlmeans/server-auth-otp 0.1.18-rc.52 → 0.1.18-rc.54

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.
Files changed (45) hide show
  1. package/README.md +2 -2
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/server-auth-otp/SKILL.md +1 -1
  4. package/build/index.d.ts +4 -0
  5. package/build/index.d.ts.map +1 -1
  6. package/build/index.js +2 -0
  7. package/build/index.js.map +1 -1
  8. package/build/keys/types.d.ts +10 -0
  9. package/build/keys/types.d.ts.map +1 -0
  10. package/build/keys/types.js +2 -0
  11. package/build/keys/types.js.map +1 -0
  12. package/build/keys.d.ts +4 -0
  13. package/build/keys.d.ts.map +1 -0
  14. package/build/keys.js +10 -0
  15. package/build/keys.js.map +1 -0
  16. package/build/plugin.js.map +1 -1
  17. package/build/service.d.ts +0 -3
  18. package/build/service.d.ts.map +1 -1
  19. package/build/service.js +13 -16
  20. package/build/service.js.map +1 -1
  21. package/build/throttle-keys/types.d.ts +8 -0
  22. package/build/throttle-keys/types.d.ts.map +1 -0
  23. package/build/throttle-keys/types.js +2 -0
  24. package/build/throttle-keys/types.js.map +1 -0
  25. package/build/throttle-keys.d.ts +4 -0
  26. package/build/throttle-keys.d.ts.map +1 -0
  27. package/build/throttle-keys.js +9 -0
  28. package/build/throttle-keys.js.map +1 -0
  29. package/build/throttle.d.ts +4 -4
  30. package/build/throttle.d.ts.map +1 -1
  31. package/build/throttle.js +6 -6
  32. package/build/throttle.js.map +1 -1
  33. package/build/types.local.d.ts +5 -3
  34. package/build/types.local.d.ts.map +1 -1
  35. package/package.json +13 -13
  36. package/src/index.ts +4 -0
  37. package/src/keys/types.ts +9 -0
  38. package/src/keys.ts +16 -0
  39. package/src/plugin.ts +2 -2
  40. package/src/service.ts +13 -20
  41. package/src/throttle-keys/types.ts +7 -0
  42. package/src/throttle-keys.ts +17 -0
  43. package/src/throttle.ts +8 -12
  44. package/src/types.local.ts +2 -2
  45. package/tests/keys.spec.ts +8 -8
package/README.md CHANGED
@@ -12,7 +12,7 @@ Email OTP `AuthPlugin` for OwlMeans servers — passwordless login via time-limi
12
12
  ## Installation
13
13
 
14
14
  ```bash
15
- bun add @owlmeans/server-auth-otp@^0.1.18-rc.52
15
+ bun add @owlmeans/server-auth-otp@^0.1.18-rc.54
16
16
  ```
17
17
 
18
18
  ## Usage
@@ -47,7 +47,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
47
47
  your project's skill store (`.agents/skills/`):
48
48
 
49
49
  ```sh
50
- npx @owlmeans/agent-skills@^0.1.18-rc.48
50
+ npx @owlmeans/agent-skills@^0.1.18-rc.51
51
51
  ```
52
52
 
53
53
  The embedded files are version-matched to this package release. Do not edit them
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/server-auth-otp",
4
- "version": "0.1.18-rc.52",
5
- "generatedAt": "2026-10-05T08:32:19.110Z",
4
+ "version": "0.1.18-rc.54",
5
+ "generatedAt": "2026-10-06T11:45:57.663Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -8,7 +8,7 @@ metadata:
8
8
 
9
9
  # Using `@owlmeans/server-auth-otp`
10
10
 
11
- **Install:** `"@owlmeans/server-auth-otp": "^0.1.18-rc.52"` in `dependencies`
11
+ **Install:** `"@owlmeans/server-auth-otp": "^0.1.18-rc.54"` in `dependencies`
12
12
 
13
13
  Email OTP authentication plugin for the OwlMeans auth-manager plugin system. Relies on `@owlmeans/auth-otp` for the OTP service interface, a Redis resource for code storage, and a `MailerService` to send codes.
14
14
 
package/build/index.d.ts CHANGED
@@ -1,8 +1,12 @@
1
1
  export * from './types.js';
2
+ export type * from './keys/types.js';
3
+ export type * from './throttle-keys/types.js';
2
4
  export * from './consts.js';
3
5
  export * from './service.js';
4
6
  export * from './plugin.js';
5
7
  export * from './errors.js';
6
8
  export * from './throttle.js';
7
9
  export * from './challenge.js';
10
+ export * from './keys.js';
11
+ export * from './throttle-keys.js';
8
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,mBAAmB,iBAAiB,CAAA;AACpC,mBAAmB,0BAA0B,CAAA;AAC7C,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,WAAW,CAAA;AACzB,cAAc,oBAAoB,CAAA"}
package/build/index.js CHANGED
@@ -5,4 +5,6 @@ export * from './plugin.js';
5
5
  export * from './errors.js';
6
6
  export * from './throttle.js';
7
7
  export * from './challenge.js';
8
+ export * from './keys.js';
9
+ export * from './throttle-keys.js';
8
10
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAG1B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,WAAW,CAAA;AACzB,cAAc,oBAAoB,CAAA"}
@@ -0,0 +1,10 @@
1
+ /** The storage keys of one OTP issuance: digests, never the raw address, id or code. */
2
+ export interface OtpKeyHelper {
3
+ /** The challenge record key of an issuance id. */
4
+ otpChallengeKey: (issuanceId: string) => string;
5
+ /** The case-insensitive digest of the address a code was sent to. */
6
+ otpEmailKey: (email: string) => string;
7
+ /** The digest of a code, bound to its issuance. */
8
+ otpCodeHash: (issuanceId: string, code: string) => string;
9
+ }
10
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/keys/types.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,MAAM,WAAW,YAAY;IAC3B,kDAAkD;IAClD,eAAe,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAA;IAC/C,qEAAqE;IACrE,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC,mDAAmD;IACnD,WAAW,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;CAC1D"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/keys/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,4 @@
1
+ import type { OtpKeyHelper } from './keys/types.js';
2
+ export declare const createOtpKeyHelper: () => OtpKeyHelper;
3
+ export declare const otpKeyHelper: OtpKeyHelper;
4
+ //# sourceMappingURL=keys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAKnD,eAAO,MAAM,kBAAkB,QAAO,YAOrC,CAAA;AAED,eAAO,MAAM,YAAY,cAAuB,CAAA"}
package/build/keys.js ADDED
@@ -0,0 +1,10 @@
1
+ import { createHash } from 'node:crypto';
2
+ const digest = (scope, value) => createHash('sha256').update(`owlmeans:otp:${scope}\0${value}`).digest('hex');
3
+ export const createOtpKeyHelper = () => {
4
+ const otpChallengeKey = (issuanceId) => `challenge:${digest('issuance', issuanceId)}`;
5
+ const otpEmailKey = (email) => digest('email', email.trim().toLowerCase());
6
+ const otpCodeHash = (issuanceId, code) => digest('code', `${issuanceId}\0${code.trim()}`);
7
+ return { otpChallengeKey, otpEmailKey, otpCodeHash };
8
+ };
9
+ export const otpKeyHelper = createOtpKeyHelper();
10
+ //# sourceMappingURL=keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAGxC,MAAM,MAAM,GAAG,CAAC,KAAa,EAAE,KAAa,EAAU,EAAE,CACtD,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,gBAAgB,KAAK,KAAK,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAE9E,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAiB,EAAE;IACnD,MAAM,eAAe,GAAG,CAAC,UAAkB,EAAU,EAAE,CAAC,aAAa,MAAM,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,CAAA;IACrG,MAAM,WAAW,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAA;IAC1F,MAAM,WAAW,GAAG,CAAC,UAAkB,EAAE,IAAY,EAAU,EAAE,CAC/D,MAAM,CAAC,MAAM,EAAE,GAAG,UAAU,KAAK,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;IAEjD,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,WAAW,EAAE,CAAA;AACtD,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,YAAY,GAAG,kBAAkB,EAAE,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,3 @@
1
1
  import { type OtpService } from '@owlmeans/auth-otp';
2
- export declare const otpChallengeKey: (issuanceId: string) => string;
3
- export declare const otpEmailKey: (email: string) => string;
4
- export declare const otpCodeHash: (issuanceId: string, code: string) => string;
5
2
  export declare const makeOtpService: (alias?: string) => OtpService;
6
3
  //# sourceMappingURL=service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,UAAU,EAAwF,MAAM,oBAAoB,CAAA;AAkB1I,eAAO,MAAM,eAAe,eAAgB,MAAM,KAAG,MAAuD,CAAA;AAC5G,eAAO,MAAM,WAAW,UAAW,MAAM,KAAG,MAAqD,CAAA;AACjG,eAAO,MAAM,WAAW,eAAgB,MAAM,QAAQ,MAAM,KAAG,MACd,CAAA;AAEjD,eAAO,MAAM,cAAc,sBAA0B,UA0EpD,CAAA"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,UAAU,EAAwF,MAAM,oBAAoB,CAAA;AAqB1I,eAAO,MAAM,cAAc,sBAA0B,UA0EpD,CAAA"}
package/build/service.js CHANGED
@@ -2,19 +2,20 @@ import { appendContextual } from '@owlmeans/context';
2
2
  import { AuthenFailed } from '@owlmeans/auth';
3
3
  import { OTP_SERVICE, OTP_TTL_SECONDS, OTP_CODE_LENGTH, OTP_MAX_FAILED_ATTEMPTS, OTP_RESOURCE } from '@owlmeans/auth-otp';
4
4
  import { MAILER_SERVICE } from '@owlmeans/mailer';
5
- import { createHash, randomBytes, randomInt } from 'node:crypto';
5
+ import { randomBytes, randomInt } from 'node:crypto';
6
6
  import { OtpChallengeOutcome, OTP_CHALLENGE_STORE } from './consts.js';
7
- import { emailThrottleKey } from './throttle.js';
7
+ import { throttleKeyHelper } from './throttle-keys.js';
8
+ import { otpKeyHelper } from './keys.js';
8
9
  import { OtpThrottled, OtpUnavailable } from './errors.js';
9
10
  import { makeRedisOtpChallengeStore } from './challenge.js';
10
11
  import { logger, logThrottle } from '@owlmeans/log';
11
12
  const log = logger('server-auth-otp');
12
13
  /** An e-mail as a log may carry it: its domain only. */
13
14
  const domainOf = (email) => email.slice(email.lastIndexOf('@') + 1);
14
- const digest = (scope, value) => createHash('sha256').update(`owlmeans:otp:${scope}\0${value}`).digest('hex');
15
- export const otpChallengeKey = (issuanceId) => `challenge:${digest('issuance', issuanceId)}`;
16
- export const otpEmailKey = (email) => digest('email', email.trim().toLowerCase());
17
- export const otpCodeHash = (issuanceId, code) => digest('code', `${issuanceId}\0${code.trim()}`);
15
+ const generateCode = () => {
16
+ const digits = randomInt(0, Math.pow(10, OTP_CODE_LENGTH));
17
+ return String(digits).padStart(OTP_CODE_LENGTH, '0');
18
+ };
18
19
  export const makeOtpService = (alias = OTP_SERVICE) => {
19
20
  let fallbackChallenges;
20
21
  const challengeStore = (ctx) => {
@@ -41,9 +42,9 @@ export const makeOtpService = (alias = OTP_SERVICE) => {
41
42
  const throttleRules = ctx.cfg.otp?.throttleRules;
42
43
  if (throttleAlias != null && throttleRules != null) {
43
44
  const decision = await ctx.service(throttleAlias)
44
- .consume(emailThrottleKey(email), throttleRules, issuanceId);
45
+ .consume(throttleKeyHelper.emailThrottleKey(email), throttleRules, issuanceId);
45
46
  if (!decision.allowed) {
46
- if (logThrottle(`otp-throttled:${otpEmailKey(email)}`, 60_000)) {
47
+ if (logThrottle(`otp-throttled:${otpKeyHelper.otpEmailKey(email)}`, 60_000)) {
47
48
  log.warn('Login code refused: throttled', {
48
49
  reason: 'otp-throttled', emailDomain: domainOf(email), retryAfter: decision.retryAfter,
49
50
  }, { event: 'auth.refused' });
@@ -52,9 +53,9 @@ export const makeOtpService = (alias = OTP_SERVICE) => {
52
53
  }
53
54
  }
54
55
  const issued = await challenges.issue({
55
- id: otpChallengeKey(issuanceId),
56
- emailKey: otpEmailKey(email),
57
- codeHash: otpCodeHash(issuanceId, code),
56
+ id: otpKeyHelper.otpChallengeKey(issuanceId),
57
+ emailKey: otpKeyHelper.otpEmailKey(email),
58
+ codeHash: otpKeyHelper.otpCodeHash(issuanceId, code),
58
59
  }, OTP_TTL_SECONDS);
59
60
  if (!issued)
60
61
  throw new OtpUnavailable('issuance-collision');
@@ -68,7 +69,7 @@ export const makeOtpService = (alias = OTP_SERVICE) => {
68
69
  },
69
70
  verifyChallenge: async (email, issuanceId, code) => {
70
71
  const ctx = service.ctx;
71
- const outcome = await challengeStore(ctx).verify(otpChallengeKey(issuanceId), otpEmailKey(email), otpCodeHash(issuanceId, code), OTP_MAX_FAILED_ATTEMPTS);
72
+ const outcome = await challengeStore(ctx).verify(otpKeyHelper.otpChallengeKey(issuanceId), otpKeyHelper.otpEmailKey(email), otpKeyHelper.otpCodeHash(issuanceId, code), OTP_MAX_FAILED_ATTEMPTS);
72
73
  if (outcome !== OtpChallengeOutcome.Verified) {
73
74
  // The sign-in refusal itself (`auth.refused`) is logged where the manager decides sign-in.
74
75
  log.debug('OTP challenge refused', { outcome, emailDomain: domainOf(email) });
@@ -78,8 +79,4 @@ export const makeOtpService = (alias = OTP_SERVICE) => {
78
79
  });
79
80
  return service;
80
81
  };
81
- const generateCode = () => {
82
- const digits = randomInt(0, Math.pow(10, OTP_CODE_LENGTH));
83
- return String(digits).padStart(OTP_CODE_LENGTH, '0');
84
- };
85
82
  //# sourceMappingURL=service.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /** Throttle subjects as non-raw keys: digests of what a request is throttled by. */
2
+ export interface ThrottleKeyHelper {
3
+ /** Collision-resistant, non-raw key for case-insensitive email throttles. */
4
+ emailThrottleKey: (email: string) => string;
5
+ /** Non-raw key for request-source throttles. Callers remain responsible for trusted-proxy parsing. */
6
+ ipThrottleKey: (ip: string) => string;
7
+ }
8
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/throttle-keys/types.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,MAAM,WAAW,iBAAiB;IAChC,6EAA6E;IAC7E,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAC3C,sGAAsG;IACtG,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAA;CACtC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/throttle-keys/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,4 @@
1
+ import type { ThrottleKeyHelper } from './throttle-keys/types.js';
2
+ export declare const createThrottleKeyHelper: () => ThrottleKeyHelper;
3
+ export declare const throttleKeyHelper: ThrottleKeyHelper;
4
+ //# sourceMappingURL=throttle-keys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"throttle-keys.d.ts","sourceRoot":"","sources":["../src/throttle-keys.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAKjE,eAAO,MAAM,uBAAuB,QAAO,iBAQ1C,CAAA;AAED,eAAO,MAAM,iBAAiB,mBAA4B,CAAA"}
@@ -0,0 +1,9 @@
1
+ import { createHash } from 'node:crypto';
2
+ const digestKey = (kind, value) => createHash('sha256').update(`owlmeans:throttle:${kind}\0${value}`).digest('hex');
3
+ export const createThrottleKeyHelper = () => {
4
+ const emailThrottleKey = (email) => `email:${digestKey('email', email.trim().toLowerCase())}`;
5
+ const ipThrottleKey = (ip) => `ip:${digestKey('ip', ip.trim().toLowerCase())}`;
6
+ return { emailThrottleKey, ipThrottleKey };
7
+ };
8
+ export const throttleKeyHelper = createThrottleKeyHelper();
9
+ //# sourceMappingURL=throttle-keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"throttle-keys.js","sourceRoot":"","sources":["../src/throttle-keys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAGxC,MAAM,SAAS,GAAG,CAAC,IAAY,EAAE,KAAa,EAAU,EAAE,CACxD,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,qBAAqB,IAAI,KAAK,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAElF,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAsB,EAAE;IAC7D,MAAM,gBAAgB,GAAG,CAAC,KAAa,EAAU,EAAE,CACjD,SAAS,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,EAAE,CAAA;IAE3D,MAAM,aAAa,GAAG,CAAC,EAAU,EAAU,EAAE,CAC3C,MAAM,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,EAAE,CAAA;IAElD,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,CAAA;AAC5C,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,iBAAiB,GAAG,uBAAuB,EAAE,CAAA"}
@@ -1,11 +1,11 @@
1
1
  import type { AuthThrottleService } from './types.js';
2
2
  import type { Clock } from './types.local.js';
3
- /** Collision-resistant, non-raw key for case-insensitive email throttles. */
4
- export declare const emailThrottleKey: (email: string) => string;
5
- /** Non-raw key for request-source throttles. Callers remain responsible for trusted-proxy parsing. */
6
- export declare const ipThrottleKey: (ip: string) => string;
7
3
  /** Real in-memory implementation for single-process development and deterministic unit tests. */
8
4
  export declare const makeMemoryThrottleService: (alias?: string, now?: Clock) => AuthThrottleService;
9
5
  /** Redis implementation: every configured window is checked and consumed in one Lua operation. */
10
6
  export declare const makeRedisThrottleService: (alias?: string, resourceAlias?: string) => AuthThrottleService;
7
+ /** @deprecated compat:factory-refactor — use `throttleKeyHelper.emailThrottleKey(…)` */
8
+ export declare const emailThrottleKey: (email: string) => string;
9
+ /** @deprecated compat:factory-refactor — use `throttleKeyHelper.ipThrottleKey(…)` */
10
+ export declare const ipThrottleKey: (ip: string) => string;
11
11
  //# sourceMappingURL=throttle.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"throttle.d.ts","sourceRoot":"","sources":["../src/throttle.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,mBAAmB,EAAyD,MAAM,YAAY,CAAA;AAG5G,OAAO,KAAK,EAAE,KAAK,EAA0B,MAAM,kBAAkB,CAAA;AAKrE,6EAA6E;AAC7E,eAAO,MAAM,gBAAgB,UAAW,MAAM,KAAG,MACU,CAAA;AAE3D,sGAAsG;AACtG,eAAO,MAAM,aAAa,OAAQ,MAAM,KAAG,MACO,CAAA;AASlD,iGAAiG;AACjG,eAAO,MAAM,yBAAyB,yBACD,KAAK,KACvC,mBAyBF,CAAA;AAED,kGAAkG;AAClG,eAAO,MAAM,wBAAwB,8CAElC,mBA4BF,CAAA"}
1
+ {"version":3,"file":"throttle.d.ts","sourceRoot":"","sources":["../src/throttle.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,mBAAmB,EAAyD,MAAM,YAAY,CAAA;AAG5G,OAAO,KAAK,EAAE,KAAK,EAA0B,MAAM,kBAAkB,CAAA;AAUrE,iGAAiG;AACjG,eAAO,MAAM,yBAAyB,yBACD,KAAK,KACvC,mBAyBF,CAAA;AAED,kGAAkG;AAClG,eAAO,MAAM,wBAAwB,8CAElC,mBA4BF,CAAA;AAED,wFAAwF;AACxF,eAAO,MAAM,gBAAgB,UAAW,MAAM,KAAG,MAAmD,CAAA;AAEpG,qFAAqF;AACrF,eAAO,MAAM,aAAa,OAAQ,MAAM,KAAG,MAA6C,CAAA"}
package/build/throttle.js CHANGED
@@ -1,13 +1,9 @@
1
- import { createHash, randomBytes } from 'node:crypto';
1
+ import { randomBytes } from 'node:crypto';
2
2
  import { createService, appendContextual } from '@owlmeans/context';
3
3
  import { OtpUnavailable } from './errors.js';
4
4
  import { OTP_RESOURCE, OTP_THROTTLE_SERVICE } from './consts.js';
5
5
  import { REDIS_THROTTLE_SCRIPT } from './consts.local.js';
6
- const digestKey = (kind, value) => createHash('sha256').update(`owlmeans:throttle:${kind}\0${value}`).digest('hex');
7
- /** Collision-resistant, non-raw key for case-insensitive email throttles. */
8
- export const emailThrottleKey = (email) => `email:${digestKey('email', email.trim().toLowerCase())}`;
9
- /** Non-raw key for request-source throttles. Callers remain responsible for trusted-proxy parsing. */
10
- export const ipThrottleKey = (ip) => `ip:${digestKey('ip', ip.trim().toLowerCase())}`;
6
+ import { throttleKeyHelper } from './throttle-keys.js';
11
7
  const assertRules = (rules) => {
12
8
  if (rules.length === 0 || rules.some(rule => !Number.isInteger(rule.limit) || rule.limit < 1
13
9
  || !Number.isInteger(rule.windowSeconds) || rule.windowSeconds < 1)) {
@@ -64,4 +60,8 @@ export const makeRedisThrottleService = (alias = OTP_THROTTLE_SERVICE, resourceA
64
60
  });
65
61
  return service;
66
62
  };
63
+ /** @deprecated compat:factory-refactor — use `throttleKeyHelper.emailThrottleKey(…)` */
64
+ export const emailThrottleKey = (email) => throttleKeyHelper.emailThrottleKey(email);
65
+ /** @deprecated compat:factory-refactor — use `throttleKeyHelper.ipThrottleKey(…)` */
66
+ export const ipThrottleKey = (ip) => throttleKeyHelper.ipThrottleKey(ip);
67
67
  //# sourceMappingURL=throttle.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"throttle.js","sourceRoot":"","sources":["../src/throttle.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"throttle.js","sourceRoot":"","sources":["../src/throttle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAEnE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAE5C,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAChE,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAA;AAEzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAEtD,MAAM,WAAW,GAAG,CAAC,KAA8B,EAAQ,EAAE;IAC3D,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,GAAG,CAAC;WACvF,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,IAAI,CAAC,aAAa,GAAG,CAAC,CAAC,EAAE,CAAC;QACtE,MAAM,IAAI,WAAW,CAAC,qEAAqE,CAAC,CAAA;IAC9F,CAAC;AACH,CAAC,CAAA;AAED,iGAAiG;AACjG,MAAM,CAAC,MAAM,yBAAyB,GAAG,CACvC,KAAK,GAAG,oBAAoB,EAAE,GAAG,GAAU,IAAI,CAAC,GAAG,EAC9B,EAAE;IACvB,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAA;IAE1C,OAAO,aAAa,CAAsB,KAAK,EAAE;QAC/C,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAA6B,EAAE;YACvD,WAAW,CAAC,KAAK,CAAC,CAAA;YAClB,MAAM,EAAE,GAAG,GAAG,EAAE,CAAA;YAChB,IAAI,UAAU,GAAG,CAAC,CAAA;YAClB,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;gBAC9B,MAAM,MAAM,GAAG,GAAG,GAAG,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,aAAa,EAAE,CAAA;gBAC3D,MAAM,SAAS,GAAG,EAAE,GAAG,IAAI,CAAC,aAAa,GAAG,IAAI,CAAA;gBAChD,MAAM,MAAM,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,GAAG,SAAS,CAAC,CAAA;gBAC5E,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;gBAC1B,IAAI,MAAM,CAAC,MAAM,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;oBAChC,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,aAAa,GAAG,IAAI,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAA;gBACnG,CAAC;gBACD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAA;YAC3B,CAAC,CAAC,CAAA;YAEF,IAAI,UAAU,GAAG,CAAC;gBAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,CAAA;YACzD,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;YAE3E,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,EAAE,CAAA;QACzC,CAAC;KACF,CAAC,CAAA;AACJ,CAAC,CAAA;AAED,kGAAkG;AAClG,MAAM,CAAC,MAAM,wBAAwB,GAAG,CACtC,KAAK,GAAG,oBAAoB,EAAE,aAAa,GAAG,YAAY,EACrC,EAAE;IACvB,MAAM,OAAO,GAAG,gBAAgB,CAAsB,KAAK,EAAE;QAC3D,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAA6B,EAAE;YAC9D,WAAW,CAAC,KAAK,CAAC,CAAA;YAClB,MAAM,GAAG,GAAG,OAAO,CAAC,GAA4B,CAAA;YAChD,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAwC,aAAa,CAAC,CAAA;YACnF,wFAAwF;YACxF,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,CACzC,aAAa,GAAG,KAAK,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,aAAa,EAAE,CACxD,CAAC,CAAA;YACF,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;YAC3F,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAA;YAEnD,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAC1C,qBAAqB,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CACjC,CAAA;gBAErB,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;YAC5E,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,WAAW,GAAG,IAAI,cAAc,CAAC,UAAU,CAAC,CAAA;gBAClD,IAAI,KAAK,YAAY,KAAK;oBAAE,WAAW,CAAC,cAAc,GAAG,GAAG,KAAK,KAAK,KAAK,CAAC,KAAK,EAAE,CAAA;gBACnF,MAAM,WAAW,CAAA;YACnB,CAAC;QACH,CAAC;KACF,CAAC,CAAA;IAEF,OAAO,OAAO,CAAA;AAChB,CAAC,CAAA;AAED,wFAAwF;AACxF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAA;AAEpG,qFAAqF;AACrF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,EAAU,EAAU,EAAE,CAAC,iBAAiB,CAAC,aAAa,CAAC,EAAE,CAAC,CAAA"}
@@ -1,10 +1,12 @@
1
1
  import type { OtpChallenge } from './types.js';
2
2
  import type { ResourceRecord } from '@owlmeans/resource';
3
- export type Clock = () => number;
4
- export type StoredChallenge = OtpChallenge & {
3
+ export interface Clock {
4
+ (): number;
5
+ }
6
+ export interface StoredChallenge extends OtpChallenge {
5
7
  failedAttempts: number;
6
8
  expiresAt: number;
7
- };
9
+ }
8
10
  export interface ChallengeResourceRecord extends ResourceRecord {
9
11
  id: string;
10
12
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.local.d.ts","sourceRoot":"","sources":["../src/types.local.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAExD,MAAM,MAAM,KAAK,GAAG,MAAM,MAAM,CAAA;AAEhC,MAAM,MAAM,eAAe,GAAG,YAAY,GAAG;IAAE,cAAc,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAA;AAE1F,MAAM,WAAW,uBAAwB,SAAQ,cAAc;IAAG,EAAE,EAAE,MAAM,CAAA;CAAE;AAE9E,MAAM,WAAW,sBAAuB,SAAQ,cAAc;IAAG,EAAE,EAAE,MAAM,CAAA;CAAE"}
1
+ {"version":3,"file":"types.local.d.ts","sourceRoot":"","sources":["../src/types.local.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAExD,MAAM,WAAW,KAAK;IAAG,IAAI,MAAM,CAAA;CAAE;AAErC,MAAM,WAAW,eAAgB,SAAQ,YAAY;IAAG,cAAc,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE;AAEnG,MAAM,WAAW,uBAAwB,SAAQ,cAAc;IAAG,EAAE,EAAE,MAAM,CAAA;CAAE;AAE9E,MAAM,WAAW,sBAAuB,SAAQ,cAAc;IAAG,EAAE,EAAE,MAAM,CAAA;CAAE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/server-auth-otp",
3
- "version": "0.1.18-rc.52",
3
+ "version": "0.1.18-rc.54",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -22,18 +22,18 @@
22
22
  }
23
23
  },
24
24
  "dependencies": {
25
- "@owlmeans/auth": "^0.1.18-rc.39",
26
- "@owlmeans/auth-otp": "^0.1.18-rc.33",
27
- "@owlmeans/context": "^0.1.18-rc.33",
28
- "@owlmeans/error": "^0.1.18-rc.37",
29
- "@owlmeans/log": "^0.1.18-rc.1",
30
- "@owlmeans/mailer": "^0.1.18-rc.35",
31
- "@owlmeans/oidc": "^0.1.18-rc.54",
32
- "@owlmeans/redis-resource": "^0.1.18-rc.43",
33
- "@owlmeans/resource": "^0.1.18-rc.39",
34
- "@owlmeans/server-auth": "^0.1.18-rc.53",
35
- "@owlmeans/server-auth-identity": "^0.1.18-rc.46",
36
- "@owlmeans/server-context": "^0.1.18-rc.42"
25
+ "@owlmeans/auth": "^0.1.18-rc.40",
26
+ "@owlmeans/auth-otp": "^0.1.18-rc.34",
27
+ "@owlmeans/context": "^0.1.18-rc.34",
28
+ "@owlmeans/error": "^0.1.18-rc.38",
29
+ "@owlmeans/log": "^0.1.18-rc.2",
30
+ "@owlmeans/mailer": "^0.1.18-rc.36",
31
+ "@owlmeans/oidc": "^0.1.18-rc.56",
32
+ "@owlmeans/redis-resource": "^0.1.18-rc.45",
33
+ "@owlmeans/resource": "^0.1.18-rc.40",
34
+ "@owlmeans/server-auth": "^0.1.18-rc.55",
35
+ "@owlmeans/server-auth-identity": "^0.1.18-rc.48",
36
+ "@owlmeans/server-context": "^0.1.18-rc.43"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@owlmeans/dep-config": "workspace:*",
package/src/index.ts CHANGED
@@ -1,7 +1,11 @@
1
1
  export * from './types.js'
2
+ export type * from './keys/types.js'
3
+ export type * from './throttle-keys/types.js'
2
4
  export * from './consts.js'
3
5
  export * from './service.js'
4
6
  export * from './plugin.js'
5
7
  export * from './errors.js'
6
8
  export * from './throttle.js'
7
9
  export * from './challenge.js'
10
+ export * from './keys.js'
11
+ export * from './throttle-keys.js'
@@ -0,0 +1,9 @@
1
+ /** The storage keys of one OTP issuance: digests, never the raw address, id or code. */
2
+ export interface OtpKeyHelper {
3
+ /** The challenge record key of an issuance id. */
4
+ otpChallengeKey: (issuanceId: string) => string
5
+ /** The case-insensitive digest of the address a code was sent to. */
6
+ otpEmailKey: (email: string) => string
7
+ /** The digest of a code, bound to its issuance. */
8
+ otpCodeHash: (issuanceId: string, code: string) => string
9
+ }
package/src/keys.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { createHash } from 'node:crypto'
2
+ import type { OtpKeyHelper } from './keys/types.js'
3
+
4
+ const digest = (scope: string, value: string): string =>
5
+ createHash('sha256').update(`owlmeans:otp:${scope}\0${value}`).digest('hex')
6
+
7
+ export const createOtpKeyHelper = (): OtpKeyHelper => {
8
+ const otpChallengeKey = (issuanceId: string): string => `challenge:${digest('issuance', issuanceId)}`
9
+ const otpEmailKey = (email: string): string => digest('email', email.trim().toLowerCase())
10
+ const otpCodeHash = (issuanceId: string, code: string): string =>
11
+ digest('code', `${issuanceId}\0${code.trim()}`)
12
+
13
+ return { otpChallengeKey, otpEmailKey, otpCodeHash }
14
+ }
15
+
16
+ export const otpKeyHelper = createOtpKeyHelper()
package/src/plugin.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ALL_SCOPES, AuthenFailed, AuthRole, AuthenticationType } from '@owlmeans/auth'
2
- import { type AuthPlugin, AuthChallengeReplayPolicy, registerPlugin } from '@owlmeans/server-auth/manager/plugins'
2
+ import { type AuthPlugin, type AuthPluginFactory, AuthChallengeReplayPolicy, registerPlugin } from '@owlmeans/server-auth/manager/plugins'
3
3
  import { type OtpService, OTP_AUTH_TYPE, OTP_SERVICE } from '@owlmeans/auth-otp'
4
4
  import { type IdentityLinkingService, AUTH_IDENTITY_LINKING } from '@owlmeans/server-auth-identity'
5
5
  import type { OtpConfig, OtpContext } from './types.js'
@@ -71,6 +71,6 @@ const otpPlugin = <C extends OtpConfig, T extends OtpContext<C>>(context: T): Au
71
71
 
72
72
  /** Register the email-OTP plugin into the server-auth plugin registry. Call once at context setup. */
73
73
  export const appendOtpPlugin = <C extends OtpConfig, T extends OtpContext<C>>(context: T): T => {
74
- registerPlugin(OTP_AUTH_TYPE, otpPlugin as Parameters<typeof registerPlugin>[1])
74
+ registerPlugin(OTP_AUTH_TYPE, otpPlugin as AuthPluginFactory)
75
75
  return context
76
76
  }
package/src/service.ts CHANGED
@@ -2,10 +2,11 @@ import { appendContextual } from '@owlmeans/context'
2
2
  import { AuthenFailed } from '@owlmeans/auth'
3
3
  import { type OtpService, OTP_SERVICE, OTP_TTL_SECONDS, OTP_CODE_LENGTH, OTP_MAX_FAILED_ATTEMPTS, OTP_RESOURCE } from '@owlmeans/auth-otp'
4
4
  import { type MailerService, MAILER_SERVICE } from '@owlmeans/mailer'
5
- import { createHash, randomBytes, randomInt } from 'node:crypto'
5
+ import { randomBytes, randomInt } from 'node:crypto'
6
6
  import type { AuthThrottleService, OtpChallengeStore, OtpConfig, OtpContext } from './types.js'
7
7
  import { OtpChallengeOutcome, OTP_CHALLENGE_STORE } from './consts.js'
8
- import { emailThrottleKey } from './throttle.js'
8
+ import { throttleKeyHelper } from './throttle-keys.js'
9
+ import { otpKeyHelper } from './keys.js'
9
10
  import { OtpThrottled, OtpUnavailable } from './errors.js'
10
11
  import { makeRedisOtpChallengeStore } from './challenge.js'
11
12
  import { logger, logThrottle } from '@owlmeans/log'
@@ -15,13 +16,10 @@ const log = logger('server-auth-otp')
15
16
  /** An e-mail as a log may carry it: its domain only. */
16
17
  const domainOf = (email: string): string => email.slice(email.lastIndexOf('@') + 1)
17
18
 
18
- const digest = (scope: string, value: string): string =>
19
- createHash('sha256').update(`owlmeans:otp:${scope}\0${value}`).digest('hex')
20
-
21
- export const otpChallengeKey = (issuanceId: string): string => `challenge:${digest('issuance', issuanceId)}`
22
- export const otpEmailKey = (email: string): string => digest('email', email.trim().toLowerCase())
23
- export const otpCodeHash = (issuanceId: string, code: string): string =>
24
- digest('code', `${issuanceId}\0${code.trim()}`)
19
+ const generateCode = (): string => {
20
+ const digits = randomInt(0, Math.pow(10, OTP_CODE_LENGTH))
21
+ return String(digits).padStart(OTP_CODE_LENGTH, '0')
22
+ }
25
23
 
26
24
  export const makeOtpService = (alias = OTP_SERVICE): OtpService => {
27
25
  let fallbackChallenges: OtpChallengeStore | undefined
@@ -54,9 +52,9 @@ export const makeOtpService = (alias = OTP_SERVICE): OtpService => {
54
52
  const throttleRules = ctx.cfg.otp?.throttleRules
55
53
  if (throttleAlias != null && throttleRules != null) {
56
54
  const decision = await ctx.service<AuthThrottleService>(throttleAlias)
57
- .consume(emailThrottleKey(email), throttleRules, issuanceId)
55
+ .consume(throttleKeyHelper.emailThrottleKey(email), throttleRules, issuanceId)
58
56
  if (!decision.allowed) {
59
- if (logThrottle(`otp-throttled:${otpEmailKey(email)}`, 60_000)) {
57
+ if (logThrottle(`otp-throttled:${otpKeyHelper.otpEmailKey(email)}`, 60_000)) {
60
58
  log.warn('Login code refused: throttled', {
61
59
  reason: 'otp-throttled', emailDomain: domainOf(email), retryAfter: decision.retryAfter,
62
60
  }, { event: 'auth.refused' })
@@ -66,9 +64,9 @@ export const makeOtpService = (alias = OTP_SERVICE): OtpService => {
66
64
  }
67
65
 
68
66
  const issued = await challenges.issue({
69
- id: otpChallengeKey(issuanceId),
70
- emailKey: otpEmailKey(email),
71
- codeHash: otpCodeHash(issuanceId, code),
67
+ id: otpKeyHelper.otpChallengeKey(issuanceId),
68
+ emailKey: otpKeyHelper.otpEmailKey(email),
69
+ codeHash: otpKeyHelper.otpCodeHash(issuanceId, code),
72
70
  }, OTP_TTL_SECONDS)
73
71
  if (!issued) throw new OtpUnavailable('issuance-collision')
74
72
 
@@ -85,7 +83,7 @@ export const makeOtpService = (alias = OTP_SERVICE): OtpService => {
85
83
  verifyChallenge: async (email: string, issuanceId: string, code: string): Promise<void> => {
86
84
  const ctx = service.ctx as OtpContext<OtpConfig>
87
85
  const outcome = await challengeStore(ctx).verify(
88
- otpChallengeKey(issuanceId), otpEmailKey(email), otpCodeHash(issuanceId, code),
86
+ otpKeyHelper.otpChallengeKey(issuanceId), otpKeyHelper.otpEmailKey(email), otpKeyHelper.otpCodeHash(issuanceId, code),
89
87
  OTP_MAX_FAILED_ATTEMPTS
90
88
  )
91
89
  if (outcome !== OtpChallengeOutcome.Verified) {
@@ -98,8 +96,3 @@ export const makeOtpService = (alias = OTP_SERVICE): OtpService => {
98
96
 
99
97
  return service
100
98
  }
101
-
102
- const generateCode = (): string => {
103
- const digits = randomInt(0, Math.pow(10, OTP_CODE_LENGTH))
104
- return String(digits).padStart(OTP_CODE_LENGTH, '0')
105
- }
@@ -0,0 +1,7 @@
1
+ /** Throttle subjects as non-raw keys: digests of what a request is throttled by. */
2
+ export interface ThrottleKeyHelper {
3
+ /** Collision-resistant, non-raw key for case-insensitive email throttles. */
4
+ emailThrottleKey: (email: string) => string
5
+ /** Non-raw key for request-source throttles. Callers remain responsible for trusted-proxy parsing. */
6
+ ipThrottleKey: (ip: string) => string
7
+ }
@@ -0,0 +1,17 @@
1
+ import { createHash } from 'node:crypto'
2
+ import type { ThrottleKeyHelper } from './throttle-keys/types.js'
3
+
4
+ const digestKey = (kind: string, value: string): string =>
5
+ createHash('sha256').update(`owlmeans:throttle:${kind}\0${value}`).digest('hex')
6
+
7
+ export const createThrottleKeyHelper = (): ThrottleKeyHelper => {
8
+ const emailThrottleKey = (email: string): string =>
9
+ `email:${digestKey('email', email.trim().toLowerCase())}`
10
+
11
+ const ipThrottleKey = (ip: string): string =>
12
+ `ip:${digestKey('ip', ip.trim().toLowerCase())}`
13
+
14
+ return { emailThrottleKey, ipThrottleKey }
15
+ }
16
+
17
+ export const throttleKeyHelper = createThrottleKeyHelper()
package/src/throttle.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { createHash, randomBytes } from 'node:crypto'
1
+ import { randomBytes } from 'node:crypto'
2
2
  import { createService, appendContextual } from '@owlmeans/context'
3
3
  import type { RedisResource } from '@owlmeans/redis-resource'
4
4
  import { OtpUnavailable } from './errors.js'
@@ -6,17 +6,7 @@ import type { AuthThrottleService, OtpConfig, OtpContext, ThrottleDecision, Thro
6
6
  import { OTP_RESOURCE, OTP_THROTTLE_SERVICE } from './consts.js'
7
7
  import { REDIS_THROTTLE_SCRIPT } from './consts.local.js'
8
8
  import type { Clock, ThrottleResourceRecord } from './types.local.js'
9
-
10
- const digestKey = (kind: string, value: string): string =>
11
- createHash('sha256').update(`owlmeans:throttle:${kind}\0${value}`).digest('hex')
12
-
13
- /** Collision-resistant, non-raw key for case-insensitive email throttles. */
14
- export const emailThrottleKey = (email: string): string =>
15
- `email:${digestKey('email', email.trim().toLowerCase())}`
16
-
17
- /** Non-raw key for request-source throttles. Callers remain responsible for trusted-proxy parsing. */
18
- export const ipThrottleKey = (ip: string): string =>
19
- `ip:${digestKey('ip', ip.trim().toLowerCase())}`
9
+ import { throttleKeyHelper } from './throttle-keys.js'
20
10
 
21
11
  const assertRules = (rules: readonly ThrottleRule[]): void => {
22
12
  if (rules.length === 0 || rules.some(rule => !Number.isInteger(rule.limit) || rule.limit < 1
@@ -87,3 +77,9 @@ export const makeRedisThrottleService = (
87
77
 
88
78
  return service
89
79
  }
80
+
81
+ /** @deprecated compat:factory-refactor — use `throttleKeyHelper.emailThrottleKey(…)` */
82
+ export const emailThrottleKey = (email: string): string => throttleKeyHelper.emailThrottleKey(email)
83
+
84
+ /** @deprecated compat:factory-refactor — use `throttleKeyHelper.ipThrottleKey(…)` */
85
+ export const ipThrottleKey = (ip: string): string => throttleKeyHelper.ipThrottleKey(ip)
@@ -1,9 +1,9 @@
1
1
  import type { OtpChallenge } from './types.js'
2
2
  import type { ResourceRecord } from '@owlmeans/resource'
3
3
 
4
- export type Clock = () => number
4
+ export interface Clock { (): number }
5
5
 
6
- export type StoredChallenge = OtpChallenge & { failedAttempts: number, expiresAt: number }
6
+ export interface StoredChallenge extends OtpChallenge { failedAttempts: number, expiresAt: number }
7
7
 
8
8
  export interface ChallengeResourceRecord extends ResourceRecord { id: string }
9
9
 
@@ -1,23 +1,23 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
- import { emailThrottleKey, ipThrottleKey } from '../src/throttle.js'
3
- import { otpEmailKey } from '../src/service.js'
2
+ import { throttleKeyHelper } from '../src/throttle-keys.js'
3
+ import { otpKeyHelper } from '../src/keys.js'
4
4
 
5
5
  describe('@owlmeans/server-auth-otp — privacy-preserving keys', () => {
6
6
  test('normalizes email case without storing the raw address', () => {
7
- const lower = emailThrottleKey('person@example.com')
8
- expect(emailThrottleKey(' PERSON@EXAMPLE.COM ')).toBe(lower)
7
+ const lower = throttleKeyHelper.emailThrottleKey('person@example.com')
8
+ expect(throttleKeyHelper.emailThrottleKey(' PERSON@EXAMPLE.COM ')).toBe(lower)
9
9
  expect(lower).not.toContain('person')
10
- expect(otpEmailKey('person@example.com')).not.toContain('person')
10
+ expect(otpKeyHelper.otpEmailKey('person@example.com')).not.toContain('person')
11
11
  })
12
12
 
13
13
  test('does not collapse punctuation the old lossy key strategy discarded', () => {
14
- expect(emailThrottleKey('a+b@example.com')).not.toBe(emailThrottleKey('a_b@example.com'))
14
+ expect(throttleKeyHelper.emailThrottleKey('a+b@example.com')).not.toBe(throttleKeyHelper.emailThrottleKey('a_b@example.com'))
15
15
  })
16
16
 
17
17
  test('namespaces IP and email digests independently and keeps the IP non-raw', () => {
18
- const ip = ipThrottleKey('203.0.113.7')
18
+ const ip = throttleKeyHelper.ipThrottleKey('203.0.113.7')
19
19
  expect(ip).toStartWith('ip:')
20
20
  expect(ip).not.toContain('203.0.113.7')
21
- expect(ip).not.toBe(emailThrottleKey('203.0.113.7'))
21
+ expect(ip).not.toBe(throttleKeyHelper.emailThrottleKey('203.0.113.7'))
22
22
  })
23
23
  })