@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.
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/server-auth-otp",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-08-
|
|
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',
|
|
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.
|
|
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.
|
|
26
|
-
"@owlmeans/auth-otp": "^0.1.18-rc.
|
|
27
|
-
"@owlmeans/basic-ids": "^0.1.18-rc.
|
|
28
|
-
"@owlmeans/context": "^0.1.18-rc.
|
|
29
|
-
"@owlmeans/mailer": "^0.1.18-rc.
|
|
30
|
-
"@owlmeans/oidc": "^0.1.18-rc.
|
|
31
|
-
"@owlmeans/redis-resource": "^0.1.18-rc.
|
|
32
|
-
"@owlmeans/resource": "^0.1.18-rc.
|
|
33
|
-
"@owlmeans/server-auth": "^0.1.18-rc.
|
|
34
|
-
"@owlmeans/server-auth-identity": "^0.1.18-rc.
|
|
35
|
-
"@owlmeans/server-context": "^0.1.18-rc.
|
|
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:*",
|