@owlmeans/server-auth 0.1.18-rc.55 → 0.1.18-rc.57

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 (85) hide show
  1. package/README.md +36 -4
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/server-auth/SKILL.md +64 -4
  4. package/build/index.d.ts +1 -0
  5. package/build/index.d.ts.map +1 -1
  6. package/build/index.js +1 -0
  7. package/build/index.js.map +1 -1
  8. package/build/manager/entrypoints.d.ts.map +1 -1
  9. package/build/manager/entrypoints.js +1 -9
  10. package/build/manager/entrypoints.js.map +1 -1
  11. package/build/manager/plugins/export.d.ts +4 -0
  12. package/build/manager/plugins/export.d.ts.map +1 -1
  13. package/build/manager/plugins/export.js +3 -0
  14. package/build/manager/plugins/export.js.map +1 -1
  15. package/build/manager/plugins/re-captcha/consts.d.ts +7 -0
  16. package/build/manager/plugins/re-captcha/consts.d.ts.map +1 -0
  17. package/build/manager/plugins/re-captcha/consts.js +7 -0
  18. package/build/manager/plugins/re-captcha/consts.js.map +1 -0
  19. package/build/manager/plugins/re-captcha/consts.local.d.ts +7 -0
  20. package/build/manager/plugins/re-captcha/consts.local.d.ts.map +1 -0
  21. package/build/manager/plugins/re-captcha/consts.local.js +9 -0
  22. package/build/manager/plugins/re-captcha/consts.local.js.map +1 -0
  23. package/build/manager/plugins/re-captcha/policy.d.ts +3 -0
  24. package/build/manager/plugins/re-captcha/policy.d.ts.map +1 -0
  25. package/build/manager/plugins/re-captcha/policy.js +44 -0
  26. package/build/manager/plugins/re-captcha/policy.js.map +1 -0
  27. package/build/manager/plugins/re-captcha/types.d.ts +79 -0
  28. package/build/manager/plugins/re-captcha/types.d.ts.map +1 -0
  29. package/build/manager/plugins/re-captcha/types.js +2 -0
  30. package/build/manager/plugins/re-captcha/types.js.map +1 -0
  31. package/build/manager/plugins/re-captcha/verifier.d.ts +12 -0
  32. package/build/manager/plugins/re-captcha/verifier.d.ts.map +1 -0
  33. package/build/manager/plugins/re-captcha/verifier.js +62 -0
  34. package/build/manager/plugins/re-captcha/verifier.js.map +1 -0
  35. package/build/manager/plugins/re-captcha.d.ts +9 -0
  36. package/build/manager/plugins/re-captcha.d.ts.map +1 -1
  37. package/build/manager/plugins/re-captcha.js +46 -33
  38. package/build/manager/plugins/re-captcha.js.map +1 -1
  39. package/build/manager/plugins/types.d.ts +0 -13
  40. package/build/manager/plugins/types.d.ts.map +1 -1
  41. package/build/re-captcha/guard.d.ts +12 -0
  42. package/build/re-captcha/guard.d.ts.map +1 -0
  43. package/build/re-captcha/guard.js +43 -0
  44. package/build/re-captcha/guard.js.map +1 -0
  45. package/build/re-captcha/index.d.ts +4 -0
  46. package/build/re-captcha/index.d.ts.map +1 -0
  47. package/build/re-captcha/index.js +3 -0
  48. package/build/re-captcha/index.js.map +1 -0
  49. package/build/re-captcha/token.d.ts +6 -0
  50. package/build/re-captcha/token.d.ts.map +1 -0
  51. package/build/re-captcha/token.js +74 -0
  52. package/build/re-captcha/token.js.map +1 -0
  53. package/build/re-captcha/types.d.ts +44 -0
  54. package/build/re-captcha/types.d.ts.map +1 -0
  55. package/build/re-captcha/types.js +2 -0
  56. package/build/re-captcha/types.js.map +1 -0
  57. package/build/service.d.ts.map +1 -1
  58. package/build/service.js +7 -2
  59. package/build/service.js.map +1 -1
  60. package/package.json +20 -20
  61. package/src/index.ts +1 -1
  62. package/src/manager/README.md +1 -1
  63. package/src/manager/entrypoints.ts +1 -10
  64. package/src/manager/plugins/export.ts +4 -0
  65. package/src/manager/plugins/re-captcha/consts.local.ts +8 -0
  66. package/src/manager/plugins/re-captcha/consts.ts +8 -0
  67. package/src/manager/plugins/re-captcha/policy.ts +56 -0
  68. package/src/manager/plugins/re-captcha/types.ts +85 -0
  69. package/src/manager/plugins/re-captcha/verifier.ts +74 -0
  70. package/src/manager/plugins/re-captcha.ts +54 -39
  71. package/src/manager/plugins/types.ts +0 -14
  72. package/src/re-captcha/guard.ts +52 -0
  73. package/src/re-captcha/index.ts +3 -0
  74. package/src/re-captcha/token.ts +89 -0
  75. package/src/re-captcha/types.ts +45 -0
  76. package/src/service.ts +7 -2
  77. package/tests/re-captcha.spec.ts +298 -0
  78. package/build/manager/actions/manager.d.ts +0 -18
  79. package/build/manager/actions/manager.d.ts.map +0 -1
  80. package/build/manager/actions/manager.js +0 -11
  81. package/build/manager/actions/manager.js.map +0 -1
  82. package/build/manager/plugins/replay-policy.d.ts +0 -8
  83. package/build/manager/plugins/replay-policy.d.ts.map +0 -1
  84. package/build/manager/plugins/replay-policy.js +0 -9
  85. package/build/manager/plugins/replay-policy.js.map +0 -1
package/README.md CHANGED
@@ -13,7 +13,7 @@ account/profile records `@owlmeans/server-auth-identity`, and the browser side `
13
13
  ## Installation
14
14
 
15
15
  ```bash
16
- bun add @owlmeans/server-auth@^0.1.18-rc.55
16
+ bun add @owlmeans/server-auth@^0.1.18-rc.57
17
17
  ```
18
18
 
19
19
  ## Concepts
@@ -144,6 +144,29 @@ registerPlugin('my-method', context => ({
144
144
  }) as AuthPlugin)
145
145
  ```
146
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'
156
+
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
+
147
170
  ## API
148
171
 
149
172
  ### Root export
@@ -159,6 +182,9 @@ registerPlugin('my-method', context => ({
159
182
  | `AUTHEN_TIMEFRAME` | const | `15 * 60 * 1000` ms — challenge lifetime and anti-replay window |
160
183
  | `makeRelyModel`, `makeProviderRely`, `makeConsumerRely` | function | Rely envelope models |
161
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 |
162
188
 
163
189
  ### `./manager`
164
190
 
@@ -166,7 +192,7 @@ registerPlugin('my-method', context => ({
166
192
  |---|---|---|
167
193
  | `makeContext(cfg, customize?)` | function | Server context with API server, API client, socket service, rely guard and static `AUTH_CACHE` |
168
194
  | `main(ctx)` | function | Register the manager entrypoints, init and listen |
169
- | `entrypoints` | const | Bindings for authen, init, authenticate, rely, api-config and reCAPTCHA siteverify |
195
+ | `entrypoints` | const | Bindings for authen, init, authenticate, rely and api-config |
170
196
  | `authenticationInit`, `authenticate`, `rely` | function | Implementations bound to the auth protocols |
171
197
  | `plugins`, `registerPlugin(type, factory)` | const / function | The plugin registry |
172
198
  | `appendSupervisorAuth(ctx, opts?)`, `setupInternalTokenCoguard(entrypoints, guard?)` | function | PK supervisor login (development only by default) |
@@ -187,7 +213,11 @@ registerPlugin('my-method', context => ({
187
213
  | `authPluginHelper.getPlugin(type, context)`, `authPluginHelper.assertType(type, plugin)` | method | Resolution; `getPlugin` throws `AuthUnknown(type)` for an unregistered type |
188
214
  | `makeBasicEd25519Plugin`, `makeReCaptchaPlugin`, `makeBasicRelyPlugin` | function | Built-in plugin factories |
189
215
  | `makeSupervisorPlugin(context, opts)` | function | PK supervisor plugin factory |
190
- | `AuthPlugin`, `AuthPluginFactory`, `AuthPluginHelper`, `RecpatchaResponse`, `RecaptchaRequest`, `RelyRecord`, `AuthRedisResource` | type | Plugin shapes |
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 |
191
221
 
192
222
  ## Common pitfalls
193
223
 
@@ -202,6 +232,8 @@ registerPlugin('my-method', context => ({
202
232
  entity id.
203
233
  - Register `appendAuthService` before `appendAuthIdentityResources` and product gate services.
204
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.
205
237
 
206
238
  ## Related packages
207
239
 
@@ -222,7 +254,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
222
254
  your project's skill store (`.agents/skills/`):
223
255
 
224
256
  ```sh
225
- npx @owlmeans/agent-skills@^0.1.18-rc.51
257
+ npx @owlmeans/agent-skills@^0.1.18-rc.53
226
258
  ```
227
259
 
228
260
  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",
4
- "version": "0.1.18-rc.55",
5
- "generatedAt": "2026-10-06T11:45:57.660Z",
4
+ "version": "0.1.18-rc.57",
5
+ "generatedAt": "2026-10-09T11:30:42.742Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: server-auth
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). Auto-invoked when importing the server auth guard, registering an auth plugin, or building the auth manager.
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,7 +8,7 @@ user-invocable: false
8
8
  # @owlmeans/server-auth
9
9
 
10
10
  **Layer:** Server
11
- **Install:** `"@owlmeans/server-auth": "^0.1.18-rc.55"` in `dependencies`
11
+ **Install:** `"@owlmeans/server-auth": "^0.1.18-rc.57"` in `dependencies`
12
12
 
13
13
  Two halves, deliberately split by subpath:
14
14
 
@@ -32,6 +32,9 @@ An ordinary service imports the root. Only the auth manager imports `./manager`.
32
32
  | `AUTHEN_TIMEFRAME` | `15 * 60 * 1000` — challenge lifetime and anti-replay window, in ms |
33
33
  | `AuthService`, `AuthServiceAppend`, `AuthSpent` | Types |
34
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 |
35
38
 
36
39
  ## Key Exports — `./manager`
37
40
 
@@ -39,7 +42,7 @@ An ordinary service imports the root. Only the auth manager imports `./manager`.
39
42
  |--------|-------------|
40
43
  | `makeContext(cfg, customize?)` | A server context preconfigured with the API server, API client, socket service and static `AUTH_CACHE` |
41
44
  | `main(ctx)` | Register the manager entrypoints, configure, init and listen |
42
- | `entrypoints` | `AUTHEN`, `AUTHEN_INIT`, `AUTHEN_AUTHEN`, `AUTHEN_RELY`, the api-config entrypoints and the reCAPTCHA siteverify entrypoint |
45
+ | `entrypoints` | `AUTHEN`, `AUTHEN_INIT`, `AUTHEN_AUTHEN`, `AUTHEN_RELY` and the api-config entrypoints |
43
46
  | `authenticationInit`, `authenticate`, `rely` | Implementations bound to auth protocols: `init(request)` → challenge, `authenticate(credential)` → signed credential envelope, and the rely socket |
44
47
  | `plugins`, `registerPlugin(type, factory)` | The plugin registry (also on `./manager/plugins`) |
45
48
  | `appendSupervisorAuth(ctx, opts?)`, `setupInternalTokenCoguard(entrypoints, guard?)` | PK supervisor login — see the `supervisor-auth` skill |
@@ -61,7 +64,11 @@ one import.
61
64
  | `authPluginHelper.getPlugin(type, context)`, `authPluginHelper.assertType(type, plugin)` | Resolution; `getPlugin` throws `AuthUnknown(type)` for an unregistered type |
62
65
  | `makeBasicEd25519Plugin`, `makeReCaptchaPlugin`, `makeBasicRelyPlugin` | The plugin factories registered out of the box, for `AuthenticationType.BasicEd25519`, `ReCaptcha` and `RelyHandshake` |
63
66
  | `makeSupervisorPlugin(context, opts)` | The PK supervisor plugin factory |
64
- | `RecpatchaResponse`, `RecaptchaRequest`, `RelyRecord`, `AuthRedisResource` | Types |
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 |
65
72
 
66
73
  ## Usage
67
74
 
@@ -92,6 +99,57 @@ registerPlugin('my-method', context => ({
92
99
  }) as AuthPlugin)
93
100
  ```
94
101
 
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
+
95
153
  ## Rules
96
154
 
97
155
  - The guard verifies a bearer token and populates `req.auth`. It decides nothing about ownership or
@@ -117,6 +175,8 @@ registerPlugin('my-method', context => ({
117
175
  `AuthenFailed('entity')`. Where no resolver is registered the value is passed through untouched.
118
176
  - Pair this package with `@owlmeans/server-auth-identity` when an external provider (Google, OIDC,
119
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").
120
180
 
121
181
  ## Depends On
122
182
 
package/build/index.d.ts CHANGED
@@ -3,4 +3,5 @@ export * from './model/index.js';
3
3
  export * from './consts.js';
4
4
  export * from './entrypoints.js';
5
5
  export * from './service.js';
6
+ export * from './re-captcha/index.js';
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,mBAAmB,YAAY,CAAA;AAC/B,cAAc,kBAAkB,CAAA;AAChC,cAAc,aAAa,CAAA;AAC3B,cAAc,kBAAkB,CAAA;AAChC,cAAc,cAAc,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,YAAY,CAAA;AAC/B,cAAc,kBAAkB,CAAA;AAChC,cAAc,aAAa,CAAA;AAC3B,cAAc,kBAAkB,CAAA;AAChC,cAAc,cAAc,CAAA;AAC5B,cAAc,uBAAuB,CAAA"}
package/build/index.js CHANGED
@@ -2,4 +2,5 @@ export * from './model/index.js';
2
2
  export * from './consts.js';
3
3
  export * from './entrypoints.js';
4
4
  export * from './service.js';
5
+ export * from './re-captcha/index.js';
5
6
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,cAAc,kBAAkB,CAAA;AAChC,cAAc,aAAa,CAAA;AAC3B,cAAc,kBAAkB,CAAA;AAChC,cAAc,cAAc,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,kBAAkB,CAAA;AAChC,cAAc,aAAa,CAAA;AAC3B,cAAc,kBAAkB,CAAA;AAChC,cAAc,cAAc,CAAA;AAC5B,cAAc,uBAAuB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"entrypoints.d.ts","sourceRoot":"","sources":["../../src/manager/entrypoints.ts"],"names":[],"mappings":"AAiBA,8DAA8D;AAC9D,eAAO,MAAM,WAAW;;;;;;iBAOvB,CAAA"}
1
+ {"version":3,"file":"entrypoints.d.ts","sourceRoot":"","sources":["../../src/manager/entrypoints.ts"],"names":[],"mappings":"AASA,8DAA8D;AAC9D,eAAO,MAAM,WAAW;;;;;;iBAMvB,CAAA"}
@@ -1,17 +1,10 @@
1
- import { MOD_RECAPTCHA } from '@owlmeans/auth';
2
1
  import { authProtocols } from '@owlmeans/auth-common';
3
2
  import { entrypoints as apiConfigBindings } from '@owlmeans/api-config-server';
4
3
  import { bind } from '@owlmeans/server-entrypoint';
5
- import { decorateEntrypoint, openProtocol } from '@owlmeans/entrypoint';
4
+ import { decorateEntrypoint } from '@owlmeans/entrypoint';
6
5
  import * as actions from './actions/index.js';
7
- import { backend, route, RouteMethod } from '@owlmeans/route';
8
6
  import { DEFAULT_RELY } from './consts.js';
9
7
  const relyProtocol = decorateEntrypoint(authProtocols.rely, { guards: DEFAULT_RELY });
10
- const reCaptchaProtocol = openProtocol(route(MOD_RECAPTCHA, '/api/siteverify', backend({
11
- host: 'https://www.google.com',
12
- base: 'recaptcha',
13
- secure: true,
14
- }, RouteMethod.POST)));
15
8
  /** Server entrypoints for the full authentication manager. */
16
9
  export const entrypoints = [
17
10
  bind(authProtocols.authen, undefined, { intermediate: true }),
@@ -19,6 +12,5 @@ export const entrypoints = [
19
12
  bind(authProtocols.authenticate, actions.authenticate(authProtocols.authenticate)),
20
13
  bind(relyProtocol, actions.rely(relyProtocol)),
21
14
  ...apiConfigBindings,
22
- bind(reCaptchaProtocol),
23
15
  ];
24
16
  //# sourceMappingURL=entrypoints.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"entrypoints.js","sourceRoot":"","sources":["../../src/manager/entrypoints.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,EAAE,WAAW,IAAI,iBAAiB,EAAE,MAAM,6BAA6B,CAAA;AAC9E,OAAO,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAA;AAClD,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACvE,OAAO,KAAK,OAAO,MAAM,oBAAoB,CAAA;AAC7C,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE1C,MAAM,YAAY,GAAG,kBAAkB,CAAC,aAAa,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAA;AACrF,MAAM,iBAAiB,GAAG,YAAY,CAAC,KAAK,CAAC,aAAa,EAAE,iBAAiB,EAAE,OAAO,CAAC;IACrF,IAAI,EAAE,wBAAwB;IAC9B,IAAI,EAAE,WAAW;IACjB,MAAM,EAAE,IAAI;CACb,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AAEtB,8DAA8D;AAC9D,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;IAC7D,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,kBAAkB,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;IACxE,IAAI,CAAC,aAAa,CAAC,YAAY,EAAE,OAAO,CAAC,YAAY,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC;IAClF,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC9C,GAAG,iBAAiB;IACpB,IAAI,CAAC,iBAAiB,CAAC;CACxB,CAAA"}
1
+ {"version":3,"file":"entrypoints.js","sourceRoot":"","sources":["../../src/manager/entrypoints.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,EAAE,WAAW,IAAI,iBAAiB,EAAE,MAAM,6BAA6B,CAAA;AAC9E,OAAO,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAA;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AACzD,OAAO,KAAK,OAAO,MAAM,oBAAoB,CAAA;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE1C,MAAM,YAAY,GAAG,kBAAkB,CAAC,aAAa,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAA;AAErF,8DAA8D;AAC9D,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;IAC7D,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,kBAAkB,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;IACxE,IAAI,CAAC,aAAa,CAAC,YAAY,EAAE,OAAO,CAAC,YAAY,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC;IAClF,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC9C,GAAG,iBAAiB;CACrB,CAAA"}
@@ -1,5 +1,9 @@
1
1
  export type * from './types.js';
2
+ export type * from './re-captcha/types.js';
2
3
  export * from './consts.js';
4
+ export * from './re-captcha/consts.js';
5
+ export * from './re-captcha/policy.js';
6
+ export * from './re-captcha/verifier.js';
3
7
  export * from './basic-ed25519.js';
4
8
  export * from './re-captcha.js';
5
9
  export * from './basic-rely.js';
@@ -1 +1 @@
1
- {"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../../../src/manager/plugins/export.ts"],"names":[],"mappings":"AACA,mBAAmB,YAAY,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,oBAAoB,CAAA;AAClC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,YAAY,CAAA;AAC1B,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../../../src/manager/plugins/export.ts"],"names":[],"mappings":"AACA,mBAAmB,YAAY,CAAA;AAC/B,mBAAmB,uBAAuB,CAAA;AAC1C,cAAc,aAAa,CAAA;AAC3B,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA;AACtC,cAAc,0BAA0B,CAAA;AACxC,cAAc,oBAAoB,CAAA;AAClC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,YAAY,CAAA;AAC1B,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA"}
@@ -1,4 +1,7 @@
1
1
  export * from './consts.js';
2
+ export * from './re-captcha/consts.js';
3
+ export * from './re-captcha/policy.js';
4
+ export * from './re-captcha/verifier.js';
2
5
  export * from './basic-ed25519.js';
3
6
  export * from './re-captcha.js';
4
7
  export * from './basic-rely.js';
@@ -1 +1 @@
1
- {"version":3,"file":"export.js","sourceRoot":"","sources":["../../../src/manager/plugins/export.ts"],"names":[],"mappings":"AAEA,cAAc,aAAa,CAAA;AAC3B,cAAc,oBAAoB,CAAA;AAClC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,YAAY,CAAA;AAC1B,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"export.js","sourceRoot":"","sources":["../../../src/manager/plugins/export.ts"],"names":[],"mappings":"AAGA,cAAc,aAAa,CAAA;AAC3B,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA;AACtC,cAAc,0BAA0B,CAAA;AACxC,cAAc,oBAAoB,CAAA;AAClC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,YAAY,CAAA;AAC1B,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA"}
@@ -0,0 +1,7 @@
1
+ /** The service alias the reCAPTCHA plugin resolves its verifier by. */
2
+ export declare const RECAPTCHA_VERIFIER = "re-captcha-verifier";
3
+ /** Google's siteverify endpoint — always https. */
4
+ export declare const RECAPTCHA_SITEVERIFY_URL = "https://www.google.com/recaptcha/api/siteverify";
5
+ /** How long the default verifier waits for Google, in ms. */
6
+ export declare const RECAPTCHA_VERIFY_TIMEOUT = 10000;
7
+ //# sourceMappingURL=consts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../../../../src/manager/plugins/re-captcha/consts.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,eAAO,MAAM,kBAAkB,wBAAwB,CAAA;AAEvD,mDAAmD;AACnD,eAAO,MAAM,wBAAwB,oDAAoD,CAAA;AAEzF,6DAA6D;AAC7D,eAAO,MAAM,wBAAwB,QAAS,CAAA"}
@@ -0,0 +1,7 @@
1
+ /** The service alias the reCAPTCHA plugin resolves its verifier by. */
2
+ export const RECAPTCHA_VERIFIER = 're-captcha-verifier';
3
+ /** Google's siteverify endpoint — always https. */
4
+ export const RECAPTCHA_SITEVERIFY_URL = 'https://www.google.com/recaptcha/api/siteverify';
5
+ /** How long the default verifier waits for Google, in ms. */
6
+ export const RECAPTCHA_VERIFY_TIMEOUT = 10_000;
7
+ //# sourceMappingURL=consts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consts.js","sourceRoot":"","sources":["../../../../src/manager/plugins/re-captcha/consts.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,MAAM,CAAC,MAAM,kBAAkB,GAAG,qBAAqB,CAAA;AAEvD,mDAAmD;AACnD,MAAM,CAAC,MAAM,wBAAwB,GAAG,iDAAiD,CAAA;AAEzF,6DAA6D;AAC7D,MAAM,CAAC,MAAM,wBAAwB,GAAG,MAAM,CAAA"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The only credential fields a reCAPTCHA guest token keeps. Everything else the caller posted —
3
+ * a profile, an organization (`entitySlug` or `entityId`), permissions, groups, an expiry, a
4
+ * `source` — is dropped: the token proves a solved challenge, never an identity.
5
+ */
6
+ export declare const RECAPTCHA_CREDENTIAL_FIELDS: readonly string[];
7
+ //# sourceMappingURL=consts.local.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consts.local.d.ts","sourceRoot":"","sources":["../../../../src/manager/plugins/re-captcha/consts.local.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,EAAE,SAAS,MAAM,EAEvD,CAAA"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The only credential fields a reCAPTCHA guest token keeps. Everything else the caller posted —
3
+ * a profile, an organization (`entitySlug` or `entityId`), permissions, groups, an expiry, a
4
+ * `source` — is dropped: the token proves a solved challenge, never an identity.
5
+ */
6
+ export const RECAPTCHA_CREDENTIAL_FIELDS = Object.freeze([
7
+ 'type', 'role', 'userId', 'scopes', 'challenge', 'credential',
8
+ ]);
9
+ //# sourceMappingURL=consts.local.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consts.local.js","sourceRoot":"","sources":["../../../../src/manager/plugins/re-captcha/consts.local.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAsB,MAAM,CAAC,MAAM,CAAC;IAC1E,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY;CAC9D,CAAC,CAAA"}
@@ -0,0 +1,3 @@
1
+ import type { ReCaptchaPluginConfig, ReCaptchaPolicyModel } from './types.js';
2
+ export declare const makeReCaptchaPolicyModel: (record: ReCaptchaPluginConfig) => ReCaptchaPolicyModel;
3
+ //# sourceMappingURL=policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../../../src/manager/plugins/re-captcha/policy.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,oBAAoB,EAAqB,MAAM,YAAY,CAAA;AAShG,eAAO,MAAM,wBAAwB,WAAY,qBAAqB,KAAG,oBA4CxE,CAAA"}
@@ -0,0 +1,44 @@
1
+ import { AuthenFailed } from '@owlmeans/auth';
2
+ import { PluginMissconfigured } from '@owlmeans/config';
3
+ /** A comma- or space-separated config string as its entries; `''` and absent are no entries. */
4
+ const listOf = (value) => typeof value === 'string' ? value.split(/[\s,]+/).map(entry => entry.trim()).filter(entry => entry !== '') : [];
5
+ /** A host as it is compared: lowercased, without a trailing dot or a leading `*.`/`.`. */
6
+ const hostOf = (value) => value.trim().toLowerCase().replace(/\.$/, '').replace(/^\*?\./, '');
7
+ export const makeReCaptchaPolicyModel = (record) => {
8
+ const hostnames = () => listOf(record.hostnames).map(hostOf).filter(host => host !== '');
9
+ const minScore = () => {
10
+ const value = record.minScore;
11
+ if (value == null || (typeof value === 'string' && value.trim() === '')) {
12
+ return null;
13
+ }
14
+ const score = typeof value === 'number' ? value : Number(value);
15
+ if (!Number.isFinite(score) || score < 0 || score > 1) {
16
+ throw new PluginMissconfigured('minScore');
17
+ }
18
+ return score;
19
+ };
20
+ const actions = () => listOf(record.actions);
21
+ const assert = (response) => {
22
+ if (response.success !== true) {
23
+ const codes = Array.isArray(response['error-codes']) ? response['error-codes'] : [];
24
+ throw new AuthenFailed('recaptcha:' + (codes.length > 0 ? codes.join(',') : 'unknown'));
25
+ }
26
+ const allowed = hostnames();
27
+ if (allowed.length > 0) {
28
+ const host = typeof response.hostname === 'string' ? hostOf(response.hostname) : '';
29
+ if (host === '' || !allowed.some(entry => host === entry || host.endsWith(`.${entry}`))) {
30
+ throw new AuthenFailed('recaptcha:hostname');
31
+ }
32
+ }
33
+ const floor = minScore();
34
+ if (floor != null && typeof response.score === 'number' && response.score < floor) {
35
+ throw new AuthenFailed('recaptcha:score');
36
+ }
37
+ const named = actions();
38
+ if (named.length > 0 && (typeof response.action !== 'string' || !named.includes(response.action))) {
39
+ throw new AuthenFailed('recaptcha:action');
40
+ }
41
+ };
42
+ return { record, hostnames, minScore, actions, assert };
43
+ };
44
+ //# sourceMappingURL=policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.js","sourceRoot":"","sources":["../../../../src/manager/plugins/re-captcha/policy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ import type { PluginConfig } from '@owlmeans/config';
2
+ import type { InitializedService } from '@owlmeans/context';
3
+ /** Google's siteverify answer, for a checkbox (v2) or a score-based (v3) token. */
4
+ export interface ReCaptchaResponse {
5
+ success: boolean;
6
+ /** When the challenge was solved, as an ISO date-time string. */
7
+ challenge_ts: string;
8
+ /** The host of the page the token was solved on. */
9
+ hostname: string;
10
+ /** Score-based keys only: 0.0 (a bot) … 1.0 (a person). */
11
+ score?: number;
12
+ /** Score-based keys only: the action the page named when it asked for the token. */
13
+ action?: string;
14
+ 'error-codes'?: string[];
15
+ }
16
+ /** The siteverify form: the key's secret, the token the browser got, optionally the browser's IP. */
17
+ export interface ReCaptchaRequest {
18
+ secret: string;
19
+ response: string;
20
+ remoteip?: string;
21
+ }
22
+ /**
23
+ * Asks Google whether a reCAPTCHA token is good. The plugin resolves it by `RECAPTCHA_VERIFIER`, so a
24
+ * test (or a proxy) registers its own under that alias; without one, the default siteverify call runs.
25
+ */
26
+ export interface ReCaptchaVerifierService extends InitializedService {
27
+ /**
28
+ * Google's answer for one token. A refusal is an answer (`success: false`), not an error.
29
+ *
30
+ * @throws {AuthUnavailable} when Google cannot be asked or answers something unreadable.
31
+ */
32
+ verify: (request: ReCaptchaRequest) => Promise<ReCaptchaResponse>;
33
+ }
34
+ /** The one call the default verifier makes — `fetch`, or a stand-in a test injects. */
35
+ export interface ReCaptchaFetch {
36
+ (url: string, init: RequestInit): Promise<Response>;
37
+ }
38
+ export interface ReCaptchaVerifierOptions {
39
+ /** The siteverify address; must be https. Default `RECAPTCHA_SITEVERIFY_URL`. */
40
+ url?: string;
41
+ /** Default: the global `fetch`. */
42
+ fetch?: ReCaptchaFetch;
43
+ /** Milliseconds before the call is abandoned. Default `RECAPTCHA_VERIFY_TIMEOUT`. */
44
+ timeout?: number;
45
+ }
46
+ /**
47
+ * The `MOD_RECAPTCHA` plugin record of the server config: the secret in `value` plus the policy.
48
+ * Every policy field is a string as a mounted config file delivers it; `''` or absent = no check.
49
+ */
50
+ export interface ReCaptchaPluginConfig extends PluginConfig {
51
+ /** Hosts a token may be solved on, comma- or space-separated; a subdomain of an entry passes. */
52
+ hostnames?: string;
53
+ /** The lowest score a score-based token may carry (0…1); a checkbox token has none and passes. */
54
+ minScore?: string | number;
55
+ /** The actions a token may be issued for, comma- or space-separated; a token naming none fails. */
56
+ actions?: string;
57
+ }
58
+ /** The policy one `MOD_RECAPTCHA` record states, applied to Google's answer. */
59
+ export interface ReCaptchaPolicyModel {
60
+ readonly record: ReCaptchaPluginConfig;
61
+ /** The allowed hosts, lowercased; empty = any host. */
62
+ hostnames: () => string[];
63
+ /**
64
+ * The lowest accepted score, or `null` for no score check.
65
+ *
66
+ * @throws {PluginMissconfigured} when the record's value is not a number between 0 and 1.
67
+ */
68
+ minScore: () => number | null;
69
+ /** The allowed actions; empty = any action. */
70
+ actions: () => string[];
71
+ /**
72
+ * Accept Google's answer or refuse it, in this order: success, hostname, score, action.
73
+ *
74
+ * @throws {AuthenFailed} `recaptcha:<error-codes>`, `recaptcha:hostname`, `recaptcha:score`,
75
+ * `recaptcha:action`.
76
+ */
77
+ assert: (response: ReCaptchaResponse) => void;
78
+ }
79
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/manager/plugins/re-captcha/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAE3D,mFAAmF;AACnF,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAA;IAChB,iEAAiE;IACjE,YAAY,EAAE,MAAM,CAAA;IACpB,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAA;IAChB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;CACzB;AAED,qGAAqG;AACrG,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,wBAAyB,SAAQ,kBAAkB;IAClE;;;;OAIG;IACH,MAAM,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAA;CAClE;AAED,uFAAuF;AACvF,MAAM,WAAW,cAAc;IAC7B,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;CACpD;AAED,MAAM,WAAW,wBAAwB;IACvC,iFAAiF;IACjF,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,mCAAmC;IACnC,KAAK,CAAC,EAAE,cAAc,CAAA;IACtB,qFAAqF;IACrF,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,qBAAsB,SAAQ,YAAY;IACzD,iGAAiG;IACjG,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,kGAAkG;IAClG,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC1B,mGAAmG;IACnG,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,gFAAgF;AAChF,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAA;IACtC,uDAAuD;IACvD,SAAS,EAAE,MAAM,MAAM,EAAE,CAAA;IACzB;;;;OAIG;IACH,QAAQ,EAAE,MAAM,MAAM,GAAG,IAAI,CAAA;IAC7B,+CAA+C;IAC/C,OAAO,EAAE,MAAM,MAAM,EAAE,CAAA;IACvB;;;;;OAKG;IACH,MAAM,EAAE,CAAC,QAAQ,EAAE,iBAAiB,KAAK,IAAI,CAAA;CAC9C"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/manager/plugins/re-captcha/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,12 @@
1
+ import type { BasicConfig, BasicContext } from '@owlmeans/context';
2
+ import type { ReCaptchaVerifierOptions, ReCaptchaVerifierService } from './types.js';
3
+ /**
4
+ * The default verifier: one form POST to Google's siteverify. It holds no state and never touches
5
+ * its context, so the plugin may also run it unregistered.
6
+ *
7
+ * @throws {SyntaxError} for a siteverify address that is not https.
8
+ */
9
+ export declare const createReCaptchaVerifierService: (alias?: string, options?: ReCaptchaVerifierOptions) => ReCaptchaVerifierService;
10
+ /** Register a verifier (the default siteverify call unless `options` say otherwise). */
11
+ export declare const appendReCaptchaVerifierService: <C extends BasicConfig, T extends BasicContext<C>>(context: T, alias?: string, options?: ReCaptchaVerifierOptions) => T;
12
+ //# sourceMappingURL=verifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verifier.d.ts","sourceRoot":"","sources":["../../../../src/manager/plugins/re-captcha/verifier.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGlE,OAAO,KAAK,EAAqB,wBAAwB,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAA;AAIvG;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B,WAClC,MAAM,YAAgC,wBAAwB,KACpE,wBA+CF,CAAA;AAED,wFAAwF;AACxF,eAAO,MAAM,8BAA8B,GAAI,CAAC,SAAS,WAAW,EAAE,CAAC,SAAS,YAAY,CAAC,CAAC,CAAC,WACpF,CAAC,UAAS,MAAM,YAAiC,wBAAwB,KACjF,CAIF,CAAA"}
@@ -0,0 +1,62 @@
1
+ import { AuthUnavailable } from '@owlmeans/auth';
2
+ import { createService } from '@owlmeans/context';
3
+ import { logger } from '@owlmeans/log';
4
+ import { RECAPTCHA_SITEVERIFY_URL, RECAPTCHA_VERIFIER, RECAPTCHA_VERIFY_TIMEOUT } from './consts.js';
5
+ const log = logger('server-auth:re-captcha');
6
+ /**
7
+ * The default verifier: one form POST to Google's siteverify. It holds no state and never touches
8
+ * its context, so the plugin may also run it unregistered.
9
+ *
10
+ * @throws {SyntaxError} for a siteverify address that is not https.
11
+ */
12
+ export const createReCaptchaVerifierService = (alias = RECAPTCHA_VERIFIER, options = {}) => {
13
+ const url = options.url ?? RECAPTCHA_SITEVERIFY_URL;
14
+ if (!url.startsWith('https://')) {
15
+ throw new SyntaxError(`reCAPTCHA siteverify must be https: ${url}`);
16
+ }
17
+ const timeout = options.timeout ?? RECAPTCHA_VERIFY_TIMEOUT;
18
+ /** The answer, when it has the shape of one. */
19
+ const readAnswer = async (response) => {
20
+ try {
21
+ const answer = await response.json();
22
+ return answer != null && typeof answer === 'object' && typeof answer.success === 'boolean' ? answer : null;
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ };
28
+ return createService(alias, {
29
+ verify: async (request) => {
30
+ const body = new URLSearchParams({ secret: request.secret, response: request.response });
31
+ if (request.remoteip != null && request.remoteip !== '') {
32
+ body.set('remoteip', request.remoteip);
33
+ }
34
+ const send = options.fetch ?? fetch;
35
+ let response;
36
+ try {
37
+ response = await send(url, {
38
+ method: 'POST',
39
+ headers: { 'content-type': 'application/x-www-form-urlencoded' },
40
+ body,
41
+ signal: AbortSignal.timeout(timeout),
42
+ });
43
+ }
44
+ catch (error) {
45
+ log.warn('reCAPTCHA siteverify unreachable', { error }, { event: 'auth.recaptcha.unavailable' });
46
+ throw new AuthUnavailable('recaptcha');
47
+ }
48
+ const answer = response.ok ? await readAnswer(response) : null;
49
+ if (answer == null) {
50
+ log.warn('reCAPTCHA siteverify answered unusably', { status: response.status }, { event: 'auth.recaptcha.unavailable' });
51
+ throw new AuthUnavailable('recaptcha');
52
+ }
53
+ return answer;
54
+ },
55
+ });
56
+ };
57
+ /** Register a verifier (the default siteverify call unless `options` say otherwise). */
58
+ export const appendReCaptchaVerifierService = (context, alias = RECAPTCHA_VERIFIER, options) => {
59
+ context.registerService(createReCaptchaVerifierService(alias, options));
60
+ return context;
61
+ };
62
+ //# sourceMappingURL=verifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verifier.js","sourceRoot":"","sources":["../../../../src/manager/plugins/re-captcha/verifier.ts"],"names":[],"mappings":"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"}