@owlmeans/server-auth-otp 0.1.18-rc.16 → 0.1.18-rc.18
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/README.md +2 -2
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/server-auth-otp/SKILL.md +58 -23
- package/build/plugin.d.ts.map +1 -1
- package/build/service.d.ts.map +1 -1
- package/package.json +12 -12
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.17
|
|
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.12
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
The embedded files are version-matched to this package release. Do not edit them
|
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-09-
|
|
4
|
+
"version": "0.1.18-rc.18",
|
|
5
|
+
"generatedAt": "2026-09-04T22:43:25.442Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -8,32 +8,51 @@ metadata:
|
|
|
8
8
|
|
|
9
9
|
# Using `@owlmeans/server-auth-otp`
|
|
10
10
|
|
|
11
|
+
**Install:** `"@owlmeans/server-auth-otp": "^0.1.18-rc.18"` 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 has no behaviour: it is the contract half of
|
|
18
|
+
this pair, and everything it declares is implemented here. It carries the `OtpService` interface —
|
|
19
|
+
`issueChallenge(email)`, which generates a code, persists it with a TTL and mails it, and
|
|
20
|
+
`verifyChallenge(email, code)`, which verifies and consumes it — plus the names both halves agree
|
|
21
|
+
on: `OTP_SERVICE`, `OTP_AUTH_TYPE` (`'email-otp'`), `OTP_RESOURCE`, `OTP_TTL_SECONDS` (600) and
|
|
22
|
+
`OTP_CODE_LENGTH` (6). Nothing else; no Redis, no mailer, no plugin.
|
|
23
|
+
|
|
24
|
+
Depend on it from a shared package that must name the auth type or type the service, and depend on
|
|
25
|
+
`@owlmeans/server-auth-otp` only where the server wires itself up — the same producer/consumer split
|
|
26
|
+
every other contracts-and-driver pair in the framework uses. Add a constant or a method signature to
|
|
27
|
+
the contracts package, never to the implementation.
|
|
28
|
+
|
|
13
29
|
## Public API surface
|
|
14
30
|
|
|
15
31
|
| Symbol | Kind | Purpose |
|
|
16
32
|
|--------|------|---------|
|
|
17
33
|
| `makeOtpService(alias?)` | fn | Service factory — stores/verifies OTP codes |
|
|
18
34
|
| `appendOtpPlugin(context)` | fn | Registers the OTP `AuthPlugin` into the server-auth plugin registry |
|
|
35
|
+
| `OTP_SERVICE` | const | `'auth-otp-service'` — the service alias `makeOtpService` registers under |
|
|
19
36
|
| `OTP_AUTH_TYPE` | const | `'email-otp'` — the auth type string to pass in `init` requests |
|
|
20
37
|
| `OTP_RESOURCE` | const | Redis resource alias for code storage |
|
|
21
38
|
| `OTP_TTL_SECONDS` | const | Code TTL (600 s = 10 min) |
|
|
22
39
|
| `OTP_CODE_LENGTH` | const | 6 |
|
|
40
|
+
| `SERVER_AUTH_OTP` | const | `'server-auth-otp'` — this package's own alias |
|
|
41
|
+
| `OtpConfig`, `OtpContext` | type | The `cfg.otp` overrides below, as a server config/context |
|
|
23
42
|
|
|
24
43
|
## Registration requirements
|
|
25
44
|
|
|
26
45
|
```ts
|
|
27
46
|
import { makeOtpService, appendOtpPlugin, OTP_RESOURCE } from '@owlmeans/server-auth-otp'
|
|
28
47
|
import { makeRedisResource } from '@owlmeans/redis-resource'
|
|
29
|
-
import {
|
|
48
|
+
import { makeDefaultConsoleMailerService, MAILER_SERVICE } from '@owlmeans/mailer'
|
|
30
49
|
import { makeMailgunMailerService } from '@owlmeans/server-mailer-mailgun'
|
|
31
50
|
|
|
32
51
|
// 1. Register the Redis code-cache resource.
|
|
33
52
|
context.registerResource(makeRedisResource(OTP_RESOURCE))
|
|
34
53
|
|
|
35
|
-
// 2. Register a MailerService (console for dev/tests, Mailgun for prod).
|
|
36
|
-
context.registerService(
|
|
54
|
+
// 2. Register a MailerService under MAILER_SERVICE (console for dev/tests, Mailgun for prod).
|
|
55
|
+
context.registerService(makeDefaultConsoleMailerService())
|
|
37
56
|
// or:
|
|
38
57
|
context.registerService(makeMailgunMailerService(MAILER_SERVICE))
|
|
39
58
|
|
|
@@ -55,8 +74,12 @@ appendOtpPlugin(context)
|
|
|
55
74
|
- Envelope is opened → `email::nonce` is extracted, split on `::` to recover the email (the nonce
|
|
56
75
|
itself is discarded — it only exists to make the challenge unique, see Gotchas).
|
|
57
76
|
- OTP service verifies the code (throws `AuthenFailed` if wrong or expired), then deletes it.
|
|
58
|
-
- `IdentityLinkingService` finds
|
|
59
|
-
|
|
77
|
+
- `IdentityLinkingService` finds the linked profile, or links this email to the person's platform
|
|
78
|
+
identity — registering an account, a profile and an organization entity only when the address is
|
|
79
|
+
new to the platform.
|
|
80
|
+
- Copies `userId`, `profileId`, `entitySlug`, `role` and `scopes` from the resolved payload onto the
|
|
81
|
+
credential, sets `credential.type = AuthenticationType.OneTimeToken`, and returns the signed auth
|
|
82
|
+
token.
|
|
60
83
|
- `type`, `role`, `scopes` are required by the shared `AuthCredentialsSchema` (spread from
|
|
61
84
|
`AuthPayloadSchema.required`) even though the OTP plugin overwrites `role`/`scopes`/`type` on
|
|
62
85
|
success — a caller that omits them never reaches the plugin at all (see Gotchas).
|
|
@@ -77,25 +100,36 @@ cfg.otp = {
|
|
|
77
100
|
|
|
78
101
|
- Always register the Redis resource AND the mailer service BEFORE the OTP service.
|
|
79
102
|
- Call `appendOtpPlugin(context)` once per context — it adds to the shared plugin registry singleton.
|
|
80
|
-
-
|
|
103
|
+
- `credential.entitySlug` on the authenticate request **selects nothing**. The plugin copies it into
|
|
104
|
+
the linking details as `clientId` (defaulting to `'default'`) and `entityId`, and
|
|
105
|
+
`@owlmeans/server-auth-identity` reads neither: `getLinkedProfile` keys on the external login key
|
|
106
|
+
built from the auth type, the `'email'` service and the address, and `linkProfile` either reuses
|
|
107
|
+
the person's existing platform profile — matched on the account name — or mints a brand-new
|
|
108
|
+
organization entity. Whatever the caller sent is then overwritten with the linked profile's own
|
|
109
|
+
slug before the envelope is signed, so the address alone decides which identity and which
|
|
110
|
+
organization the token names.
|
|
81
111
|
- Errors from this plugin are `AuthenFailed` (from `@owlmeans/auth`) — callers catch that, not raw `Error`.
|
|
82
|
-
- For tests,
|
|
112
|
+
- For tests, register `makeDefaultConsoleMailerService()` and read `svc.captured[n].text` to extract
|
|
113
|
+
the code. Bare `makeConsoleMailerService()` registers under `CONSOLE_MAILER` (`'console-mailer'`),
|
|
114
|
+
which is not the alias `makeOtpService` resolves — it looks up `cfg.otp?.mailerAlias ?? MAILER_SERVICE`.
|
|
83
115
|
|
|
84
116
|
## Gotchas
|
|
85
117
|
|
|
86
118
|
- **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
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
119
|
+
(`AUTH_CACHE` in `@owlmeans/server-auth`) burns the *decoded* challenge into a create-once record
|
|
120
|
+
before the plugin's own credential check runs. `AUTH_CACHE` defaults to a **static, in-memory**
|
|
121
|
+
resource — the manager's context appends it, and nothing here upgrades it to Redis; the OTP codes
|
|
122
|
+
are the only thing this package keeps in Redis. If `init()` returned the bare email, that decoded
|
|
123
|
+
value would be identical across every independent login attempt for the same address, so a second
|
|
124
|
+
legitimate login within the cache TTL (`AUTHEN_TIMEFRAME`, 15 min) — right code or wrong —
|
|
125
|
+
collides with the still-cached prior attempt and throws `AuthenFailed('challenge')` (the
|
|
126
|
+
resource's `RecordExists` underneath), not an OTP-specific error. Fix: `init()` appends a fresh
|
|
127
|
+
`createIdOfLength(16, IdStyle.Base58)` nonce (`'<email>::<nonce>'`); `authenticate()` splits it
|
|
128
|
+
back apart. Never revert to a bare-email challenge.
|
|
95
129
|
- **`AuthCredentialsSchema.credential` has a `minLength` floor** (from `@owlmeans/auth`) sized for
|
|
96
130
|
long tokens/signatures from other plugins (Ed25519 signature, OAuth code). A 6-digit OTP code is
|
|
97
|
-
legitimately shorter — the
|
|
98
|
-
|
|
131
|
+
legitimately shorter — the floor is `minLength: 1` and must stay low enough to admit it, or every
|
|
132
|
+
authenticate call 400s before the plugin ever runs.
|
|
99
133
|
- **`scopes`/`role`/`type` are schema-required on the authenticate body**, spread from
|
|
100
134
|
`AuthPayloadSchema.required` into `AuthCredentialsSchema` — even though this plugin overwrites
|
|
101
135
|
all three on success. A caller built without going through `@owlmeans/client-auth`'s
|
|
@@ -106,10 +140,11 @@ cfg.otp = {
|
|
|
106
140
|
accepts this token (e.g. an OIDC `PROVIDER_INTERACTION` finalizer) must size its own `token`
|
|
107
141
|
field schema accordingly; the generic `AuthTokenSchema` (`maxLength: 1024`) is too small.
|
|
108
142
|
|
|
109
|
-
## Related
|
|
143
|
+
## Related
|
|
110
144
|
|
|
111
|
-
- `@owlmeans/auth-otp`
|
|
112
|
-
- `@owlmeans/mailer`
|
|
113
|
-
- `@owlmeans/server-mailer-mailgun`
|
|
114
|
-
-
|
|
115
|
-
- `auth-
|
|
145
|
+
- `@owlmeans/auth-otp` — the `OtpService` interface and the shared constants
|
|
146
|
+
- `@owlmeans/mailer` — `MailerService`, the console transport
|
|
147
|
+
- `@owlmeans/server-mailer-mailgun` — the production Mailgun transport
|
|
148
|
+
- `server-auth` skill — the auth-manager plugin system this plugin registers into
|
|
149
|
+
- `server-auth-identity` skill — how the linked profile and its organization entity are stored
|
|
150
|
+
- `auth-protocol` skill — error hierarchy and identity read rules
|
package/build/plugin.d.ts.map
CHANGED
|
@@ -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;AAsEvD,sGAAsG;AACtG,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,SAAS,EAAE,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,
|
|
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;AAsEvD,sGAAsG;AACtG,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,SAAS,EAAE,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC,KAAG,CAG1F,CAAA"}
|
package/build/service.d.ts.map
CHANGED
|
@@ -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,
|
|
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"}
|
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.18",
|
|
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.9",
|
|
26
|
+
"@owlmeans/auth-otp": "^0.1.18-rc.8",
|
|
27
|
+
"@owlmeans/basic-ids": "^0.1.18-rc.9",
|
|
28
|
+
"@owlmeans/context": "^0.1.18-rc.8",
|
|
29
|
+
"@owlmeans/mailer": "^0.1.18-rc.8",
|
|
30
|
+
"@owlmeans/oidc": "^0.1.18-rc.13",
|
|
31
|
+
"@owlmeans/redis-resource": "^0.1.18-rc.12",
|
|
32
|
+
"@owlmeans/resource": "^0.1.18-rc.9",
|
|
33
|
+
"@owlmeans/server-auth": "^0.1.18-rc.18",
|
|
34
|
+
"@owlmeans/server-auth-identity": "^0.1.18-rc.13",
|
|
35
|
+
"@owlmeans/server-context": "^0.1.18-rc.12"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@owlmeans/dep-config": "workspace:*",
|