@ractive-ch/tab-mcp 0.0.0-stage → 0.1.0-alpha.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/AI_NOTICE +9 -0
- package/LICENSE +21 -0
- package/address.d.ts +14 -0
- package/config.d.ts +122 -0
- package/exchange.d.ts +69 -0
- package/index.d.ts +12 -0
- package/index.js +3558 -0
- package/index.js.map +25 -0
- package/ip.d.ts +22 -0
- package/log.d.ts +33 -0
- package/main.js +3627 -0
- package/main.js.map +26 -0
- package/oauth/cimd.d.ts +77 -0
- package/oauth/html.d.ts +13 -0
- package/oauth/pairing.d.ts +72 -0
- package/oauth/server.d.ts +68 -0
- package/package.json +41 -5
- package/protocol.d.ts +185 -0
- package/ratelimit.d.ts +21 -0
- package/relay.d.ts +42 -0
- package/session.d.ts +74 -0
- package/store/conformance.d.ts +11 -0
- package/store/memory.d.ts +16 -0
- package/store/sqlite.d.ts +17 -0
- package/store/types.d.ts +89 -0
- package/testing.d.ts +5 -0
- package/testing.js +204 -0
- package/testing.js.map +10 -0
- package/tokens.d.ts +45 -0
- package/README.md +0 -3
package/store/types.d.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The token store (DEC-030, DEC-041): where the relay keeps grants and the
|
|
3
|
+
* hashes of access and refresh tokens. Implementations: `MemoryTokenStore`,
|
|
4
|
+
* `SqliteTokenStore`, or the library user's own (Prisma, Redis, libSQL, …),
|
|
5
|
+
* checked with the conformance suite in `@ractive-ch/tab-mcp/testing`.
|
|
6
|
+
*
|
|
7
|
+
* Guarantees every implementation must give:
|
|
8
|
+
* - Records are keyed by `hash`, the SHA-256 of the token; plain tokens never
|
|
9
|
+
* reach the store.
|
|
10
|
+
* - `put` inserts or replaces the record with the same hash.
|
|
11
|
+
* - `get` returns the record even when it is expired or used; the caller
|
|
12
|
+
* decides. It returns `undefined` after `revoke` or `deleteExpired` removed it.
|
|
13
|
+
* - `markUsed` is atomic: for one hash, exactly one call ever returns `true`,
|
|
14
|
+
* even when several run at the same time.
|
|
15
|
+
* - `revoke` leaves a tombstone at least until `keepUntil`: until then `put` refuses
|
|
16
|
+
* (returns `false`, stores nothing) a record of a revoked grant, or of a
|
|
17
|
+
* grant of a revoked address created at or before the revocation. Checking
|
|
18
|
+
* the tombstone and inserting must be atomic, so tokens minted by a refresh
|
|
19
|
+
* that raced a revocation can never outlive it.
|
|
20
|
+
* - Times are milliseconds since the Unix epoch.
|
|
21
|
+
*/
|
|
22
|
+
export type TokenKind = "access" | "refresh";
|
|
23
|
+
export interface TokenRecord {
|
|
24
|
+
/** SHA-256 of the token, base64url without padding. */
|
|
25
|
+
hash: string;
|
|
26
|
+
kind: TokenKind;
|
|
27
|
+
grantId: string;
|
|
28
|
+
/** The address the grant belongs to (DEC-027). */
|
|
29
|
+
addressId: string;
|
|
30
|
+
/** The OAuth client id (the CIMD URL). */
|
|
31
|
+
clientId: string;
|
|
32
|
+
/** The client's self-declared name, for display only. */
|
|
33
|
+
clientName: string;
|
|
34
|
+
scopes: string[];
|
|
35
|
+
/** When the grant was created (the first token of the grant). */
|
|
36
|
+
grantCreatedAt: number;
|
|
37
|
+
issuedAt: number;
|
|
38
|
+
expiresAt: number;
|
|
39
|
+
/** Refresh tokens: when it was exchanged for a new one. */
|
|
40
|
+
usedAt?: number;
|
|
41
|
+
}
|
|
42
|
+
/** One connected AI app of an address. */
|
|
43
|
+
export interface GrantSummary {
|
|
44
|
+
grantId: string;
|
|
45
|
+
clientId: string;
|
|
46
|
+
clientName: string;
|
|
47
|
+
scopes: string[];
|
|
48
|
+
createdAt: number;
|
|
49
|
+
}
|
|
50
|
+
export type RevokeSelector = {
|
|
51
|
+
grantId: string;
|
|
52
|
+
} | {
|
|
53
|
+
addressId: string;
|
|
54
|
+
};
|
|
55
|
+
export interface RevokeOptions {
|
|
56
|
+
/** When the revocation happens. */
|
|
57
|
+
now: number;
|
|
58
|
+
/** Keep the tombstone until then (the longest a token issued before could live). */
|
|
59
|
+
keepUntil: number;
|
|
60
|
+
}
|
|
61
|
+
export interface TokenStore {
|
|
62
|
+
/**
|
|
63
|
+
* Insert or replace a token record. Returns `false`, and stores nothing,
|
|
64
|
+
* if its grant was revoked (see the tombstone rule above).
|
|
65
|
+
*/
|
|
66
|
+
put(record: TokenRecord): Promise<boolean>;
|
|
67
|
+
/** The record for a token hash, or `undefined`. */
|
|
68
|
+
get(hash: string): Promise<TokenRecord | undefined>;
|
|
69
|
+
/**
|
|
70
|
+
* Mark a token as used at `now`. Returns `true` for the one call that marked
|
|
71
|
+
* it, `false` if it was already used or does not exist. Must be atomic.
|
|
72
|
+
*/
|
|
73
|
+
markUsed(hash: string, now: number): Promise<boolean>;
|
|
74
|
+
/**
|
|
75
|
+
* Delete every token of a grant or of an address and leave a tombstone
|
|
76
|
+
* until `options.keepUntil`. Returns how many tokens were deleted.
|
|
77
|
+
*/
|
|
78
|
+
revoke(selector: RevokeSelector, options: RevokeOptions): Promise<number>;
|
|
79
|
+
/**
|
|
80
|
+
* The grants of an address that still have a usable token at `now` (an
|
|
81
|
+
* unexpired access token or an unexpired, unused refresh token), oldest
|
|
82
|
+
* first.
|
|
83
|
+
*/
|
|
84
|
+
listGrants(addressId: string, now: number): Promise<GrantSummary[]>;
|
|
85
|
+
/** Delete every record with `expiresAt <= now` and tombstones past `keepUntil`. Returns how many records. */
|
|
86
|
+
deleteExpired(now: number): Promise<number>;
|
|
87
|
+
/** Release resources (database handles). Optional. */
|
|
88
|
+
close?(): Promise<void>;
|
|
89
|
+
}
|
package/testing.d.ts
ADDED
package/testing.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/store/conformance.ts
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
4
|
+
function tokenStoreConformance(name, factory) {
|
|
5
|
+
describe(`TokenStore conformance: ${name}`, () => {
|
|
6
|
+
let store;
|
|
7
|
+
const now = 1800000000000;
|
|
8
|
+
let seq = 0;
|
|
9
|
+
const until = { now, keepUntil: now + 86400000 };
|
|
10
|
+
const record = (over = {}) => {
|
|
11
|
+
seq++;
|
|
12
|
+
return {
|
|
13
|
+
hash: `hash-${seq}`,
|
|
14
|
+
kind: "access",
|
|
15
|
+
grantId: "g_1",
|
|
16
|
+
addressId: "addr-a",
|
|
17
|
+
clientId: "https://client.example/cimd",
|
|
18
|
+
clientName: "Client",
|
|
19
|
+
scopes: ["tools"],
|
|
20
|
+
grantCreatedAt: now - 1000,
|
|
21
|
+
issuedAt: now,
|
|
22
|
+
expiresAt: now + 3600000,
|
|
23
|
+
...over
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
beforeEach(async () => {
|
|
27
|
+
store = await factory();
|
|
28
|
+
});
|
|
29
|
+
afterEach(async () => {
|
|
30
|
+
await store.close?.();
|
|
31
|
+
});
|
|
32
|
+
test("put then get returns an equal record", async () => {
|
|
33
|
+
const r = record({ scopes: ["tools", "extra"] });
|
|
34
|
+
expect(await store.put(r)).toBe(true);
|
|
35
|
+
expect(await store.get(r.hash)).toEqual(r);
|
|
36
|
+
});
|
|
37
|
+
test("get of an unknown hash is undefined", async () => {
|
|
38
|
+
expect(await store.get("nope")).toBeUndefined();
|
|
39
|
+
});
|
|
40
|
+
test("put replaces the record with the same hash", async () => {
|
|
41
|
+
const r = record();
|
|
42
|
+
await store.put(r);
|
|
43
|
+
await store.put({ ...r, clientName: "Renamed", expiresAt: now + 5 });
|
|
44
|
+
const got = await store.get(r.hash);
|
|
45
|
+
expect(got?.clientName).toBe("Renamed");
|
|
46
|
+
expect(got?.expiresAt).toBe(now + 5);
|
|
47
|
+
});
|
|
48
|
+
test("get returns expired and used records unchanged", async () => {
|
|
49
|
+
const expired = record({ expiresAt: now - 1 });
|
|
50
|
+
const used = record({ kind: "refresh", usedAt: now - 10 });
|
|
51
|
+
await store.put(expired);
|
|
52
|
+
await store.put(used);
|
|
53
|
+
expect(await store.get(expired.hash)).toEqual(expired);
|
|
54
|
+
expect(await store.get(used.hash)).toEqual(used);
|
|
55
|
+
});
|
|
56
|
+
test("a record without usedAt comes back without the key", async () => {
|
|
57
|
+
const r = record();
|
|
58
|
+
await store.put(r);
|
|
59
|
+
const got = await store.get(r.hash);
|
|
60
|
+
expect(got !== undefined && "usedAt" in got).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
test("markUsed succeeds once, then fails", async () => {
|
|
63
|
+
const r = record({ kind: "refresh" });
|
|
64
|
+
await store.put(r);
|
|
65
|
+
expect(await store.markUsed(r.hash, now + 1)).toBe(true);
|
|
66
|
+
expect(await store.markUsed(r.hash, now + 2)).toBe(false);
|
|
67
|
+
expect((await store.get(r.hash))?.usedAt).toBe(now + 1);
|
|
68
|
+
});
|
|
69
|
+
test("markUsed of an unknown hash fails", async () => {
|
|
70
|
+
expect(await store.markUsed("nope", now)).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
test("concurrent markUsed calls: exactly one wins", async () => {
|
|
73
|
+
const r = record({ kind: "refresh" });
|
|
74
|
+
await store.put(r);
|
|
75
|
+
const results = await Promise.all(Array.from({ length: 10 }, (_, i) => store.markUsed(r.hash, now + i)));
|
|
76
|
+
expect(results.filter(Boolean)).toHaveLength(1);
|
|
77
|
+
});
|
|
78
|
+
test("revoke by grant deletes only that grant's tokens", async () => {
|
|
79
|
+
const a = record({ grantId: "g_a" });
|
|
80
|
+
const a2 = record({ grantId: "g_a", kind: "refresh" });
|
|
81
|
+
const b = record({ grantId: "g_b" });
|
|
82
|
+
for (const r of [a, a2, b])
|
|
83
|
+
await store.put(r);
|
|
84
|
+
expect(await store.revoke({ grantId: "g_a" }, until)).toBe(2);
|
|
85
|
+
expect(await store.get(a.hash)).toBeUndefined();
|
|
86
|
+
expect(await store.get(a2.hash)).toBeUndefined();
|
|
87
|
+
expect(await store.get(b.hash)).toEqual(b);
|
|
88
|
+
});
|
|
89
|
+
test("revoke by address deletes only that address's tokens", async () => {
|
|
90
|
+
const a = record({ addressId: "addr-a", grantId: "g_a" });
|
|
91
|
+
const b = record({ addressId: "addr-b", grantId: "g_b" });
|
|
92
|
+
await store.put(a);
|
|
93
|
+
await store.put(b);
|
|
94
|
+
expect(await store.revoke({ addressId: "addr-a" }, until)).toBe(1);
|
|
95
|
+
expect(await store.get(a.hash)).toBeUndefined();
|
|
96
|
+
expect(await store.get(b.hash)).toEqual(b);
|
|
97
|
+
});
|
|
98
|
+
test("revoke of nothing returns 0", async () => {
|
|
99
|
+
expect(await store.revoke({ grantId: "none" }, until)).toBe(0);
|
|
100
|
+
});
|
|
101
|
+
test("a revoked grant gets no new tokens until its tombstone expires", async () => {
|
|
102
|
+
await store.revoke({ grantId: "g_dead" }, until);
|
|
103
|
+
const late = record({ grantId: "g_dead", kind: "refresh" });
|
|
104
|
+
expect(await store.put(late)).toBe(false);
|
|
105
|
+
expect(await store.get(late.hash)).toBeUndefined();
|
|
106
|
+
expect(await store.put(record({ grantId: "g_alive" }))).toBe(true);
|
|
107
|
+
await store.deleteExpired(until.keepUntil);
|
|
108
|
+
expect(await store.put(record({ grantId: "g_dead" }))).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
test("a revoked address refuses grants created up to the revocation, not later ones", async () => {
|
|
111
|
+
await store.revoke({ addressId: "addr-a" }, until);
|
|
112
|
+
expect(await store.put(record({ grantId: "g_before", grantCreatedAt: now }))).toBe(false);
|
|
113
|
+
expect(await store.put(record({ grantId: "g_after", grantCreatedAt: now + 1 }))).toBe(true);
|
|
114
|
+
expect(await store.put(record({
|
|
115
|
+
grantId: "g_elsewhere",
|
|
116
|
+
addressId: "addr-b",
|
|
117
|
+
grantCreatedAt: now
|
|
118
|
+
}))).toBe(true);
|
|
119
|
+
});
|
|
120
|
+
test("a put racing a revocation of its grant never survives it", async () => {
|
|
121
|
+
for (let i = 0;i < 20; i++) {
|
|
122
|
+
const r = record({ grantId: `g_race_${i}`, kind: "refresh" });
|
|
123
|
+
await Promise.all([
|
|
124
|
+
store.put(r),
|
|
125
|
+
store.revoke({ grantId: r.grantId }, until)
|
|
126
|
+
]);
|
|
127
|
+
expect(await store.get(r.hash)).toBeUndefined();
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
test("deleteExpired removes records with expiresAt <= now only", async () => {
|
|
131
|
+
const gone = record({ expiresAt: now - 1 });
|
|
132
|
+
const edge = record({ expiresAt: now });
|
|
133
|
+
const kept = record({ expiresAt: now + 1 });
|
|
134
|
+
for (const r of [gone, edge, kept])
|
|
135
|
+
await store.put(r);
|
|
136
|
+
expect(await store.deleteExpired(now)).toBe(2);
|
|
137
|
+
expect(await store.get(gone.hash)).toBeUndefined();
|
|
138
|
+
expect(await store.get(edge.hash)).toBeUndefined();
|
|
139
|
+
expect(await store.get(kept.hash)).toEqual(kept);
|
|
140
|
+
});
|
|
141
|
+
test("listGrants groups live tokens of one address by grant", async () => {
|
|
142
|
+
await store.put(record({
|
|
143
|
+
grantId: "g_new",
|
|
144
|
+
grantCreatedAt: now - 10,
|
|
145
|
+
clientName: "New"
|
|
146
|
+
}));
|
|
147
|
+
await store.put(record({
|
|
148
|
+
grantId: "g_old",
|
|
149
|
+
grantCreatedAt: now - 500,
|
|
150
|
+
clientName: "Old",
|
|
151
|
+
scopes: ["tools"]
|
|
152
|
+
}));
|
|
153
|
+
await store.put(record({
|
|
154
|
+
grantId: "g_old",
|
|
155
|
+
kind: "refresh",
|
|
156
|
+
grantCreatedAt: now - 500,
|
|
157
|
+
clientName: "Old"
|
|
158
|
+
}));
|
|
159
|
+
await store.put(record({ grantId: "g_other", addressId: "addr-b" }));
|
|
160
|
+
expect(await store.listGrants("addr-a", now)).toEqual([
|
|
161
|
+
{
|
|
162
|
+
grantId: "g_old",
|
|
163
|
+
clientId: "https://client.example/cimd",
|
|
164
|
+
clientName: "Old",
|
|
165
|
+
scopes: ["tools"],
|
|
166
|
+
createdAt: now - 500
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
grantId: "g_new",
|
|
170
|
+
clientId: "https://client.example/cimd",
|
|
171
|
+
clientName: "New",
|
|
172
|
+
scopes: ["tools"],
|
|
173
|
+
createdAt: now - 10
|
|
174
|
+
}
|
|
175
|
+
]);
|
|
176
|
+
});
|
|
177
|
+
test("listGrants leaves out grants without a usable token", async () => {
|
|
178
|
+
await store.put(record({ grantId: "g_expired", expiresAt: now }));
|
|
179
|
+
await store.put(record({ grantId: "g_used", kind: "refresh", usedAt: now - 1 }));
|
|
180
|
+
await store.put(record({ grantId: "g_live", kind: "refresh", expiresAt: now + 1 }));
|
|
181
|
+
const grants = await store.listGrants("addr-a", now);
|
|
182
|
+
expect(grants.map((g) => g.grantId)).toEqual(["g_live"]);
|
|
183
|
+
});
|
|
184
|
+
test("listGrants of an unknown address is empty", async () => {
|
|
185
|
+
expect(await store.listGrants("nobody", now)).toEqual([]);
|
|
186
|
+
});
|
|
187
|
+
test("changing a returned record does not change the store", async () => {
|
|
188
|
+
const r = record();
|
|
189
|
+
await store.put(r);
|
|
190
|
+
const got = await store.get(r.hash);
|
|
191
|
+
if (got === undefined)
|
|
192
|
+
throw new Error("missing");
|
|
193
|
+
got.scopes.push("mutated");
|
|
194
|
+
got.expiresAt = 0;
|
|
195
|
+
expect(await store.get(r.hash)).toEqual(r);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
export {
|
|
200
|
+
tokenStoreConformance
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
//# debugId=894AE659D8460D9664756E2164756E21
|
|
204
|
+
//# sourceMappingURL=testing.js.map
|
package/testing.js.map
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/store/conformance.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { afterEach, beforeEach, describe, expect, test } from \"bun:test\";\nimport type { TokenRecord, TokenStore } from \"./types.ts\";\n\n/**\n * The conformance suite every `TokenStore` must pass (DEC-041). Call it from a\n * `bun test` file with a factory that returns a fresh, empty store:\n *\n * ```ts\n * import { tokenStoreConformance } from \"@ractive-ch/tab-mcp/testing\";\n * tokenStoreConformance(\"RedisTokenStore\", async () => new RedisTokenStore(…));\n * ```\n */\nexport function tokenStoreConformance(\n\tname: string,\n\tfactory: () => TokenStore | Promise<TokenStore>,\n): void {\n\tdescribe(`TokenStore conformance: ${name}`, () => {\n\t\tlet store: TokenStore;\n\t\tconst now = 1_800_000_000_000;\n\t\tlet seq = 0;\n\n\t\tconst until = { now, keepUntil: now + 86_400_000 };\n\n\t\tconst record = (over: Partial<TokenRecord> = {}): TokenRecord => {\n\t\t\tseq++;\n\t\t\treturn {\n\t\t\t\thash: `hash-${seq}`,\n\t\t\t\tkind: \"access\",\n\t\t\t\tgrantId: \"g_1\",\n\t\t\t\taddressId: \"addr-a\",\n\t\t\t\tclientId: \"https://client.example/cimd\",\n\t\t\t\tclientName: \"Client\",\n\t\t\t\tscopes: [\"tools\"],\n\t\t\t\tgrantCreatedAt: now - 1000,\n\t\t\t\tissuedAt: now,\n\t\t\t\texpiresAt: now + 3_600_000,\n\t\t\t\t...over,\n\t\t\t};\n\t\t};\n\n\t\tbeforeEach(async () => {\n\t\t\tstore = await factory();\n\t\t});\n\n\t\tafterEach(async () => {\n\t\t\tawait store.close?.();\n\t\t});\n\n\t\ttest(\"put then get returns an equal record\", async () => {\n\t\t\tconst r = record({ scopes: [\"tools\", \"extra\"] });\n\t\t\texpect(await store.put(r)).toBe(true);\n\t\t\texpect(await store.get(r.hash)).toEqual(r);\n\t\t});\n\n\t\ttest(\"get of an unknown hash is undefined\", async () => {\n\t\t\texpect(await store.get(\"nope\")).toBeUndefined();\n\t\t});\n\n\t\ttest(\"put replaces the record with the same hash\", async () => {\n\t\t\tconst r = record();\n\t\t\tawait store.put(r);\n\t\t\tawait store.put({ ...r, clientName: \"Renamed\", expiresAt: now + 5 });\n\t\t\tconst got = await store.get(r.hash);\n\t\t\texpect(got?.clientName).toBe(\"Renamed\");\n\t\t\texpect(got?.expiresAt).toBe(now + 5);\n\t\t});\n\n\t\ttest(\"get returns expired and used records unchanged\", async () => {\n\t\t\tconst expired = record({ expiresAt: now - 1 });\n\t\t\tconst used = record({ kind: \"refresh\", usedAt: now - 10 });\n\t\t\tawait store.put(expired);\n\t\t\tawait store.put(used);\n\t\t\texpect(await store.get(expired.hash)).toEqual(expired);\n\t\t\texpect(await store.get(used.hash)).toEqual(used);\n\t\t});\n\n\t\ttest(\"a record without usedAt comes back without the key\", async () => {\n\t\t\tconst r = record();\n\t\t\tawait store.put(r);\n\t\t\tconst got = await store.get(r.hash);\n\t\t\texpect(got !== undefined && \"usedAt\" in got).toBe(false);\n\t\t});\n\n\t\ttest(\"markUsed succeeds once, then fails\", async () => {\n\t\t\tconst r = record({ kind: \"refresh\" });\n\t\t\tawait store.put(r);\n\t\t\texpect(await store.markUsed(r.hash, now + 1)).toBe(true);\n\t\t\texpect(await store.markUsed(r.hash, now + 2)).toBe(false);\n\t\t\texpect((await store.get(r.hash))?.usedAt).toBe(now + 1);\n\t\t});\n\n\t\ttest(\"markUsed of an unknown hash fails\", async () => {\n\t\t\texpect(await store.markUsed(\"nope\", now)).toBe(false);\n\t\t});\n\n\t\ttest(\"concurrent markUsed calls: exactly one wins\", async () => {\n\t\t\tconst r = record({ kind: \"refresh\" });\n\t\t\tawait store.put(r);\n\t\t\tconst results = await Promise.all(\n\t\t\t\tArray.from({ length: 10 }, (_, i) => store.markUsed(r.hash, now + i)),\n\t\t\t);\n\t\t\texpect(results.filter(Boolean)).toHaveLength(1);\n\t\t});\n\n\t\ttest(\"revoke by grant deletes only that grant's tokens\", async () => {\n\t\t\tconst a = record({ grantId: \"g_a\" });\n\t\t\tconst a2 = record({ grantId: \"g_a\", kind: \"refresh\" });\n\t\t\tconst b = record({ grantId: \"g_b\" });\n\t\t\tfor (const r of [a, a2, b]) await store.put(r);\n\t\t\texpect(await store.revoke({ grantId: \"g_a\" }, until)).toBe(2);\n\t\t\texpect(await store.get(a.hash)).toBeUndefined();\n\t\t\texpect(await store.get(a2.hash)).toBeUndefined();\n\t\t\texpect(await store.get(b.hash)).toEqual(b);\n\t\t});\n\n\t\ttest(\"revoke by address deletes only that address's tokens\", async () => {\n\t\t\tconst a = record({ addressId: \"addr-a\", grantId: \"g_a\" });\n\t\t\tconst b = record({ addressId: \"addr-b\", grantId: \"g_b\" });\n\t\t\tawait store.put(a);\n\t\t\tawait store.put(b);\n\t\t\texpect(await store.revoke({ addressId: \"addr-a\" }, until)).toBe(1);\n\t\t\texpect(await store.get(a.hash)).toBeUndefined();\n\t\t\texpect(await store.get(b.hash)).toEqual(b);\n\t\t});\n\n\t\ttest(\"revoke of nothing returns 0\", async () => {\n\t\t\texpect(await store.revoke({ grantId: \"none\" }, until)).toBe(0);\n\t\t});\n\n\t\ttest(\"a revoked grant gets no new tokens until its tombstone expires\", async () => {\n\t\t\tawait store.revoke({ grantId: \"g_dead\" }, until);\n\t\t\tconst late = record({ grantId: \"g_dead\", kind: \"refresh\" });\n\t\t\texpect(await store.put(late)).toBe(false);\n\t\t\texpect(await store.get(late.hash)).toBeUndefined();\n\t\t\texpect(await store.put(record({ grantId: \"g_alive\" }))).toBe(true);\n\t\t\tawait store.deleteExpired(until.keepUntil);\n\t\t\texpect(await store.put(record({ grantId: \"g_dead\" }))).toBe(true);\n\t\t});\n\n\t\ttest(\"a revoked address refuses grants created up to the revocation, not later ones\", async () => {\n\t\t\tawait store.revoke({ addressId: \"addr-a\" }, until);\n\t\t\texpect(\n\t\t\t\tawait store.put(record({ grantId: \"g_before\", grantCreatedAt: now })),\n\t\t\t).toBe(false);\n\t\t\texpect(\n\t\t\t\tawait store.put(\n\t\t\t\t\trecord({ grantId: \"g_after\", grantCreatedAt: now + 1 }),\n\t\t\t\t),\n\t\t\t).toBe(true);\n\t\t\texpect(\n\t\t\t\tawait store.put(\n\t\t\t\t\trecord({\n\t\t\t\t\t\tgrantId: \"g_elsewhere\",\n\t\t\t\t\t\taddressId: \"addr-b\",\n\t\t\t\t\t\tgrantCreatedAt: now,\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t).toBe(true);\n\t\t});\n\n\t\ttest(\"a put racing a revocation of its grant never survives it\", async () => {\n\t\t\tfor (let i = 0; i < 20; i++) {\n\t\t\t\tconst r = record({ grantId: `g_race_${i}`, kind: \"refresh\" });\n\t\t\t\tawait Promise.all([\n\t\t\t\t\tstore.put(r),\n\t\t\t\t\tstore.revoke({ grantId: r.grantId }, until),\n\t\t\t\t]);\n\t\t\t\texpect(await store.get(r.hash)).toBeUndefined();\n\t\t\t}\n\t\t});\n\n\t\ttest(\"deleteExpired removes records with expiresAt <= now only\", async () => {\n\t\t\tconst gone = record({ expiresAt: now - 1 });\n\t\t\tconst edge = record({ expiresAt: now });\n\t\t\tconst kept = record({ expiresAt: now + 1 });\n\t\t\tfor (const r of [gone, edge, kept]) await store.put(r);\n\t\t\texpect(await store.deleteExpired(now)).toBe(2);\n\t\t\texpect(await store.get(gone.hash)).toBeUndefined();\n\t\t\texpect(await store.get(edge.hash)).toBeUndefined();\n\t\t\texpect(await store.get(kept.hash)).toEqual(kept);\n\t\t});\n\n\t\ttest(\"listGrants groups live tokens of one address by grant\", async () => {\n\t\t\tawait store.put(\n\t\t\t\trecord({\n\t\t\t\t\tgrantId: \"g_new\",\n\t\t\t\t\tgrantCreatedAt: now - 10,\n\t\t\t\t\tclientName: \"New\",\n\t\t\t\t}),\n\t\t\t);\n\t\t\tawait store.put(\n\t\t\t\trecord({\n\t\t\t\t\tgrantId: \"g_old\",\n\t\t\t\t\tgrantCreatedAt: now - 500,\n\t\t\t\t\tclientName: \"Old\",\n\t\t\t\t\tscopes: [\"tools\"],\n\t\t\t\t}),\n\t\t\t);\n\t\t\tawait store.put(\n\t\t\t\trecord({\n\t\t\t\t\tgrantId: \"g_old\",\n\t\t\t\t\tkind: \"refresh\",\n\t\t\t\t\tgrantCreatedAt: now - 500,\n\t\t\t\t\tclientName: \"Old\",\n\t\t\t\t}),\n\t\t\t);\n\t\t\tawait store.put(record({ grantId: \"g_other\", addressId: \"addr-b\" }));\n\t\t\texpect(await store.listGrants(\"addr-a\", now)).toEqual([\n\t\t\t\t{\n\t\t\t\t\tgrantId: \"g_old\",\n\t\t\t\t\tclientId: \"https://client.example/cimd\",\n\t\t\t\t\tclientName: \"Old\",\n\t\t\t\t\tscopes: [\"tools\"],\n\t\t\t\t\tcreatedAt: now - 500,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tgrantId: \"g_new\",\n\t\t\t\t\tclientId: \"https://client.example/cimd\",\n\t\t\t\t\tclientName: \"New\",\n\t\t\t\t\tscopes: [\"tools\"],\n\t\t\t\t\tcreatedAt: now - 10,\n\t\t\t\t},\n\t\t\t]);\n\t\t});\n\n\t\ttest(\"listGrants leaves out grants without a usable token\", async () => {\n\t\t\tawait store.put(record({ grantId: \"g_expired\", expiresAt: now }));\n\t\t\tawait store.put(\n\t\t\t\trecord({ grantId: \"g_used\", kind: \"refresh\", usedAt: now - 1 }),\n\t\t\t);\n\t\t\tawait store.put(\n\t\t\t\trecord({ grantId: \"g_live\", kind: \"refresh\", expiresAt: now + 1 }),\n\t\t\t);\n\t\t\tconst grants = await store.listGrants(\"addr-a\", now);\n\t\t\texpect(grants.map((g) => g.grantId)).toEqual([\"g_live\"]);\n\t\t});\n\n\t\ttest(\"listGrants of an unknown address is empty\", async () => {\n\t\t\texpect(await store.listGrants(\"nobody\", now)).toEqual([]);\n\t\t});\n\n\t\ttest(\"changing a returned record does not change the store\", async () => {\n\t\t\tconst r = record();\n\t\t\tawait store.put(r);\n\t\t\tconst got = await store.get(r.hash);\n\t\t\tif (got === undefined) throw new Error(\"missing\");\n\t\t\tgot.scopes.push(\"mutated\");\n\t\t\tgot.expiresAt = 0;\n\t\t\texpect(await store.get(r.hash)).toEqual(r);\n\t\t});\n\t});\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "894AE659D8460D9664756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
package/tokens.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { TokenKind, TokenRecord, TokenStore } from "./store/types.js";
|
|
2
|
+
/** A new opaque token with 256 bits of randomness. */
|
|
3
|
+
export declare function newToken(kind: TokenKind): string;
|
|
4
|
+
/** A random identifier with a readable prefix, e.g. `g_…`, `s_…`. */
|
|
5
|
+
export declare function newId(prefix: string): string;
|
|
6
|
+
/** SHA-256 of a token, base64url without padding: the store key. */
|
|
7
|
+
export declare function hashToken(token: string): string;
|
|
8
|
+
/** The parts of a grant needed to issue tokens for it. */
|
|
9
|
+
export interface GrantInput {
|
|
10
|
+
grantId?: string;
|
|
11
|
+
addressId: string;
|
|
12
|
+
clientId: string;
|
|
13
|
+
clientName: string;
|
|
14
|
+
scopes?: string[];
|
|
15
|
+
grantCreatedAt?: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Issue an access token for a grant and store its hash, without OAuth. An
|
|
19
|
+
* internal helper for tests that need a token but not the flow; the token
|
|
20
|
+
* endpoint issues through `issueTokenPair`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function issueAccessToken(store: TokenStore, grant: GrantInput, options: {
|
|
23
|
+
ttlMs: number;
|
|
24
|
+
now?: number;
|
|
25
|
+
}): Promise<{
|
|
26
|
+
token: string;
|
|
27
|
+
record: TokenRecord;
|
|
28
|
+
}>;
|
|
29
|
+
/**
|
|
30
|
+
* A new access token and refresh token for a grant (DEC-029). Neither lives
|
|
31
|
+
* beyond `grantEndsAt` (DEC-062). Returns `undefined` if the store refuses
|
|
32
|
+
* them because the grant was revoked meanwhile (its tombstone, DEC-063).
|
|
33
|
+
*/
|
|
34
|
+
export declare function issueTokenPair(store: TokenStore, grant: Required<Omit<GrantInput, "scopes">> & {
|
|
35
|
+
scopes: string[];
|
|
36
|
+
}, options: {
|
|
37
|
+
accessTtlMs: number;
|
|
38
|
+
refreshTtlMs: number;
|
|
39
|
+
grantEndsAt: number;
|
|
40
|
+
now?: number;
|
|
41
|
+
}): Promise<{
|
|
42
|
+
accessToken: string;
|
|
43
|
+
refreshToken: string;
|
|
44
|
+
expiresIn: number;
|
|
45
|
+
} | undefined>;
|
package/README.md
DELETED