@hexok/auth-redis 0.0.0-stage → 0.1.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/README.md CHANGED
@@ -1,3 +1,76 @@
1
- # Temporary Holding Version
1
+ # @hexok/auth-redis
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Redis sessions for the `SessionStore` port of `@hexok/auth`. Install with `npm i @hexok/auth @hexok/auth-redis ioredis`. Connections run on `@hexok/ioredis-lifecycle`.
4
+
5
+ ## Use
6
+
7
+ ```ts
8
+ import { OpaqueSessionToken, Sessions, sessionConfig } from '@hexok/auth';
9
+ import { RedisSessionStore } from '@hexok/auth-redis';
10
+ import { z } from 'zod';
11
+
12
+ const store = new RedisSessionStore({
13
+ redis: env.REDIS_URL,
14
+ identity: z.object({ organizationId: z.string(), userId: z.string() }),
15
+ ...sessionConfig(env),
16
+ });
17
+ await store.start();
18
+
19
+ const sessions = new Sessions({ store, token: new OpaqueSessionToken() });
20
+ ```
21
+
22
+ `identity` is a Standard Schema of the identity's JSON form. It types the store, so `open` takes that shape and `resolve` returns it. `open` refuses an identity the schema refuses with a `VALIDATION` error. Every read is checked again. Use plain JSON values. A `Date` does not survive the round trip.
23
+
24
+ Start the store before the first call and stop it on shutdown. `Sessions` does not start or stop it.
25
+
26
+ ## Records
27
+
28
+ Each session is one hash at `keyPrefix` plus a SHA-256 of the session id. Redis never holds the id a token carries, so read access to Redis does not yield a live credential.
29
+
30
+ | Field | Value |
31
+ | --- | --- |
32
+ | `i` | The identity as JSON. |
33
+ | `c` | When the session opened, in ms since the epoch. |
34
+ | `e` | The absolute end, in ms since the epoch. |
35
+ | `v` | The record version, `1`. |
36
+
37
+ The key's TTL is the idle window, never past the absolute end.
38
+
39
+ - `open` writes the hash and its TTL in one script. The id is 32 random bytes.
40
+ - `resolve` runs one script. A missing key is `ended`. A record past `e` is deleted and is `expired`. Otherwise the TTL becomes the lesser of the idle window and the time left, and the identity is returned.
41
+ - `resolve` compares `e` with the app clock, not the Redis clock. A Redis clock that runs ahead expires keys early. That reads as `ended`.
42
+ - A record that cannot be read, such as bad JSON, an identity the schema refuses, or a key of another type, is deleted, logged at `warn` with its key, and is `ended`.
43
+ - `close` deletes the key.
44
+
45
+ To debug a session, hash its id with `sessionKey(id, keyPrefix)`, then use `HGETALL`, `PTTL`, and `DEL`. A `PTTL` of `-2` means the key is gone. Redis cannot tell a deleted key from an expired one.
46
+
47
+ ## Configuration
48
+
49
+ | Option | Default | Meaning |
50
+ | --- | --- | --- |
51
+ | `redis` | required | A Redis URL or ioredis options. The store opens its own client. |
52
+ | `identity` | required | The identity's Standard Schema. |
53
+ | `idleSeconds`, `absoluteSeconds` | required | Both clocks. `sessionConfig(env)` from `@hexok/auth` reads them. |
54
+ | `keyPrefix` | `hexok:session:` | Every key starts with it. |
55
+ | `logger` | silent | A `LogSink`, such as `@hexok/logger` or `console`. |
56
+ | `startTimeoutMs` | unset | Rejects `start()` when Redis is not ready in time. |
57
+ | `stopTimeoutMs` | `5000` | The total time `stop()` may take. |
58
+
59
+ `state` is `ready` from `start()` until `stop()`, even while Redis is down. Use it to gate traffic, not as a Redis health check. A call outside that window rejects with a `LifecycleError`, which this package re-exports.
60
+
61
+ ## ACL
62
+
63
+ This is the least-privilege user for this adapter. Replace `CHANGE_ME`.
64
+
65
+ ```text
66
+ ACL SETUSER hexok-session on >CHANGE_ME resetchannels ~hexok:session:* -@all +hset +hmget +pexpire +del +eval +info +client|setinfo
67
+ ```
68
+
69
+ The key pattern follows `keyPrefix`. `open` and `resolve` are scripts, and scripts run with this user's rights. So they need `+eval`, plus the `+hset`, `+hmget`, `+pexpire`, and `+del` they send. `close` sends `+del`.
70
+
71
+ - `+info` is the ioredis ready check, on every connection. `+client|setinfo` is the ioredis client tag. ioredis ignores a refused `CLIENT SETINFO`, but `ACL LOG` records each refusal.
72
+ - Redis 7.0 rejects `+client|setinfo` in `ACL SETUSER`, because it has no such subcommand. Remove that rule on 7.0.
73
+ - `HELLO`, `AUTH`, and `QUIT` need no rule. Redis does not check them against the ACL.
74
+ - Add `+select` when the URL names a database, and `+client|setname` when `connectionName` is set.
75
+
76
+ An integration test runs this line as a real user and fails if `ACL LOG` records a refusal.
@@ -0,0 +1,3 @@
1
+ export { LifecycleError, type LogSink } from '@hexok/ioredis-lifecycle';
2
+ export { RedisSessionStore, type RedisSessionStoreConfig, sessionKey, } from './redis-session-store.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACxE,OAAO,EACL,iBAAiB,EACjB,KAAK,uBAAuB,EAC5B,UAAU,GACX,MAAM,0BAA0B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { LifecycleError } from '@hexok/ioredis-lifecycle';
2
+ export { RedisSessionStore, sessionKey, } from './redis-session-store.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAgB,MAAM,0BAA0B,CAAC;AACxE,OAAO,EACL,iBAAiB,EAEjB,UAAU,GACX,MAAM,0BAA0B,CAAC"}
@@ -0,0 +1,59 @@
1
+ import { type OpenedSession, type SessionConfig, SessionStore } from '@hexok/auth';
2
+ import { type LifecycleState, type LogSink } from '@hexok/ioredis-lifecycle';
3
+ import { type Infer, type Result, type StandardSchemaV1 } from 'hexok';
4
+ import type { RedisOptions } from 'ioredis';
5
+ export interface RedisSessionStoreConfig<S extends StandardSchemaV1> extends SessionConfig {
6
+ /** A Redis URL or ioredis options. The store opens its own client. */
7
+ readonly redis: string | RedisOptions;
8
+ /**
9
+ * The identity a session holds, as its JSON form. It types the store and
10
+ * checks every identity on `open` and on every read.
11
+ */
12
+ readonly identity: S;
13
+ /** Default `hexok:session:`. Every key is this prefix and a SHA-256 of the session id. */
14
+ readonly keyPrefix?: string | undefined;
15
+ readonly logger?: LogSink | undefined;
16
+ /** Rejects `start()` when Redis is not ready in time. Unset, `start()` waits. */
17
+ readonly startTimeoutMs?: number | undefined;
18
+ /** The total time `stop()` may take. Default 5000. */
19
+ readonly stopTimeoutMs?: number | undefined;
20
+ /** Milliseconds since the epoch. Tests pass a fake clock. Default `Date.now`. */
21
+ readonly now?: (() => number) | undefined;
22
+ }
23
+ /**
24
+ * The Redis key of a session id, for debugging with `HGETALL`, `PTTL`, and
25
+ * `DEL`. The key is a hash, so Redis never holds the id a token carries.
26
+ */
27
+ export declare function sessionKey(id: string, keyPrefix?: string): string;
28
+ declare const RedisSessionStore_base: (abstract new (...args: any[]) => {
29
+ start(): Promise<void>;
30
+ stop(): Promise<void>;
31
+ }) & typeof SessionStore;
32
+ /**
33
+ * Sessions in Redis. One hash per session holds the identity JSON (`i`), the
34
+ * open time (`c`), the absolute end (`e`), and the record version (`v`). The
35
+ * key's TTL is the idle window, never past the absolute end. `resolve` checks
36
+ * the absolute end and slides the TTL in one script, against the app clock.
37
+ *
38
+ * ```ts
39
+ * const store = new RedisSessionStore({
40
+ * redis: process.env.REDIS_URL,
41
+ * identity: z.object({ organizationId: z.string(), userId: z.string() }),
42
+ * ...sessionConfig(process.env),
43
+ * })
44
+ * await store.start()
45
+ * ```
46
+ */
47
+ export declare class RedisSessionStore<S extends StandardSchemaV1> extends RedisSessionStore_base<Infer<S>> {
48
+ #private;
49
+ constructor(config: RedisSessionStoreConfig<S>);
50
+ /** `ready` from `start()` until `stop()`, even while Redis is down. Use it to gate traffic, not as a Redis health check. */
51
+ get state(): LifecycleState;
52
+ start(): Promise<void>;
53
+ stop(): Promise<void>;
54
+ open(identity: Infer<S>): Promise<OpenedSession>;
55
+ resolve(id: string): Promise<Result<Infer<S>, 'ended' | 'expired'>>;
56
+ close(id: string): Promise<void>;
57
+ }
58
+ export {};
59
+ //# sourceMappingURL=redis-session-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-session-store.d.ts","sourceRoot":"","sources":["../src/redis-session-store.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,YAAY,EACb,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,OAAO,EAGb,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAGL,KAAK,KAAK,EACV,KAAK,MAAM,EACX,KAAK,gBAAgB,EAGtB,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAS,YAAY,EAAE,MAAM,SAAS,CAAC;AA8CnD,MAAM,WAAW,uBAAuB,CAAC,CAAC,SAAS,gBAAgB,CACjE,SAAQ,aAAa;IACrB,sEAAsE;IACtE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,CAAC;IACtC;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;IACrB,0FAA0F;IAC1F,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACtC,iFAAiF;IACjF,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7C,sDAAsD;IACtD,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C,iFAAiF;IACjF,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC;CAC3C;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,SAAqB,GAAG,MAAM,CAE7E;;;;;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,iBAAiB,CAAC,CAAC,SAAS,gBAAgB,CAAE,SAAQ,uBAEjE,KAAK,CAAC,CAAC,CAAC,CAAC;;gBAUG,MAAM,EAAE,uBAAuB,CAAC,CAAC,CAAC;IAkB9C,4HAA4H;IAC5H,IAAI,KAAK,IAAI,cAAc,CAE1B;IAEQ,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAIf,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC;IA0BhD,OAAO,CACpB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC,CAAC;IAgBlC,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAwBhD"}
@@ -0,0 +1,150 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import { assertSessionConfig, SessionStore, } from '@hexok/auth';
3
+ import { openRedis, RedisLifecycle, } from '@hexok/ioredis-lifecycle';
4
+ import { Adapter, fail, validate, validationError, } from 'hexok';
5
+ const ADAPTER = 'auth-session';
6
+ const DEFAULT_KEY_PREFIX = 'hexok:session:';
7
+ const RECORD_VERSION = '1';
8
+ const silent = {
9
+ debug() { },
10
+ info() { },
11
+ warn() { },
12
+ error() { },
13
+ };
14
+ // KEYS[1] the session key. ARGV: identity JSON, created ms, absolute end ms, version, TTL ms.
15
+ const openScript = `
16
+ redis.call('HSET', KEYS[1], 'i', ARGV[1], 'c', ARGV[2], 'e', ARGV[3], 'v', ARGV[4])
17
+ redis.call('PEXPIRE', KEYS[1], ARGV[5])
18
+ return 1
19
+ `;
20
+ // KEYS[1] the session key. ARGV: now ms (the app clock), idle ms.
21
+ // Returns {'live', identity JSON}, {'ended'}, {'expired'}, or {'corrupt'}. Expired and corrupt records are deleted.
22
+ const resolveScript = `
23
+ local fields = redis.pcall('HMGET', KEYS[1], 'i', 'e')
24
+ if type(fields) == 'table' and fields.err then
25
+ redis.call('DEL', KEYS[1])
26
+ return {'corrupt'}
27
+ end
28
+ local identity, raw = fields[1], fields[2]
29
+ if not identity and not raw then return {'ended'} end
30
+ local absoluteEnd = tonumber(raw)
31
+ if not identity or not absoluteEnd then
32
+ redis.call('DEL', KEYS[1])
33
+ return {'corrupt'}
34
+ end
35
+ local now, idle = tonumber(ARGV[1]), tonumber(ARGV[2])
36
+ if absoluteEnd <= now then
37
+ redis.call('DEL', KEYS[1])
38
+ return {'expired'}
39
+ end
40
+ redis.call('PEXPIRE', KEYS[1], math.min(idle, absoluteEnd - now))
41
+ return {'live', identity}
42
+ `;
43
+ /**
44
+ * The Redis key of a session id, for debugging with `HGETALL`, `PTTL`, and
45
+ * `DEL`. The key is a hash, so Redis never holds the id a token carries.
46
+ */
47
+ export function sessionKey(id, keyPrefix = DEFAULT_KEY_PREFIX) {
48
+ return `${keyPrefix}${createHash('sha256').update(id).digest('base64url')}`;
49
+ }
50
+ /**
51
+ * Sessions in Redis. One hash per session holds the identity JSON (`i`), the
52
+ * open time (`c`), the absolute end (`e`), and the record version (`v`). The
53
+ * key's TTL is the idle window, never past the absolute end. `resolve` checks
54
+ * the absolute end and slides the TTL in one script, against the app clock.
55
+ *
56
+ * ```ts
57
+ * const store = new RedisSessionStore({
58
+ * redis: process.env.REDIS_URL,
59
+ * identity: z.object({ organizationId: z.string(), userId: z.string() }),
60
+ * ...sessionConfig(process.env),
61
+ * })
62
+ * await store.start()
63
+ * ```
64
+ */
65
+ export class RedisSessionStore extends Adapter(SessionStore) {
66
+ #redis;
67
+ #identity;
68
+ #keyPrefix;
69
+ #idleMs;
70
+ #absoluteMs;
71
+ #logger;
72
+ #now;
73
+ #lifecycle;
74
+ constructor(config) {
75
+ super();
76
+ assertSessionConfig(config);
77
+ this.#redis = config.redis;
78
+ this.#identity = config.identity;
79
+ this.#keyPrefix = config.keyPrefix ?? DEFAULT_KEY_PREFIX;
80
+ this.#idleMs = config.idleSeconds * 1000;
81
+ this.#absoluteMs = config.absoluteSeconds * 1000;
82
+ this.#logger = config.logger ?? silent;
83
+ this.#now = config.now ?? Date.now;
84
+ this.#lifecycle = new RedisLifecycle({
85
+ adapter: ADAPTER,
86
+ stopTimeoutMs: config.stopTimeoutMs ?? 5000,
87
+ startTimeoutMs: config.startTimeoutMs,
88
+ logger: config.logger,
89
+ });
90
+ }
91
+ /** `ready` from `start()` until `stop()`, even while Redis is down. Use it to gate traffic, not as a Redis health check. */
92
+ get state() {
93
+ return this.#lifecycle.state;
94
+ }
95
+ start() {
96
+ return this.#lifecycle.start(() => ({ commands: openRedis(this.#redis) }));
97
+ }
98
+ stop() {
99
+ return this.#lifecycle.stop();
100
+ }
101
+ async open(identity) {
102
+ const checked = validate(this.#identity, identity);
103
+ if (!checked.ok) {
104
+ throw validationError('auth-session: the identity does not match its schema', checked.issues);
105
+ }
106
+ const id = randomBytes(32).toString('base64url');
107
+ const now = this.#now();
108
+ const absoluteEnd = now + this.#absoluteMs;
109
+ await this.#lifecycle.run('open', ({ commands }) => commands.eval(openScript, 1, sessionKey(id, this.#keyPrefix), JSON.stringify(identity), now, absoluteEnd, RECORD_VERSION, Math.min(this.#idleMs, this.#absoluteMs)));
110
+ return { id, expiresAt: new Date(absoluteEnd) };
111
+ }
112
+ async resolve(id) {
113
+ const key = sessionKey(id, this.#keyPrefix);
114
+ const reply = await this.#lifecycle.run('resolve', ({ commands }) => commands.eval(resolveScript, 1, key, this.#now(), this.#idleMs));
115
+ const [status, json] = Array.isArray(reply) ? reply : [];
116
+ if (status === 'ended')
117
+ return fail('ended');
118
+ if (status === 'expired')
119
+ return fail('expired');
120
+ if (status === 'live' && typeof json === 'string') {
121
+ const identity = this.#parse(json);
122
+ if (identity.ok)
123
+ return identity;
124
+ }
125
+ await this.#discard(key);
126
+ return fail('ended');
127
+ }
128
+ async close(id) {
129
+ await this.#lifecycle.run('close', ({ commands }) => commands.del(sessionKey(id, this.#keyPrefix)));
130
+ }
131
+ #parse(json) {
132
+ let value;
133
+ try {
134
+ value = JSON.parse(json);
135
+ }
136
+ catch {
137
+ return fail('VALIDATION');
138
+ }
139
+ return validate(this.#identity, value);
140
+ }
141
+ // A record this store cannot read proves nothing. It is deleted and logged by its key, which is not a credential.
142
+ async #discard(key) {
143
+ await this.#lifecycle.run('resolve', ({ commands }) => commands.del(key));
144
+ this.#logger.warn('auth-session: deleted an unreadable session record', {
145
+ adapter: ADAPTER,
146
+ key,
147
+ });
148
+ }
149
+ }
150
+ //# sourceMappingURL=redis-session-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-session-store.js","sourceRoot":"","sources":["../src/redis-session-store.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,54 @@
1
1
  {
2
2
  "name": "@hexok/auth-redis",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Redis session store for the @hexok/auth SessionStore port.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "sideEffects": false,
8
+ "keywords": [
9
+ "hexok",
10
+ "auth",
11
+ "session",
12
+ "redis"
13
+ ],
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/crobinson42/hexok.git",
17
+ "directory": "packages/auth-redis"
18
+ },
19
+ "homepage": "https://crobinson42.github.io/hexok/",
20
+ "bugs": {
21
+ "url": "https://github.com/crobinson42/hexok/issues"
22
+ },
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "import": "./dist/index.js",
30
+ "default": "./dist/index.js"
31
+ },
32
+ "./package.json": "./package.json"
33
+ },
34
+ "files": [
35
+ "dist"
36
+ ],
37
+ "scripts": {
38
+ "build": "tsc -b",
39
+ "test": "vitest run",
40
+ "type-check": "tsc --noEmit -p tsconfig.check.json",
41
+ "lint": "biome check .",
42
+ "clean": "rm -rf dist *.tsbuildinfo"
43
+ },
44
+ "dependencies": {
45
+ "@hexok/auth": "^0.1.0",
46
+ "@hexok/ioredis-lifecycle": "^1.0.0",
47
+ "hexok": "^2.2.6",
48
+ "ioredis": "^6"
49
+ },
50
+ "devDependencies": {
51
+ "@hexok/test-redis": "0.0.0",
52
+ "zod": "^4.1.5"
53
+ }
54
+ }