@fluixi/oauth2 1.0.0-alpha.1 → 1.0.0-alpha.10
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/{browser-ONMHX3Z3.mjs → browser-36FHB6KG.mjs} +2 -2
- package/dist/browser.cjs +4 -2
- package/dist/browser.mjs +4 -2
- package/dist/callback.cjs +6 -5
- package/dist/callback.mjs +1 -0
- package/dist/{chunk-4NSOGPLM.mjs → chunk-F52RUVDM.mjs} +1 -1
- package/dist/{chunk-25WE6UWI.mjs → chunk-OGS6MQ3D.mjs} +1 -1
- package/dist/{dpop-LVPWWADA.mjs → dpop-GOWCCCFR.mjs} +26 -6
- package/dist/{dpop-NS7NKOWM.mjs → dpop-YPGCGL7S.mjs} +22 -2
- package/dist/dpop-store.cjs +4 -2
- package/dist/dpop-store.mjs +4 -2
- package/dist/dpop.cjs +22 -2
- package/dist/dpop.d.ts +18 -1
- package/dist/dpop.d.ts.map +1 -1
- package/dist/dpop.js +23 -2
- package/dist/dpop.mjs +22 -2
- package/dist/index.cjs +28 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.mjs +31 -6
- package/dist/module.cjs +44 -0
- package/dist/module.d.ts +13 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +22 -0
- package/dist/module.mjs +23 -0
- package/dist/server.cjs +138 -25
- package/dist/server.d.ts +20 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +79 -17
- package/dist/server.mjs +140 -27
- package/dist/service-client.cjs +1 -1
- package/dist/service-client.mjs +3 -3
- package/dist/service.cjs +12 -2
- package/dist/service.d.ts +2 -18
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +11 -0
- package/dist/service.mjs +12 -2
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/dist/types.d.ts +4 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -12
- package/dist/verify.cjs +24 -3
- package/dist/verify.mjs +6 -6
- package/package.json +15 -15
package/dist/server.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/*! @fluixi/oauth2 v1.0.0-alpha.
|
|
2
|
-
import "./chunk-
|
|
1
|
+
/*! @fluixi/oauth2 v1.0.0-alpha.10 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
|
|
2
|
+
import "./chunk-OGS6MQ3D.mjs";
|
|
3
3
|
|
|
4
4
|
// src/server.ts
|
|
5
5
|
import { createSessionCookie } from "@fluixi/session/server";
|
|
@@ -53,7 +53,7 @@ function resolveMethod(auth) {
|
|
|
53
53
|
}
|
|
54
54
|
async function loadJose() {
|
|
55
55
|
try {
|
|
56
|
-
return await import("./browser-
|
|
56
|
+
return await import("./browser-36FHB6KG.mjs");
|
|
57
57
|
} catch {
|
|
58
58
|
throw new OAuthError(
|
|
59
59
|
"private_key_jwt needs `jose`. Install it: npm i jose. It is an optional peer, so a client using a secret does not carry it.",
|
|
@@ -173,7 +173,106 @@ function expiryFrom(expiresIn, now = Date.now()) {
|
|
|
173
173
|
return Number.isFinite(seconds) && seconds > 0 ? now + seconds * 1e3 : void 0;
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
// src/dpop.ts
|
|
177
|
+
async function loadJose2() {
|
|
178
|
+
try {
|
|
179
|
+
return await import("./browser-36FHB6KG.mjs");
|
|
180
|
+
} catch {
|
|
181
|
+
throw new OAuthError(
|
|
182
|
+
"DPoP needs `jose`. Install it: npm i jose. It is an optional peer, so a client not using DPoP does not carry it.",
|
|
183
|
+
"jose_missing"
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async function createDpopKey(alg = "ES256", options = {}) {
|
|
188
|
+
const jose = await loadJose2();
|
|
189
|
+
const { privateKey, publicKey } = await jose.generateKeyPair(alg, {
|
|
190
|
+
extractable: options.extractable ?? false
|
|
191
|
+
});
|
|
192
|
+
const publicJwk = { ...await jose.exportJWK(publicKey) };
|
|
193
|
+
return {
|
|
194
|
+
privateKey,
|
|
195
|
+
publicJwk,
|
|
196
|
+
thumbprint: await jose.calculateJwkThumbprint(publicJwk, "sha256"),
|
|
197
|
+
alg
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
async function exportDpopKey(key) {
|
|
201
|
+
const jose = await loadJose2();
|
|
202
|
+
return { jwk: { ...await jose.exportJWK(key.privateKey) }, alg: key.alg };
|
|
203
|
+
}
|
|
204
|
+
var PRIVATE_MEMBERS = /* @__PURE__ */ new Set(["d", "p", "q", "dp", "dq", "qi", "oth"]);
|
|
205
|
+
async function importDpopKey(stored) {
|
|
206
|
+
const jose = await loadJose2();
|
|
207
|
+
const privateKey = await jose.importJWK(stored.jwk, stored.alg);
|
|
208
|
+
const publicJwk = Object.fromEntries(Object.entries(stored.jwk).filter(([k]) => !PRIVATE_MEMBERS.has(k)));
|
|
209
|
+
return {
|
|
210
|
+
privateKey,
|
|
211
|
+
publicJwk,
|
|
212
|
+
thumbprint: await jose.calculateJwkThumbprint(publicJwk, "sha256"),
|
|
213
|
+
alg: stored.alg
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
async function proofFor(key, options) {
|
|
217
|
+
const jose = await loadJose2();
|
|
218
|
+
const htu = (() => {
|
|
219
|
+
try {
|
|
220
|
+
const url = new URL(options.url);
|
|
221
|
+
url.search = "";
|
|
222
|
+
url.hash = "";
|
|
223
|
+
return url.toString();
|
|
224
|
+
} catch {
|
|
225
|
+
return options.url;
|
|
226
|
+
}
|
|
227
|
+
})();
|
|
228
|
+
const claims = {
|
|
229
|
+
htm: options.method.toUpperCase(),
|
|
230
|
+
htu,
|
|
231
|
+
jti: crypto.randomUUID()
|
|
232
|
+
};
|
|
233
|
+
if (options.accessToken) {
|
|
234
|
+
const digest = await crypto.subtle.digest(
|
|
235
|
+
"SHA-256",
|
|
236
|
+
new TextEncoder().encode(options.accessToken)
|
|
237
|
+
);
|
|
238
|
+
let binary = "";
|
|
239
|
+
for (const byte of new Uint8Array(digest)) binary += String.fromCharCode(byte);
|
|
240
|
+
claims.ath = btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
241
|
+
}
|
|
242
|
+
if (options.nonce) claims.nonce = options.nonce;
|
|
243
|
+
return new jose.SignJWT(claims).setProtectedHeader({ typ: "dpop+jwt", alg: key.alg, jwk: key.publicJwk }).setIssuedAt().sign(key.privateKey);
|
|
244
|
+
}
|
|
245
|
+
function nonceFrom(response) {
|
|
246
|
+
return response.headers.get("dpop-nonce");
|
|
247
|
+
}
|
|
248
|
+
|
|
176
249
|
// src/server.ts
|
|
250
|
+
import { defineModule } from "@fluixi/start/app";
|
|
251
|
+
var dpopNonces = /* @__PURE__ */ new Map();
|
|
252
|
+
async function prove(headers, key, method, url, accessToken) {
|
|
253
|
+
headers.dpop = await proofFor(key, { method, url, accessToken, nonce: dpopNonces.get(new URL(url).origin) });
|
|
254
|
+
}
|
|
255
|
+
function learnNonce(url, response) {
|
|
256
|
+
const offered = nonceFrom(response);
|
|
257
|
+
if (!offered) return false;
|
|
258
|
+
const origin = new URL(url).origin;
|
|
259
|
+
const fresh = offered !== dpopNonces.get(origin);
|
|
260
|
+
dpopNonces.set(origin, offered);
|
|
261
|
+
return fresh && !response.ok;
|
|
262
|
+
}
|
|
263
|
+
async function withToken(url, session) {
|
|
264
|
+
const key = session.dpop && session.tokenType?.toLowerCase() === "dpop" ? await importDpopKey(session.dpop) : null;
|
|
265
|
+
const send = async () => {
|
|
266
|
+
const headers = {
|
|
267
|
+
authorization: `${key ? "DPoP" : "Bearer"} ${session.accessToken}`
|
|
268
|
+
};
|
|
269
|
+
if (key) await prove(headers, key, "GET", url, session.accessToken);
|
|
270
|
+
return fetch(url, { headers });
|
|
271
|
+
};
|
|
272
|
+
let response = await send();
|
|
273
|
+
if (key && learnNonce(url, response)) response = await send();
|
|
274
|
+
return response;
|
|
275
|
+
}
|
|
177
276
|
function oauthRoutes(config) {
|
|
178
277
|
const base = (config.basePath ?? "/auth").replace(/\/$/, "");
|
|
179
278
|
const scopes = config.scopes ?? ["openid", "profile", "email"];
|
|
@@ -205,14 +304,19 @@ function oauthRoutes(config) {
|
|
|
205
304
|
keyId: config.keyId,
|
|
206
305
|
assertionAlgorithm: config.assertionAlgorithm
|
|
207
306
|
};
|
|
208
|
-
async function tokenRequest(body) {
|
|
307
|
+
async function tokenRequest(body, key = null) {
|
|
209
308
|
const { token } = await resolve();
|
|
210
|
-
const
|
|
211
|
-
|
|
309
|
+
const attempt = async () => {
|
|
310
|
+
const headers = {
|
|
311
|
+
"content-type": "application/x-www-form-urlencoded"
|
|
312
|
+
};
|
|
313
|
+
const form = new URLSearchParams(body);
|
|
314
|
+
await applyClientAuth(clientAuth, token, headers, form);
|
|
315
|
+
if (key) await prove(headers, key, "POST", token);
|
|
316
|
+
return fetch(token, { method: "POST", headers, body: form.toString() });
|
|
212
317
|
};
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const response = await fetch(token, { method: "POST", headers, body: form.toString() });
|
|
318
|
+
let response = await attempt();
|
|
319
|
+
if (key && learnNonce(token, response)) response = await attempt();
|
|
216
320
|
const payload = await response.json().catch(() => ({}));
|
|
217
321
|
if (!response.ok) {
|
|
218
322
|
throw new OAuthError(
|
|
@@ -279,17 +383,23 @@ function oauthRoutes(config) {
|
|
|
279
383
|
if (!pending || !state || !timingSafeEqual(pending.state, state)) {
|
|
280
384
|
return json({ error: "state_mismatch" }, 400);
|
|
281
385
|
}
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
386
|
+
const key = config.dpop ? await createDpopKey(config.dpopAlgorithm, { extractable: true }) : null;
|
|
387
|
+
const payload = await tokenRequest(
|
|
388
|
+
{
|
|
389
|
+
grant_type: "authorization_code",
|
|
390
|
+
code,
|
|
391
|
+
redirect_uri: config.redirectUri,
|
|
392
|
+
code_verifier: pending.verifier
|
|
393
|
+
},
|
|
394
|
+
key
|
|
395
|
+
);
|
|
288
396
|
const signedIn = await cookie.write({
|
|
289
397
|
accessToken: String(payload.access_token ?? ""),
|
|
290
398
|
refreshToken: typeof payload.refresh_token === "string" ? payload.refresh_token : void 0,
|
|
291
399
|
idToken: typeof payload.id_token === "string" ? payload.id_token : void 0,
|
|
292
|
-
expiresAt: expiryFrom(payload.expires_in)
|
|
400
|
+
expiresAt: expiryFrom(payload.expires_in),
|
|
401
|
+
tokenType: typeof payload.token_type === "string" ? payload.token_type : void 0,
|
|
402
|
+
dpop: key ? await exportDpopKey(key) : void 0
|
|
293
403
|
});
|
|
294
404
|
return new Response(null, {
|
|
295
405
|
status: 302,
|
|
@@ -309,17 +419,20 @@ function oauthRoutes(config) {
|
|
|
309
419
|
if (route === "/refresh") {
|
|
310
420
|
const current = await session();
|
|
311
421
|
if (!current?.refreshToken) return json({ error: "no_session" }, 401);
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
refresh_token: current.refreshToken
|
|
315
|
-
|
|
422
|
+
const key = current.dpop ? await importDpopKey(current.dpop) : null;
|
|
423
|
+
const payload = await tokenRequest(
|
|
424
|
+
{ grant_type: "refresh_token", refresh_token: current.refreshToken },
|
|
425
|
+
key
|
|
426
|
+
);
|
|
316
427
|
const refreshed = await cookie.write({
|
|
317
428
|
accessToken: String(payload.access_token ?? ""),
|
|
318
429
|
// A provider that rotates refresh tokens sends a new one; one that does not
|
|
319
430
|
// expects the old one to keep working.
|
|
320
431
|
refreshToken: typeof payload.refresh_token === "string" ? payload.refresh_token : current.refreshToken,
|
|
321
432
|
idToken: current.idToken,
|
|
322
|
-
expiresAt: expiryFrom(payload.expires_in)
|
|
433
|
+
expiresAt: expiryFrom(payload.expires_in),
|
|
434
|
+
tokenType: typeof payload.token_type === "string" ? payload.token_type : current.tokenType,
|
|
435
|
+
dpop: current.dpop
|
|
323
436
|
});
|
|
324
437
|
return json({ ok: true }, 200, { "set-cookie": refreshed });
|
|
325
438
|
}
|
|
@@ -328,9 +441,7 @@ function oauthRoutes(config) {
|
|
|
328
441
|
if (!current?.accessToken) return json({ error: "no_session" }, 401);
|
|
329
442
|
const { userinfo } = await resolve();
|
|
330
443
|
if (!userinfo) return json({ error: "no_userinfo" }, 501);
|
|
331
|
-
const response = await
|
|
332
|
-
headers: { authorization: `Bearer ${current.accessToken}` }
|
|
333
|
-
});
|
|
444
|
+
const response = await withToken(userinfo, current);
|
|
334
445
|
if (!response.ok) return json({ error: "userinfo_failed" }, response.status);
|
|
335
446
|
const claims = await response.json();
|
|
336
447
|
return json(config.mapUser ? config.mapUser(claims, current.accessToken) : claims);
|
|
@@ -420,9 +531,7 @@ function sessionLocals(config) {
|
|
|
420
531
|
try {
|
|
421
532
|
const endpoints = config.endpoints ?? (config.issuer ? await discover(config.issuer) : null);
|
|
422
533
|
if (endpoints?.userinfo) {
|
|
423
|
-
const response = await
|
|
424
|
-
headers: { authorization: `Bearer ${session.accessToken}` }
|
|
425
|
-
});
|
|
534
|
+
const response = await withToken(endpoints.userinfo, session);
|
|
426
535
|
if (response.ok) {
|
|
427
536
|
const claims = await response.json();
|
|
428
537
|
user = config.mapUser ? config.mapUser(claims, session.accessToken) : claims;
|
|
@@ -435,7 +544,11 @@ function sessionLocals(config) {
|
|
|
435
544
|
return next();
|
|
436
545
|
};
|
|
437
546
|
}
|
|
547
|
+
function authServerModule(config) {
|
|
548
|
+
return defineModule({ name: "oauth2-server", middleware: [oauthRoutes(config), sessionLocals(config)] });
|
|
549
|
+
}
|
|
438
550
|
export {
|
|
551
|
+
authServerModule,
|
|
439
552
|
oauthRoutes,
|
|
440
553
|
sessionLocals
|
|
441
554
|
};
|
package/dist/service-client.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @fluixi/oauth2 v1.0.0-alpha.
|
|
1
|
+
/*! @fluixi/oauth2 v1.0.0-alpha.10 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
|
|
2
2
|
"use strict";
|
|
3
3
|
var __create = Object.create;
|
|
4
4
|
var __defProp = Object.defineProperty;
|
package/dist/service-client.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/*! @fluixi/oauth2 v1.0.0-alpha.
|
|
2
|
-
import "./chunk-
|
|
1
|
+
/*! @fluixi/oauth2 v1.0.0-alpha.10 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
|
|
2
|
+
import "./chunk-OGS6MQ3D.mjs";
|
|
3
3
|
|
|
4
4
|
// src/types.ts
|
|
5
5
|
var OAuthError = class extends Error {
|
|
@@ -42,7 +42,7 @@ function assertUsable(auth, who) {
|
|
|
42
42
|
}
|
|
43
43
|
async function loadJose() {
|
|
44
44
|
try {
|
|
45
|
-
return await import("./browser-
|
|
45
|
+
return await import("./browser-36FHB6KG.mjs");
|
|
46
46
|
} catch {
|
|
47
47
|
throw new OAuthError(
|
|
48
48
|
"private_key_jwt needs `jose`. Install it: npm i jose. It is an optional peer, so a client using a secret does not carry it.",
|
package/dist/service.cjs
CHANGED
|
@@ -34,6 +34,7 @@ __export(service_exports, {
|
|
|
34
34
|
paramsFromUrl: () => paramsFromUrl
|
|
35
35
|
});
|
|
36
36
|
module.exports = __toCommonJS(service_exports);
|
|
37
|
+
var import_signal = require("@fluixi/reactive/signal");
|
|
37
38
|
var import_session = require("@fluixi/session");
|
|
38
39
|
var import_client = require("@fluixi/auth/client");
|
|
39
40
|
|
|
@@ -191,9 +192,11 @@ async function loadJose2() {
|
|
|
191
192
|
);
|
|
192
193
|
}
|
|
193
194
|
}
|
|
194
|
-
async function createDpopKey(alg = "ES256") {
|
|
195
|
+
async function createDpopKey(alg = "ES256", options = {}) {
|
|
195
196
|
const jose = await loadJose2();
|
|
196
|
-
const { privateKey, publicKey } = await jose.generateKeyPair(alg, {
|
|
197
|
+
const { privateKey, publicKey } = await jose.generateKeyPair(alg, {
|
|
198
|
+
extractable: options.extractable ?? false
|
|
199
|
+
});
|
|
197
200
|
const publicJwk = { ...await jose.exportJWK(publicKey) };
|
|
198
201
|
return {
|
|
199
202
|
privateKey,
|
|
@@ -829,6 +832,13 @@ function clientFor(options) {
|
|
|
829
832
|
return options.config.tokens === "server" ? createServerClient(options.config) : createBrowserClient(options.config);
|
|
830
833
|
}
|
|
831
834
|
function createAuthService(options) {
|
|
835
|
+
const tokens = options.config.tokens;
|
|
836
|
+
const scope = (0, import_signal.openReactiveScope)({ kind: "service", name: "AuthService", ...tokens ? { detail: `tokens: ${tokens}` } : {} });
|
|
837
|
+
const service = (0, import_signal.withReactiveScope)(scope, () => buildAuthService(options));
|
|
838
|
+
(0, import_signal.markReactiveScope)(service, scope);
|
|
839
|
+
return service;
|
|
840
|
+
}
|
|
841
|
+
function buildAuthService(options) {
|
|
832
842
|
const client = clientFor(options);
|
|
833
843
|
let latest = null;
|
|
834
844
|
const session = (0, import_session.createSession)({
|
package/dist/service.d.ts
CHANGED
|
@@ -1,19 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The application-facing half.
|
|
3
|
-
*
|
|
4
|
-
* `AuthClient` knows the protocol. This knows the application: who is signed in, whether
|
|
5
|
-
* the session is still good, when to refresh, and where to send someone who is not
|
|
6
|
-
* allowed in. It is built on `@fluixi/session`, which already owns that state machine,
|
|
7
|
-
* rather than a second one written here.
|
|
8
|
-
*
|
|
9
|
-
* The mapping onto a session adapter is the part worth explaining. `createSession`'s
|
|
10
|
-
* `login(creds)` means "turn these credentials into a token". In a redirect flow the
|
|
11
|
-
* credential is the authorization code on the callback URL, so `login` is the code
|
|
12
|
-
* exchange and the redirect itself is a separate call that never returns. Everything
|
|
13
|
-
* else lines up directly: `fetchUser`, `refresh`, `logout` and `getExpiry`.
|
|
14
|
-
*/
|
|
15
1
|
import { type AccessControl, type Session } from '@fluixi/session';
|
|
16
|
-
import { createAuthHooks } from '@fluixi/auth/client';
|
|
2
|
+
import { createAuthHooks, type AuthHooksOptions } from '@fluixi/auth/client';
|
|
17
3
|
import type { AuthClient, AuthConfig, CallbackParams, LoginOptions } from './types.js';
|
|
18
4
|
/** The credential a redirect flow produces. */
|
|
19
5
|
export type AuthCredentials = CallbackParams;
|
|
@@ -55,9 +41,7 @@ export interface AuthServiceOptions<U, P = unknown, Q = (p: P) => boolean> {
|
|
|
55
41
|
* client-side navigation; leave it out and it assigns `location`, which reloads the
|
|
56
42
|
* document and discards the reactive tree.
|
|
57
43
|
*/
|
|
58
|
-
navigate?:
|
|
59
|
-
handler: () => (to: string, options?: unknown) => void;
|
|
60
|
-
};
|
|
44
|
+
navigate?: AuthHooksOptions['navigate'];
|
|
61
45
|
/** Where a signed-out visitor is sent. Default `/login`. */
|
|
62
46
|
loginPath?: string;
|
|
63
47
|
/** Where a signed-in visitor is sent from a guest-only page. Default `/`. */
|
package/dist/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAeA,OAAO,EAAiB,KAAK,aAAa,EAAE,KAAK,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAClF,OAAO,EAAE,eAAe,EAAE,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAM7E,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,cAAc,EAAE,YAAY,EAAU,MAAM,YAAY,CAAC;AAE/F,+CAA+C;AAC/C,MAAM,MAAM,eAAe,GAAG,cAAc,CAAC;AAE7C,MAAM,WAAW,kBAAkB,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO;IACvE,mDAAmD;IACnD,MAAM,EAAE,UAAU,CAAC;IACnB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAChD,+FAA+F;IAC/F,MAAM,CAAC,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAChC,qCAAqC;IACrC,WAAW,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACjE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAE/C;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,gBAAgB,CAAC,UAAU,CAAC,CAAC;IACxC,4DAA4D;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4DAA4D;IAC5D,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO,CAChE,SAAQ,OAAO,CAAC,CAAC,EAAE,eAAe,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,EAChD,IAAI,CACF,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAC3C,gBAAgB,GAAG,KAAK,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CACxE;IACH,oFAAoF;IACpF,MAAM,CAAC,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACnD,kEAAkE;IAClE,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;CAC7B;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,GAAG,GAAG,cAAc,CAWhE;AAUD,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO,EACrE,OAAO,EAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GACnC,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAStB"}
|
package/dist/service.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* exchange and the redirect itself is a separate call that never returns. Everything
|
|
13
13
|
* else lines up directly: `fetchUser`, `refresh`, `logout` and `getExpiry`.
|
|
14
14
|
*/
|
|
15
|
+
import { markReactiveScope, openReactiveScope, withReactiveScope } from '@fluixi/reactive/signal';
|
|
15
16
|
import { createSession } from '@fluixi/session';
|
|
16
17
|
import { createAuthHooks } from '@fluixi/auth/client';
|
|
17
18
|
import { expiryFromJwt } from './pkce.js';
|
|
@@ -48,6 +49,16 @@ function clientFor(options) {
|
|
|
48
49
|
: createBrowserClient(options.config);
|
|
49
50
|
}
|
|
50
51
|
export function createAuthService(options) {
|
|
52
|
+
// One group in the devtools graph for everything the service holds: the session's `user`,
|
|
53
|
+
// `token`, `status` and the rest. Marked on the instance, so `authModule(auth)` places the
|
|
54
|
+
// group under the module that provides it.
|
|
55
|
+
const tokens = options.config.tokens;
|
|
56
|
+
const scope = openReactiveScope({ kind: 'service', name: 'AuthService', ...(tokens ? { detail: `tokens: ${tokens}` } : {}) });
|
|
57
|
+
const service = withReactiveScope(scope, () => buildAuthService(options));
|
|
58
|
+
markReactiveScope(service, scope);
|
|
59
|
+
return service;
|
|
60
|
+
}
|
|
61
|
+
function buildAuthService(options) {
|
|
51
62
|
const client = clientFor(options);
|
|
52
63
|
// Held so `getExpiry` can answer for a token the session hands back, and so a
|
|
53
64
|
// server-mode session has something to report when the token is a cookie.
|
package/dist/service.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import "./chunk-7P6ASYW6.mjs";
|
|
2
2
|
|
|
3
3
|
// src/service.ts
|
|
4
|
+
import { markReactiveScope, openReactiveScope, withReactiveScope } from "@fluixi/reactive/signal";
|
|
4
5
|
import { createSession } from "@fluixi/session";
|
|
5
6
|
import { createAuthHooks } from "@fluixi/auth/client";
|
|
6
7
|
|
|
@@ -158,9 +159,11 @@ async function loadJose2() {
|
|
|
158
159
|
);
|
|
159
160
|
}
|
|
160
161
|
}
|
|
161
|
-
async function createDpopKey(alg = "ES256") {
|
|
162
|
+
async function createDpopKey(alg = "ES256", options = {}) {
|
|
162
163
|
const jose = await loadJose2();
|
|
163
|
-
const { privateKey, publicKey } = await jose.generateKeyPair(alg, {
|
|
164
|
+
const { privateKey, publicKey } = await jose.generateKeyPair(alg, {
|
|
165
|
+
extractable: options.extractable ?? false
|
|
166
|
+
});
|
|
164
167
|
const publicJwk = { ...await jose.exportJWK(publicKey) };
|
|
165
168
|
return {
|
|
166
169
|
privateKey,
|
|
@@ -796,6 +799,13 @@ function clientFor(options) {
|
|
|
796
799
|
return options.config.tokens === "server" ? createServerClient(options.config) : createBrowserClient(options.config);
|
|
797
800
|
}
|
|
798
801
|
function createAuthService(options) {
|
|
802
|
+
const tokens = options.config.tokens;
|
|
803
|
+
const scope = openReactiveScope({ kind: "service", name: "AuthService", ...tokens ? { detail: `tokens: ${tokens}` } : {} });
|
|
804
|
+
const service = withReactiveScope(scope, () => buildAuthService(options));
|
|
805
|
+
markReactiveScope(service, scope);
|
|
806
|
+
return service;
|
|
807
|
+
}
|
|
808
|
+
function buildAuthService(options) {
|
|
799
809
|
const client = clientFor(options);
|
|
800
810
|
let latest = null;
|
|
801
811
|
const session = createSession({
|