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

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.
@@ -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.6",
5
+ "generatedAt": "2026-08-18T14:48:00.518Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -48,13 +48,18 @@ appendOtpPlugin(context)
48
48
 
49
49
  **Init** — client sends `{ type: 'email-otp', userId: 'user@email.com' }`:
50
50
  - 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.
51
+ - Returns `{ challenge: '<email>::<nonce>' }` in a signed envelope — see Gotchas below for why the
52
+ nonce is required, not optional.
52
53
 
53
- **Authenticate** — client sends `{ challenge: <signed-envelope>, userId: email, credential: '123456', entityId: 'the-entity' }`:
54
- - Envelope is opened → email is extracted.
54
+ **Authenticate** — client sends `{ challenge: <signed-envelope>, userId: email, credential: '123456', type: 'email-otp', role: AuthRole.User, scopes: [ALL_SCOPES] }`:
55
+ - Envelope is opened → `email::nonce` is extracted, split on `::` to recover the email (the nonce
56
+ itself is discarded — it only exists to make the challenge unique, see Gotchas).
55
57
  - OTP service verifies the code (throws `AuthenFailed` if wrong or expired), then deletes it.
56
58
  - `IdentityLinkingService` finds or creates the user profile scoped to `entityId`.
57
59
  - Sets `credential.type = AuthenticationType.OneTimeToken` and returns the signed auth token.
60
+ - `type`, `role`, `scopes` are required by the shared `AuthCredentialsSchema` (spread from
61
+ `AuthPayloadSchema.required`) even though the OTP plugin overwrites `role`/`scopes`/`type` on
62
+ success — a caller that omits them never reaches the plugin at all (see Gotchas).
58
63
 
59
64
  ## Config overrides (optional)
60
65
 
@@ -76,6 +81,31 @@ cfg.otp = {
76
81
  - Errors from this plugin are `AuthenFailed` (from `@owlmeans/auth`) — callers catch that, not raw `Error`.
77
82
  - For tests, use `makeConsoleMailerService()` and read `svc.captured[n].text` to extract the code.
78
83
 
84
+ ## Gotchas
85
+
86
+ - **The challenge must never be just the plaintext email.** The auth manager's anti-replay guard
87
+ (`AUTH_CACHE` in `@owlmeans/server-auth`) burns the *decoded* challenge into a Redis
88
+ create-once record before the plugin's own credential check runs. If `init()` returned the bare
89
+ email, that decoded value would be identical across every independent login attempt for the same
90
+ address, so a second legitimate login within the cache TTL (`AUTHEN_TIMEFRAME`, 10 min) — right
91
+ code or wrong — collides with the still-cached prior attempt and throws
92
+ `AuthenFailed('challenge')` (a `RecordExists` underneath), not an OTP-specific error. Fix: `init()`
93
+ appends a fresh `createIdOfLength(16, IdStyle.Base58)` nonce (`'<email>::<nonce>'`); `authenticate()`
94
+ splits it back apart. Never revert to a bare-email challenge.
95
+ - **`AuthCredentialsSchema.credential` has a `minLength` floor** (from `@owlmeans/auth`) sized for
96
+ long tokens/signatures from other plugins (Ed25519 signature, OAuth code). A 6-digit OTP code is
97
+ legitimately shorter — the schema's floor must stay low enough (`minLength: 1` as of this
98
+ writing) to admit it, or every authenticate call 400s before the plugin ever runs.
99
+ - **`scopes`/`role`/`type` are schema-required on the authenticate body**, spread from
100
+ `AuthPayloadSchema.required` into `AuthCredentialsSchema` — even though this plugin overwrites
101
+ all three on success. A caller built without going through `@owlmeans/client-auth`'s
102
+ `AuthenticationControl` (which fills them automatically) must set them explicitly or the request
103
+ fails Fastify schema validation (`FST_ERR_VALIDATION`) before reaching this plugin at all.
104
+ - **The resulting auth token can exceed 1024 characters** — it wraps the full `AuthCredentials`
105
+ envelope, including the original allowance challenge, base64-encoded. A consumer route that
106
+ accepts this token (e.g. an OIDC `PROVIDER_INTERACTION` finalizer) must size its own `token`
107
+ field schema accordingly; the generic `AuthTokenSchema` (`maxLength: 1024`) is too small.
108
+
79
109
  ## Related instructions
80
110
 
81
111
  - `@owlmeans/auth-otp` (common) — `OtpService` interface and constants
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/server-auth-otp",
3
- "version": "0.1.18-rc.4",
3
+ "version": "0.1.18-rc.6",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -22,17 +22,17 @@
22
22
  }
23
23
  },
24
24
  "dependencies": {
25
- "@owlmeans/auth": "^0.1.18-rc.4",
26
- "@owlmeans/auth-otp": "^0.1.18-rc.4",
27
- "@owlmeans/basic-ids": "^0.1.18-rc.4",
28
- "@owlmeans/context": "^0.1.18-rc.4",
29
- "@owlmeans/mailer": "^0.1.18-rc.4",
30
- "@owlmeans/oidc": "^0.1.18-rc.4",
31
- "@owlmeans/redis-resource": "^0.1.18-rc.4",
32
- "@owlmeans/resource": "^0.1.18-rc.4",
33
- "@owlmeans/server-auth": "^0.1.18-rc.4",
34
- "@owlmeans/server-auth-identity": "^0.1.18-rc.4",
35
- "@owlmeans/server-context": "^0.1.18-rc.4"
25
+ "@owlmeans/auth": "^0.1.18-rc.6",
26
+ "@owlmeans/auth-otp": "^0.1.18-rc.6",
27
+ "@owlmeans/basic-ids": "^0.1.18-rc.6",
28
+ "@owlmeans/context": "^0.1.18-rc.6",
29
+ "@owlmeans/mailer": "^0.1.18-rc.6",
30
+ "@owlmeans/oidc": "^0.1.18-rc.6",
31
+ "@owlmeans/redis-resource": "^0.1.18-rc.6",
32
+ "@owlmeans/resource": "^0.1.18-rc.6",
33
+ "@owlmeans/server-auth": "^0.1.18-rc.6",
34
+ "@owlmeans/server-auth-identity": "^0.1.18-rc.6",
35
+ "@owlmeans/server-context": "^0.1.18-rc.6"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@owlmeans/dep-config": "workspace:*",