@rodit/rodit-auth-be 9.11.20 → 9.13.0
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/CHANGELOG.md +100 -0
- package/README.md +1712 -2324
- package/index.js +104 -16
- package/lib/auth/sessionmanager.js +131 -16
- package/lib/auth/tokenservice.js +153 -33
- package/lib/middleware/authenticationmw.js +73 -3
- package/lib/middleware/webhookhandlermw.js +398 -28
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,106 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@rodit/rodit-auth-be` are documented here.
|
|
4
4
|
|
|
5
|
+
## [9.13.0] — 2026-07-15
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Federated login across APIs in the same SR/CR family.** A client RODiT
|
|
10
|
+
issued for API A can log into API B via
|
|
11
|
+
`login_server({ apiEndpoint: '<federated URL>' })`. Mutual auth remains
|
|
12
|
+
optional; federation is login → remint a local JWT (foreign JWTs are not
|
|
13
|
+
accepted without re-login).
|
|
14
|
+
- **JWT claim `rodit_subjectuniqueidentifier_url` (always present).** Same
|
|
15
|
+
unset-as-`null` rule as `config_*` in 9.12:
|
|
16
|
+
- Same-API login: claim is `null`; `iss` = receiving server URL
|
|
17
|
+
- Federated login: claim = federated API URL; `iss` = client home
|
|
18
|
+
`subjectuniqueidentifier_url`
|
|
19
|
+
- Server validation treats non-empty claim strings as federated JWTs
|
|
20
|
+
(`isNonEmptyUrlClaim`); renewal always copies/normalizes to `null` or the
|
|
21
|
+
URL (9.12 tokens without the claim renew into `null`)
|
|
22
|
+
- **Client MITM check.** After crypto validation, `login_server` verifies that
|
|
23
|
+
a federated JWT's `rodit_subjectuniqueidentifier_url` matches the intended
|
|
24
|
+
`apiEndpoint` and that `iss` matches the client home URL. Failures:
|
|
25
|
+
`FEDERATED_ISSUER_MISSING`, `FEDERATED_ISSUER_MISMATCH`.
|
|
26
|
+
- **Exported helpers:** `normalizeUrlWithoutPort`, `isNonEmptyUrlClaim`,
|
|
27
|
+
`isFederatedRoditLogin`, `validateFederatedLoginTarget`.
|
|
28
|
+
|
|
29
|
+
## [9.12.0] — 2026-07-02
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- **Multi-peer inbound webhook verification.** Inbound webhook signatures were
|
|
34
|
+
verified against `stateManager.getPeerBase64urlJwkPublicKey()`, a single
|
|
35
|
+
mutable "current peer" slot. When an agent was connected to several peers the
|
|
36
|
+
slot was clobbered by the most recent peer, so webhooks from other senders
|
|
37
|
+
(including the central API) failed with a false `401`
|
|
38
|
+
`WEBHOOK_SIGNATURE_INVALID` / `PEER_KEY_UNAVAILABLE`.
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- **Self-identifying webhooks.** The signer's Ed25519 public key now travels
|
|
43
|
+
with each webhook, so verification no longer depends on a shared "current
|
|
44
|
+
peer" slot and is correct when connected to many peers at once:
|
|
45
|
+
- `send_webhook` advertises the signer identity via `X-Rodit-Public-Key`,
|
|
46
|
+
`X-Rodit-Implicit-Account` (hex of the signing key), and `X-Rodit-Token-Id`.
|
|
47
|
+
- `webhookHandler.extractWebhookSignerKey(headers)` reads that key directly
|
|
48
|
+
(implicit account is authoritative on its own; a raw advertised key must
|
|
49
|
+
agree with it). Exported from the package root as `extractWebhookSignerKey`.
|
|
50
|
+
- **Signer ↔ session authorization gate.** A valid signature only proves the
|
|
51
|
+
sender holds the advertised private key; it does not prove that identity is a
|
|
52
|
+
peer we established a session with. `createWebhookAuthenticationMiddleware`
|
|
53
|
+
now enforces, after the signature verifies, that the (signed) `session_id`
|
|
54
|
+
maps to a live session whose `ownerId` equals the verified signer's implicit
|
|
55
|
+
account. New rejections: `401 WEBHOOK_SESSION_REQUIRED`,
|
|
56
|
+
`401 WEBHOOK_SESSION_INVALID`, `403 WEBHOOK_SIGNER_SESSION_MISMATCH`
|
|
57
|
+
(bypassed only under `NODE_ENV=test` or
|
|
58
|
+
`SECURITY_OPTIONS.BYPASS_WEBHOOK_VERIFICATION`). The validated binding is
|
|
59
|
+
exposed as `req.webhook_session`, `req.webhook_session_id`, and
|
|
60
|
+
`req.webhook_signer_implicit_account`.
|
|
61
|
+
- **Webhook ↔ session correlation.** `send_webhook` stamps the originating
|
|
62
|
+
session id into the **signed** payload (`session_id`) so receivers can link an
|
|
63
|
+
inbound webhook to the session opened at login:
|
|
64
|
+
- `extractWebhookSessionId({ headers, rawPayload, parsedBody })` returns the
|
|
65
|
+
session id (signed payload authoritative, `X-Rodit-Session-Id` header as
|
|
66
|
+
fallback). Exported from the package root and from `webhookHandler`.
|
|
67
|
+
- `createPublicKeyMiddleware` sets `req.webhook_session_id` for downstream
|
|
68
|
+
handlers.
|
|
69
|
+
- The session id is resolved without requiring a request context: it falls
|
|
70
|
+
back to **session storage** (the SessionManager singleton, in-memory by
|
|
71
|
+
default) via `findSessionsByRoditId(options.sessionRoditId)`, so a JWT issuer
|
|
72
|
+
(`login_client`) can stamp the shared session id for out-of-band webhooks.
|
|
73
|
+
- `RoditClient.sendWebhook` / `sendWebhookToEndpoint` / `sendWakeHook` /
|
|
74
|
+
`sendAgentHook` accept an `options` argument (`sessionId`, `sessionRoditId`)
|
|
75
|
+
and default `sessionId` to the client's own `this.sessionId` (set at
|
|
76
|
+
`login_server`), so a peer that initiated the login stamps its session
|
|
77
|
+
automatically.
|
|
78
|
+
- **Client-side session recording (symmetric session store).** The
|
|
79
|
+
`SessionManager` facility used by servers to track client sessions is now also
|
|
80
|
+
used by clients to record the sessions they open:
|
|
81
|
+
- `SessionManager.recordSession({ id, roditId, ... })` stores an
|
|
82
|
+
externally-issued session under its own id (idempotent), tagged
|
|
83
|
+
`origin: "client"`. `createSession` now tags issued sessions
|
|
84
|
+
`origin: "server"`.
|
|
85
|
+
- `SessionManager.hasSession(id)` quietly reports whether an id is a known,
|
|
86
|
+
live session (no warn logs, no lastAccessedAt bump); `forgetSession(id)`
|
|
87
|
+
removes one.
|
|
88
|
+
- `RoditClient.login_server` records each opened session keyed by the server's
|
|
89
|
+
roditId, so a client connected to many servers can cross-reference an inbound
|
|
90
|
+
webhook's `session_id` to a live session (multi-peer safe, unlike the single
|
|
91
|
+
`this.sessionId` slot). `clearSession` forgets it.
|
|
92
|
+
- `RoditClient.isKnownSession(id)` cross-references a webhook's session id
|
|
93
|
+
against the client's open sessions.
|
|
94
|
+
- Issuer-side webhook session lookup only considers `origin !== "client"`
|
|
95
|
+
sessions, so mutual peers don't cross-stamp.
|
|
96
|
+
|
|
97
|
+
### Changed
|
|
98
|
+
|
|
99
|
+
- **`createPublicKeyMiddleware`** extracts the verification key from the webhook
|
|
100
|
+
itself (`extractWebhookSignerKey`) instead of the mutable
|
|
101
|
+
`stateManager.getPeerBase64urlJwkPublicKey()` slot, and attaches
|
|
102
|
+
`req.webhook_session_id`. Authentication (key from the webhook) and
|
|
103
|
+
authorization (binding to a session) are now cleanly separated.
|
|
104
|
+
|
|
5
105
|
## [8.0.0] — 2026-05-02
|
|
6
106
|
|
|
7
107
|
### Breaking
|