@owlmeans/server-auth-session 0.1.18-rc.16 → 0.1.18-rc.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,32 +1,134 @@
1
1
  # @owlmeans/server-auth-session
2
2
 
3
- A bounded seven-day authority registry for OwlMeans bearer and OIDC sessions. It supplies an
4
- in-memory implementation for local/single-process applications and an atomic Redis implementation
5
- for multi-instance production deployments.
3
+ An authorization-freshness registry for OwlMeans bearer and OIDC sessions: every issued session is
4
+ registered under an opaque id with an absolute seven-day lifetime, and a profile's authority can be
5
+ fenced, then refreshed or revoked, so the next bearer decision sees the change. It ships an
6
+ in-memory manager for a single process and an atomic Redis manager for multi-instance deployments.
7
+ A server uses it through `@owlmeans/server-auth`: `appendAuthService` already registers the memory
8
+ manager when none is present, so an app touches this package directly only to install the Redis
9
+ manager or to fence/refresh/revoke a profile after changing its permissions, role or status. It is
10
+ not a browser-token store and holds no claims — tokens and their payloads stay with
11
+ `@owlmeans/server-auth`.
6
12
 
7
13
  ## Installation
8
14
 
9
15
  ```bash
10
- bun add @owlmeans/server-auth-session@^0.1.18-rc.16
16
+ bun add @owlmeans/server-auth-session@^0.1.18-rc.18
11
17
  ```
12
18
 
19
+ ## Concepts
20
+
21
+ - **Session** — an `AuthSessionRecord` addressed by an opaque `id`, of an `AuthSessionKind`
22
+ (`'bearer'`, `'oidc-access'`, `'oidc-refresh'`, `'oidc-provider-session'`), carrying the subject
23
+ version it was issued at and its absolute `expiresAt`.
24
+ - **Subject** — the stable selector `{ entityId, profileId, clientId? }` shared by all sessions of
25
+ one profile (per client). `entityId` is storage-only — never the renameable `entitySlug`, and never
26
+ placed in an authentication envelope.
27
+ - **Subject state** — `active`, `pending` (fenced while authority changes), `refresh` (claims
28
+ changed; re-issue required), `revoked`. Each completed transition increments the subject
29
+ `version`.
30
+ - **Decision** — `AuthSessionDecision`: `{ state: 'active' | 'refresh', version, expiresAt }` or
31
+ `{ state: 'pending' | 'revoked' | 'missing' | 'expired' }`.
32
+ - **Fence** — `fence(selector, operationId)` marks the subject pending; only `refresh` or `revoke`
33
+ with the same `operationId` completes it.
34
+
13
35
  ## Usage
14
36
 
15
- Register the memory manager before `appendAuthService` when a local application needs immediate
16
- revocation or claim-refresh decisions:
37
+ ### Use the Redis manager in a scaled deployment
38
+
39
+ Register it before `appendAuthService`; both append helpers are idempotent by alias, so the first
40
+ one registered wins.
17
41
 
18
42
  ```typescript
19
- import { appendMemoryAuthSessionManager } from '@owlmeans/server-auth-session'
20
43
  import { appendAuthService } from '@owlmeans/server-auth'
44
+ import { appendRedisAuthSessionManager } from '@owlmeans/server-auth-session'
21
45
 
22
- appendMemoryAuthSessionManager(context)
46
+ appendRedisAuthSessionManager(context) // registers AUTH_SESSION_RESOURCE as a Redis resource when absent
23
47
  appendAuthService(context)
24
48
  ```
25
49
 
26
- Use `appendRedisAuthSessionManager` in a multi-instance deployment. The application must register
27
- its Redis resource first. Call `fence()` before changing a profile's authority, then `refresh()`
28
- to require fresh claims or `revoke()` to reject all affected sessions. Session expiry is absolute;
29
- neither manager extends it beyond seven days.
50
+ `appendRedisAuthSessionManager(context, { resourceAlias?, dbAlias?, serviceAlias?, alias?, now? })`
51
+ passes `dbAlias` / `serviceAlias` to `makeRedisResource`.
52
+
53
+ ### Change a profile's authority
54
+
55
+ ```typescript
56
+ import { AUTH_SESSION_MANAGER } from '@owlmeans/server-auth-session'
57
+ import type { AuthSessionManager } from '@owlmeans/server-auth-session'
58
+
59
+ const sessions = context.service<AuthSessionManager>(AUTH_SESSION_MANAGER)
60
+ const selector = { entityId, profileId, clientId }
61
+ const operationId = crypto.randomUUID()
62
+
63
+ await sessions.fence(selector, operationId) // every session of the subject now answers 'pending'
64
+ await profiles.update({ ...profile, role }) // the durable authority change
65
+ await sessions.refresh(selector, operationId) // sessions answer 'refresh' → claims are re-issued
66
+ // or, on disable/removal:
67
+ await sessions.revoke(selector, operationId) // sessions answer 'revoked'
68
+ ```
69
+
70
+ If the write or the completion fails, leave the subject pending — it is refused until a matching
71
+ completion runs.
72
+
73
+ ### Register and inspect a session
74
+
75
+ ```typescript
76
+ const issued = await sessions.register({ id: sessionId, kind: 'bearer', entityId, profileId, clientId })
77
+ // { state: 'active', version, expiresAt } — expiresAt never exceeds now + AUTH_SESSION_TTL
78
+
79
+ const decision = await sessions.inspect(sessionId)
80
+ ```
81
+
82
+ ### Test with a controllable clock
83
+
84
+ ```typescript
85
+ import { makeMemoryAuthSessionManager } from '@owlmeans/server-auth-session'
86
+
87
+ let now = 1_000
88
+ const manager = makeMemoryAuthSessionManager({ now: () => now })
89
+ await manager.init()
90
+ ```
91
+
92
+ ## API
93
+
94
+ | Symbol | Kind | Purpose |
95
+ |---|---|---|
96
+ | `AuthSessionManager` | interface | `register(session)`, `inspect(id)`, `fence(selector, op)`, `refresh(selector, op) → version`, `revoke(selector, op) → version` |
97
+ | `makeMemoryAuthSessionManager(options?)` | function | process-local manager; state is lost on restart |
98
+ | `appendMemoryAuthSessionManager(context, options?)` | function | registers the memory manager unless the alias is taken |
99
+ | `makeRedisAuthSessionManager(resourceAlias?, options?)` | function | Redis manager; atomic `SET NX` registration and Lua subject transitions |
100
+ | `appendRedisAuthSessionManager(context, options?)` | function | registers the Redis resource (if absent) and the Redis manager (if absent) |
101
+ | `AUTH_SESSION_TTL` | const | `7 * 24 * 60 * 60 * 1000` ms — absolute maximum, never extended |
102
+ | `AUTH_SESSION_MANAGER` | const | `'auth-session-manager'` — service alias |
103
+ | `AUTH_SESSION_RESOURCE` | const | `'auth-session-record'` — Redis resource alias |
104
+ | `AuthSessionManagerOptions` | type | `{ alias?, now? }` |
105
+ | `AuthSessionSelector`, `RegisterAuthSession` | types | `{ entityId, profileId, clientId? }`; selector + `id`, `kind`, `issuedAt?`, `expiresAt?` |
106
+ | `AuthSessionRecord`, `AuthSessionSubject`, `AuthSessionStoredRecord` | types | stored shapes (`record: 'session' \| 'subject'`) |
107
+ | `AuthSessionKind`, `AuthSessionState`, `AuthSessionDecision` | types | see Concepts |
108
+
109
+ ## Common pitfalls
110
+
111
+ - Registering the Redis manager after `appendAuthService` has no effect — the memory manager
112
+ already holds `AUTH_SESSION_MANAGER`.
113
+ - The memory manager forgets everything on restart; use it only where another authority decides
114
+ each request live, or in development and tests.
115
+ - `register` clamps a caller-supplied `expiresAt` to `AUTH_SESSION_TTL`; re-registering after a
116
+ claim refresh keeps the original absolute expiry.
117
+ - A `refresh` or `revoke` whose `operationId` differs from a pending fence does not complete it
118
+ (memory returns the unchanged version; Redis throws `auth-session:transition-conflict`).
119
+ - A registry read or write failure is surfaced by `@owlmeans/server-auth` as `AuthUnavailable` (503) —
120
+ never treat it as a missing session or a logout.
121
+ - Do not replace the Redis Lua transitions with a read-modify-save sequence; a concurrent fence
122
+ would be overwritten.
123
+
124
+ ## Related packages
125
+
126
+ - [`@owlmeans/server-auth`](../server-auth) — issues sessions and consults this registry at every bearer decision
127
+ - [`@owlmeans/redis-resource`](../redis-resource) — the Redis resource behind the scaled manager
128
+ - [`@owlmeans/server-auth-identity`](../server-auth-identity) — identity profiles whose authority changes are fenced here
129
+ - [`@owlmeans/context`](../context) — `createService` and the service lifecycle
130
+
131
+ The `server-auth-session` skill states the fence/refresh/revoke rules for agents.
30
132
 
31
133
  <!-- owlmeans:agent-guidance:start -->
32
134
  ## Agent guidance
@@ -36,7 +138,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
36
138
  your project's skill store (`.agents/skills/`):
37
139
 
38
140
  ```sh
39
- npx @owlmeans/agent-skills@^0.1.18-rc.49
141
+ npx @owlmeans/agent-skills@^0.1.18-rc.52
40
142
  ```
41
143
 
42
144
  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-session",
4
- "version": "0.1.18-rc.16",
5
- "generatedAt": "2026-10-05T21:03:06.367Z",
4
+ "version": "0.1.18-rc.18",
5
+ "generatedAt": "2026-10-08T21:27:54.285Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/server-auth-session",
3
- "version": "0.1.18-rc.16",
3
+ "version": "0.1.18-rc.18",
4
4
  "license": "MIT",
5
5
  "description": "Short-lived OwlMeans session registry with memory and Redis implementations.",
6
6
  "type": "module",
@@ -22,9 +22,9 @@
22
22
  },
23
23
  "dependencies": {
24
24
  "@owlmeans/context": "^0.1.18-rc.34",
25
- "@owlmeans/redis-resource": "^0.1.18-rc.44",
25
+ "@owlmeans/redis-resource": "^0.1.18-rc.46",
26
26
  "@owlmeans/resource": "^0.1.18-rc.40",
27
- "@owlmeans/server-context": "^0.1.18-rc.43"
27
+ "@owlmeans/server-context": "^0.1.18-rc.44"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@owlmeans/dep-config": "workspace:*",
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=service.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.spec.d.ts","sourceRoot":"","sources":["service.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,21 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { makeMemoryAuthSessionManager } from '../src/memory.js';
3
+ describe('memory auth session manager', () => {
4
+ test('refreshes and revokes an existing seven-day session', async () => {
5
+ let now = 1_000;
6
+ const manager = makeMemoryAuthSessionManager({ now: () => now });
7
+ await manager.init();
8
+ const selector = { entityId: 'entity-1', profileId: 'profile-1', clientId: 'client-1' };
9
+ const issued = await manager.register({ ...selector, id: 'session-1', kind: 'bearer' });
10
+ expect(issued.state).toBe('active');
11
+ await manager.fence(selector, 'change-1');
12
+ expect((await manager.inspect('session-1')).state).toBe('pending');
13
+ await manager.refresh(selector, 'change-1');
14
+ expect((await manager.inspect('session-1')).state).toBe('refresh');
15
+ await manager.revoke(selector, 'change-2');
16
+ expect((await manager.inspect('session-1')).state).toBe('revoked');
17
+ now += 7 * 24 * 60 * 60 * 1000 + 1;
18
+ expect((await manager.inspect('session-1')).state).toBe('expired');
19
+ });
20
+ });
21
+ //# sourceMappingURL=service.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.spec.js","sourceRoot":"","sources":["service.spec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,UAAU,CAAA;AACjD,OAAO,EAAE,4BAA4B,EAAE,MAAM,kBAAkB,CAAA;AAE/D,QAAQ,CAAC,6BAA6B,EAAE,GAAG,EAAE;IAC3C,IAAI,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;QACrE,IAAI,GAAG,GAAG,KAAK,CAAA;QACf,MAAM,OAAO,GAAG,4BAA4B,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,CAAA;QAChE,MAAM,OAAO,CAAC,IAAI,EAAE,CAAA;QACpB,MAAM,QAAQ,GAAG,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAA;QACvF,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,EAAE,GAAG,QAAQ,EAAE,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAA;QACvF,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QACnC,MAAM,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAA;QACzC,MAAM,CAAC,CAAC,MAAM,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QAClE,MAAM,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAA;QAC3C,MAAM,CAAC,CAAC,MAAM,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QAClE,MAAM,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAA;QAC1C,MAAM,CAAC,CAAC,MAAM,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QAClE,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,GAAG,CAAC,CAAA;QAClC,MAAM,CAAC,CAAC,MAAM,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;IACpE,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}