@fluixi/oauth2 1.0.0-alpha.1

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.
Files changed (116) hide show
  1. package/LICENSE +21 -0
  2. package/dist/api-guard.cjs +185 -0
  3. package/dist/api-guard.d.ts +94 -0
  4. package/dist/api-guard.d.ts.map +1 -0
  5. package/dist/api-guard.js +127 -0
  6. package/dist/api-guard.mjs +141 -0
  7. package/dist/browser-FDUOEUM2.mjs +3544 -0
  8. package/dist/browser-ONMHX3Z3.mjs +3545 -0
  9. package/dist/browser.cjs +735 -0
  10. package/dist/browser.d.ts +3 -0
  11. package/dist/browser.d.ts.map +1 -0
  12. package/dist/browser.js +448 -0
  13. package/dist/browser.mjs +704 -0
  14. package/dist/callback.cjs +125 -0
  15. package/dist/callback.d.ts +113 -0
  16. package/dist/callback.d.ts.map +1 -0
  17. package/dist/callback.js +183 -0
  18. package/dist/callback.mjs +106 -0
  19. package/dist/chunk-25WE6UWI.mjs +10 -0
  20. package/dist/chunk-4NSOGPLM.mjs +15 -0
  21. package/dist/chunk-7P6ASYW6.mjs +9 -0
  22. package/dist/chunk-OGSFNTKA.mjs +13 -0
  23. package/dist/client-auth.cjs +115 -0
  24. package/dist/client-auth.d.ts +43 -0
  25. package/dist/client-auth.d.ts.map +1 -0
  26. package/dist/client-auth.js +112 -0
  27. package/dist/client-auth.mjs +84 -0
  28. package/dist/discovery.cjs +86 -0
  29. package/dist/discovery.d.ts +15 -0
  30. package/dist/discovery.d.ts.map +1 -0
  31. package/dist/discovery.js +51 -0
  32. package/dist/discovery.mjs +63 -0
  33. package/dist/dpop-LVPWWADA.mjs +80 -0
  34. package/dist/dpop-NS7NKOWM.mjs +79 -0
  35. package/dist/dpop-store.cjs +152 -0
  36. package/dist/dpop-store.d.ts +37 -0
  37. package/dist/dpop-store.d.ts.map +1 -0
  38. package/dist/dpop-store.js +119 -0
  39. package/dist/dpop-store.mjs +121 -0
  40. package/dist/dpop.cjs +117 -0
  41. package/dist/dpop.d.ts +72 -0
  42. package/dist/dpop.d.ts.map +1 -0
  43. package/dist/dpop.js +114 -0
  44. package/dist/dpop.mjs +86 -0
  45. package/dist/env-config.cjs +366 -0
  46. package/dist/env-config.d.ts +103 -0
  47. package/dist/env-config.d.ts.map +1 -0
  48. package/dist/env-config.js +184 -0
  49. package/dist/env-config.mjs +343 -0
  50. package/dist/env-credentials.cjs +82 -0
  51. package/dist/env-credentials.d.ts +68 -0
  52. package/dist/env-credentials.d.ts.map +1 -0
  53. package/dist/env-credentials.js +76 -0
  54. package/dist/env-credentials.mjs +51 -0
  55. package/dist/index.cjs +940 -0
  56. package/dist/index.d.ts +104 -0
  57. package/dist/index.d.ts.map +1 -0
  58. package/dist/index.js +101 -0
  59. package/dist/index.mjs +909 -0
  60. package/dist/pkce.cjs +87 -0
  61. package/dist/pkce.d.ts +58 -0
  62. package/dist/pkce.d.ts.map +1 -0
  63. package/dist/pkce.js +112 -0
  64. package/dist/pkce.mjs +66 -0
  65. package/dist/presets.cjs +240 -0
  66. package/dist/presets.d.ts +140 -0
  67. package/dist/presets.d.ts.map +1 -0
  68. package/dist/presets.js +182 -0
  69. package/dist/presets.mjs +217 -0
  70. package/dist/return-to.cjs +46 -0
  71. package/dist/return-to.d.ts +49 -0
  72. package/dist/return-to.d.ts.map +1 -0
  73. package/dist/return-to.js +71 -0
  74. package/dist/return-to.mjs +25 -0
  75. package/dist/roles.cjs +56 -0
  76. package/dist/roles.d.ts +35 -0
  77. package/dist/roles.d.ts.map +1 -0
  78. package/dist/roles.js +37 -0
  79. package/dist/roles.mjs +35 -0
  80. package/dist/server-client.cjs +134 -0
  81. package/dist/server-client.d.ts +12 -0
  82. package/dist/server-client.d.ts.map +1 -0
  83. package/dist/server-client.js +107 -0
  84. package/dist/server-client.mjs +111 -0
  85. package/dist/server.cjs +470 -0
  86. package/dist/server.d.ts +89 -0
  87. package/dist/server.d.ts.map +1 -0
  88. package/dist/server.js +360 -0
  89. package/dist/server.mjs +441 -0
  90. package/dist/service-client.cjs +244 -0
  91. package/dist/service-client.d.ts +89 -0
  92. package/dist/service-client.d.ts.map +1 -0
  93. package/dist/service-client.js +112 -0
  94. package/dist/service-client.mjs +213 -0
  95. package/dist/service.cjs +903 -0
  96. package/dist/service.d.ts +96 -0
  97. package/dist/service.d.ts.map +1 -0
  98. package/dist/service.js +129 -0
  99. package/dist/service.mjs +874 -0
  100. package/dist/tokens.cjs +29 -0
  101. package/dist/tokens.d.ts +13 -0
  102. package/dist/tokens.d.ts.map +1 -0
  103. package/dist/tokens.js +21 -0
  104. package/dist/tokens.mjs +8 -0
  105. package/dist/tsconfig.lib.tsbuildinfo +1 -0
  106. package/dist/types.cjs +33 -0
  107. package/dist/types.d.ts +239 -0
  108. package/dist/types.d.ts.map +1 -0
  109. package/dist/types.js +23 -0
  110. package/dist/types.mjs +12 -0
  111. package/dist/verify.cjs +724 -0
  112. package/dist/verify.d.ts +205 -0
  113. package/dist/verify.d.ts.map +1 -0
  114. package/dist/verify.js +371 -0
  115. package/dist/verify.mjs +590 -0
  116. package/package.json +121 -0
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/client-auth.ts
31
+ var client_auth_exports = {};
32
+ __export(client_auth_exports, {
33
+ applyClientAuth: () => applyClientAuth,
34
+ assertUsable: () => assertUsable,
35
+ resolveMethod: () => resolveMethod
36
+ });
37
+ module.exports = __toCommonJS(client_auth_exports);
38
+
39
+ // src/types.ts
40
+ var OAuthError = class extends Error {
41
+ constructor(message, code, description) {
42
+ super(message);
43
+ this.code = code;
44
+ this.description = description;
45
+ this.name = "OAuthError";
46
+ }
47
+ };
48
+
49
+ // src/client-auth.ts
50
+ var CANONICAL = {
51
+ basic: "client_secret_basic",
52
+ body: "client_secret_post",
53
+ client_secret_basic: "client_secret_basic",
54
+ client_secret_post: "client_secret_post",
55
+ private_key_jwt: "private_key_jwt",
56
+ none: "none"
57
+ };
58
+ function resolveMethod(auth) {
59
+ if (auth.method) return CANONICAL[auth.method] ?? "client_secret_basic";
60
+ if (auth.privateKey) return "private_key_jwt";
61
+ return auth.clientSecret ? "client_secret_basic" : "none";
62
+ }
63
+ function assertUsable(auth, who) {
64
+ if (!auth.clientId) throw new Error(`[fluixi/oauth2] ${who} needs a clientId.`);
65
+ const method = resolveMethod(auth);
66
+ if (method === "private_key_jwt" && !auth.privateKey) {
67
+ throw new Error(
68
+ `[fluixi/oauth2] ${who}: private_key_jwt needs \`privateKey\`, the JWK whose public half the provider holds.`
69
+ );
70
+ }
71
+ if ((method === "client_secret_basic" || method === "client_secret_post") && !auth.clientSecret) {
72
+ throw new Error(
73
+ `[fluixi/oauth2] ${who}: ${method} needs a \`clientSecret\`. A public client should use \`method: 'none'\`, which PKCE is what protects.`
74
+ );
75
+ }
76
+ return method;
77
+ }
78
+ async function loadJose() {
79
+ try {
80
+ return await import("jose");
81
+ } catch {
82
+ throw new OAuthError(
83
+ "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.",
84
+ "jose_missing"
85
+ );
86
+ }
87
+ }
88
+ async function signAssertion(auth, endpoint) {
89
+ const jose = await loadJose();
90
+ const alg = auth.assertionAlgorithm ?? "RS256";
91
+ const key = await jose.importJWK(auth.privateKey, alg);
92
+ const kid = auth.keyId ?? auth.privateKey.kid;
93
+ return new jose.SignJWT({}).setProtectedHeader(kid ? { alg, kid } : { alg }).setIssuer(auth.clientId).setSubject(auth.clientId).setAudience(endpoint).setJti(crypto.randomUUID()).setIssuedAt().setExpirationTime("1m").sign(key);
94
+ }
95
+ async function applyClientAuth(auth, endpoint, headers, form) {
96
+ switch (resolveMethod(auth)) {
97
+ case "client_secret_basic": {
98
+ const pair = `${encodeURIComponent(auth.clientId)}:${encodeURIComponent(auth.clientSecret)}`;
99
+ headers.authorization = `Basic ${btoa(pair)}`;
100
+ break;
101
+ }
102
+ case "client_secret_post":
103
+ form.set("client_id", auth.clientId);
104
+ form.set("client_secret", auth.clientSecret);
105
+ break;
106
+ case "private_key_jwt":
107
+ form.set("client_id", auth.clientId);
108
+ form.set("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
109
+ form.set("client_assertion", await signAssertion(auth, endpoint));
110
+ break;
111
+ case "none":
112
+ form.set("client_id", auth.clientId);
113
+ break;
114
+ }
115
+ }
@@ -0,0 +1,43 @@
1
+ export type ClientAuthMethod =
2
+ /** `Authorization: Basic`, the secret outside the body. The usual default. */
3
+ 'client_secret_basic'
4
+ /** The secret in the form. Some providers accept only this. */
5
+ | 'client_secret_post'
6
+ /** A short-lived assertion signed with a key the provider knows. No secret travels. */
7
+ | 'private_key_jwt'
8
+ /** A public client: nothing but the id, which PKCE is what protects. */
9
+ | 'none';
10
+ /** Shorter spellings, because `basic` and `body` are what an app naturally writes. */
11
+ export type ClientAuthMethodInput = ClientAuthMethod | 'basic' | 'body';
12
+ export interface ClientAuth {
13
+ clientId: string;
14
+ /**
15
+ * Defaults to `client_secret_basic` when a secret is present and `none` when it is not,
16
+ * which is what each kind of client wants without being told.
17
+ */
18
+ method?: ClientAuthMethodInput;
19
+ clientSecret?: string;
20
+ /** The signing key for `private_key_jwt`, as a JWK. */
21
+ privateKey?: Record<string, unknown>;
22
+ /** Names which key, for a provider holding more than one. Read from the key otherwise. */
23
+ keyId?: string;
24
+ /** Default `RS256`. */
25
+ assertionAlgorithm?: string;
26
+ }
27
+ /** What the method resolves to, given what the caller supplied. */
28
+ export declare function resolveMethod(auth: ClientAuth): ClientAuthMethod;
29
+ /**
30
+ * Check a configuration before anything tries to use it.
31
+ *
32
+ * Separate from applying it so a constructor can refuse early, naming what is missing,
33
+ * rather than failing on the first request with a provider's word for it.
34
+ */
35
+ export declare function assertUsable(auth: ClientAuth, who: string): ClientAuthMethod;
36
+ /**
37
+ * Put the client's credentials on a request to `endpoint`.
38
+ *
39
+ * Mutates `headers` and `form` rather than returning a new pair, because every caller
40
+ * already has both and is about to send them.
41
+ */
42
+ export declare function applyClientAuth(auth: ClientAuth, endpoint: string, headers: Record<string, string>, form: URLSearchParams): Promise<void>;
43
+ //# sourceMappingURL=client-auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-auth.d.ts","sourceRoot":"","sources":["../src/client-auth.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,gBAAgB;AAC1B,8EAA8E;AAC5E,qBAAqB;AACvB,+DAA+D;GAC7D,oBAAoB;AACtB,uFAAuF;GACrF,iBAAiB;AACnB,wEAAwE;GACtE,MAAM,CAAC;AAEX,sFAAsF;AACtF,MAAM,MAAM,qBAAqB,GAAG,gBAAgB,GAAG,OAAO,GAAG,MAAM,CAAC;AAWxE,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,MAAM,CAAC,EAAE,qBAAqB,CAAC;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uDAAuD;IACvD,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,0FAA0F;IAC1F,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uBAAuB;IACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,mEAAmE;AACnE,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,gBAAgB,CAIhE;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAkB5E;AAsCD;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC,IAAI,CAAC,CA6Bf"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * How a client proves who it is to an endpoint that asks.
3
+ *
4
+ * One implementation, used by every call that authenticates: the code exchange, the
5
+ * refresh, the client credentials grant, introspection and revocation. It was four
6
+ * implementations before, which is how `tokens: 'server'` ended up unable to use
7
+ * `private_key_jwt` while two other paths could. Adding a method now happens here and
8
+ * reaches all of them.
9
+ *
10
+ * The method names are the ones OIDC uses in `token_endpoint_auth_methods_supported`, so
11
+ * a provider's discovery document and an app's configuration say the same words.
12
+ */
13
+ import { OAuthError } from './types.js';
14
+ const CANONICAL = {
15
+ basic: 'client_secret_basic',
16
+ body: 'client_secret_post',
17
+ client_secret_basic: 'client_secret_basic',
18
+ client_secret_post: 'client_secret_post',
19
+ private_key_jwt: 'private_key_jwt',
20
+ none: 'none',
21
+ };
22
+ /** What the method resolves to, given what the caller supplied. */
23
+ export function resolveMethod(auth) {
24
+ if (auth.method)
25
+ return CANONICAL[auth.method] ?? 'client_secret_basic';
26
+ if (auth.privateKey)
27
+ return 'private_key_jwt';
28
+ return auth.clientSecret ? 'client_secret_basic' : 'none';
29
+ }
30
+ /**
31
+ * Check a configuration before anything tries to use it.
32
+ *
33
+ * Separate from applying it so a constructor can refuse early, naming what is missing,
34
+ * rather than failing on the first request with a provider's word for it.
35
+ */
36
+ export function assertUsable(auth, who) {
37
+ if (!auth.clientId)
38
+ throw new Error(`[fluixi/oauth2] ${who} needs a clientId.`);
39
+ const method = resolveMethod(auth);
40
+ if (method === 'private_key_jwt' && !auth.privateKey) {
41
+ throw new Error(`[fluixi/oauth2] ${who}: private_key_jwt needs \`privateKey\`, the JWK whose public half the provider holds.`);
42
+ }
43
+ if ((method === 'client_secret_basic' || method === 'client_secret_post') &&
44
+ !auth.clientSecret) {
45
+ throw new Error(`[fluixi/oauth2] ${who}: ${method} needs a \`clientSecret\`. A public client should use \`method: 'none'\`, which PKCE is what protects.`);
46
+ }
47
+ return method;
48
+ }
49
+ /** `jose`, loaded only when an assertion actually has to be signed. */
50
+ async function loadJose() {
51
+ try {
52
+ return await import('jose');
53
+ }
54
+ catch {
55
+ throw new OAuthError('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.', 'jose_missing');
56
+ }
57
+ }
58
+ /**
59
+ * An assertion, per RFC 7523.
60
+ *
61
+ * `aud` is the endpoint being called, so it cannot be presented at a different one.
62
+ * `jti` is fresh, so it cannot be presented twice at the same one. A minute of life makes
63
+ * a captured one worth almost nothing.
64
+ */
65
+ async function signAssertion(auth, endpoint) {
66
+ const jose = await loadJose();
67
+ const alg = auth.assertionAlgorithm ?? 'RS256';
68
+ const key = await jose.importJWK(auth.privateKey, alg);
69
+ const kid = auth.keyId ?? auth.privateKey.kid;
70
+ return new jose.SignJWT({})
71
+ .setProtectedHeader(kid ? { alg, kid } : { alg })
72
+ .setIssuer(auth.clientId)
73
+ .setSubject(auth.clientId)
74
+ .setAudience(endpoint)
75
+ .setJti(crypto.randomUUID())
76
+ .setIssuedAt()
77
+ .setExpirationTime('1m')
78
+ .sign(key);
79
+ }
80
+ /**
81
+ * Put the client's credentials on a request to `endpoint`.
82
+ *
83
+ * Mutates `headers` and `form` rather than returning a new pair, because every caller
84
+ * already has both and is about to send them.
85
+ */
86
+ export async function applyClientAuth(auth, endpoint, headers, form) {
87
+ switch (resolveMethod(auth)) {
88
+ case 'client_secret_basic': {
89
+ // Form-encoded before base64, which is what the spec asks for and what a secret
90
+ // containing a colon or a space depends on.
91
+ const pair = `${encodeURIComponent(auth.clientId)}:${encodeURIComponent(auth.clientSecret)}`;
92
+ headers.authorization = `Basic ${btoa(pair)}`;
93
+ // Deliberately not also in the form: sending it twice is how a provider that reads
94
+ // the body first ends up ignoring the header.
95
+ break;
96
+ }
97
+ case 'client_secret_post':
98
+ form.set('client_id', auth.clientId);
99
+ form.set('client_secret', auth.clientSecret);
100
+ break;
101
+ case 'private_key_jwt':
102
+ form.set('client_id', auth.clientId);
103
+ form.set('client_assertion_type', 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer');
104
+ form.set('client_assertion', await signAssertion(auth, endpoint));
105
+ break;
106
+ case 'none':
107
+ // A public client names itself and proves nothing, which is the whole reason the
108
+ // authorization code flow needs PKCE.
109
+ form.set('client_id', auth.clientId);
110
+ break;
111
+ }
112
+ }
@@ -0,0 +1,84 @@
1
+ import "./chunk-7P6ASYW6.mjs";
2
+
3
+ // src/types.ts
4
+ var OAuthError = class extends Error {
5
+ constructor(message, code, description) {
6
+ super(message);
7
+ this.code = code;
8
+ this.description = description;
9
+ this.name = "OAuthError";
10
+ }
11
+ };
12
+
13
+ // src/client-auth.ts
14
+ var CANONICAL = {
15
+ basic: "client_secret_basic",
16
+ body: "client_secret_post",
17
+ client_secret_basic: "client_secret_basic",
18
+ client_secret_post: "client_secret_post",
19
+ private_key_jwt: "private_key_jwt",
20
+ none: "none"
21
+ };
22
+ function resolveMethod(auth) {
23
+ if (auth.method) return CANONICAL[auth.method] ?? "client_secret_basic";
24
+ if (auth.privateKey) return "private_key_jwt";
25
+ return auth.clientSecret ? "client_secret_basic" : "none";
26
+ }
27
+ function assertUsable(auth, who) {
28
+ if (!auth.clientId) throw new Error(`[fluixi/oauth2] ${who} needs a clientId.`);
29
+ const method = resolveMethod(auth);
30
+ if (method === "private_key_jwt" && !auth.privateKey) {
31
+ throw new Error(
32
+ `[fluixi/oauth2] ${who}: private_key_jwt needs \`privateKey\`, the JWK whose public half the provider holds.`
33
+ );
34
+ }
35
+ if ((method === "client_secret_basic" || method === "client_secret_post") && !auth.clientSecret) {
36
+ throw new Error(
37
+ `[fluixi/oauth2] ${who}: ${method} needs a \`clientSecret\`. A public client should use \`method: 'none'\`, which PKCE is what protects.`
38
+ );
39
+ }
40
+ return method;
41
+ }
42
+ async function loadJose() {
43
+ try {
44
+ return await import("./browser-FDUOEUM2.mjs");
45
+ } catch {
46
+ throw new OAuthError(
47
+ "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.",
48
+ "jose_missing"
49
+ );
50
+ }
51
+ }
52
+ async function signAssertion(auth, endpoint) {
53
+ const jose = await loadJose();
54
+ const alg = auth.assertionAlgorithm ?? "RS256";
55
+ const key = await jose.importJWK(auth.privateKey, alg);
56
+ const kid = auth.keyId ?? auth.privateKey.kid;
57
+ return new jose.SignJWT({}).setProtectedHeader(kid ? { alg, kid } : { alg }).setIssuer(auth.clientId).setSubject(auth.clientId).setAudience(endpoint).setJti(crypto.randomUUID()).setIssuedAt().setExpirationTime("1m").sign(key);
58
+ }
59
+ async function applyClientAuth(auth, endpoint, headers, form) {
60
+ switch (resolveMethod(auth)) {
61
+ case "client_secret_basic": {
62
+ const pair = `${encodeURIComponent(auth.clientId)}:${encodeURIComponent(auth.clientSecret)}`;
63
+ headers.authorization = `Basic ${btoa(pair)}`;
64
+ break;
65
+ }
66
+ case "client_secret_post":
67
+ form.set("client_id", auth.clientId);
68
+ form.set("client_secret", auth.clientSecret);
69
+ break;
70
+ case "private_key_jwt":
71
+ form.set("client_id", auth.clientId);
72
+ form.set("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
73
+ form.set("client_assertion", await signAssertion(auth, endpoint));
74
+ break;
75
+ case "none":
76
+ form.set("client_id", auth.clientId);
77
+ break;
78
+ }
79
+ }
80
+ export {
81
+ applyClientAuth,
82
+ assertUsable,
83
+ resolveMethod
84
+ };
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/discovery.ts
21
+ var discovery_exports = {};
22
+ __export(discovery_exports, {
23
+ clearDiscoveryCache: () => clearDiscoveryCache,
24
+ discover: () => discover
25
+ });
26
+ module.exports = __toCommonJS(discovery_exports);
27
+
28
+ // src/types.ts
29
+ var OAuthError = class extends Error {
30
+ constructor(message, code, description) {
31
+ super(message);
32
+ this.code = code;
33
+ this.description = description;
34
+ this.name = "OAuthError";
35
+ }
36
+ };
37
+
38
+ // src/discovery.ts
39
+ var cache = /* @__PURE__ */ new Map();
40
+ function documentUrl(issuer) {
41
+ return `${issuer.replace(/\/$/, "")}/.well-known/openid-configuration`;
42
+ }
43
+ async function discover(issuer, fetcher = fetch) {
44
+ const cached = cache.get(issuer);
45
+ if (cached) return cached;
46
+ const pending = (async () => {
47
+ let response;
48
+ try {
49
+ response = await fetcher(documentUrl(issuer));
50
+ } catch (cause) {
51
+ throw new OAuthError(
52
+ `Could not read the discovery document at ${documentUrl(issuer)}. In a browser this is usually the provider refusing a cross-origin read: give \`endpoints\` directly, or resolve them on a server. (${cause instanceof Error ? cause.message : String(cause)})`,
53
+ "discovery_unreachable"
54
+ );
55
+ }
56
+ if (!response.ok) {
57
+ throw new OAuthError(
58
+ `Discovery failed for ${issuer}: ${response.status}`,
59
+ "discovery_failed"
60
+ );
61
+ }
62
+ const doc = await response.json();
63
+ if (!doc.authorization_endpoint || !doc.token_endpoint) {
64
+ throw new OAuthError(
65
+ `${issuer} published a discovery document with no authorization or token endpoint.`,
66
+ "discovery_incomplete"
67
+ );
68
+ }
69
+ return {
70
+ authorization: doc.authorization_endpoint,
71
+ token: doc.token_endpoint,
72
+ userinfo: doc.userinfo_endpoint,
73
+ endSession: doc.end_session_endpoint,
74
+ jwks: doc.jwks_uri,
75
+ introspection: doc.introspection_endpoint,
76
+ revocation: doc.revocation_endpoint,
77
+ issuer: doc.issuer ?? issuer
78
+ };
79
+ })();
80
+ cache.set(issuer, pending);
81
+ pending.catch(() => cache.delete(issuer));
82
+ return pending;
83
+ }
84
+ function clearDiscoveryCache() {
85
+ cache.clear();
86
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Endpoints, from the issuer's own document.
3
+ *
4
+ * An OIDC provider publishes `/.well-known/openid-configuration`, so an app configures
5
+ * one issuer URL rather than five endpoints that have to stay in step with it. Curity,
6
+ * Keycloak, Auth0 and Okta all serve it.
7
+ *
8
+ * The result is cached per issuer for the life of the page. The document changes when
9
+ * the provider is reconfigured, which is not something to re-check on every login.
10
+ */
11
+ import type { Endpoints } from './types.js';
12
+ export declare function discover(issuer: string, fetcher?: typeof fetch): Promise<Endpoints>;
13
+ /** For a test that needs a clean slate. */
14
+ export declare function clearDiscoveryCache(): void;
15
+ //# sourceMappingURL=discovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAsB5C,wBAAsB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,OAAO,KAAa,GAAG,OAAO,CAAC,SAAS,CAAC,CAkDhG;AAED,2CAA2C;AAC3C,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C"}
@@ -0,0 +1,51 @@
1
+ import { OAuthError } from './types.js';
2
+ const cache = new Map();
3
+ /** `https://host/` and `https://host` both give `https://host/.well-known/...`. */
4
+ function documentUrl(issuer) {
5
+ return `${issuer.replace(/\/$/, '')}/.well-known/openid-configuration`;
6
+ }
7
+ export async function discover(issuer, fetcher = fetch) {
8
+ const cached = cache.get(issuer);
9
+ if (cached)
10
+ return cached;
11
+ const pending = (async () => {
12
+ let response;
13
+ try {
14
+ response = await fetcher(documentUrl(issuer));
15
+ }
16
+ catch (cause) {
17
+ // In a browser a blocked cross-origin read throws before any status exists, and
18
+ // `fetch` reports only "Failed to fetch". Not every provider allows a page to read
19
+ // its discovery document: Keycloak does once an origin is configured, Dex and
20
+ // Google do not. Naming the cause saves a long hunt for a network fault.
21
+ throw new OAuthError(`Could not read the discovery document at ${documentUrl(issuer)}. In a browser this is usually the provider refusing a cross-origin read: give \`endpoints\` directly, or resolve them on a server. (${cause instanceof Error ? cause.message : String(cause)})`, 'discovery_unreachable');
22
+ }
23
+ if (!response.ok) {
24
+ throw new OAuthError(`Discovery failed for ${issuer}: ${response.status}`, 'discovery_failed');
25
+ }
26
+ const doc = (await response.json());
27
+ if (!doc.authorization_endpoint || !doc.token_endpoint) {
28
+ throw new OAuthError(`${issuer} published a discovery document with no authorization or token endpoint.`, 'discovery_incomplete');
29
+ }
30
+ return {
31
+ authorization: doc.authorization_endpoint,
32
+ token: doc.token_endpoint,
33
+ userinfo: doc.userinfo_endpoint,
34
+ endSession: doc.end_session_endpoint,
35
+ jwks: doc.jwks_uri,
36
+ introspection: doc.introspection_endpoint,
37
+ revocation: doc.revocation_endpoint,
38
+ issuer: doc.issuer ?? issuer,
39
+ };
40
+ })();
41
+ // Cached before it resolves, so two logins starting at once make one request. Dropped
42
+ // on failure, so a provider that was briefly down is retried rather than remembered
43
+ // as broken.
44
+ cache.set(issuer, pending);
45
+ pending.catch(() => cache.delete(issuer));
46
+ return pending;
47
+ }
48
+ /** For a test that needs a clean slate. */
49
+ export function clearDiscoveryCache() {
50
+ cache.clear();
51
+ }
@@ -0,0 +1,63 @@
1
+ // src/types.ts
2
+ var OAuthError = class extends Error {
3
+ constructor(message, code, description) {
4
+ super(message);
5
+ this.code = code;
6
+ this.description = description;
7
+ this.name = "OAuthError";
8
+ }
9
+ };
10
+
11
+ // src/discovery.ts
12
+ var cache = /* @__PURE__ */ new Map();
13
+ function documentUrl(issuer) {
14
+ return `${issuer.replace(/\/$/, "")}/.well-known/openid-configuration`;
15
+ }
16
+ async function discover(issuer, fetcher = fetch) {
17
+ const cached = cache.get(issuer);
18
+ if (cached) return cached;
19
+ const pending = (async () => {
20
+ let response;
21
+ try {
22
+ response = await fetcher(documentUrl(issuer));
23
+ } catch (cause) {
24
+ throw new OAuthError(
25
+ `Could not read the discovery document at ${documentUrl(issuer)}. In a browser this is usually the provider refusing a cross-origin read: give \`endpoints\` directly, or resolve them on a server. (${cause instanceof Error ? cause.message : String(cause)})`,
26
+ "discovery_unreachable"
27
+ );
28
+ }
29
+ if (!response.ok) {
30
+ throw new OAuthError(
31
+ `Discovery failed for ${issuer}: ${response.status}`,
32
+ "discovery_failed"
33
+ );
34
+ }
35
+ const doc = await response.json();
36
+ if (!doc.authorization_endpoint || !doc.token_endpoint) {
37
+ throw new OAuthError(
38
+ `${issuer} published a discovery document with no authorization or token endpoint.`,
39
+ "discovery_incomplete"
40
+ );
41
+ }
42
+ return {
43
+ authorization: doc.authorization_endpoint,
44
+ token: doc.token_endpoint,
45
+ userinfo: doc.userinfo_endpoint,
46
+ endSession: doc.end_session_endpoint,
47
+ jwks: doc.jwks_uri,
48
+ introspection: doc.introspection_endpoint,
49
+ revocation: doc.revocation_endpoint,
50
+ issuer: doc.issuer ?? issuer
51
+ };
52
+ })();
53
+ cache.set(issuer, pending);
54
+ pending.catch(() => cache.delete(issuer));
55
+ return pending;
56
+ }
57
+ function clearDiscoveryCache() {
58
+ cache.clear();
59
+ }
60
+ export {
61
+ clearDiscoveryCache,
62
+ discover
63
+ };
@@ -0,0 +1,80 @@
1
+ /*! @fluixi/oauth2 v1.0.0-alpha.1 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
+ import {
3
+ OAuthError
4
+ } from "./chunk-4NSOGPLM.mjs";
5
+ import "./chunk-25WE6UWI.mjs";
6
+
7
+ // src/dpop.ts
8
+ async function loadJose() {
9
+ try {
10
+ return await import("./browser-ONMHX3Z3.mjs");
11
+ } catch {
12
+ throw new OAuthError(
13
+ "DPoP needs `jose`. Install it: npm i jose. It is an optional peer, so a client not using DPoP does not carry it.",
14
+ "jose_missing"
15
+ );
16
+ }
17
+ }
18
+ async function createDpopKey(alg = "ES256") {
19
+ const jose = await loadJose();
20
+ const { privateKey, publicKey } = await jose.generateKeyPair(alg, { extractable: false });
21
+ const publicJwk = { ...await jose.exportJWK(publicKey) };
22
+ return {
23
+ privateKey,
24
+ publicJwk,
25
+ thumbprint: await jose.calculateJwkThumbprint(publicJwk, "sha256"),
26
+ alg
27
+ };
28
+ }
29
+ async function thumbprintOf(jwk) {
30
+ const jose = await loadJose();
31
+ return jose.calculateJwkThumbprint(jwk, "sha256");
32
+ }
33
+ async function proofFor(key, options) {
34
+ const jose = await loadJose();
35
+ const htu = (() => {
36
+ try {
37
+ const url = new URL(options.url);
38
+ url.search = "";
39
+ url.hash = "";
40
+ return url.toString();
41
+ } catch {
42
+ return options.url;
43
+ }
44
+ })();
45
+ const claims = {
46
+ htm: options.method.toUpperCase(),
47
+ htu,
48
+ jti: crypto.randomUUID()
49
+ };
50
+ if (options.accessToken) {
51
+ const digest = await crypto.subtle.digest(
52
+ "SHA-256",
53
+ new TextEncoder().encode(options.accessToken)
54
+ );
55
+ let binary = "";
56
+ for (const byte of new Uint8Array(digest)) binary += String.fromCharCode(byte);
57
+ claims.ath = btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
58
+ }
59
+ if (options.nonce) claims.nonce = options.nonce;
60
+ return new jose.SignJWT(claims).setProtectedHeader({ typ: "dpop+jwt", alg: key.alg, jwk: key.publicJwk }).setIssuedAt().sign(key.privateKey);
61
+ }
62
+ function nonceFrom(response) {
63
+ return response.headers.get("dpop-nonce");
64
+ }
65
+ function wantsNonce(error, response) {
66
+ const code = typeof error === "string" ? error : error?.error;
67
+ return code === "use_dpop_nonce" || !!response && !!nonceFrom(response);
68
+ }
69
+ function boundKeyOf(claims) {
70
+ const cnf = claims.cnf;
71
+ return typeof cnf?.jkt === "string" ? cnf.jkt : null;
72
+ }
73
+ export {
74
+ boundKeyOf,
75
+ createDpopKey,
76
+ nonceFrom,
77
+ proofFor,
78
+ thumbprintOf,
79
+ wantsNonce
80
+ };