@owlmeans/server-auth 0.1.18-rc.8 → 0.1.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 +216 -38
- package/agent-meta/manifest.json +10 -3
- package/agent-meta/skills/server-auth/SKILL.md +161 -22
- package/agent-meta/skills/supervisor-auth/SKILL.md +128 -0
- package/build/actions/service.d.ts +6 -2
- package/build/actions/service.d.ts.map +1 -1
- package/build/actions/service.js +5 -4
- package/build/actions/service.js.map +1 -1
- package/build/actions/types.local.d.ts +6 -0
- package/build/actions/types.local.d.ts.map +1 -0
- package/build/actions/types.local.js +2 -0
- package/build/actions/types.local.js.map +1 -0
- package/build/entrypoints.d.ts +5 -0
- package/build/entrypoints.d.ts.map +1 -0
- package/build/entrypoints.js +9 -0
- package/build/entrypoints.js.map +1 -0
- package/build/index.d.ts +2 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2 -1
- package/build/index.js.map +1 -1
- package/build/manager/actions/authenticate.d.ts +8 -0
- package/build/manager/actions/authenticate.d.ts.map +1 -0
- package/build/manager/actions/authenticate.js +5 -0
- package/build/manager/actions/authenticate.js.map +1 -0
- package/build/manager/actions/index.d.ts +3 -1
- package/build/manager/actions/index.d.ts.map +1 -1
- package/build/manager/actions/index.js +3 -1
- package/build/manager/actions/index.js.map +1 -1
- package/build/manager/actions/init.d.ts +8 -0
- package/build/manager/actions/init.d.ts.map +1 -0
- package/build/manager/actions/init.js +5 -0
- package/build/manager/actions/init.js.map +1 -0
- package/build/manager/actions/rely.d.ts +8 -0
- package/build/manager/actions/rely.d.ts.map +1 -0
- package/build/manager/actions/rely.js +7 -0
- package/build/manager/actions/rely.js.map +1 -0
- package/build/manager/consts.local.d.ts +2 -0
- package/build/manager/consts.local.d.ts.map +1 -0
- package/build/manager/consts.local.js +2 -0
- package/build/manager/consts.local.js.map +1 -0
- package/build/manager/context.d.ts.map +1 -1
- package/build/manager/context.js +0 -1
- package/build/manager/context.js.map +1 -1
- package/build/manager/entrypoints.d.ts +9 -0
- package/build/manager/entrypoints.d.ts.map +1 -0
- package/build/manager/entrypoints.js +16 -0
- package/build/manager/entrypoints.js.map +1 -0
- package/build/manager/exports.d.ts +1 -2
- package/build/manager/exports.d.ts.map +1 -1
- package/build/manager/exports.js +1 -2
- package/build/manager/exports.js.map +1 -1
- package/build/manager/index.d.ts +1 -1
- package/build/manager/index.d.ts.map +1 -1
- package/build/manager/index.js +1 -1
- package/build/manager/index.js.map +1 -1
- package/build/manager/main.d.ts.map +1 -1
- package/build/manager/main.js +2 -2
- package/build/manager/main.js.map +1 -1
- package/build/manager/model.d.ts.map +1 -1
- package/build/manager/model.js +71 -32
- package/build/manager/model.js.map +1 -1
- package/build/manager/plugins/basic-ed25519.d.ts +1 -1
- package/build/manager/plugins/basic-ed25519.d.ts.map +1 -1
- package/build/manager/plugins/basic-ed25519.js +6 -6
- package/build/manager/plugins/basic-ed25519.js.map +1 -1
- package/build/manager/plugins/basic-rely.d.ts +1 -1
- package/build/manager/plugins/basic-rely.d.ts.map +1 -1
- package/build/manager/plugins/basic-rely.js +11 -8
- package/build/manager/plugins/basic-rely.js.map +1 -1
- package/build/manager/plugins/consts.d.ts +8 -0
- package/build/manager/plugins/consts.d.ts.map +1 -0
- package/build/manager/plugins/consts.js +9 -0
- package/build/manager/plugins/consts.js.map +1 -0
- package/build/manager/plugins/export.d.ts +5 -0
- package/build/manager/plugins/export.d.ts.map +1 -1
- package/build/manager/plugins/export.js +4 -0
- package/build/manager/plugins/export.js.map +1 -1
- package/build/manager/plugins/index.d.ts +3 -4
- package/build/manager/plugins/index.d.ts.map +1 -1
- package/build/manager/plugins/index.js +6 -6
- package/build/manager/plugins/index.js.map +1 -1
- package/build/manager/plugins/re-captcha/consts.d.ts +7 -0
- package/build/manager/plugins/re-captcha/consts.d.ts.map +1 -0
- package/build/manager/plugins/re-captcha/consts.js +7 -0
- package/build/manager/plugins/re-captcha/consts.js.map +1 -0
- package/build/manager/plugins/re-captcha/consts.local.d.ts +7 -0
- package/build/manager/plugins/re-captcha/consts.local.d.ts.map +1 -0
- package/build/manager/plugins/re-captcha/consts.local.js +9 -0
- package/build/manager/plugins/re-captcha/consts.local.js.map +1 -0
- package/build/manager/plugins/re-captcha/policy.d.ts +3 -0
- package/build/manager/plugins/re-captcha/policy.d.ts.map +1 -0
- package/build/manager/plugins/re-captcha/policy.js +44 -0
- package/build/manager/plugins/re-captcha/policy.js.map +1 -0
- package/build/manager/plugins/re-captcha/types.d.ts +79 -0
- package/build/manager/plugins/re-captcha/types.d.ts.map +1 -0
- package/build/manager/plugins/re-captcha/types.js +2 -0
- package/build/manager/plugins/re-captcha/types.js.map +1 -0
- package/build/manager/plugins/re-captcha/verifier.d.ts +12 -0
- package/build/manager/plugins/re-captcha/verifier.d.ts.map +1 -0
- package/build/manager/plugins/re-captcha/verifier.js +62 -0
- package/build/manager/plugins/re-captcha/verifier.js.map +1 -0
- package/build/manager/plugins/re-captcha.d.ts +10 -1
- package/build/manager/plugins/re-captcha.d.ts.map +1 -1
- package/build/manager/plugins/re-captcha.js +50 -37
- package/build/manager/plugins/re-captcha.js.map +1 -1
- package/build/manager/plugins/supervisor.d.ts +1 -2
- package/build/manager/plugins/supervisor.d.ts.map +1 -1
- package/build/manager/plugins/supervisor.js +6 -6
- package/build/manager/plugins/supervisor.js.map +1 -1
- package/build/manager/plugins/types.d.ts +22 -14
- package/build/manager/plugins/types.d.ts.map +1 -1
- package/build/manager/plugins/utils.d.ts +5 -4
- package/build/manager/plugins/utils.d.ts.map +1 -1
- package/build/manager/plugins/utils.js +20 -15
- package/build/manager/plugins/utils.js.map +1 -1
- package/build/manager/rely/flow.d.ts.map +1 -1
- package/build/manager/rely/flow.js +13 -11
- package/build/manager/rely/flow.js.map +1 -1
- package/build/manager/rely.d.ts.map +1 -1
- package/build/manager/rely.js.map +1 -1
- package/build/manager/supervisor.d.ts +4 -45
- package/build/manager/supervisor.d.ts.map +1 -1
- package/build/manager/supervisor.js +5 -5
- package/build/manager/supervisor.js.map +1 -1
- package/build/manager/types.d.ts +41 -1
- package/build/manager/types.d.ts.map +1 -1
- package/build/manager/types.local.d.ts +4 -0
- package/build/manager/types.local.d.ts.map +1 -0
- package/build/manager/types.local.js +2 -0
- package/build/manager/types.local.js.map +1 -0
- package/build/manager/utils/trusted.d.ts.map +1 -1
- package/build/manager/utils/trusted.js.map +1 -1
- package/build/model/rely/consumer.d.ts +3 -1
- package/build/model/rely/consumer.d.ts.map +1 -1
- package/build/model/rely/consumer.js.map +1 -1
- package/build/model/rely/peer.d.ts.map +1 -1
- package/build/model/rely/peer.js +7 -7
- package/build/model/rely/peer.js.map +1 -1
- package/build/model/rely/provider.d.ts +3 -1
- package/build/model/rely/provider.d.ts.map +1 -1
- package/build/model/rely/provider.js.map +1 -1
- package/build/re-captcha/guard.d.ts +12 -0
- package/build/re-captcha/guard.d.ts.map +1 -0
- package/build/re-captcha/guard.js +43 -0
- package/build/re-captcha/guard.js.map +1 -0
- package/build/re-captcha/index.d.ts +4 -0
- package/build/re-captcha/index.d.ts.map +1 -0
- package/build/re-captcha/index.js +3 -0
- package/build/re-captcha/index.js.map +1 -0
- package/build/re-captcha/token.d.ts +6 -0
- package/build/re-captcha/token.d.ts.map +1 -0
- package/build/re-captcha/token.js +74 -0
- package/build/re-captcha/token.js.map +1 -0
- package/build/re-captcha/types.d.ts +44 -0
- package/build/re-captcha/types.d.ts.map +1 -0
- package/build/re-captcha/types.js +2 -0
- package/build/re-captcha/types.js.map +1 -0
- package/build/service.d.ts +2 -3
- package/build/service.d.ts.map +1 -1
- package/build/service.js +136 -13
- package/build/service.js.map +1 -1
- package/build/types.local.d.ts +4 -0
- package/build/types.local.d.ts.map +1 -0
- package/build/types.local.js +2 -0
- package/build/types.local.js.map +1 -0
- package/package.json +33 -30
- package/src/actions/service.ts +10 -12
- package/src/actions/types.local.ts +6 -0
- package/src/entrypoints.ts +12 -0
- package/src/index.ts +2 -2
- package/src/manager/README.md +14 -18
- package/src/manager/actions/authenticate.ts +12 -0
- package/src/manager/actions/index.ts +3 -1
- package/src/manager/actions/init.ts +12 -0
- package/src/manager/actions/rely.ts +12 -0
- package/src/manager/consts.local.ts +1 -0
- package/src/manager/context.ts +0 -2
- package/src/manager/entrypoints.ts +17 -0
- package/src/manager/exports.ts +1 -2
- package/src/manager/index.ts +1 -1
- package/src/manager/main.ts +2 -2
- package/src/manager/model.ts +82 -41
- package/src/manager/plugins/basic-ed25519.ts +8 -8
- package/src/manager/plugins/basic-rely.ts +14 -9
- package/src/manager/plugins/consts.ts +7 -0
- package/src/manager/plugins/export.ts +5 -0
- package/src/manager/plugins/index.ts +10 -12
- package/src/manager/plugins/re-captcha/consts.local.ts +8 -0
- package/src/manager/plugins/re-captcha/consts.ts +8 -0
- package/src/manager/plugins/re-captcha/policy.ts +56 -0
- package/src/manager/plugins/re-captcha/types.ts +85 -0
- package/src/manager/plugins/re-captcha/verifier.ts +74 -0
- package/src/manager/plugins/re-captcha.ts +58 -43
- package/src/manager/plugins/supervisor.ts +9 -11
- package/src/manager/plugins/types.ts +23 -14
- package/src/manager/plugins/utils.ts +24 -14
- package/src/manager/rely/flow.ts +14 -11
- package/src/manager/rely.ts +2 -5
- package/src/manager/supervisor.ts +8 -54
- package/src/manager/types.local.ts +5 -0
- package/src/manager/types.ts +46 -1
- package/src/model/rely/consumer.ts +3 -1
- package/src/model/rely/peer.ts +8 -8
- package/src/model/rely/provider.ts +3 -1
- package/src/re-captcha/guard.ts +52 -0
- package/src/re-captcha/index.ts +3 -0
- package/src/re-captcha/token.ts +89 -0
- package/src/re-captcha/types.ts +45 -0
- package/src/service.ts +133 -19
- package/src/types.local.ts +5 -0
- package/tests/context.ts +3 -2
- package/tests/re-captcha.spec.ts +298 -0
- package/tests/replay-policy.spec.ts +36 -0
- package/tests/supervisor.spec.ts +6 -4
- package/build/.gitkeep +0 -0
- package/src/manager/actions/manager.ts +0 -23
- package/src/manager/modules.ts +0 -29
- package/src/modules.ts +0 -17
package/README.md
CHANGED
|
@@ -1,72 +1,250 @@
|
|
|
1
1
|
# @owlmeans/server-auth
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
3
|
+
The server side of OwlMeans authentication, in two halves. The root export is what every protected
|
|
4
|
+
API service needs: the guard that verifies an Ed25519-signed bearer token and resolves
|
|
5
|
+
`request.auth`, and the dispatcher bindings that exchange a credential issued by the auth manager
|
|
6
|
+
for a bearer of this service. The `./manager` subpath is the auth manager application itself — the
|
|
7
|
+
challenge endpoints, the authentication plugin registry, the signed credential envelope and the rely
|
|
8
|
+
handshake. An ordinary service imports the root (usually implicitly, through
|
|
9
|
+
`@owlmeans/server-app`'s `makeContext`); only the auth manager imports `./manager`. OIDC tokens are
|
|
10
|
+
`@owlmeans/server-oidc-rp`, long-lived API keys `@owlmeans/server-auth-token`, local
|
|
11
|
+
account/profile records `@owlmeans/server-auth-identity`, and the browser side `@owlmeans/client-auth`.
|
|
11
12
|
|
|
12
13
|
## Installation
|
|
13
14
|
|
|
14
15
|
```bash
|
|
15
|
-
bun add @owlmeans/server-auth
|
|
16
|
+
bun add @owlmeans/server-auth@^0.1.18
|
|
16
17
|
```
|
|
17
18
|
|
|
19
|
+
## Concepts
|
|
20
|
+
|
|
21
|
+
- **Guard** — the `AuthService` registered as `auth` (the value of `DEFAULT_GUARD`). `match` hits a
|
|
22
|
+
request carrying an `ed25519-basic-token`; `handle` verifies it against this service's own trusted
|
|
23
|
+
key (`cfg.alias ?? cfg.service`) and resolves the `Auth`. It authenticates only — authorization is
|
|
24
|
+
gates and handlers.
|
|
25
|
+
- **Token exchange** — `authenticate(token)` verifies a credential envelope against the
|
|
26
|
+
`auth-service` trusted record (`AUTH_SRV_KEY`), burns its challenge into `AUTH_CACHE`, and returns
|
|
27
|
+
a bearer signed by this service.
|
|
28
|
+
- **`AUTH_CACHE`** — the anti-replay store: each challenge is created once with an
|
|
29
|
+
`AUTHEN_TIMEFRAME` TTL. A static in-process resource by default.
|
|
30
|
+
- **Auth manager** — the `./manager` application: `init(request)` returns a challenge,
|
|
31
|
+
`authenticate(credential)` returns a signed credential envelope, and the slug on the credential is
|
|
32
|
+
canonicalized through the entity resolver before signing.
|
|
33
|
+
- **Authentication plugin** — `{ type, init, authenticate }` registered under an
|
|
34
|
+
`AuthenticationType`; the manager dispatches by the request's `type`.
|
|
35
|
+
- **Rely** — the wallet handshake that lets one authenticated party vouch for another over a socket
|
|
36
|
+
(`DEFAULT_RELY` guard, `RELY_TUNNEL`).
|
|
37
|
+
|
|
18
38
|
## Usage
|
|
19
39
|
|
|
20
|
-
|
|
40
|
+
### Protect routes on an ordinary service
|
|
21
41
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
import { appendAuthService, AUTH_CACHE } from '@owlmeans/server-auth'
|
|
42
|
+
`@owlmeans/server-app`'s `makeContext(cfg)` registers the guard. A protocol opts in with `guards`,
|
|
43
|
+
and everything declared under it inherits them.
|
|
25
44
|
|
|
26
|
-
|
|
45
|
+
```ts
|
|
46
|
+
import { contract, openProtocol, protocol, typed } from '@owlmeans/entrypoint'
|
|
47
|
+
import { route, RouteMethod } from '@owlmeans/route'
|
|
48
|
+
import { DEFAULT_GUARD } from '@owlmeans/auth-common'
|
|
27
49
|
|
|
28
|
-
|
|
29
|
-
|
|
50
|
+
const invoiceBase = openProtocol(route('my-app:api:invoice', '/invoice'), { guards: DEFAULT_GUARD })
|
|
51
|
+
|
|
52
|
+
export const invoiceProtocols = {
|
|
53
|
+
base: invoiceBase,
|
|
54
|
+
list: protocol(
|
|
55
|
+
route('my-app:api:invoice:list', '/', { parent: invoiceBase, method: RouteMethod.GET }),
|
|
56
|
+
contract(typed<Invoice[]>()),
|
|
57
|
+
),
|
|
58
|
+
}
|
|
30
59
|
```
|
|
31
60
|
|
|
32
|
-
|
|
61
|
+
```ts
|
|
62
|
+
import { handlers } from '@owlmeans/server-app'
|
|
63
|
+
import { makeEntityScope } from '@owlmeans/auth-common'
|
|
33
64
|
|
|
34
|
-
|
|
35
|
-
import { DAUTH_GUARD } from '@owlmeans/server-app'
|
|
36
|
-
// same as DEFAULT_ALIAS from @owlmeans/server-auth
|
|
65
|
+
const api = handlers<Context>()
|
|
37
66
|
|
|
38
|
-
const
|
|
67
|
+
export const list = api.request(invoiceProtocols.list, async (request, context) => {
|
|
68
|
+
// request.auth is the verified Auth: userId, profileId, role, scopes, entitySlug
|
|
69
|
+
const { items } = await context.invoice().list({ entityId: makeEntityScope(request).requireEntityKey() })
|
|
70
|
+
return items
|
|
71
|
+
})
|
|
39
72
|
```
|
|
40
73
|
|
|
41
|
-
|
|
74
|
+
### Scaled deployment
|
|
75
|
+
|
|
76
|
+
Replace the in-process cache with a shared one before registering the guard, and declare the trusted
|
|
77
|
+
keys the guard verifies against.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { makeContext as makeAppContext } from '@owlmeans/server-app'
|
|
81
|
+
import { appendRedis } from '@owlmeans/redis'
|
|
82
|
+
import { makeRedisResource } from '@owlmeans/redis-resource'
|
|
83
|
+
import { appendAuthService, AUTH_CACHE, AUTH_SRV_KEY } from '@owlmeans/server-auth'
|
|
84
|
+
import { appendAuthIdentityResources } from '@owlmeans/server-auth-identity'
|
|
85
|
+
|
|
86
|
+
// config.ts — keys are loaded from the environment, never committed
|
|
87
|
+
cfg.trusted.push({
|
|
88
|
+
id: process.env.AUTH_SERVICE_DID!, name: AUTH_SRV_KEY,
|
|
89
|
+
credential: process.env.AUTH_SERVICE_PUB!, scopes: ['*'],
|
|
90
|
+
})
|
|
91
|
+
cfg.trusted.push({
|
|
92
|
+
id: process.env.MY_APP_API_DID!, name: MY_APP_API,
|
|
93
|
+
credential: process.env.MY_APP_API_PUB!, secret: process.env.MY_APP_API_PK!, scopes: ['*'],
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
// context.ts
|
|
97
|
+
export const makeContext = <C extends Config, T extends Context<C>>(cfg: C): T => {
|
|
98
|
+
const context = makeAppContext<C, T>(cfg, true)
|
|
99
|
+
appendRedis<C, T>(context)
|
|
100
|
+
|
|
101
|
+
context.registerResource(makeRedisResource(AUTH_CACHE))
|
|
102
|
+
appendAuthService<C, T>(context)
|
|
103
|
+
appendAuthIdentityResources(context)
|
|
104
|
+
|
|
105
|
+
return context
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Verify a token outside the HTTP pipeline
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
import type { AuthServiceAppend } from '@owlmeans/server-auth'
|
|
113
|
+
|
|
114
|
+
const readAuth = async (context: Context & AuthServiceAppend, authorization: string) =>
|
|
115
|
+
context.auth().unpack(authorization) // 'ED25519-BASIC-TOKEN <envelope>' -> Auth, or throws
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Run the auth manager
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import { config, makeContext, main } from '@owlmeans/server-auth/manager'
|
|
122
|
+
import type { AppConfig } from '@owlmeans/server-auth/manager'
|
|
123
|
+
import '@owlmeans/server-oidc-rp/auth/plugins' // optional: OIDC and Google login plugins
|
|
124
|
+
|
|
125
|
+
const cfg = config<AppConfig>(MY_APP_AUTH, commonConfig as AppConfig)
|
|
126
|
+
const context = makeContext(cfg)
|
|
127
|
+
|
|
128
|
+
await main(context)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Add an authentication method
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
import { registerPlugin } from '@owlmeans/server-auth/manager/plugins'
|
|
135
|
+
import type { AuthPlugin } from '@owlmeans/server-auth/manager/plugins'
|
|
42
136
|
|
|
43
|
-
|
|
137
|
+
registerPlugin('my-method', context => ({
|
|
138
|
+
type: 'my-method',
|
|
139
|
+
init: async request => ({ challenge: /* unique per request */ '' }),
|
|
140
|
+
authenticate: async credential => {
|
|
141
|
+
// verify credential.credential, then set userId / profileId / entitySlug / role / scopes
|
|
142
|
+
return { token: '' } // '' keeps the manager's own challenge as the credential token
|
|
143
|
+
},
|
|
144
|
+
}) as AuthPlugin)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Accept a reCAPTCHA-proven guest on one route
|
|
148
|
+
|
|
149
|
+
The manager's reCAPTCHA plugin signs a GUEST token (`GUEST_ID`, role Guest, `[AUTH_SCOPE]`) whatever
|
|
150
|
+
identity the caller posts. A route takes it through the reCAPTCHA guard, which spends it once:
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
import { CMOD_RECAPTCHA, MOD_RECAPTCHA, RECAPTCHA_GUARD } from '@owlmeans/auth'
|
|
154
|
+
import { clientPlugin, plugin } from '@owlmeans/config'
|
|
155
|
+
import { makeReCaptchaGuard, reCaptchaTokenOf } from '@owlmeans/server-auth'
|
|
44
156
|
|
|
45
|
-
|
|
157
|
+
// config: the secret plus the policy (strings, '' = no check), and the browser's site key
|
|
158
|
+
plugin(cfg, {
|
|
159
|
+
value: '/etc/master-secret/recaptcha-secret',
|
|
160
|
+
hostnames: 'example.org, example.com', minScore: '0.5', actions: 'inquiry',
|
|
161
|
+
}, MOD_RECAPTCHA)
|
|
162
|
+
clientPlugin(cfg, '/etc/app-config/recaptcha-site-key', CMOD_RECAPTCHA)
|
|
163
|
+
|
|
164
|
+
// context: the guard of the route's `guards: [RECAPTCHA_GUARD]` (`Authorization: RE-CAPTCHA <token>`)
|
|
165
|
+
context.registerService(makeReCaptchaGuard())
|
|
166
|
+
// a pre-parse check that does not spend:
|
|
167
|
+
const guest = await reCaptchaTokenOf(context).inspect(request) // null = 401
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## API
|
|
46
171
|
|
|
47
|
-
###
|
|
172
|
+
### Root export
|
|
48
173
|
|
|
49
|
-
|
|
174
|
+
| Symbol | Kind | Purpose |
|
|
175
|
+
|---|---|---|
|
|
176
|
+
| `appendAuthService(ctx, alias = DEFAULT_ALIAS)` | function | Register the guard as `context.auth()`; registers a static `AUTH_CACHE` when the context has none |
|
|
177
|
+
| `makeAuthService(alias?)` | function | The guard: `match`, `handle`, `unpack(token)`, `authenticate(token)` |
|
|
178
|
+
| `entrypoints` | const | Server bindings of `authProtocols.dispatcher` and `authProtocols.dispatcherAuthenticate` |
|
|
179
|
+
| `DEFAULT_ALIAS` | const | `'auth'` — equals `DEFAULT_GUARD`; re-exported as `DAUTH_GUARD` by `@owlmeans/server-app` |
|
|
180
|
+
| `AUTH_CACHE` | const | `'auth-cache'` — the single-use challenge store |
|
|
181
|
+
| `AUTH_SRV_KEY` | const | `'auth-service'` — the trusted record whose key signs credential envelopes |
|
|
182
|
+
| `AUTHEN_TIMEFRAME` | const | `15 * 60 * 1000` ms — challenge lifetime and anti-replay window |
|
|
183
|
+
| `makeRelyModel`, `makeProviderRely`, `makeConsumerRely` | function | Rely envelope models |
|
|
184
|
+
| `AuthService`, `AuthServiceAppend`, `AuthSpent`, `RelyOptions` | type | Guard service, context mixin, cache record, rely options |
|
|
185
|
+
| `makeReCaptchaGuard(alias = RECAPTCHA_GUARD)` | function | Guard of a route a reCAPTCHA guest calls once: verifies and spends `RE-CAPTCHA <token>` |
|
|
186
|
+
| `makeReCaptchaTokenHelper(ctx)`, `reCaptchaTokenOf(ctx)` | function | `inspect(req)` / `verify(token)` (no spend) and `spend(guest)` |
|
|
187
|
+
| `ReCaptchaGuest`, `ReCaptchaCarrier`, `ReCaptchaTokenHelper` | type | Inspection result, header carrier, token checks |
|
|
50
188
|
|
|
51
|
-
### `
|
|
189
|
+
### `./manager`
|
|
52
190
|
|
|
53
|
-
|
|
191
|
+
| Symbol | Kind | Purpose |
|
|
192
|
+
|---|---|---|
|
|
193
|
+
| `makeContext(cfg, customize?)` | function | Server context with API server, API client, socket service, rely guard and static `AUTH_CACHE` |
|
|
194
|
+
| `main(ctx)` | function | Register the manager entrypoints, init and listen |
|
|
195
|
+
| `entrypoints` | const | Bindings for authen, init, authenticate, rely and api-config |
|
|
196
|
+
| `authenticationInit`, `authenticate`, `rely` | function | Implementations bound to the auth protocols |
|
|
197
|
+
| `plugins`, `registerPlugin(type, factory)` | const / function | The plugin registry |
|
|
198
|
+
| `appendSupervisorAuth(ctx, opts?)`, `setupInternalTokenCoguard(entrypoints, guard?)` | function | PK supervisor login (development only by default) |
|
|
199
|
+
| `createRelyService(alias?)`, `createRelyFlow(context, conn, auth?)` | function | Rely guard service and socket authentication flow |
|
|
200
|
+
| `DEFAULT_RELY`, `RELY_TUNNEL` | const | `'auth-rely'`, `'rely-tunnel'` |
|
|
201
|
+
| `config`, `service`, `TRUSTED`, `addWebService`, `AppType`, `assertContext`, `klusterize`, `stab`, `backend`, `filterObject` | re-export | Config and context helpers for a manager app |
|
|
202
|
+
| `handleIntermediate`, `handleBody`, `handleRequest`, `handleParams` | re-export | `@owlmeans/server-api` wrappers |
|
|
203
|
+
| `AUTHEN`, `AUTHEN_AUTHEN`, `AUTHEN_INIT`, `GUARD_ED25519`, `BED255_CASHE_RESOURCE` | re-export | Auth aliases |
|
|
204
|
+
| `AppConfig`, `AppContext`, `AuthModel`, `RelyService`, `RelyAllowanceRequest`, `RelyLinker`, `RelyCarrier` | type | Manager shapes |
|
|
205
|
+
| `SupervisorAuthOptions`, `SupervisorUserResolver`, `SupervisorUserResolution`, `SupervisorPluginOptions` | type | Supervisor options |
|
|
206
|
+
| `TrustedRecord`, `RefedEntrypointHandler`, `AbstractRequest`, `AbstractResponse` | type | Re-exported types |
|
|
54
207
|
|
|
55
|
-
### `
|
|
208
|
+
### `./manager/plugins`
|
|
56
209
|
|
|
57
|
-
|
|
210
|
+
| Symbol | Kind | Purpose |
|
|
211
|
+
|---|---|---|
|
|
212
|
+
| `registerPlugin(type, factory)`, `plugins` | function / const | The module-level registry |
|
|
213
|
+
| `authPluginHelper.getPlugin(type, context)`, `authPluginHelper.assertType(type, plugin)` | method | Resolution; `getPlugin` throws `AuthUnknown(type)` for an unregistered type |
|
|
214
|
+
| `makeBasicEd25519Plugin`, `makeReCaptchaPlugin`, `makeBasicRelyPlugin` | function | Built-in plugin factories |
|
|
215
|
+
| `makeSupervisorPlugin(context, opts)` | function | PK supervisor plugin factory |
|
|
216
|
+
| `createReCaptchaVerifierService(alias?, opts?)`, `appendReCaptchaVerifierService(ctx, alias?, opts?)` | function | Google siteverify over `fetch` (https only); the plugin's seam |
|
|
217
|
+
| `makeReCaptchaPolicyModel(record)` | function | The `MOD_RECAPTCHA` record's policy: `hostnames()`, `minScore()`, `actions()`, `assert(answer)` |
|
|
218
|
+
| `RECAPTCHA_VERIFIER`, `RECAPTCHA_SITEVERIFY_URL`, `RECAPTCHA_VERIFY_TIMEOUT` | const | `'re-captcha-verifier'`, Google's endpoint, 10 s |
|
|
219
|
+
| `AuthPlugin`, `AuthPluginFactory`, `AuthPluginHelper`, `RelyRecord`, `AuthRedisResource` | type | Plugin shapes |
|
|
220
|
+
| `ReCaptchaResponse`, `ReCaptchaRequest`, `ReCaptchaVerifierService`, `ReCaptchaVerifierOptions`, `ReCaptchaFetch`, `ReCaptchaPluginConfig`, `ReCaptchaPolicyModel` | type | reCAPTCHA shapes |
|
|
58
221
|
|
|
59
|
-
##
|
|
222
|
+
## Common pitfalls
|
|
60
223
|
|
|
61
|
-
-
|
|
62
|
-
|
|
63
|
-
-
|
|
64
|
-
|
|
224
|
+
- The guard verifies a bearer and sets `request.auth`; it decides nothing about ownership. Pair it
|
|
225
|
+
with gates and keep a handler-level organization check.
|
|
226
|
+
- The default static `AUTH_CACHE` is correct for one replica only. Register a Redis resource under
|
|
227
|
+
the same alias before `appendAuthService` in any scaled deployment.
|
|
228
|
+
- A plugin's `init` must return a challenge unique per request; a repeated challenge collides in
|
|
229
|
+
`AUTH_CACHE` and fails as `AuthenFailed('challenge')`.
|
|
230
|
+
- The manager replaces whatever slug a plugin sets on `credential.entitySlug` with the current slug;
|
|
231
|
+
an unresolvable value fails as `AuthenFailed('entity')`. A token carries `entitySlug`, never an
|
|
232
|
+
entity id.
|
|
233
|
+
- Register `appendAuthService` before `appendAuthIdentityResources` and product gate services.
|
|
234
|
+
- Import `./manager` only in the auth manager process.
|
|
235
|
+
- A reCAPTCHA token is a guest token: the bearer exchange (`authenticate`) refuses it, and only
|
|
236
|
+
`makeReCaptchaGuard` accepts it — once.
|
|
65
237
|
|
|
66
|
-
## Related
|
|
238
|
+
## Related packages
|
|
67
239
|
|
|
68
|
-
- [`@owlmeans/auth
|
|
240
|
+
- [`@owlmeans/auth`](../auth) — `Auth`, `AuthCredentials`, error classes
|
|
241
|
+
- [`@owlmeans/auth-common`](../auth-common) — `DEFAULT_GUARD`, `authProtocols`, `trust()`, entity helpers
|
|
242
|
+
- [`@owlmeans/server-auth-identity`](../server-auth-identity) — local identity records and the entity resolver
|
|
243
|
+
- [`@owlmeans/server-oidc-rp`](../server-oidc-rp) — OIDC login plugins and guard
|
|
244
|
+
- [`@owlmeans/server-auth-otp`](../server-auth-otp) — email OTP plugin
|
|
245
|
+
- [`@owlmeans/server-auth-token`](../server-auth-token) — long-lived access tokens
|
|
69
246
|
- [`@owlmeans/server-app`](../server-app) — `makeContext` calls `appendAuthService` by default
|
|
247
|
+
- [`@owlmeans/client-auth`](../client-auth) — the browser side
|
|
70
248
|
|
|
71
249
|
<!-- owlmeans:agent-guidance:start -->
|
|
72
250
|
## Agent guidance
|
|
@@ -76,7 +254,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
76
254
|
your project's skill store (`.agents/skills/`):
|
|
77
255
|
|
|
78
256
|
```sh
|
|
79
|
-
npx @owlmeans/agent-skills
|
|
257
|
+
npx @owlmeans/agent-skills@^0.1.18
|
|
80
258
|
```
|
|
81
259
|
|
|
82
260
|
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
|
-
"schemaVersion":
|
|
2
|
+
"schemaVersion": 3,
|
|
3
3
|
"package": "@owlmeans/server-auth",
|
|
4
|
-
"version": "0.1.18
|
|
5
|
-
"generatedAt": "2026-
|
|
4
|
+
"version": "0.1.18",
|
|
5
|
+
"generatedAt": "2026-10-28T00:31:34.722Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -11,6 +11,13 @@
|
|
|
11
11
|
"category": "package-specific",
|
|
12
12
|
"file": "skills/server-auth/SKILL.md",
|
|
13
13
|
"canonicalPath": ".agents/skills/server-auth/SKILL.md"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"kind": "skill",
|
|
17
|
+
"name": "supervisor-auth",
|
|
18
|
+
"category": "multi-package",
|
|
19
|
+
"file": "skills/supervisor-auth/SKILL.md",
|
|
20
|
+
"canonicalPath": ".agents/skills/supervisor-auth/SKILL.md"
|
|
14
21
|
}
|
|
15
22
|
]
|
|
16
23
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: server-auth
|
|
3
|
-
description: How to use @owlmeans/server-auth — server
|
|
3
|
+
description: How to use @owlmeans/server-auth — the server side of OwlMeans authentication. Two halves in one package - appendAuthService/makeAuthService, which verify Ed25519 bearer tokens on an ordinary API server, and the ./manager subpath, which IS the auth manager service (challenge, plugin registry, credential envelope, rely). Also the reCAPTCHA guest path - the plugin's verifier and policy record, and makeReCaptchaGuard, which spends a guest token once. Auto-invoked when importing the server auth guard or the reCAPTCHA guard, registering an auth plugin, or building the auth manager.
|
|
4
4
|
user-invocable: false
|
|
5
5
|
---
|
|
6
6
|
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
@@ -8,41 +8,180 @@ user-invocable: false
|
|
|
8
8
|
# @owlmeans/server-auth
|
|
9
9
|
|
|
10
10
|
**Layer:** Server
|
|
11
|
-
**Install:** `"@owlmeans/server-auth": "^0.1.18
|
|
11
|
+
**Install:** `"@owlmeans/server-auth": "^0.1.18"` in `dependencies`
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Two halves, deliberately split by subpath:
|
|
14
|
+
|
|
15
|
+
- the **root** export is what every protected API server needs — the guard that verifies an
|
|
16
|
+
`Ed25519BasicToken` bearer and resolves an `Auth`;
|
|
17
|
+
- **`./manager`** is the auth manager application itself — it serves `/authentication/init` and
|
|
18
|
+
`/authentication/authenticate`, owns the plugin registry, and signs the credential envelope.
|
|
19
|
+
|
|
20
|
+
An ordinary service imports the root. Only the auth manager imports `./manager`.
|
|
21
|
+
|
|
22
|
+
## Key Exports — root
|
|
23
|
+
|
|
24
|
+
| Export | Description |
|
|
25
|
+
|--------|-------------|
|
|
26
|
+
| `appendAuthService(ctx, alias?)` | Register the guard on a server context and expose it as `context.auth()`. Registers a static `AUTH_CACHE` resource when the context has none |
|
|
27
|
+
| `makeAuthService(alias?)` | The guard service itself: `match`, `handle`, `unpack(token)`, `authenticate(token)` |
|
|
28
|
+
| `entrypoints` | `DISPATCHER` and `DISPATCHER_AUTHEN`, server-bound — spread these into a service that accepts a manager-issued credential and exchanges it for a bearer |
|
|
29
|
+
| `DEFAULT_ALIAS` | `'auth'` — the guard's service alias, matching `DEFAULT_GUARD` |
|
|
30
|
+
| `AUTH_CACHE` | `'auth-cache'` — the single-use challenge store |
|
|
31
|
+
| `AUTH_SRV_KEY` | `'auth-service'` — the TRUSTED record whose key signs credential envelopes |
|
|
32
|
+
| `AUTHEN_TIMEFRAME` | `15 * 60 * 1000` — challenge lifetime and anti-replay window, in ms |
|
|
33
|
+
| `AuthService`, `AuthServiceAppend`, `AuthSpent` | Types |
|
|
34
|
+
| `makeRelyModel`, `makeProviderRely`, `makeConsumerRely`, `RelyOptions` | The rely (wallet handshake) models |
|
|
35
|
+
| `makeReCaptchaGuard(alias = RECAPTCHA_GUARD)` | The guard of a route a reCAPTCHA guest calls once — see "reCAPTCHA guests" |
|
|
36
|
+
| `makeReCaptchaTokenHelper(ctx)`, `reCaptchaTokenOf(ctx)` | `inspect(req)` / `verify(token)` → `ReCaptchaGuest \| null` without spending; `spend(guest)` → `false` when spent before |
|
|
37
|
+
| `ReCaptchaGuest`, `ReCaptchaCarrier`, `ReCaptchaTokenHelper` | Types: `{ credential, challenge, expiresAt }`, `{ headers? }`, the helper |
|
|
38
|
+
|
|
39
|
+
## Key Exports — `./manager`
|
|
14
40
|
|
|
15
41
|
| Export | Description |
|
|
16
42
|
|--------|-------------|
|
|
17
|
-
| `
|
|
18
|
-
| `
|
|
19
|
-
|
|
|
20
|
-
|
|
|
43
|
+
| `makeContext(cfg, customize?)` | A server context preconfigured with the API server, API client, socket service and static `AUTH_CACHE` |
|
|
44
|
+
| `main(ctx)` | Register the manager entrypoints, configure, init and listen |
|
|
45
|
+
| `entrypoints` | `AUTHEN`, `AUTHEN_INIT`, `AUTHEN_AUTHEN`, `AUTHEN_RELY` and the api-config entrypoints |
|
|
46
|
+
| `authenticationInit`, `authenticate`, `rely` | Implementations bound to auth protocols: `init(request)` → challenge, `authenticate(credential)` → signed credential envelope, and the rely socket |
|
|
47
|
+
| `plugins`, `registerPlugin(type, factory)` | The plugin registry (also on `./manager/plugins`) |
|
|
48
|
+
| `appendSupervisorAuth(ctx, opts?)`, `setupInternalTokenCoguard(entrypoints, guard?)` | PK supervisor login — see the `supervisor-auth` skill |
|
|
49
|
+
| `createRelyService(alias?)`, `DEFAULT_RELY`, `RELY_TUNNEL` | The rely guard service |
|
|
50
|
+
| `AppConfig`, `AppContext`, `AuthModel`, `RelyService`, `RelyAllowanceRequest`, `RelyLinker`, `RelyCarrier` | Types |
|
|
21
51
|
|
|
22
|
-
|
|
52
|
+
`./manager` also re-exports the handful of symbols a manager application needs from elsewhere
|
|
53
|
+
(`config`, `service`, `TRUSTED`, `bind`, `handlers`, `backend`,
|
|
54
|
+
`GUARD_ED25519`, `AUTHEN*` aliases, `TrustedRecord`), so a manager app can be written against this
|
|
55
|
+
one import.
|
|
23
56
|
|
|
24
|
-
|
|
25
|
-
|
|
57
|
+
## Key Exports — `./manager/plugins`
|
|
58
|
+
|
|
59
|
+
| Export | Description |
|
|
60
|
+
|--------|-------------|
|
|
61
|
+
| `AuthPlugin` | `{ type, init(request), authenticate(credential) }` — `AuthModel` minus `rely` |
|
|
62
|
+
| `registerPlugin(type, factory)` | Add a plugin under a type string. The registry is a module-level singleton |
|
|
63
|
+
| `plugins` | The registry map |
|
|
64
|
+
| `authPluginHelper.getPlugin(type, context)`, `authPluginHelper.assertType(type, plugin)` | Resolution; `getPlugin` throws `AuthUnknown(type)` for an unregistered type |
|
|
65
|
+
| `makeBasicEd25519Plugin`, `makeReCaptchaPlugin`, `makeBasicRelyPlugin` | The plugin factories registered out of the box, for `AuthenticationType.BasicEd25519`, `ReCaptcha` and `RelyHandshake` |
|
|
66
|
+
| `makeSupervisorPlugin(context, opts)` | The PK supervisor plugin factory |
|
|
67
|
+
| `createReCaptchaVerifierService(alias = RECAPTCHA_VERIFIER, opts?)`, `appendReCaptchaVerifierService(ctx, alias?, opts?)` | Google siteverify: one `fetch` form POST (`opts.url` must be https, `opts.fetch` injectable, `opts.timeout` default 10 s) |
|
|
68
|
+
| `makeReCaptchaPolicyModel(record)` | The `MOD_RECAPTCHA` record's policy: `hostnames()`, `minScore()`, `actions()`, `assert(answer)` |
|
|
69
|
+
| `RECAPTCHA_VERIFIER`, `RECAPTCHA_SITEVERIFY_URL`, `RECAPTCHA_VERIFY_TIMEOUT` | `'re-captcha-verifier'`, `https://www.google.com/recaptcha/api/siteverify`, `10_000` |
|
|
70
|
+
| `ReCaptchaResponse`, `ReCaptchaRequest`, `ReCaptchaVerifierService`, `ReCaptchaVerifierOptions`, `ReCaptchaFetch`, `ReCaptchaPluginConfig`, `ReCaptchaPolicyModel` | reCAPTCHA types |
|
|
71
|
+
| `RelyRecord`, `AuthRedisResource` | Types |
|
|
26
72
|
|
|
27
73
|
## Usage
|
|
28
74
|
|
|
75
|
+
Protect an ordinary API server:
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { appendAuthService } from '@owlmeans/server-auth'
|
|
79
|
+
import { appendAuthIdentityResources } from '@owlmeans/server-auth-identity'
|
|
80
|
+
|
|
81
|
+
// in makeContext, after the db/cache services are appended:
|
|
82
|
+
appendAuthService(context)
|
|
83
|
+
appendAuthIdentityResources(context)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Add an authentication method to the manager:
|
|
87
|
+
|
|
29
88
|
```typescript
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
89
|
+
import { registerPlugin } from '@owlmeans/server-auth/manager/plugins'
|
|
90
|
+
import type { AuthPlugin } from '@owlmeans/server-auth/manager/plugins'
|
|
32
91
|
|
|
33
|
-
|
|
92
|
+
registerPlugin('my-method', context => ({
|
|
93
|
+
type: 'my-method',
|
|
94
|
+
init: async request => ({ challenge: /* unique per request */ '' }),
|
|
95
|
+
authenticate: async credential => {
|
|
96
|
+
// verify credential.credential, then set userId / profileId / entitySlug / role / scopes
|
|
97
|
+
return { token: '' } // '' keeps the manager's own challenge as the credential token
|
|
98
|
+
},
|
|
99
|
+
}) as AuthPlugin)
|
|
34
100
|
```
|
|
35
101
|
|
|
36
|
-
##
|
|
102
|
+
## reCAPTCHA guests
|
|
103
|
+
|
|
104
|
+
A reCAPTCHA sign-in proves that a person solved a challenge — nothing about who they are.
|
|
105
|
+
|
|
106
|
+
**Config.** One Backend plugin record `MOD_RECAPTCHA` holds the secret in `value` and the policy in
|
|
107
|
+
extra string fields (`''` or absent = no check; each may be a file path the file config reader
|
|
108
|
+
resolves); the browser's site key is a separate Frontend record `CMOD_RECAPTCHA`. Only Frontend
|
|
109
|
+
records are advertised, so the secret and the policy never reach a browser.
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
plugin(cfg, { value: '/etc/master-secret/recaptcha-secret',
|
|
113
|
+
hostnames: 'example.org, example.com', minScore: '0.5', actions: 'inquiry' }, MOD_RECAPTCHA)
|
|
114
|
+
clientPlugin(cfg, '/etc/app-config/recaptcha-site-key', CMOD_RECAPTCHA)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
| Field | Check (refusal) |
|
|
118
|
+
|---|---|
|
|
119
|
+
| `value` | the siteverify secret; missing or `''` → `PluginMissconfigured('value')` before Google is asked |
|
|
120
|
+
| — | Google's `success` (`AuthenFailed('recaptcha:<error-codes>')`, `recaptcha:unknown` without codes) |
|
|
121
|
+
| `hostnames` | comma/space list; the answer's `hostname` equals an entry or is its subdomain (`recaptcha:hostname`) |
|
|
122
|
+
| `minScore` | 0…1; a score-based answer with `score < minScore` (`recaptcha:score`); a checkbox answer has no score and passes; a non-number → `PluginMissconfigured('minScore')` |
|
|
123
|
+
| `actions` | comma/space list; the answer's `action` must be one of them — an answer without one fails (`recaptcha:action`) |
|
|
124
|
+
|
|
125
|
+
**Plugin.** `makeReCaptchaPlugin` asks the verifier registered under `RECAPTCHA_VERIFIER` (a test
|
|
126
|
+
or proxy registers its own; without one the default siteverify `fetch` runs — Google is always
|
|
127
|
+
https, `cfg.security.unsecure` does not apply). Google unreachable or answering garbage is
|
|
128
|
+
`AuthUnavailable` (503), never a refusal. After a passing answer the credential keeps ONLY `type`,
|
|
129
|
+
`role`, `userId`, `scopes`, `challenge`, `credential`, set to a guest: `GUEST_ID`, `AuthRole.Guest`,
|
|
130
|
+
`[AUTH_SCOPE]`, type ReCaptcha. A posted `profileId`, `entitySlug`/`entityId`, `permissions`,
|
|
131
|
+
`groups`, `expiresAt`, `source` or `publicKey` is dropped; `source` never changes the spend key.
|
|
132
|
+
|
|
133
|
+
**Token.** The manager signs it as usual: an `AUTHEN_TIMEFRAME` envelope of type `re-captcha` whose
|
|
134
|
+
`challenge` is the server-issued wrapped challenge (the spend key) and whose `credential` is the
|
|
135
|
+
`AUTH_SRV_KEY` record's id. The browser sends it as `Authorization: RE-CAPTCHA <token>`.
|
|
136
|
+
|
|
137
|
+
**Guard.** A route takes it with `guards: [RECAPTCHA_GUARD]` and `makeReCaptchaGuard()` registered
|
|
138
|
+
in a context holding the `AUTH_SRV_KEY` trusted record (public key is enough) and a shared
|
|
139
|
+
`AUTH_CACHE`. `reCaptchaTokenOf(ctx).inspect(req)` checks, without spending: envelope type, a TTL
|
|
140
|
+
of at most `AUTHEN_TIMEFRAME`, signature and expiry, the auth record's stamp, type ReCaptcha, role
|
|
141
|
+
Guest, `GUEST_ID`, a non-empty challenge. The guard then spends it — an atomic create-once of the
|
|
142
|
+
challenge in `AUTH_CACHE` (Redis `SET NX`), the same key a bearer exchange and the integrated IAM's
|
|
143
|
+
finalizer claim, so a token is spent once across the platform. A replay, or any failed check, is
|
|
144
|
+
401. `req.auth` becomes a guest with `token: ''` and `expiresAt` = the token's expiry. Use
|
|
145
|
+
`inspect` for a pre-parse check (before a large body is read); the route still needs the guard.
|
|
146
|
+
|
|
147
|
+
**Never a session.** `AuthService.authenticate` (the dispatcher's bearer exchange) refuses a
|
|
148
|
+
credential whose envelope or `type` is ReCaptcha (`AuthenFailed('guest')`) before spending it. A
|
|
149
|
+
service that judges manager tokens itself (a finalizer) must check the method it expects — the
|
|
150
|
+
token's type and its challenge's envelope type — not just the signature, since one key signs every
|
|
151
|
+
method's token.
|
|
152
|
+
|
|
153
|
+
## Rules
|
|
37
154
|
|
|
38
|
-
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
155
|
+
- The guard verifies a bearer token and populates `req.auth`. It decides nothing about ownership or
|
|
156
|
+
permissions — pair it with `@owlmeans/entrypoint` gates, and keep a handler-level organization
|
|
157
|
+
check as a second line of defence.
|
|
158
|
+
- `AUTH_CACHE` is the anti-replay store: the manager burns each decoded challenge into it as a
|
|
159
|
+
create-once record with `AUTHEN_TIMEFRAME` TTL. `appendAuthService` registers a **static**
|
|
160
|
+
(in-process) resource when the context has none, which is correct for a single replica only —
|
|
161
|
+
register a Redis resource under the same alias before calling it in any scaled deployment.
|
|
162
|
+
- A plugin's `init` must return a challenge that is unique per request. A challenge that repeats
|
|
163
|
+
across independent attempts collides in `AUTH_CACHE` and surfaces as `AuthenFailed('challenge')`.
|
|
164
|
+
- Bearers issued by this service carry an absolute seven-day expiry and an opaque session id. In a
|
|
165
|
+
context with `AUTH_SESSION_MANAGER`, the guard rejects an absent, expired, pending or revoked
|
|
166
|
+
session and re-signs fresh profile claims after a registry revision. Registry or identity-store
|
|
167
|
+
failure is `AuthUnavailable` (503), never a false 401/logout.
|
|
168
|
+
- The session registry (`AUTH_SESSION_MANAGER`) is `@owlmeans/server-auth-session`'s, not exported
|
|
169
|
+
here; `appendAuthService` installs its memory manager only as a single-process default. A
|
|
170
|
+
central or multi-replica issuer registers `appendRedisAuthSessionManager(context)` before it;
|
|
171
|
+
the manager is the authority that fences, refreshes or revokes an organization profile's sessions.
|
|
172
|
+
- The manager canonicalizes the organization entity: whatever slug (current, retired, or the frozen
|
|
173
|
+
key) a plugin leaves on `credential.entitySlug` is resolved through `ENTITY_RESOLVER` and replaced
|
|
174
|
+
with the current slug before the envelope is signed. An unresolvable value throws
|
|
175
|
+
`AuthenFailed('entity')`. Where no resolver is registered the value is passed through untouched.
|
|
176
|
+
- Pair this package with `@owlmeans/server-auth-identity` when an external provider (Google, OIDC,
|
|
177
|
+
email OTP) must map onto local account/profile/credential records.
|
|
178
|
+
- A reCAPTCHA token is a guest token: only `makeReCaptchaGuard` accepts it, once; the bearer
|
|
179
|
+
exchange refuses it (see "reCAPTCHA guests").
|
|
43
180
|
|
|
44
181
|
## Depends On
|
|
45
182
|
|
|
46
|
-
- `@owlmeans/auth`, `@owlmeans/auth-common`
|
|
47
|
-
- `@owlmeans/
|
|
48
|
-
- `@owlmeans/
|
|
183
|
+
- `@owlmeans/auth`, `@owlmeans/auth-common` — types, aliases, `trust()`, `extractAuthToken`
|
|
184
|
+
- `@owlmeans/basic-envelope`, `@owlmeans/basic-keys` — envelope signing and Ed25519 verification
|
|
185
|
+
- `@owlmeans/server-context`, `@owlmeans/server-entrypoint`, `@owlmeans/server-api`
|
|
186
|
+
- `@owlmeans/config` — the `TRUSTED` config resource
|
|
187
|
+
- `@owlmeans/static-resource` — the default `AUTH_CACHE` backing
|