@owlmeans/server-auth-otp 0.1.18-rc.4 → 0.1.18-rc.40

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 (46) 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 +82 -21
  4. package/build/challenge.d.ts +8 -0
  5. package/build/challenge.d.ts.map +1 -0
  6. package/build/challenge.js +98 -0
  7. package/build/challenge.js.map +1 -0
  8. package/build/consts.d.ts +10 -1
  9. package/build/consts.d.ts.map +1 -1
  10. package/build/consts.js +7 -1
  11. package/build/consts.js.map +1 -1
  12. package/build/errors.d.ts +18 -0
  13. package/build/errors.d.ts.map +1 -0
  14. package/build/errors.js +32 -0
  15. package/build/errors.js.map +1 -0
  16. package/build/index.d.ts +4 -1
  17. package/build/index.d.ts.map +1 -1
  18. package/build/index.js +4 -0
  19. package/build/index.js.map +1 -1
  20. package/build/plugin.d.ts.map +1 -1
  21. package/build/plugin.js +18 -17
  22. package/build/plugin.js.map +1 -1
  23. package/build/service.d.ts +3 -0
  24. package/build/service.d.ts.map +1 -1
  25. package/build/service.js +45 -22
  26. package/build/service.js.map +1 -1
  27. package/build/throttle.d.ts +12 -0
  28. package/build/throttle.d.ts.map +1 -0
  29. package/build/throttle.js +93 -0
  30. package/build/throttle.js.map +1 -0
  31. package/build/types.d.ts +34 -0
  32. package/build/types.d.ts.map +1 -1
  33. package/build/types.js +7 -1
  34. package/build/types.js.map +1 -1
  35. package/package.json +13 -13
  36. package/src/challenge.ts +114 -0
  37. package/src/consts.ts +11 -1
  38. package/src/errors.ts +37 -0
  39. package/src/index.ts +4 -1
  40. package/src/plugin.ts +18 -17
  41. package/src/service.ts +59 -34
  42. package/src/throttle.ts +120 -0
  43. package/src/types.ts +42 -0
  44. package/tests/context.ts +43 -0
  45. package/tests/keys.spec.ts +23 -0
  46. package/tests/service.spec.ts +88 -0
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
15
+ bun add @owlmeans/server-auth-otp@^0.1.18-rc.40
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
50
+ npx @owlmeans/agent-skills@^0.1.18-rc.34
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.0",
5
- "generatedAt": "2026-08-16T22:20:50.506Z",
4
+ "version": "0.1.18-rc.40",
5
+ "generatedAt": "2026-09-22T20:45:24.051Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -8,32 +8,50 @@ metadata:
8
8
 
9
9
  # Using `@owlmeans/server-auth-otp`
10
10
 
11
+ **Install:** `"@owlmeans/server-auth-otp": "^0.1.18-rc.40"` in `dependencies`
12
+
11
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.
12
14
 
15
+ ## `@owlmeans/auth-otp` — the contracts
16
+
17
+ `@owlmeans/auth-otp` has no skill of its own because it is the contracts half of this pair. Its
18
+ `OtpService` issues an opaque issuance id, persists and mails a code, and verifies that exact
19
+ issuance; it also owns `OTP_SERVICE`, `OTP_AUTH_TYPE` (`'email-otp'`), `OTP_RESOURCE`,
20
+ `OTP_TTL_SECONDS` (600), `OTP_CODE_LENGTH` (6), and the five-attempt policy. The server package
21
+ provides the challenge stores, throttles, mailer integration and plugin.
22
+
23
+ Depend on it from a shared package that must name the auth type or type the service, and depend on
24
+ `@owlmeans/server-auth-otp` only where the server wires itself up — the same producer/consumer split
25
+ every other contracts-and-driver pair in the framework uses. Add a constant or a method signature to
26
+ the contracts package, never to the implementation.
27
+
13
28
  ## Public API surface
14
29
 
15
30
  | Symbol | Kind | Purpose |
16
31
  |--------|------|---------|
17
32
  | `makeOtpService(alias?)` | fn | Service factory — stores/verifies OTP codes |
18
33
  | `appendOtpPlugin(context)` | fn | Registers the OTP `AuthPlugin` into the server-auth plugin registry |
34
+ | `OTP_SERVICE` | const | `'auth-otp-service'` — the service alias `makeOtpService` registers under |
19
35
  | `OTP_AUTH_TYPE` | const | `'email-otp'` — the auth type string to pass in `init` requests |
20
36
  | `OTP_RESOURCE` | const | Redis resource alias for code storage |
21
37
  | `OTP_TTL_SECONDS` | const | Code TTL (600 s = 10 min) |
22
38
  | `OTP_CODE_LENGTH` | const | 6 |
39
+ | `SERVER_AUTH_OTP` | const | `'server-auth-otp'` — this package's own alias |
40
+ | `OtpConfig`, `OtpContext` | type | The `cfg.otp` overrides below, as a server config/context |
23
41
 
24
42
  ## Registration requirements
25
43
 
26
44
  ```ts
27
45
  import { makeOtpService, appendOtpPlugin, OTP_RESOURCE } from '@owlmeans/server-auth-otp'
28
46
  import { makeRedisResource } from '@owlmeans/redis-resource'
29
- import { makeConsoleMailerService, CONSOLE_MAILER, MAILER_SERVICE } from '@owlmeans/mailer'
47
+ import { makeDefaultConsoleMailerService, MAILER_SERVICE } from '@owlmeans/mailer'
30
48
  import { makeMailgunMailerService } from '@owlmeans/server-mailer-mailgun'
31
49
 
32
50
  // 1. Register the Redis code-cache resource.
33
51
  context.registerResource(makeRedisResource(OTP_RESOURCE))
34
52
 
35
- // 2. Register a MailerService (console for dev/tests, Mailgun for prod).
36
- context.registerService(makeConsoleMailerService(MAILER_SERVICE))
53
+ // 2. Register a MailerService under MAILER_SERVICE (console for dev/tests, Mailgun for prod).
54
+ context.registerService(makeDefaultConsoleMailerService())
37
55
  // or:
38
56
  context.registerService(makeMailgunMailerService(MAILER_SERVICE))
39
57
 
@@ -48,13 +66,20 @@ appendOtpPlugin(context)
48
66
 
49
67
  **Init** — client sends `{ type: 'email-otp', userId: 'user@email.com' }`:
50
68
  - OTP service generates a 6-digit code, stores it in Redis with 10 min TTL, emails it.
51
- - Returns `{ challenge: email }` in a signed envelope.
52
-
53
- **Authenticate** — client sends `{ challenge: <signed-envelope>, userId: email, credential: '123456', entityId: 'the-entity' }`:
54
- - Envelope is opened → email is extracted.
55
- - OTP service verifies the code (throws `AuthenFailed` if wrong or expired), then deletes it.
56
- - `IdentityLinkingService` finds or creates the user profile scoped to `entityId`.
57
- - Sets `credential.type = AuthenticationType.OneTimeToken` and returns the signed auth token.
69
+ - Returns an opaque issuance id in a signed envelope. The id never contains the email or code.
70
+
71
+ **Authenticate** — client sends `{ challenge: <signed-envelope>, userId: email, credential: '123456', type: 'email-otp', role: AuthRole.User, scopes: [ALL_SCOPES] }`:
72
+ - Envelope is opened → the plugin verifies the opaque issuance and code atomically. Five failed
73
+ attempts invalidate it; one concurrent correct verification can consume it.
74
+ - `IdentityLinkingService` finds the linked profile, or links this email to the person's platform
75
+ identity — registering an account, a profile and an organization entity only when the address is
76
+ new to the platform.
77
+ - Copies `userId`, `profileId`, `entitySlug`, `role` and `scopes` from the resolved payload onto the
78
+ credential, sets `credential.type = AuthenticationType.OneTimeToken`, and returns the signed auth
79
+ token.
80
+ - `type`, `role`, `scopes` are required by the shared `AuthCredentialsSchema` (spread from
81
+ `AuthPayloadSchema.required`) even though the OTP plugin overwrites `role`/`scopes`/`type` on
82
+ success — a caller that omits them never reaches the plugin at all (see Gotchas).
58
83
 
59
84
  ## Config overrides (optional)
60
85
 
@@ -70,16 +95,52 @@ cfg.otp = {
70
95
 
71
96
  ## Rules
72
97
 
73
- - Always register the Redis resource AND the mailer service BEFORE the OTP service.
98
+ - Always register the Redis challenge store, Redis throttle service, and mailer service BEFORE the
99
+ OTP service. Production uses `makeRedisOtpChallengeStore` and `makeRedisThrottleService`; memory
100
+ implementations are local/test defaults only.
74
101
  - Call `appendOtpPlugin(context)` once per context — it adds to the shared plugin registry singleton.
75
- - The `credential.entityId` in the authenticate request determines which entity the resulting identity profile is scoped to. Pass it from the OIDC interaction.
102
+ - `credential.entitySlug` on the authenticate request **selects nothing**. The plugin copies it into
103
+ the linking details as `clientId` (defaulting to `'default'`) and `entityId`, and
104
+ `@owlmeans/server-auth-identity` reads neither: `getLinkedProfile` keys on the external login key
105
+ built from the auth type, the `'email'` service and the address, and `linkProfile` either reuses
106
+ the person's existing platform profile — matched on the account name — or mints a brand-new
107
+ organization entity. Whatever the caller sent is then overwritten with the linked profile's own
108
+ slug before the envelope is signed, so the address alone decides which identity and which
109
+ organization the token names.
76
110
  - Errors from this plugin are `AuthenFailed` (from `@owlmeans/auth`) — callers catch that, not raw `Error`.
77
- - For tests, use `makeConsoleMailerService()` and read `svc.captured[n].text` to extract the code.
78
-
79
- ## Related instructions
80
-
81
- - `@owlmeans/auth-otp` (common) — `OtpService` interface and constants
82
- - `@owlmeans/mailer` (common) — `MailerService` interface, console transport
83
- - `@owlmeans/server-mailer-mailgun` (common) — production Mailgun transport
84
- - `@owlmeans/server-auth` (common) — auth-manager plugin system
85
- - `auth-protocol` instructions — error hierarchy, identity read rules
111
+ - Use the email throttle for the public integrated-IAM flow: one issuance per minute and ten per
112
+ hour, with a 429 and Retry-After. The companion IP throttle is available for an ingress that
113
+ safely supplies a normalized client address; throttle keys hash email/IP values and never store
114
+ the raw identifier.
115
+ - For tests, register `makeDefaultConsoleMailerService()` and read `svc.captured[n].text` to extract
116
+ the code. Bare `makeConsoleMailerService()` registers under `CONSOLE_MAILER` (`'console-mailer'`),
117
+ which is not the alias `makeOtpService` resolves — it looks up `cfg.otp?.mailerAlias ?? MAILER_SERVICE`.
118
+
119
+ ## Gotchas
120
+
121
+ - **The OTP challenge must be opaque and plugin-owned.** The generic auth-manager replay policy
122
+ does not consume it before the OTP store can count attempts; the plugin atomically owns verify
123
+ and consume. Never encode the email or code in the issuance id, and never replace the bounded
124
+ attempt counter with a bare auth-cache replay key.
125
+ - **`AuthCredentialsSchema.credential` has a `minLength` floor** (from `@owlmeans/auth`) sized for
126
+ long tokens/signatures from other plugins (Ed25519 signature, OAuth code). A 6-digit OTP code is
127
+ legitimately shorter — the floor is `minLength: 1` and must stay low enough to admit it, or every
128
+ authenticate call 400s before the plugin ever runs.
129
+ - **`scopes`/`role`/`type` are schema-required on the authenticate body**, spread from
130
+ `AuthPayloadSchema.required` into `AuthCredentialsSchema` — even though this plugin overwrites
131
+ all three on success. A caller built without going through `@owlmeans/client-auth`'s
132
+ `AuthenticationControl` (which fills them automatically) must set them explicitly or the request
133
+ fails Fastify schema validation (`FST_ERR_VALIDATION`) before reaching this plugin at all.
134
+ - **The resulting auth token can exceed 1024 characters** — it wraps the full `AuthCredentials`
135
+ envelope, including the original allowance challenge, base64-encoded. A consumer route that
136
+ accepts this token (e.g. an OIDC `PROVIDER_INTERACTION` finalizer) must size its own `token`
137
+ field schema accordingly; the generic `AuthTokenSchema` (`maxLength: 1024`) is too small.
138
+
139
+ ## Related
140
+
141
+ - `@owlmeans/auth-otp` — the `OtpService` interface and the shared constants
142
+ - `@owlmeans/mailer` — `MailerService`, the console transport
143
+ - `@owlmeans/server-mailer-mailgun` — the production Mailgun transport
144
+ - `server-auth` skill — the auth-manager plugin system this plugin registers into
145
+ - `server-auth-identity` skill — how the linked profile and its organization entity are stored
146
+ - `auth-protocol` skill — error hierarchy and identity read rules
@@ -0,0 +1,8 @@
1
+ import type { OtpChallengeStore } from './types.js';
2
+ type Clock = () => number;
3
+ /** Real in-memory challenge store with synchronous consume/update semantics. */
4
+ export declare const makeMemoryOtpChallengeStore: (alias?: string, now?: Clock) => OtpChallengeStore;
5
+ /** Redis challenge store: compare, attempt increment, exhaustion, and consume are one operation. */
6
+ export declare const makeRedisOtpChallengeStore: (alias?: string, resourceAlias?: string) => OtpChallengeStore;
7
+ export {};
8
+ //# sourceMappingURL=challenge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"challenge.d.ts","sourceRoot":"","sources":["../src/challenge.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACyB,iBAAiB,EACrD,MAAM,YAAY,CAAA;AAGnB,KAAK,KAAK,GAAG,MAAM,MAAM,CAAA;AAGzB,gFAAgF;AAChF,eAAO,MAAM,2BAA2B,yBACJ,KAAK,KACtC,iBA+BF,CAAA;AA0BD,oGAAoG;AACpG,eAAO,MAAM,0BAA0B,8CAEpC,iBAqCF,CAAA"}
@@ -0,0 +1,98 @@
1
+ import { appendContextual, createService } from '@owlmeans/context';
2
+ import { OtpUnavailable } from './errors.js';
3
+ import { OTP_CHALLENGE_STORE, OTP_RESOURCE } from './consts.js';
4
+ import { OtpChallengeOutcome as Outcome } from './types.js';
5
+ /** Real in-memory challenge store with synchronous consume/update semantics. */
6
+ export const makeMemoryOtpChallengeStore = (alias = OTP_CHALLENGE_STORE, now = Date.now) => {
7
+ const records = new Map();
8
+ return createService(alias, {
9
+ issue: async (challenge, ttlSeconds) => {
10
+ const current = records.get(challenge.id);
11
+ if (current != null && current.expiresAt > now())
12
+ return false;
13
+ records.set(challenge.id, {
14
+ ...challenge, failedAttempts: 0, expiresAt: now() + ttlSeconds * 1000,
15
+ });
16
+ return true;
17
+ },
18
+ verify: async (id, emailKey, codeHash, maxFailedAttempts) => {
19
+ const record = records.get(id);
20
+ if (record == null || record.expiresAt <= now()) {
21
+ records.delete(id);
22
+ return Outcome.Missing;
23
+ }
24
+ if (record.emailKey === emailKey && record.codeHash === codeHash) {
25
+ records.delete(id);
26
+ return Outcome.Verified;
27
+ }
28
+ record.failedAttempts += 1;
29
+ if (record.failedAttempts >= maxFailedAttempts) {
30
+ records.delete(id);
31
+ return Outcome.Exhausted;
32
+ }
33
+ return Outcome.Invalid;
34
+ },
35
+ });
36
+ };
37
+ const REDIS_VERIFY_SCRIPT = `
38
+ local raw = redis.call('GET', KEYS[1])
39
+ if not raw then return 0 end
40
+ local record = cjson.decode(raw)
41
+ if record.emailKey == ARGV[1] and record.codeHash == ARGV[2] then
42
+ redis.call('DEL', KEYS[1])
43
+ return 1
44
+ end
45
+ record.failedAttempts = tonumber(record.failedAttempts or 0) + 1
46
+ if record.failedAttempts >= tonumber(ARGV[3]) then
47
+ redis.call('DEL', KEYS[1])
48
+ return -2
49
+ end
50
+ local ttl = redis.call('PTTL', KEYS[1])
51
+ if ttl <= 0 then
52
+ redis.call('DEL', KEYS[1])
53
+ return 0
54
+ end
55
+ redis.call('SET', KEYS[1], cjson.encode(record), 'XX', 'PX', ttl)
56
+ return -1
57
+ `;
58
+ /** Redis challenge store: compare, attempt increment, exhaustion, and consume are one operation. */
59
+ export const makeRedisOtpChallengeStore = (alias = OTP_CHALLENGE_STORE, resourceAlias = OTP_RESOURCE) => {
60
+ const service = appendContextual(alias, {
61
+ issue: async (challenge, ttlSeconds) => {
62
+ const ctx = service.ctx;
63
+ const resource = ctx.resource(resourceAlias);
64
+ try {
65
+ const result = await resource.db.client.set(resource.key(challenge.id), JSON.stringify({ ...challenge, failedAttempts: 0 }), 'EX', ttlSeconds, 'NX');
66
+ return result === 'OK';
67
+ }
68
+ catch (error) {
69
+ const unavailable = new OtpUnavailable('challenge');
70
+ if (error instanceof Error)
71
+ unavailable.oiriginalStack = `${error}: ${error.stack}`;
72
+ throw unavailable;
73
+ }
74
+ },
75
+ verify: async (id, emailKey, codeHash, maxFailedAttempts) => {
76
+ const ctx = service.ctx;
77
+ const resource = ctx.resource(resourceAlias);
78
+ try {
79
+ const result = Number(await resource.db.client.eval(REDIS_VERIFY_SCRIPT, 1, resource.key(id), emailKey, codeHash, String(maxFailedAttempts)));
80
+ if (result === 1)
81
+ return Outcome.Verified;
82
+ if (result === -1)
83
+ return Outcome.Invalid;
84
+ if (result === -2)
85
+ return Outcome.Exhausted;
86
+ return Outcome.Missing;
87
+ }
88
+ catch (error) {
89
+ const unavailable = new OtpUnavailable('challenge');
90
+ if (error instanceof Error)
91
+ unavailable.oiriginalStack = `${error}: ${error.stack}`;
92
+ throw unavailable;
93
+ }
94
+ },
95
+ });
96
+ return service;
97
+ };
98
+ //# sourceMappingURL=challenge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"challenge.js","sourceRoot":"","sources":["../src/challenge.ts"],"names":[],"mappings":"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"}
package/build/consts.d.ts CHANGED
@@ -1,4 +1,13 @@
1
1
  export declare const SERVER_AUTH_OTP = "server-auth-otp";
2
+ export declare const OTP_CHALLENGE_STORE = "auth-otp-challenge-store";
3
+ export declare const OTP_THROTTLE_SERVICE = "auth-otp-throttle";
4
+ export declare const OTP_EMAIL_THROTTLE_RULES: readonly (Readonly<{
5
+ limit: 1;
6
+ windowSeconds: 60;
7
+ }> | Readonly<{
8
+ limit: 10;
9
+ windowSeconds: number;
10
+ }>)[];
2
11
  /** The OTP auth plugin type key — passed as credential.type in init requests */
3
- export { OTP_AUTH_TYPE, OTP_SERVICE, OTP_RESOURCE, OTP_TTL_SECONDS, OTP_CODE_LENGTH } from '@owlmeans/auth-otp';
12
+ export { OTP_AUTH_TYPE, OTP_SERVICE, OTP_RESOURCE, OTP_TTL_SECONDS, OTP_CODE_LENGTH, OTP_MAX_FAILED_ATTEMPTS, } from '@owlmeans/auth-otp';
4
13
  //# sourceMappingURL=consts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,eAAe,oBAAoB,CAAA;AAEhD,gFAAgF;AAChF,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA"}
1
+ {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,eAAe,oBAAoB,CAAA;AAChD,eAAO,MAAM,mBAAmB,6BAA6B,CAAA;AAC7D,eAAO,MAAM,oBAAoB,sBAAsB,CAAA;AAEvD,eAAO,MAAM,wBAAwB;;;;;;KAGnC,CAAA;AAEF,gFAAgF;AAChF,OAAO,EACL,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe,EAC1E,uBAAuB,GACxB,MAAM,oBAAoB,CAAA"}
package/build/consts.js CHANGED
@@ -1,4 +1,10 @@
1
1
  export const SERVER_AUTH_OTP = 'server-auth-otp';
2
+ export const OTP_CHALLENGE_STORE = 'auth-otp-challenge-store';
3
+ export const OTP_THROTTLE_SERVICE = 'auth-otp-throttle';
4
+ export const OTP_EMAIL_THROTTLE_RULES = Object.freeze([
5
+ Object.freeze({ limit: 1, windowSeconds: 60 }),
6
+ Object.freeze({ limit: 10, windowSeconds: 60 * 60 }),
7
+ ]);
2
8
  /** The OTP auth plugin type key — passed as credential.type in init requests */
3
- export { OTP_AUTH_TYPE, OTP_SERVICE, OTP_RESOURCE, OTP_TTL_SECONDS, OTP_CODE_LENGTH } from '@owlmeans/auth-otp';
9
+ export { OTP_AUTH_TYPE, OTP_SERVICE, OTP_RESOURCE, OTP_TTL_SECONDS, OTP_CODE_LENGTH, OTP_MAX_FAILED_ATTEMPTS, } from '@owlmeans/auth-otp';
4
10
  //# sourceMappingURL=consts.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG,iBAAiB,CAAA;AAEhD,gFAAgF;AAChF,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA"}
1
+ {"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG,iBAAiB,CAAA;AAChD,MAAM,CAAC,MAAM,mBAAmB,GAAG,0BAA0B,CAAA;AAC7D,MAAM,CAAC,MAAM,oBAAoB,GAAG,mBAAmB,CAAA;AAEvD,MAAM,CAAC,MAAM,wBAAwB,GAAG,MAAM,CAAC,MAAM,CAAC;IACpD,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,aAAa,EAAE,EAAE,EAAE,CAAC;IAC9C,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,aAAa,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC;CACrD,CAAC,CAAA;AAEF,gFAAgF;AAChF,OAAO,EACL,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe,EAC1E,uBAAuB,GACxB,MAAM,oBAAoB,CAAA"}
@@ -0,0 +1,18 @@
1
+ import { ResilientError } from '@owlmeans/error';
2
+ export declare class OtpError extends ResilientError {
3
+ static typeName: string;
4
+ constructor(message?: string);
5
+ }
6
+ export declare class OtpThrottled extends OtpError {
7
+ static typeName: string;
8
+ static httpStatus: number;
9
+ readonly retryAfter: number;
10
+ constructor(retryAfter?: number | string, stack?: string);
11
+ }
12
+ export declare class OtpUnavailable extends OtpError {
13
+ static typeName: string;
14
+ static httpStatus: number;
15
+ static allowServerErrorStatus: boolean;
16
+ constructor(message?: string);
17
+ }
18
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,qBAAa,QAAS,SAAQ,cAAc;IAC1C,OAAuB,QAAQ,SAAa;IAE5C,YAAY,OAAO,GAAE,MAAgB,EAEpC;CACF;AAED,qBAAa,YAAa,SAAQ,QAAQ;IACxC,OAAuB,QAAQ,SAAkC;IACjE,OAAc,UAAU,SAAM;IAC9B,SAAgB,UAAU,EAAE,MAAM,CAAA;IAElC,YAAY,UAAU,GAAE,MAAM,GAAG,MAAU,EAAE,KAAK,CAAC,EAAE,MAAM,EAK1D;CACF;AAED,qBAAa,cAAe,SAAQ,QAAQ;IAC1C,OAAuB,QAAQ,SAAoC;IACnE,OAAc,UAAU,SAAM;IAC9B,OAAc,sBAAsB,UAAO;IAE3C,YAAY,OAAO,GAAE,MAAgB,EAGpC;CACF"}
@@ -0,0 +1,32 @@
1
+ import { ResilientError } from '@owlmeans/error';
2
+ export class OtpError extends ResilientError {
3
+ static typeName = 'OtpError';
4
+ constructor(message = 'error') {
5
+ super(OtpError.typeName, `otp:${message}`);
6
+ }
7
+ }
8
+ export class OtpThrottled extends OtpError {
9
+ static typeName = `${OtpError.typeName}Throttled`;
10
+ static httpStatus = 429;
11
+ retryAfter;
12
+ constructor(retryAfter = 1, stack) {
13
+ super(typeof retryAfter === 'string' ? retryAfter : 'throttled');
14
+ this.type = OtpThrottled.typeName;
15
+ this.retryAfter = typeof retryAfter === 'number' ? Math.max(1, Math.ceil(retryAfter)) : 1;
16
+ if (stack != null)
17
+ this.oiriginalStack = stack;
18
+ }
19
+ }
20
+ export class OtpUnavailable extends OtpError {
21
+ static typeName = `${OtpError.typeName}Unavailable`;
22
+ static httpStatus = 503;
23
+ static allowServerErrorStatus = true;
24
+ constructor(message = 'store') {
25
+ super(`unavailable:${message}`);
26
+ this.type = OtpUnavailable.typeName;
27
+ }
28
+ }
29
+ ResilientError.registerErrorClass(OtpError);
30
+ ResilientError.registerErrorClass(OtpThrottled);
31
+ ResilientError.registerErrorClass(OtpUnavailable);
32
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,MAAM,OAAO,QAAS,SAAQ,cAAc;IACnC,MAAM,CAAU,QAAQ,GAAG,UAAU,CAAA;IAE5C,YAAY,OAAO,GAAW,OAAO;QACnC,KAAK,CAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,OAAO,EAAE,CAAC,CAAA;IAC5C,CAAC;CACF;AAED,MAAM,OAAO,YAAa,SAAQ,QAAQ;IACjC,MAAM,CAAU,QAAQ,GAAG,GAAG,QAAQ,CAAC,QAAQ,WAAW,CAAA;IAC1D,MAAM,CAAC,UAAU,GAAG,GAAG,CAAA;IACd,UAAU,CAAQ;IAElC,YAAY,UAAU,GAAoB,CAAC,EAAE,KAAc;QACzD,KAAK,CAAC,OAAO,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC,CAAA;QAChE,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC,QAAQ,CAAA;QACjC,IAAI,CAAC,UAAU,GAAG,OAAO,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;QACzF,IAAI,KAAK,IAAI,IAAI;YAAE,IAAI,CAAC,cAAc,GAAG,KAAK,CAAA;IAChD,CAAC;CACF;AAED,MAAM,OAAO,cAAe,SAAQ,QAAQ;IACnC,MAAM,CAAU,QAAQ,GAAG,GAAG,QAAQ,CAAC,QAAQ,aAAa,CAAA;IAC5D,MAAM,CAAC,UAAU,GAAG,GAAG,CAAA;IACvB,MAAM,CAAC,sBAAsB,GAAG,IAAI,CAAA;IAE3C,YAAY,OAAO,GAAW,OAAO;QACnC,KAAK,CAAC,eAAe,OAAO,EAAE,CAAC,CAAA;QAC/B,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC,QAAQ,CAAA;IACrC,CAAC;CACF;AAED,cAAc,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAA;AAC3C,cAAc,CAAC,kBAAkB,CAAC,YAAY,CAAC,CAAA;AAC/C,cAAc,CAAC,kBAAkB,CAAC,cAAc,CAAC,CAAA"}
package/build/index.d.ts CHANGED
@@ -1,5 +1,8 @@
1
- export type * from './types.js';
1
+ export * from './types.js';
2
2
  export * from './consts.js';
3
3
  export * from './service.js';
4
4
  export * from './plugin.js';
5
+ export * from './errors.js';
6
+ export * from './throttle.js';
7
+ export * from './challenge.js';
5
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,YAAY,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA"}
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"}
package/build/index.js CHANGED
@@ -1,4 +1,8 @@
1
+ export * from './types.js';
1
2
  export * from './consts.js';
2
3
  export * from './service.js';
3
4
  export * from './plugin.js';
5
+ export * from './errors.js';
6
+ export * from './throttle.js';
7
+ export * from './challenge.js';
4
8
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA"}
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 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAmEvD,sGAAsG;AACtG,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,SAAS,EAAE,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC,KAAG,CAG1F,CAAA"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAqEvD,sGAAsG;AACtG,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,SAAS,EAAE,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC,KAAG,CAG1F,CAAA"}
package/build/plugin.js CHANGED
@@ -1,47 +1,48 @@
1
1
  import { ALL_SCOPES, AuthenFailed, AuthRole, AuthenticationType } from '@owlmeans/auth';
2
- import { registerPlugin } from '@owlmeans/server-auth/manager/plugins';
2
+ import { AuthChallengeReplayPolicy, registerPlugin } from '@owlmeans/server-auth/manager/plugins';
3
3
  import { OTP_AUTH_TYPE, OTP_SERVICE } from '@owlmeans/auth-otp';
4
4
  import { AUTH_IDENTITY_LINKING } from '@owlmeans/server-auth-identity';
5
- import { createIdOfLength, IdStyle } from '@owlmeans/basic-ids';
6
- // Separates the email from the per-request nonce inside the challenge (see init() below).
5
+ // Separates the email from the opaque issuance id inside the signed challenge.
7
6
  const CHALLENGE_DELIMITER = '::';
8
7
  /** Factory that creates the email-OTP AuthPlugin bound to the given context. */
9
8
  const otpPlugin = (context) => ({
10
9
  type: OTP_AUTH_TYPE,
10
+ challengeReplayPolicy: AuthChallengeReplayPolicy.Plugin,
11
11
  init: async (request) => {
12
12
  if (request.userId == null || !request.userId.includes('@')) {
13
13
  throw new AuthenFailed('otp:email');
14
14
  }
15
15
  const email = request.userId.trim().toLowerCase();
16
16
  const otp = context.service(OTP_SERVICE);
17
- await otp.issueChallenge(email);
18
- // The auth manager's anti-replay cache (AUTH_CACHE) keys on this decoded challenge, so it
19
- // must be unique per request — the plaintext email alone would be identical across every
20
- // independent login attempt for that address and collide with a still-cached prior one.
21
- const nonce = createIdOfLength(16, IdStyle.Base58);
22
- return { challenge: `${email}${CHALLENGE_DELIMITER}${nonce}` };
17
+ const issuanceId = await otp.issueChallenge(email);
18
+ return { challenge: `${email}${CHALLENGE_DELIMITER}${issuanceId}` };
23
19
  },
24
20
  authenticate: async (credential) => {
25
- // credential.challenge is "email::nonce" (opened from the signed envelope in makeAuthModel).
26
- const email = credential.challenge?.split(CHALLENGE_DELIMITER)[0];
21
+ // credential.challenge is "email::issuanceId" (opened from the signed envelope).
22
+ const parts = credential.challenge?.split(CHALLENGE_DELIMITER) ?? [];
23
+ const email = parts.length === 2 ? parts[0] : undefined;
24
+ const issuanceId = parts.length === 2 ? parts[1] : undefined;
27
25
  const code = credential.credential;
28
- if (!email || !email.includes('@')) {
26
+ if (!email || !email.includes('@') || !issuanceId) {
29
27
  throw new AuthenFailed('otp:email');
30
28
  }
31
29
  if (!code) {
32
30
  throw new AuthenFailed('otp:code');
33
31
  }
34
32
  const otp = context.service(OTP_SERVICE);
35
- await otp.verifyChallenge(email, code);
33
+ await otp.verifyChallenge(email, issuanceId, code);
36
34
  const identityAlias = context.cfg.otp?.identityAlias ?? AUTH_IDENTITY_LINKING;
37
35
  const identity = context.service(identityAlias);
38
- const entityId = credential.entityId;
36
+ // What arrives on the credential is the organization SLUG the user is logging into — it is
37
+ // what a person can be asked to type. The linked profile below answers with the canonical
38
+ // slug, which is what the token ends up carrying.
39
+ const entitySlug = credential.entitySlug;
39
40
  const details = {
40
41
  type: OTP_AUTH_TYPE,
41
42
  service: 'email',
42
- clientId: entityId ?? 'default',
43
+ clientId: entitySlug ?? 'default',
43
44
  userId: email,
44
- entityId,
45
+ entityId: entitySlug,
45
46
  };
46
47
  let payload = await identity.getLinkedProfile(details);
47
48
  if (payload == null) {
@@ -51,7 +52,7 @@ const otpPlugin = (context) => ({
51
52
  credential.type = AuthenticationType.OneTimeToken;
52
53
  credential.userId = payload.userId;
53
54
  credential.profileId = payload.profileId;
54
- credential.entityId = payload.entityId;
55
+ credential.entitySlug = payload.entitySlug;
55
56
  credential.role = payload.role ?? AuthRole.User;
56
57
  credential.scopes = payload.scopes ?? [ALL_SCOPES];
57
58
  return { token: '' };
@@ -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,3 +1,6 @@
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;
2
5
  export declare const makeOtpService: (alias?: string) => OtpService;
3
6
  //# sourceMappingURL=service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAgBpD,eAAO,MAAM,cAAc,sBAA0B,UA+CpD,CAAA"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAkBpD,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,UAiEpD,CAAA"}