@flytedesk/app-kit 0.5.0 → 0.7.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/dist/auth/client/httpClient.d.ts +85 -0
- package/dist/auth/client/httpClient.js +218 -0
- package/dist/auth/client/httpClient.js.map +1 -0
- package/dist/auth/client/index.d.ts +9 -0
- package/dist/auth/client/index.js +9 -0
- package/dist/auth/client/index.js.map +1 -0
- package/dist/auth/client/useAuthSession.d.ts +25 -0
- package/dist/auth/client/useAuthSession.js +97 -0
- package/dist/auth/client/useAuthSession.js.map +1 -0
- package/dist/auth/errors.d.ts +47 -0
- package/dist/auth/errors.js +75 -0
- package/dist/auth/errors.js.map +1 -0
- package/dist/auth/index.d.ts +3 -1
- package/dist/auth/index.js +1 -0
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/oidc-client.d.ts +3 -14
- package/dist/auth/oidc-client.js +5 -4
- package/dist/auth/oidc-client.js.map +1 -1
- package/dist/auth/plugin.js +149 -28
- package/dist/auth/plugin.js.map +1 -1
- package/dist/auth/shared-types.d.ts +49 -0
- package/dist/auth/shared-types.js +11 -0
- package/dist/auth/shared-types.js.map +1 -0
- package/dist/auth/testing/fake-idp.d.ts +98 -0
- package/dist/auth/testing/fake-idp.js +336 -0
- package/dist/auth/testing/fake-idp.js.map +1 -0
- package/dist/auth/testing/index.d.ts +5 -0
- package/dist/auth/testing/index.js +6 -0
- package/dist/auth/testing/index.js.map +1 -0
- package/dist/auth/types.d.ts +6 -18
- package/package.json +20 -2
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
3
|
+
import { generateKeyPair, exportJWK, SignJWT } from "jose";
|
|
4
|
+
export class FakeIdp {
|
|
5
|
+
server;
|
|
6
|
+
baseUrl = "";
|
|
7
|
+
clientId;
|
|
8
|
+
appId;
|
|
9
|
+
identities;
|
|
10
|
+
nextIdentityKey;
|
|
11
|
+
// Whether this browser/client still holds a live flytedesk-id end-user session cookie
|
|
12
|
+
// — real oidc-provider's `prompt=none` handling (OIDC Core 3.1.2.1) answers straight
|
|
13
|
+
// from that cookie with no interaction at all: a fresh code if it's live,
|
|
14
|
+
// `error=login_required` back to redirect_uri if it's not. Defaults to true.
|
|
15
|
+
idpSessionLive = true;
|
|
16
|
+
accessTokenTtlSeconds;
|
|
17
|
+
// Test-control: forces every subsequent refresh_token grant to fail — see
|
|
18
|
+
// setForceRefreshFailure's doc comment.
|
|
19
|
+
forceRefreshFailure = false;
|
|
20
|
+
keys;
|
|
21
|
+
publicJwk;
|
|
22
|
+
kid = "fake-idp-key";
|
|
23
|
+
codes = new Map();
|
|
24
|
+
grants = new Map();
|
|
25
|
+
constructor(opts) {
|
|
26
|
+
this.clientId = opts.clientId;
|
|
27
|
+
this.appId = opts.appId ?? opts.clientId;
|
|
28
|
+
this.identities = opts.identities;
|
|
29
|
+
this.accessTokenTtlSeconds = opts.accessTokenTtlSeconds ?? 900;
|
|
30
|
+
if (!(opts.defaultIdentityKey in this.identities)) {
|
|
31
|
+
throw new Error(`FakeIdp: defaultIdentityKey "${opts.defaultIdentityKey}" is not in identities`);
|
|
32
|
+
}
|
|
33
|
+
this.nextIdentityKey = opts.defaultIdentityKey;
|
|
34
|
+
}
|
|
35
|
+
/** Pick the identity the NEXT `/auth` grants. */
|
|
36
|
+
setNextIdentity(key) {
|
|
37
|
+
if (!(key in this.identities)) {
|
|
38
|
+
throw new Error(`FakeIdp: no identity registered for "${key}"`);
|
|
39
|
+
}
|
|
40
|
+
this.nextIdentityKey = key;
|
|
41
|
+
}
|
|
42
|
+
/** See `idpSessionLive`'s doc comment. */
|
|
43
|
+
setIdpSessionLive(live) {
|
|
44
|
+
this.idpSessionLive = live;
|
|
45
|
+
}
|
|
46
|
+
/** Forces every subsequent `grant_type=refresh_token` call to fail with
|
|
47
|
+
* `invalid_grant`, exactly as real flytedesk-id would for a revoked/expired refresh
|
|
48
|
+
* token — lets a consumer's test exercise its refresh-failure handling (AK-16;
|
|
49
|
+
* `@flytedesk/app-kit/auth`'s `loadAuthedUser`/`getLiveIdpAccessToken` force
|
|
50
|
+
* re-login on this) without waiting on a real expiry or needing a separate
|
|
51
|
+
* `/revoke` call. */
|
|
52
|
+
setForceRefreshFailure(force) {
|
|
53
|
+
this.forceRefreshFailure = force;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Mutates a registered identity's roles/permissions/apps IN PLACE. Every live grant
|
|
57
|
+
* already holds a reference to this identity object (this fixture keeps identities by
|
|
58
|
+
* reference, never a copy — see the file doc comment), so the change is visible on
|
|
59
|
+
* that identity's very next request, with NO new login required. Lets a consumer's
|
|
60
|
+
* test simulate flytedesk-id revoking a role or changing an app's catalog entry mid-
|
|
61
|
+
* session (AK-16), which the plain "log in as a different identity" flow can't do.
|
|
62
|
+
*/
|
|
63
|
+
updateIdentity(key, patch) {
|
|
64
|
+
const identity = this.identities[key];
|
|
65
|
+
if (!identity) {
|
|
66
|
+
throw new Error(`FakeIdp: no identity registered for "${key}"`);
|
|
67
|
+
}
|
|
68
|
+
Object.assign(identity, patch);
|
|
69
|
+
}
|
|
70
|
+
get url() {
|
|
71
|
+
if (!this.baseUrl)
|
|
72
|
+
throw new Error("FakeIdp not started");
|
|
73
|
+
return this.baseUrl;
|
|
74
|
+
}
|
|
75
|
+
async start(port = 0) {
|
|
76
|
+
this.keys = await generateKeyPair("RS256", { extractable: true });
|
|
77
|
+
this.publicJwk = {
|
|
78
|
+
...(await exportJWK(this.keys.publicKey)),
|
|
79
|
+
kid: this.kid,
|
|
80
|
+
use: "sig",
|
|
81
|
+
alg: "RS256",
|
|
82
|
+
};
|
|
83
|
+
this.server = createServer((req, res) => {
|
|
84
|
+
this.handle(req, res).catch((err) => {
|
|
85
|
+
res.writeHead(500, { "content-type": "application/json" });
|
|
86
|
+
res.end(JSON.stringify({ error: "fake_idp_error", detail: String(err) }));
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
await new Promise((resolve) => this.server.listen(port, "127.0.0.1", resolve));
|
|
90
|
+
const addr = this.server.address();
|
|
91
|
+
if (!addr || typeof addr === "string")
|
|
92
|
+
throw new Error("FakeIdp failed to bind a port");
|
|
93
|
+
this.baseUrl = `http://127.0.0.1:${addr.port}`;
|
|
94
|
+
return this.baseUrl;
|
|
95
|
+
}
|
|
96
|
+
async stop() {
|
|
97
|
+
if (this.server)
|
|
98
|
+
await new Promise((resolve) => this.server.close(() => resolve()));
|
|
99
|
+
this.server = undefined;
|
|
100
|
+
}
|
|
101
|
+
json(res, status, body) {
|
|
102
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
103
|
+
res.end(JSON.stringify(body));
|
|
104
|
+
}
|
|
105
|
+
async readForm(req) {
|
|
106
|
+
const chunks = [];
|
|
107
|
+
for await (const c of req)
|
|
108
|
+
chunks.push(c);
|
|
109
|
+
return new URLSearchParams(Buffer.concat(chunks).toString("utf8"));
|
|
110
|
+
}
|
|
111
|
+
bearer(req) {
|
|
112
|
+
const h = req.headers.authorization;
|
|
113
|
+
return h?.startsWith("Bearer ") ? h.slice(7) : null;
|
|
114
|
+
}
|
|
115
|
+
async signIdToken(identity) {
|
|
116
|
+
return new SignJWT({})
|
|
117
|
+
.setProtectedHeader({ alg: "RS256", kid: this.kid })
|
|
118
|
+
.setIssuer(this.baseUrl)
|
|
119
|
+
.setAudience(this.clientId)
|
|
120
|
+
.setSubject(identity.sub)
|
|
121
|
+
.setIssuedAt()
|
|
122
|
+
.setExpirationTime("15m")
|
|
123
|
+
.sign(this.keys.privateKey);
|
|
124
|
+
}
|
|
125
|
+
issueGrant(identity) {
|
|
126
|
+
const accessToken = `at-${randomBytes(24).toString("base64url")}`;
|
|
127
|
+
const refreshToken = `rt-${randomBytes(24).toString("base64url")}`;
|
|
128
|
+
this.grants.set(accessToken, { identity });
|
|
129
|
+
this.grants.set(refreshToken, { identity });
|
|
130
|
+
return { accessToken, refreshToken };
|
|
131
|
+
}
|
|
132
|
+
async handle(req, res) {
|
|
133
|
+
const u = new URL(req.url ?? "/", this.baseUrl);
|
|
134
|
+
const path = u.pathname;
|
|
135
|
+
if (req.method === "GET" && path === "/.well-known/openid-configuration") {
|
|
136
|
+
return this.json(res, 200, {
|
|
137
|
+
issuer: this.baseUrl,
|
|
138
|
+
authorization_endpoint: `${this.baseUrl}/auth`,
|
|
139
|
+
token_endpoint: `${this.baseUrl}/token`,
|
|
140
|
+
jwks_uri: `${this.baseUrl}/jwks`,
|
|
141
|
+
userinfo_endpoint: `${this.baseUrl}/me`,
|
|
142
|
+
// No revocation_endpoint (AK-16): real flytedesk-id's oidc-provider config
|
|
143
|
+
// (src/lib/oidcProvider.ts) never enables `features.revocation`, so its own
|
|
144
|
+
// discovery document never advertises one either — this fixture must match
|
|
145
|
+
// that exactly, or a test against it would pass while the real deployment
|
|
146
|
+
// silently no-ops the same code path (see oidc-client.ts's revokeIdpToken,
|
|
147
|
+
// which is a deliberate no-op whenever revocation_endpoint is absent). The
|
|
148
|
+
// POST /revoke route below is a separate, fixed test-control mechanism, not an
|
|
149
|
+
// OIDC endpoint this fixture ever claims to have.
|
|
150
|
+
response_types_supported: ["code"],
|
|
151
|
+
id_token_signing_alg_values_supported: ["RS256"],
|
|
152
|
+
code_challenge_methods_supported: ["S256"],
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
if (req.method === "GET" && path === "/jwks") {
|
|
156
|
+
return this.json(res, 200, { keys: [this.publicJwk] });
|
|
157
|
+
}
|
|
158
|
+
// Authorize: auto-grant the currently-selected identity, redirect back with a code —
|
|
159
|
+
// UNLESS this is a `prompt=none` silent request and idpSessionLive has been turned
|
|
160
|
+
// off, in which case real oidc-provider answers `error=login_required` with no
|
|
161
|
+
// interaction UI at all, and this fake mirrors that exactly.
|
|
162
|
+
if (req.method === "GET" && path === "/auth") {
|
|
163
|
+
const redirectUri = u.searchParams.get("redirect_uri");
|
|
164
|
+
const state = u.searchParams.get("state") ?? "";
|
|
165
|
+
const codeChallenge = u.searchParams.get("code_challenge") ?? "";
|
|
166
|
+
const prompt = u.searchParams.get("prompt");
|
|
167
|
+
if (!redirectUri)
|
|
168
|
+
return this.json(res, 400, { error: "missing redirect_uri" });
|
|
169
|
+
const loc = new URL(redirectUri);
|
|
170
|
+
if (prompt === "none" && !this.idpSessionLive) {
|
|
171
|
+
loc.searchParams.set("error", "login_required");
|
|
172
|
+
loc.searchParams.set("state", state);
|
|
173
|
+
res.writeHead(302, { location: loc.toString() });
|
|
174
|
+
return void res.end();
|
|
175
|
+
}
|
|
176
|
+
const code = `code-${randomBytes(18).toString("base64url")}`;
|
|
177
|
+
this.codes.set(code, { identity: this.identities[this.nextIdentityKey], codeChallenge });
|
|
178
|
+
loc.searchParams.set("code", code);
|
|
179
|
+
loc.searchParams.set("state", state);
|
|
180
|
+
res.writeHead(302, { location: loc.toString() });
|
|
181
|
+
return void res.end();
|
|
182
|
+
}
|
|
183
|
+
if (req.method === "POST" && path === "/token") {
|
|
184
|
+
const form = await this.readForm(req);
|
|
185
|
+
const grantType = form.get("grant_type");
|
|
186
|
+
if (grantType === "authorization_code") {
|
|
187
|
+
const code = form.get("code") ?? "";
|
|
188
|
+
const verifier = form.get("code_verifier") ?? "";
|
|
189
|
+
const entry = this.codes.get(code);
|
|
190
|
+
if (!entry)
|
|
191
|
+
return this.json(res, 400, { error: "invalid_grant" });
|
|
192
|
+
this.codes.delete(code);
|
|
193
|
+
// PKCE S256: base64url(sha256(verifier)) must equal the stored challenge.
|
|
194
|
+
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
195
|
+
if (entry.codeChallenge && challenge !== entry.codeChallenge) {
|
|
196
|
+
return this.json(res, 400, { error: "invalid_grant", detail: "pkce_mismatch" });
|
|
197
|
+
}
|
|
198
|
+
const { accessToken, refreshToken } = this.issueGrant(entry.identity);
|
|
199
|
+
return this.json(res, 200, {
|
|
200
|
+
access_token: accessToken,
|
|
201
|
+
id_token: await this.signIdToken(entry.identity),
|
|
202
|
+
refresh_token: refreshToken,
|
|
203
|
+
token_type: "Bearer",
|
|
204
|
+
expires_in: this.accessTokenTtlSeconds,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
if (grantType === "refresh_token") {
|
|
208
|
+
// setForceRefreshFailure (AK-16): simulates flytedesk-id rejecting/expiring the
|
|
209
|
+
// refresh token, without needing a real expiry or a separate /revoke call.
|
|
210
|
+
if (this.forceRefreshFailure) {
|
|
211
|
+
return this.json(res, 400, { error: "invalid_grant", detail: "forced_refresh_failure" });
|
|
212
|
+
}
|
|
213
|
+
const rt = form.get("refresh_token") ?? "";
|
|
214
|
+
const grant = this.grants.get(rt);
|
|
215
|
+
if (!grant)
|
|
216
|
+
return this.json(res, 400, { error: "invalid_grant" });
|
|
217
|
+
// rotateRefreshToken: true, matching real flytedesk-id (oidcProvider.ts) —
|
|
218
|
+
// issue a fresh pair for the same identity, and invalidate the token that was
|
|
219
|
+
// just used: a real oidc-provider marks a rotated refresh token consumed the
|
|
220
|
+
// instant it's used, so presenting it again is a replay, not a valid refresh
|
|
221
|
+
// (AK-16 — this fixture previously left the old token live in `grants`
|
|
222
|
+
// forever, which would have let a test pass against behaviour no real
|
|
223
|
+
// flytedesk-id token actually has).
|
|
224
|
+
this.grants.delete(rt);
|
|
225
|
+
const { accessToken, refreshToken } = this.issueGrant(grant.identity);
|
|
226
|
+
return this.json(res, 200, {
|
|
227
|
+
access_token: accessToken,
|
|
228
|
+
id_token: await this.signIdToken(grant.identity),
|
|
229
|
+
refresh_token: refreshToken,
|
|
230
|
+
token_type: "Bearer",
|
|
231
|
+
expires_in: this.accessTokenTtlSeconds,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
return this.json(res, 400, { error: "unsupported_grant_type" });
|
|
235
|
+
}
|
|
236
|
+
if (req.method === "GET" && path === "/me") {
|
|
237
|
+
const grant = this.tokenGrant(req);
|
|
238
|
+
if (!grant)
|
|
239
|
+
return this.json(res, 401, { error: "invalid_token" });
|
|
240
|
+
return this.json(res, 200, {
|
|
241
|
+
sub: grant.identity.sub,
|
|
242
|
+
email: grant.identity.email,
|
|
243
|
+
email_verified: true,
|
|
244
|
+
name: grant.identity.name,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
if (req.method === "GET" && path === `/apps/${this.appId}/authorization`) {
|
|
248
|
+
const grant = this.tokenGrant(req);
|
|
249
|
+
if (!grant)
|
|
250
|
+
return this.json(res, 401, { error: "invalid_token" });
|
|
251
|
+
return this.json(res, 200, {
|
|
252
|
+
userId: grant.identity.sub,
|
|
253
|
+
appId: this.appId,
|
|
254
|
+
roles: grant.identity.roles,
|
|
255
|
+
// App-prefixed, exactly as real flytedesk-id returns (the consuming app's
|
|
256
|
+
// flytedeskAuth plugin strips it back off — see plugin.ts's loadAuthedUser).
|
|
257
|
+
permissions: grant.identity.permissions.map((p) => `${this.appId}:${p}`),
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
if (req.method === "GET" && path === "/me/apps") {
|
|
261
|
+
const grant = this.tokenGrant(req);
|
|
262
|
+
if (!grant)
|
|
263
|
+
return this.json(res, 401, { error: "invalid_token" });
|
|
264
|
+
return this.json(res, 200, { apps: grant.identity.apps ?? [] });
|
|
265
|
+
}
|
|
266
|
+
if (req.method === "POST" && path === "/revoke") {
|
|
267
|
+
const form = await this.readForm(req);
|
|
268
|
+
this.grants.delete(form.get("token") ?? "");
|
|
269
|
+
return this.json(res, 200, {});
|
|
270
|
+
}
|
|
271
|
+
// Test-control (out-of-band, not part of OIDC): pick the identity the next /auth
|
|
272
|
+
// grants. The in-process setNextIdentity() covers a Vitest-style harness; this HTTP
|
|
273
|
+
// hook lets a Playwright spec (a separate process from the server under test) choose
|
|
274
|
+
// the persona cross-process.
|
|
275
|
+
if (req.method === "POST" && path === "/__set-identity") {
|
|
276
|
+
const form = await this.readForm(req);
|
|
277
|
+
const key = form.get("key") ?? "";
|
|
278
|
+
if (!(key in this.identities)) {
|
|
279
|
+
return this.json(res, 400, { error: "unknown_identity", key });
|
|
280
|
+
}
|
|
281
|
+
this.setNextIdentity(key);
|
|
282
|
+
return this.json(res, 200, { key });
|
|
283
|
+
}
|
|
284
|
+
// Test-control: flip whether this fake IdP currently has a live end-user session
|
|
285
|
+
// (see idpSessionLive's doc comment) — same cross-process HTTP-hook pattern as
|
|
286
|
+
// /__set-identity.
|
|
287
|
+
if (req.method === "POST" && path === "/__set-idp-session") {
|
|
288
|
+
const form = await this.readForm(req);
|
|
289
|
+
const live = form.get("live");
|
|
290
|
+
if (live !== "true" && live !== "false") {
|
|
291
|
+
return this.json(res, 400, { error: "invalid_live", live });
|
|
292
|
+
}
|
|
293
|
+
this.setIdpSessionLive(live === "true");
|
|
294
|
+
return this.json(res, 200, { live: this.idpSessionLive });
|
|
295
|
+
}
|
|
296
|
+
// Test-control: cross-process form of setForceRefreshFailure — see its doc comment.
|
|
297
|
+
if (req.method === "POST" && path === "/__set-force-refresh-failure") {
|
|
298
|
+
const form = await this.readForm(req);
|
|
299
|
+
const force = form.get("force");
|
|
300
|
+
if (force !== "true" && force !== "false") {
|
|
301
|
+
return this.json(res, 400, { error: "invalid_force", force });
|
|
302
|
+
}
|
|
303
|
+
this.setForceRefreshFailure(force === "true");
|
|
304
|
+
return this.json(res, 200, { force: this.forceRefreshFailure });
|
|
305
|
+
}
|
|
306
|
+
// Test-control: cross-process form of updateIdentity — see its doc comment. `patch`
|
|
307
|
+
// is a JSON-encoded Partial<FakeIdpIdentity> (minus `sub`), form-encoded like every
|
|
308
|
+
// other test-control hook here rather than a JSON request body, for the same
|
|
309
|
+
// reason /__set-identity and /__set-idp-session are: one readForm() implementation
|
|
310
|
+
// for every hook.
|
|
311
|
+
if (req.method === "POST" && path === "/__update-identity") {
|
|
312
|
+
const form = await this.readForm(req);
|
|
313
|
+
const key = form.get("key") ?? "";
|
|
314
|
+
let patch;
|
|
315
|
+
try {
|
|
316
|
+
patch = JSON.parse(form.get("patch") ?? "{}");
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
return this.json(res, 400, { error: "invalid_patch" });
|
|
320
|
+
}
|
|
321
|
+
try {
|
|
322
|
+
this.updateIdentity(key, patch);
|
|
323
|
+
}
|
|
324
|
+
catch (err) {
|
|
325
|
+
return this.json(res, 400, { error: "unknown_identity", detail: String(err) });
|
|
326
|
+
}
|
|
327
|
+
return this.json(res, 200, { key });
|
|
328
|
+
}
|
|
329
|
+
return this.json(res, 404, { error: "not_found", path });
|
|
330
|
+
}
|
|
331
|
+
tokenGrant(req) {
|
|
332
|
+
const token = this.bearer(req);
|
|
333
|
+
return token ? (this.grants.get(token) ?? null) : null;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
//# sourceMappingURL=fake-idp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fake-idp.js","sourceRoot":"","sources":["../../../src/auth/testing/fake-idp.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/auth/testing/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,OAAO,EAA6C,MAAM,eAAe,CAAC"}
|
package/dist/auth/types.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { FastifyRequest } from "fastify";
|
|
2
|
+
import type { AuthedUserDto, IdpRole } from "./shared-types.js";
|
|
3
|
+
export type { IdpRole };
|
|
2
4
|
/**
|
|
3
5
|
* Profile claims returned by flytedesk-id's userinfo endpoint (`GET {issuer}/me`).
|
|
4
6
|
* Deliberately does NOT include roles/permissions — those come from flytedesk-id's
|
|
@@ -13,24 +15,10 @@ export interface UserinfoClaims {
|
|
|
13
15
|
emailVerified: boolean;
|
|
14
16
|
name?: string;
|
|
15
17
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
/** What ends up on `request.authUser` for every authenticated request. */
|
|
22
|
-
export interface AuthedUser {
|
|
23
|
-
id: string;
|
|
24
|
-
email: string;
|
|
25
|
-
name: string;
|
|
26
|
-
roles: IdpRole[];
|
|
27
|
-
/** Already stripped of the `${appId}:` prefix flytedesk-id's catalog uses to keep
|
|
28
|
-
* permission keys collision-free across apps — see loadAuthedUser in ./plugin.ts. */
|
|
29
|
-
permissions: string[];
|
|
30
|
-
/** The IdpSession row id backing this session — the same value AuthStore rows key
|
|
31
|
-
* refresh tokens to via `idpSessionId`. */
|
|
32
|
-
sessionId: string;
|
|
33
|
-
}
|
|
18
|
+
/** What ends up on `request.authUser` for every authenticated request — the identical
|
|
19
|
+
* shape `GET {api}/me` returns to the browser, see ./shared-types.js's AuthedUserDto
|
|
20
|
+
* doc comment for why this is a single definition rather than two. */
|
|
21
|
+
export type AuthedUser = AuthedUserDto;
|
|
34
22
|
export interface RefreshTokenRow {
|
|
35
23
|
id: string;
|
|
36
24
|
/** Shared by every refresh token descended from the same login, via rotation.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flytedesk/app-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Shared platform kit for flytedesk apps: flytedesk-id auth (BFF/OIDC client) and a Postgres-native trace/audit layer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -37,6 +37,14 @@
|
|
|
37
37
|
"types": "./dist/auth/index.d.ts",
|
|
38
38
|
"import": "./dist/auth/index.js"
|
|
39
39
|
},
|
|
40
|
+
"./auth/testing": {
|
|
41
|
+
"types": "./dist/auth/testing/index.d.ts",
|
|
42
|
+
"import": "./dist/auth/testing/index.js"
|
|
43
|
+
},
|
|
44
|
+
"./auth/client": {
|
|
45
|
+
"types": "./dist/auth/client/index.d.ts",
|
|
46
|
+
"import": "./dist/auth/client/index.js"
|
|
47
|
+
},
|
|
40
48
|
"./trace": {
|
|
41
49
|
"types": "./dist/trace/index.d.ts",
|
|
42
50
|
"import": "./dist/trace/index.js"
|
|
@@ -79,11 +87,15 @@
|
|
|
79
87
|
},
|
|
80
88
|
"peerDependencies": {
|
|
81
89
|
"@prisma/client": "^7",
|
|
82
|
-
"fastify": "^4 || ^5"
|
|
90
|
+
"fastify": "^4 || ^5",
|
|
91
|
+
"react": "^18 || ^19"
|
|
83
92
|
},
|
|
84
93
|
"peerDependenciesMeta": {
|
|
85
94
|
"@prisma/client": {
|
|
86
95
|
"optional": true
|
|
96
|
+
},
|
|
97
|
+
"react": {
|
|
98
|
+
"optional": true
|
|
87
99
|
}
|
|
88
100
|
},
|
|
89
101
|
"overrides": {
|
|
@@ -97,9 +109,12 @@
|
|
|
97
109
|
"@changesets/cli": "^3.0.1",
|
|
98
110
|
"@prisma/client": "^7.10.0",
|
|
99
111
|
"@testcontainers/postgresql": "^12.1.0",
|
|
112
|
+
"@testing-library/react": "^16.3.3",
|
|
100
113
|
"@types/cross-spawn": "^6.0.6",
|
|
101
114
|
"@types/node": "^24.0.0",
|
|
102
115
|
"@types/pg": "^8.23.1",
|
|
116
|
+
"@types/react": "^19.3.0",
|
|
117
|
+
"@types/react-dom": "^19.3.0",
|
|
103
118
|
"@typescript/typescript6": "^6.0.2",
|
|
104
119
|
"@vitest/coverage-v8": "^4.1.11",
|
|
105
120
|
"eslint": "^10.9.1",
|
|
@@ -107,9 +122,12 @@
|
|
|
107
122
|
"eslint-plugin-n": "^18.3.0",
|
|
108
123
|
"fastify": "^5.12.0",
|
|
109
124
|
"globals": "^17.12.0",
|
|
125
|
+
"jsdom": "^30.1.1",
|
|
110
126
|
"pg": "^8.23.0",
|
|
111
127
|
"prettier": "^3.9.6",
|
|
112
128
|
"publint": "^0.3.24",
|
|
129
|
+
"react": "^19.3.0",
|
|
130
|
+
"react-dom": "^19.3.0",
|
|
113
131
|
"testcontainers": "^12.1.0",
|
|
114
132
|
"typescript": "^7.0.2",
|
|
115
133
|
"typescript-eslint": "^8.69.0",
|