@hanzo/iam 0.8.0 → 0.9.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 (73) hide show
  1. package/dist/auth.cjs +111 -0
  2. package/dist/auth.cjs.map +1 -0
  3. package/dist/auth.d.cts +19 -0
  4. package/dist/auth.d.ts +7 -4
  5. package/dist/auth.js +94 -121
  6. package/dist/auth.js.map +1 -1
  7. package/dist/betterauth.cjs +34 -0
  8. package/dist/betterauth.cjs.map +1 -0
  9. package/dist/betterauth.d.cts +64 -0
  10. package/dist/betterauth.d.ts +7 -10
  11. package/dist/betterauth.js +28 -62
  12. package/dist/betterauth.js.map +1 -1
  13. package/dist/billing.cjs +8 -0
  14. package/dist/billing.cjs.map +1 -0
  15. package/dist/billing.d.cts +2 -0
  16. package/dist/billing.d.ts +2 -16
  17. package/dist/billing.js +5 -17
  18. package/dist/billing.js.map +1 -1
  19. package/dist/browser.cjs +680 -0
  20. package/dist/browser.cjs.map +1 -0
  21. package/dist/browser.d.cts +217 -0
  22. package/dist/browser.d.ts +16 -13
  23. package/dist/browser.js +645 -663
  24. package/dist/browser.js.map +1 -1
  25. package/dist/index.cjs +1087 -0
  26. package/dist/index.cjs.map +1 -0
  27. package/dist/{client.d.ts → index.d.cts} +23 -4
  28. package/dist/index.d.ts +86 -23
  29. package/dist/index.js +1077 -29
  30. package/dist/index.js.map +1 -1
  31. package/dist/nextauth.cjs +35 -0
  32. package/dist/nextauth.cjs.map +1 -0
  33. package/dist/nextauth.d.cts +55 -0
  34. package/dist/nextauth.d.ts +4 -7
  35. package/dist/nextauth.js +30 -66
  36. package/dist/nextauth.js.map +1 -1
  37. package/dist/passport.cjs +49 -0
  38. package/dist/passport.cjs.map +1 -0
  39. package/dist/passport.d.cts +47 -0
  40. package/dist/passport.d.ts +8 -5
  41. package/dist/passport.js +45 -65
  42. package/dist/passport.js.map +1 -1
  43. package/dist/react.cjs +1434 -0
  44. package/dist/react.cjs.map +1 -0
  45. package/dist/react.d.cts +133 -0
  46. package/dist/react.d.ts +19 -50
  47. package/dist/react.js +1399 -494
  48. package/dist/react.js.map +1 -1
  49. package/dist/types.cjs +4 -0
  50. package/dist/types.cjs.map +1 -0
  51. package/dist/types.d.cts +219 -0
  52. package/dist/types.d.ts +25 -24
  53. package/dist/types.js +2 -5
  54. package/dist/types.js.map +1 -1
  55. package/package.json +24 -13
  56. package/src/browser.ts +13 -13
  57. package/src/react.ts +7 -6
  58. package/dist/auth.d.ts.map +0 -1
  59. package/dist/betterauth.d.ts.map +0 -1
  60. package/dist/billing.d.ts.map +0 -1
  61. package/dist/browser.d.ts.map +0 -1
  62. package/dist/client.d.ts.map +0 -1
  63. package/dist/client.js +0 -292
  64. package/dist/client.js.map +0 -1
  65. package/dist/index.d.ts.map +0 -1
  66. package/dist/nextauth.d.ts.map +0 -1
  67. package/dist/passport.d.ts.map +0 -1
  68. package/dist/pkce.d.ts +0 -13
  69. package/dist/pkce.d.ts.map +0 -1
  70. package/dist/pkce.js +0 -36
  71. package/dist/pkce.js.map +0 -1
  72. package/dist/react.d.ts.map +0 -1
  73. package/dist/types.d.ts.map +0 -1
package/dist/auth.cjs ADDED
@@ -0,0 +1,111 @@
1
+ 'use strict';
2
+
3
+ var jose = require('jose');
4
+
5
+ // src/auth.ts
6
+ var jwksSets = /* @__PURE__ */ new Map();
7
+ function getJwksKeySet(jwksUri) {
8
+ let keySet = jwksSets.get(jwksUri);
9
+ if (!keySet) {
10
+ keySet = jose.createRemoteJWKSet(new URL(jwksUri));
11
+ jwksSets.set(jwksUri, keySet);
12
+ }
13
+ return keySet;
14
+ }
15
+ function clearJwksCache() {
16
+ jwksSets.clear();
17
+ }
18
+ var discoveryCache = /* @__PURE__ */ new Map();
19
+ var DISCOVERY_TTL_MS = 5 * 60 * 1e3;
20
+ async function resolveJwksUri(serverUrl) {
21
+ const baseUrl = serverUrl.replace(/\/+$/, "");
22
+ const cached = discoveryCache.get(baseUrl);
23
+ if (cached && Date.now() - cached.fetchedAt < DISCOVERY_TTL_MS) {
24
+ return { jwksUri: cached.jwksUri, issuer: cached.issuer };
25
+ }
26
+ const controller = new AbortController();
27
+ const timer = setTimeout(() => controller.abort(), 8e3);
28
+ try {
29
+ const res = await fetch(`${baseUrl}/.well-known/openid-configuration`, {
30
+ signal: controller.signal,
31
+ headers: { Accept: "application/json" }
32
+ });
33
+ if (!res.ok) {
34
+ throw new Error(`OIDC discovery failed: ${res.status}`);
35
+ }
36
+ const body = await res.json();
37
+ const jwksUri = body.jwks_uri;
38
+ const issuer = body.issuer ?? baseUrl;
39
+ if (!jwksUri) {
40
+ throw new Error("OIDC discovery response missing jwks_uri");
41
+ }
42
+ discoveryCache.set(baseUrl, { jwksUri, issuer, fetchedAt: Date.now() });
43
+ return { jwksUri, issuer };
44
+ } finally {
45
+ clearTimeout(timer);
46
+ }
47
+ }
48
+ async function validateToken(token, config) {
49
+ if (!token || typeof token !== "string") {
50
+ return { ok: false, reason: "iam_token_missing" };
51
+ }
52
+ let jwksUri;
53
+ let issuer;
54
+ try {
55
+ const discovery = await resolveJwksUri(config.serverUrl);
56
+ jwksUri = discovery.jwksUri;
57
+ issuer = discovery.issuer;
58
+ } catch {
59
+ return { ok: false, reason: "iam_discovery_failed" };
60
+ }
61
+ const keySet = getJwksKeySet(jwksUri);
62
+ let payload;
63
+ try {
64
+ const result = await jose.jwtVerify(token, keySet, {
65
+ issuer,
66
+ audience: config.clientId,
67
+ clockTolerance: 30
68
+ // 30s clock skew
69
+ });
70
+ payload = result.payload;
71
+ } catch (err) {
72
+ const message = err instanceof Error ? err.message : String(err);
73
+ if (message.includes("expired")) {
74
+ return { ok: false, reason: "iam_token_expired" };
75
+ }
76
+ if (message.includes("audience")) {
77
+ try {
78
+ const result = await jose.jwtVerify(token, keySet, {
79
+ issuer,
80
+ clockTolerance: 30
81
+ });
82
+ payload = result.payload;
83
+ } catch {
84
+ return { ok: false, reason: "iam_signature_invalid" };
85
+ }
86
+ } else {
87
+ return { ok: false, reason: "iam_signature_invalid" };
88
+ }
89
+ }
90
+ const claims = payload;
91
+ const sub = claims.sub || (typeof claims.owner === "string" && typeof claims.name === "string" ? `${claims.owner}/${claims.name}` : void 0);
92
+ if (!sub) {
93
+ return { ok: false, reason: "iam_subject_missing" };
94
+ }
95
+ const parts = sub.split("/");
96
+ const owner = parts.length > 1 ? parts[0] : config.orgName ?? "unknown";
97
+ return {
98
+ ok: true,
99
+ userId: sub,
100
+ email: typeof claims.email === "string" ? claims.email : void 0,
101
+ name: typeof claims.name === "string" ? claims.name : typeof claims.preferred_username === "string" ? claims.preferred_username : void 0,
102
+ avatar: typeof claims.picture === "string" ? claims.picture : void 0,
103
+ owner,
104
+ claims
105
+ };
106
+ }
107
+
108
+ exports.clearJwksCache = clearJwksCache;
109
+ exports.validateToken = validateToken;
110
+ //# sourceMappingURL=auth.cjs.map
111
+ //# sourceMappingURL=auth.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/auth.ts"],"names":["createRemoteJWKSet","jwtVerify"],"mappings":"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","file":"auth.cjs","sourcesContent":["/**\n * JWT validation using jose library + OIDC JWKS discovery.\n *\n * Validates access/ID tokens issued by Hanzo IAM.\n */\n\nimport { createRemoteJWKSet, jwtVerify, type JWTPayload } from \"jose\";\nimport type { IamConfig, IamAuthResult, IamJwtClaims } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// JWKS key set cache (per issuer)\n// ---------------------------------------------------------------------------\n\nconst jwksSets = new Map<string, ReturnType<typeof createRemoteJWKSet>>();\n\nfunction getJwksKeySet(jwksUri: string): ReturnType<typeof createRemoteJWKSet> {\n let keySet = jwksSets.get(jwksUri);\n if (!keySet) {\n keySet = createRemoteJWKSet(new URL(jwksUri));\n jwksSets.set(jwksUri, keySet);\n }\n return keySet;\n}\n\n/** Clear cached JWKS key sets (useful for testing or key rotation). */\nexport function clearJwksCache(): void {\n jwksSets.clear();\n}\n\n// ---------------------------------------------------------------------------\n// OIDC discovery cache (lightweight, no full client needed)\n// ---------------------------------------------------------------------------\n\ntype CachedDiscovery = { jwksUri: string; issuer: string; fetchedAt: number };\nconst discoveryCache = new Map<string, CachedDiscovery>();\nconst DISCOVERY_TTL_MS = 5 * 60 * 1000;\n\nasync function resolveJwksUri(serverUrl: string): Promise<{ jwksUri: string; issuer: string }> {\n const baseUrl = serverUrl.replace(/\\/+$/, \"\");\n const cached = discoveryCache.get(baseUrl);\n if (cached && Date.now() - cached.fetchedAt < DISCOVERY_TTL_MS) {\n return { jwksUri: cached.jwksUri, issuer: cached.issuer };\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 8_000);\n try {\n const res = await fetch(`${baseUrl}/.well-known/openid-configuration`, {\n signal: controller.signal,\n headers: { Accept: \"application/json\" },\n });\n if (!res.ok) {\n throw new Error(`OIDC discovery failed: ${res.status}`);\n }\n const body = (await res.json()) as { jwks_uri?: string; issuer?: string };\n const jwksUri = body.jwks_uri;\n const issuer = body.issuer ?? baseUrl;\n if (!jwksUri) {\n throw new Error(\"OIDC discovery response missing jwks_uri\");\n }\n discoveryCache.set(baseUrl, { jwksUri, issuer, fetchedAt: Date.now() });\n return { jwksUri, issuer };\n } finally {\n clearTimeout(timer);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Token validation\n// ---------------------------------------------------------------------------\n\n/**\n * Validate a JWT access token against IAM's JWKS.\n *\n * Uses OIDC discovery to find the JWKS URI, then verifies the token\n * signature, issuer, audience, and expiry using the `jose` library.\n */\nexport async function validateToken(\n token: string,\n config: IamConfig,\n): Promise<IamAuthResult> {\n if (!token || typeof token !== \"string\") {\n return { ok: false, reason: \"iam_token_missing\" };\n }\n\n let jwksUri: string;\n let issuer: string;\n try {\n const discovery = await resolveJwksUri(config.serverUrl);\n jwksUri = discovery.jwksUri;\n issuer = discovery.issuer;\n } catch {\n return { ok: false, reason: \"iam_discovery_failed\" };\n }\n\n const keySet = getJwksKeySet(jwksUri);\n\n let payload: JWTPayload;\n try {\n const result = await jwtVerify(token, keySet, {\n issuer,\n audience: config.clientId,\n clockTolerance: 30, // 30s clock skew\n });\n payload = result.payload;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (message.includes(\"expired\")) {\n return { ok: false, reason: \"iam_token_expired\" };\n }\n if (message.includes(\"audience\")) {\n // Retry without audience check - some IAM configs don't set aud\n try {\n const result = await jwtVerify(token, keySet, {\n issuer,\n clockTolerance: 30,\n });\n payload = result.payload;\n } catch {\n return { ok: false, reason: \"iam_signature_invalid\" };\n }\n } else {\n return { ok: false, reason: \"iam_signature_invalid\" };\n }\n }\n\n const claims = payload as unknown as IamJwtClaims;\n\n // Hanzo IAM tokens may use owner/name instead of sub claim\n const sub =\n claims.sub ||\n (typeof claims.owner === \"string\" && typeof claims.name === \"string\"\n ? `${claims.owner}/${claims.name}`\n : undefined);\n\n if (!sub) {\n return { ok: false, reason: \"iam_subject_missing\" };\n }\n\n // IAM sub format is \"org/username\" - extract owner\n const parts = sub.split(\"/\");\n const owner = parts.length > 1 ? parts[0] : config.orgName ?? \"unknown\";\n\n return {\n ok: true,\n userId: sub,\n email: typeof claims.email === \"string\" ? claims.email : undefined,\n name:\n typeof claims.name === \"string\"\n ? claims.name\n : typeof claims.preferred_username === \"string\"\n ? claims.preferred_username\n : undefined,\n avatar: typeof claims.picture === \"string\" ? claims.picture : undefined,\n owner,\n claims,\n };\n}\n"]}
@@ -0,0 +1,19 @@
1
+ import { IamConfig, IamAuthResult } from './types.cjs';
2
+
3
+ /**
4
+ * JWT validation using jose library + OIDC JWKS discovery.
5
+ *
6
+ * Validates access/ID tokens issued by Hanzo IAM.
7
+ */
8
+
9
+ /** Clear cached JWKS key sets (useful for testing or key rotation). */
10
+ declare function clearJwksCache(): void;
11
+ /**
12
+ * Validate a JWT access token against IAM's JWKS.
13
+ *
14
+ * Uses OIDC discovery to find the JWKS URI, then verifies the token
15
+ * signature, issuer, audience, and expiry using the `jose` library.
16
+ */
17
+ declare function validateToken(token: string, config: IamConfig): Promise<IamAuthResult>;
18
+
19
+ export { clearJwksCache, validateToken };
package/dist/auth.d.ts CHANGED
@@ -1,16 +1,19 @@
1
+ import { IamConfig, IamAuthResult } from './types.js';
2
+
1
3
  /**
2
4
  * JWT validation using jose library + OIDC JWKS discovery.
3
5
  *
4
6
  * Validates access/ID tokens issued by Hanzo IAM.
5
7
  */
6
- import type { IamConfig, IamAuthResult } from "./types.js";
8
+
7
9
  /** Clear cached JWKS key sets (useful for testing or key rotation). */
8
- export declare function clearJwksCache(): void;
10
+ declare function clearJwksCache(): void;
9
11
  /**
10
12
  * Validate a JWT access token against IAM's JWKS.
11
13
  *
12
14
  * Uses OIDC discovery to find the JWKS URI, then verifies the token
13
15
  * signature, issuer, audience, and expiry using the `jose` library.
14
16
  */
15
- export declare function validateToken(token: string, config: IamConfig): Promise<IamAuthResult>;
16
- //# sourceMappingURL=auth.d.ts.map
17
+ declare function validateToken(token: string, config: IamConfig): Promise<IamAuthResult>;
18
+
19
+ export { clearJwksCache, validateToken };
package/dist/auth.js CHANGED
@@ -1,135 +1,108 @@
1
- /**
2
- * JWT validation using jose library + OIDC JWKS discovery.
3
- *
4
- * Validates access/ID tokens issued by Hanzo IAM.
5
- */
6
- import { createRemoteJWKSet, jwtVerify } from "jose";
7
- // ---------------------------------------------------------------------------
8
- // JWKS key set cache (per issuer)
9
- // ---------------------------------------------------------------------------
10
- const jwksSets = new Map();
1
+ import { jwtVerify, createRemoteJWKSet } from 'jose';
2
+
3
+ // src/auth.ts
4
+ var jwksSets = /* @__PURE__ */ new Map();
11
5
  function getJwksKeySet(jwksUri) {
12
- let keySet = jwksSets.get(jwksUri);
13
- if (!keySet) {
14
- keySet = createRemoteJWKSet(new URL(jwksUri));
15
- jwksSets.set(jwksUri, keySet);
16
- }
17
- return keySet;
6
+ let keySet = jwksSets.get(jwksUri);
7
+ if (!keySet) {
8
+ keySet = createRemoteJWKSet(new URL(jwksUri));
9
+ jwksSets.set(jwksUri, keySet);
10
+ }
11
+ return keySet;
18
12
  }
19
- /** Clear cached JWKS key sets (useful for testing or key rotation). */
20
- export function clearJwksCache() {
21
- jwksSets.clear();
13
+ function clearJwksCache() {
14
+ jwksSets.clear();
22
15
  }
23
- const discoveryCache = new Map();
24
- const DISCOVERY_TTL_MS = 5 * 60 * 1000;
16
+ var discoveryCache = /* @__PURE__ */ new Map();
17
+ var DISCOVERY_TTL_MS = 5 * 60 * 1e3;
25
18
  async function resolveJwksUri(serverUrl) {
26
- const baseUrl = serverUrl.replace(/\/+$/, "");
27
- const cached = discoveryCache.get(baseUrl);
28
- if (cached && Date.now() - cached.fetchedAt < DISCOVERY_TTL_MS) {
29
- return { jwksUri: cached.jwksUri, issuer: cached.issuer };
30
- }
31
- const controller = new AbortController();
32
- const timer = setTimeout(() => controller.abort(), 8_000);
33
- try {
34
- const res = await fetch(`${baseUrl}/.well-known/openid-configuration`, {
35
- signal: controller.signal,
36
- headers: { Accept: "application/json" },
37
- });
38
- if (!res.ok) {
39
- throw new Error(`OIDC discovery failed: ${res.status}`);
40
- }
41
- const body = (await res.json());
42
- const jwksUri = body.jwks_uri;
43
- const issuer = body.issuer ?? baseUrl;
44
- if (!jwksUri) {
45
- throw new Error("OIDC discovery response missing jwks_uri");
46
- }
47
- discoveryCache.set(baseUrl, { jwksUri, issuer, fetchedAt: Date.now() });
48
- return { jwksUri, issuer };
19
+ const baseUrl = serverUrl.replace(/\/+$/, "");
20
+ const cached = discoveryCache.get(baseUrl);
21
+ if (cached && Date.now() - cached.fetchedAt < DISCOVERY_TTL_MS) {
22
+ return { jwksUri: cached.jwksUri, issuer: cached.issuer };
23
+ }
24
+ const controller = new AbortController();
25
+ const timer = setTimeout(() => controller.abort(), 8e3);
26
+ try {
27
+ const res = await fetch(`${baseUrl}/.well-known/openid-configuration`, {
28
+ signal: controller.signal,
29
+ headers: { Accept: "application/json" }
30
+ });
31
+ if (!res.ok) {
32
+ throw new Error(`OIDC discovery failed: ${res.status}`);
49
33
  }
50
- finally {
51
- clearTimeout(timer);
34
+ const body = await res.json();
35
+ const jwksUri = body.jwks_uri;
36
+ const issuer = body.issuer ?? baseUrl;
37
+ if (!jwksUri) {
38
+ throw new Error("OIDC discovery response missing jwks_uri");
52
39
  }
40
+ discoveryCache.set(baseUrl, { jwksUri, issuer, fetchedAt: Date.now() });
41
+ return { jwksUri, issuer };
42
+ } finally {
43
+ clearTimeout(timer);
44
+ }
53
45
  }
54
- // ---------------------------------------------------------------------------
55
- // Token validation
56
- // ---------------------------------------------------------------------------
57
- /**
58
- * Validate a JWT access token against IAM's JWKS.
59
- *
60
- * Uses OIDC discovery to find the JWKS URI, then verifies the token
61
- * signature, issuer, audience, and expiry using the `jose` library.
62
- */
63
- export async function validateToken(token, config) {
64
- if (!token || typeof token !== "string") {
65
- return { ok: false, reason: "iam_token_missing" };
66
- }
67
- let jwksUri;
68
- let issuer;
69
- try {
70
- const discovery = await resolveJwksUri(config.serverUrl);
71
- jwksUri = discovery.jwksUri;
72
- issuer = discovery.issuer;
46
+ async function validateToken(token, config) {
47
+ if (!token || typeof token !== "string") {
48
+ return { ok: false, reason: "iam_token_missing" };
49
+ }
50
+ let jwksUri;
51
+ let issuer;
52
+ try {
53
+ const discovery = await resolveJwksUri(config.serverUrl);
54
+ jwksUri = discovery.jwksUri;
55
+ issuer = discovery.issuer;
56
+ } catch {
57
+ return { ok: false, reason: "iam_discovery_failed" };
58
+ }
59
+ const keySet = getJwksKeySet(jwksUri);
60
+ let payload;
61
+ try {
62
+ const result = await jwtVerify(token, keySet, {
63
+ issuer,
64
+ audience: config.clientId,
65
+ clockTolerance: 30
66
+ // 30s clock skew
67
+ });
68
+ payload = result.payload;
69
+ } catch (err) {
70
+ const message = err instanceof Error ? err.message : String(err);
71
+ if (message.includes("expired")) {
72
+ return { ok: false, reason: "iam_token_expired" };
73
73
  }
74
- catch {
75
- return { ok: false, reason: "iam_discovery_failed" };
76
- }
77
- const keySet = getJwksKeySet(jwksUri);
78
- let payload;
79
- try {
74
+ if (message.includes("audience")) {
75
+ try {
80
76
  const result = await jwtVerify(token, keySet, {
81
- issuer,
82
- audience: config.clientId,
83
- clockTolerance: 30, // 30s clock skew
77
+ issuer,
78
+ clockTolerance: 30
84
79
  });
85
80
  payload = result.payload;
81
+ } catch {
82
+ return { ok: false, reason: "iam_signature_invalid" };
83
+ }
84
+ } else {
85
+ return { ok: false, reason: "iam_signature_invalid" };
86
86
  }
87
- catch (err) {
88
- const message = err instanceof Error ? err.message : String(err);
89
- if (message.includes("expired")) {
90
- return { ok: false, reason: "iam_token_expired" };
91
- }
92
- if (message.includes("audience")) {
93
- // Retry without audience check - some IAM configs don't set aud
94
- try {
95
- const result = await jwtVerify(token, keySet, {
96
- issuer,
97
- clockTolerance: 30,
98
- });
99
- payload = result.payload;
100
- }
101
- catch {
102
- return { ok: false, reason: "iam_signature_invalid" };
103
- }
104
- }
105
- else {
106
- return { ok: false, reason: "iam_signature_invalid" };
107
- }
108
- }
109
- const claims = payload;
110
- // Hanzo IAM tokens may use owner/name instead of sub claim
111
- const sub = claims.sub ||
112
- (typeof claims.owner === "string" && typeof claims.name === "string"
113
- ? `${claims.owner}/${claims.name}`
114
- : undefined);
115
- if (!sub) {
116
- return { ok: false, reason: "iam_subject_missing" };
117
- }
118
- // IAM sub format is "org/username" - extract owner
119
- const parts = sub.split("/");
120
- const owner = parts.length > 1 ? parts[0] : config.orgName ?? "unknown";
121
- return {
122
- ok: true,
123
- userId: sub,
124
- email: typeof claims.email === "string" ? claims.email : undefined,
125
- name: typeof claims.name === "string"
126
- ? claims.name
127
- : typeof claims.preferred_username === "string"
128
- ? claims.preferred_username
129
- : undefined,
130
- avatar: typeof claims.picture === "string" ? claims.picture : undefined,
131
- owner,
132
- claims,
133
- };
87
+ }
88
+ const claims = payload;
89
+ const sub = claims.sub || (typeof claims.owner === "string" && typeof claims.name === "string" ? `${claims.owner}/${claims.name}` : void 0);
90
+ if (!sub) {
91
+ return { ok: false, reason: "iam_subject_missing" };
92
+ }
93
+ const parts = sub.split("/");
94
+ const owner = parts.length > 1 ? parts[0] : config.orgName ?? "unknown";
95
+ return {
96
+ ok: true,
97
+ userId: sub,
98
+ email: typeof claims.email === "string" ? claims.email : void 0,
99
+ name: typeof claims.name === "string" ? claims.name : typeof claims.preferred_username === "string" ? claims.preferred_username : void 0,
100
+ avatar: typeof claims.picture === "string" ? claims.picture : void 0,
101
+ owner,
102
+ claims
103
+ };
134
104
  }
105
+
106
+ export { clearJwksCache, validateToken };
107
+ //# sourceMappingURL=auth.js.map
135
108
  //# sourceMappingURL=auth.js.map
package/dist/auth.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"sources":["../src/auth.ts"],"names":[],"mappings":"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","file":"auth.js","sourcesContent":["/**\n * JWT validation using jose library + OIDC JWKS discovery.\n *\n * Validates access/ID tokens issued by Hanzo IAM.\n */\n\nimport { createRemoteJWKSet, jwtVerify, type JWTPayload } from \"jose\";\nimport type { IamConfig, IamAuthResult, IamJwtClaims } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// JWKS key set cache (per issuer)\n// ---------------------------------------------------------------------------\n\nconst jwksSets = new Map<string, ReturnType<typeof createRemoteJWKSet>>();\n\nfunction getJwksKeySet(jwksUri: string): ReturnType<typeof createRemoteJWKSet> {\n let keySet = jwksSets.get(jwksUri);\n if (!keySet) {\n keySet = createRemoteJWKSet(new URL(jwksUri));\n jwksSets.set(jwksUri, keySet);\n }\n return keySet;\n}\n\n/** Clear cached JWKS key sets (useful for testing or key rotation). */\nexport function clearJwksCache(): void {\n jwksSets.clear();\n}\n\n// ---------------------------------------------------------------------------\n// OIDC discovery cache (lightweight, no full client needed)\n// ---------------------------------------------------------------------------\n\ntype CachedDiscovery = { jwksUri: string; issuer: string; fetchedAt: number };\nconst discoveryCache = new Map<string, CachedDiscovery>();\nconst DISCOVERY_TTL_MS = 5 * 60 * 1000;\n\nasync function resolveJwksUri(serverUrl: string): Promise<{ jwksUri: string; issuer: string }> {\n const baseUrl = serverUrl.replace(/\\/+$/, \"\");\n const cached = discoveryCache.get(baseUrl);\n if (cached && Date.now() - cached.fetchedAt < DISCOVERY_TTL_MS) {\n return { jwksUri: cached.jwksUri, issuer: cached.issuer };\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 8_000);\n try {\n const res = await fetch(`${baseUrl}/.well-known/openid-configuration`, {\n signal: controller.signal,\n headers: { Accept: \"application/json\" },\n });\n if (!res.ok) {\n throw new Error(`OIDC discovery failed: ${res.status}`);\n }\n const body = (await res.json()) as { jwks_uri?: string; issuer?: string };\n const jwksUri = body.jwks_uri;\n const issuer = body.issuer ?? baseUrl;\n if (!jwksUri) {\n throw new Error(\"OIDC discovery response missing jwks_uri\");\n }\n discoveryCache.set(baseUrl, { jwksUri, issuer, fetchedAt: Date.now() });\n return { jwksUri, issuer };\n } finally {\n clearTimeout(timer);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Token validation\n// ---------------------------------------------------------------------------\n\n/**\n * Validate a JWT access token against IAM's JWKS.\n *\n * Uses OIDC discovery to find the JWKS URI, then verifies the token\n * signature, issuer, audience, and expiry using the `jose` library.\n */\nexport async function validateToken(\n token: string,\n config: IamConfig,\n): Promise<IamAuthResult> {\n if (!token || typeof token !== \"string\") {\n return { ok: false, reason: \"iam_token_missing\" };\n }\n\n let jwksUri: string;\n let issuer: string;\n try {\n const discovery = await resolveJwksUri(config.serverUrl);\n jwksUri = discovery.jwksUri;\n issuer = discovery.issuer;\n } catch {\n return { ok: false, reason: \"iam_discovery_failed\" };\n }\n\n const keySet = getJwksKeySet(jwksUri);\n\n let payload: JWTPayload;\n try {\n const result = await jwtVerify(token, keySet, {\n issuer,\n audience: config.clientId,\n clockTolerance: 30, // 30s clock skew\n });\n payload = result.payload;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (message.includes(\"expired\")) {\n return { ok: false, reason: \"iam_token_expired\" };\n }\n if (message.includes(\"audience\")) {\n // Retry without audience check - some IAM configs don't set aud\n try {\n const result = await jwtVerify(token, keySet, {\n issuer,\n clockTolerance: 30,\n });\n payload = result.payload;\n } catch {\n return { ok: false, reason: \"iam_signature_invalid\" };\n }\n } else {\n return { ok: false, reason: \"iam_signature_invalid\" };\n }\n }\n\n const claims = payload as unknown as IamJwtClaims;\n\n // Hanzo IAM tokens may use owner/name instead of sub claim\n const sub =\n claims.sub ||\n (typeof claims.owner === \"string\" && typeof claims.name === \"string\"\n ? `${claims.owner}/${claims.name}`\n : undefined);\n\n if (!sub) {\n return { ok: false, reason: \"iam_subject_missing\" };\n }\n\n // IAM sub format is \"org/username\" - extract owner\n const parts = sub.split(\"/\");\n const owner = parts.length > 1 ? parts[0] : config.orgName ?? \"unknown\";\n\n return {\n ok: true,\n userId: sub,\n email: typeof claims.email === \"string\" ? claims.email : undefined,\n name:\n typeof claims.name === \"string\"\n ? claims.name\n : typeof claims.preferred_username === \"string\"\n ? claims.preferred_username\n : undefined,\n avatar: typeof claims.picture === \"string\" ? claims.picture : undefined,\n owner,\n claims,\n };\n}\n"]}
@@ -0,0 +1,34 @@
1
+ 'use strict';
2
+
3
+ // src/betterauth.ts
4
+ function iamProvider(config) {
5
+ const baseUrl = config.serverUrl.replace(/\/+$/, "");
6
+ return {
7
+ id: "iam",
8
+ name: "IAM",
9
+ type: "oidc",
10
+ issuer: baseUrl,
11
+ clientId: config.clientId,
12
+ clientSecret: config.clientSecret,
13
+ authorization: {
14
+ url: `${baseUrl}/oauth/authorize`,
15
+ params: { scope: "openid profile email" }
16
+ },
17
+ token: { url: `${baseUrl}/oauth/token` },
18
+ userinfo: { url: `${baseUrl}/oauth/userinfo` },
19
+ profile(profile) {
20
+ return {
21
+ id: profile.sub ?? profile.id ?? "",
22
+ name: profile.displayName ?? profile.name ?? profile.preferred_username ?? "",
23
+ email: profile.email ?? "",
24
+ image: profile.avatar ?? profile.picture ?? null
25
+ };
26
+ }
27
+ };
28
+ }
29
+
30
+ exports.hanzoIamProvider = iamProvider;
31
+ exports.hanzoIamSocialProvider = iamProvider;
32
+ exports.iamProvider = iamProvider;
33
+ //# sourceMappingURL=betterauth.cjs.map
34
+ //# sourceMappingURL=betterauth.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/betterauth.ts"],"names":[],"mappings":";;;AAmDO,SAAS,YACd,MAAA,EACmB;AACnB,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,SAAA,CAAU,OAAA,CAAQ,QAAQ,EAAE,CAAA;AAEnD,EAAA,OAAO;AAAA,IACL,EAAA,EAAI,KAAA;AAAA,IACJ,IAAA,EAAM,KAAA;AAAA,IACN,IAAA,EAAM,MAAA;AAAA,IACN,MAAA,EAAQ,OAAA;AAAA,IACR,UAAU,MAAA,CAAO,QAAA;AAAA,IACjB,cAAc,MAAA,CAAO,YAAA;AAAA,IACrB,aAAA,EAAe;AAAA,MACb,GAAA,EAAK,GAAG,OAAO,CAAA,gBAAA,CAAA;AAAA,MACf,MAAA,EAAQ,EAAE,KAAA,EAAO,sBAAA;AAAuB,KAC1C;AAAA,IACA,KAAA,EAAO,EAAE,GAAA,EAAK,CAAA,EAAG,OAAO,CAAA,YAAA,CAAA,EAAe;AAAA,IACvC,QAAA,EAAU,EAAE,GAAA,EAAK,CAAA,EAAG,OAAO,CAAA,eAAA,CAAA,EAAkB;AAAA,IAC7C,QAAQ,OAAA,EAAkC;AACxC,MAAA,OAAO;AAAA,QACL,EAAA,EAAK,OAAA,CAAQ,GAAA,IAAmB,OAAA,CAAQ,EAAA,IAAiB,EAAA;AAAA,QACzD,MACG,OAAA,CAAQ,WAAA,IACR,OAAA,CAAQ,IAAA,IACR,QAAQ,kBAAA,IACT,EAAA;AAAA,QACF,KAAA,EAAQ,QAAQ,KAAA,IAAoB,EAAA;AAAA,QACpC,KAAA,EAAQ,OAAA,CAAQ,MAAA,IAAsB,OAAA,CAAQ,OAAA,IAAsB;AAAA,OACtE;AAAA,IACF;AAAA,GACF;AACF","file":"betterauth.cjs","sourcesContent":["/**\n * BetterAuth SSO provider configuration for IAM.\n *\n * Returns a provider config object compatible with BetterAuth's\n * `socialProviders` or generic OAuth plugin.\n *\n * @example\n * ```ts\n * import { betterAuth } from \"better-auth\";\n * import { iamProvider } from \"@hanzo/iam/betterauth\";\n *\n * export const auth = betterAuth({\n * socialProviders: [\n * iamProvider({\n * serverUrl: process.env.IAM_SERVER_URL!,\n * clientId: process.env.IAM_CLIENT_ID!,\n * clientSecret: process.env.IAM_CLIENT_SECRET!,\n * }),\n * ],\n * });\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { IamConfig } from \"./types.js\";\n\nexport interface IamSocialProvider {\n id: string;\n name: string;\n type: \"oidc\";\n issuer: string;\n clientId: string;\n clientSecret?: string;\n authorization: { url: string; params: { scope: string } };\n token: { url: string };\n userinfo: { url: string };\n profile: (profile: Record<string, unknown>) => {\n id: string;\n name: string;\n email: string;\n image: string | null;\n };\n}\n\n/**\n * Create a BetterAuth-compatible social provider for IAM.\n *\n * Works with BetterAuth's SSO plugin or generic OAuth integration.\n * Uses standard OIDC endpoints.\n */\nexport function iamProvider(\n config: IamConfig & { redirectUri?: string },\n): IamSocialProvider {\n const baseUrl = config.serverUrl.replace(/\\/+$/, \"\");\n\n return {\n id: \"iam\",\n name: \"IAM\",\n type: \"oidc\",\n issuer: baseUrl,\n clientId: config.clientId,\n clientSecret: config.clientSecret,\n authorization: {\n url: `${baseUrl}/oauth/authorize`,\n params: { scope: \"openid profile email\" },\n },\n token: { url: `${baseUrl}/oauth/token` },\n userinfo: { url: `${baseUrl}/oauth/userinfo` },\n profile(profile: Record<string, unknown>) {\n return {\n id: (profile.sub as string) ?? (profile.id as string) ?? \"\",\n name:\n (profile.displayName as string) ??\n (profile.name as string) ??\n (profile.preferred_username as string) ??\n \"\",\n email: (profile.email as string) ?? \"\",\n image: (profile.avatar as string) ?? (profile.picture as string) ?? null,\n };\n },\n };\n}\n\n// Backwards-compatible aliases\n/** @deprecated Use iamProvider instead */\nexport { iamProvider as hanzoIamProvider };\n/** @deprecated Use iamProvider instead */\nexport { iamProvider as hanzoIamSocialProvider };\n/** @deprecated Use IamSocialProvider instead */\nexport type { IamSocialProvider as HanzoIamSocialProvider };\n"]}
@@ -0,0 +1,64 @@
1
+ import { IamConfig } from './types.cjs';
2
+
3
+ /**
4
+ * BetterAuth SSO provider configuration for IAM.
5
+ *
6
+ * Returns a provider config object compatible with BetterAuth's
7
+ * `socialProviders` or generic OAuth plugin.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import { betterAuth } from "better-auth";
12
+ * import { iamProvider } from "@hanzo/iam/betterauth";
13
+ *
14
+ * export const auth = betterAuth({
15
+ * socialProviders: [
16
+ * iamProvider({
17
+ * serverUrl: process.env.IAM_SERVER_URL!,
18
+ * clientId: process.env.IAM_CLIENT_ID!,
19
+ * clientSecret: process.env.IAM_CLIENT_SECRET!,
20
+ * }),
21
+ * ],
22
+ * });
23
+ * ```
24
+ *
25
+ * @packageDocumentation
26
+ */
27
+
28
+ interface IamSocialProvider {
29
+ id: string;
30
+ name: string;
31
+ type: "oidc";
32
+ issuer: string;
33
+ clientId: string;
34
+ clientSecret?: string;
35
+ authorization: {
36
+ url: string;
37
+ params: {
38
+ scope: string;
39
+ };
40
+ };
41
+ token: {
42
+ url: string;
43
+ };
44
+ userinfo: {
45
+ url: string;
46
+ };
47
+ profile: (profile: Record<string, unknown>) => {
48
+ id: string;
49
+ name: string;
50
+ email: string;
51
+ image: string | null;
52
+ };
53
+ }
54
+ /**
55
+ * Create a BetterAuth-compatible social provider for IAM.
56
+ *
57
+ * Works with BetterAuth's SSO plugin or generic OAuth integration.
58
+ * Uses standard OIDC endpoints.
59
+ */
60
+ declare function iamProvider(config: IamConfig & {
61
+ redirectUri?: string;
62
+ }): IamSocialProvider;
63
+
64
+ export { type IamSocialProvider as HanzoIamSocialProvider, type IamSocialProvider, iamProvider as hanzoIamProvider, iamProvider as hanzoIamSocialProvider, iamProvider };
@@ -1,3 +1,5 @@
1
+ import { IamConfig } from './types.js';
2
+
1
3
  /**
2
4
  * BetterAuth SSO provider configuration for IAM.
3
5
  *
@@ -22,8 +24,8 @@
22
24
  *
23
25
  * @packageDocumentation
24
26
  */
25
- import type { IamConfig } from "./types.js";
26
- export interface IamSocialProvider {
27
+
28
+ interface IamSocialProvider {
27
29
  id: string;
28
30
  name: string;
29
31
  type: "oidc";
@@ -55,13 +57,8 @@ export interface IamSocialProvider {
55
57
  * Works with BetterAuth's SSO plugin or generic OAuth integration.
56
58
  * Uses standard OIDC endpoints.
57
59
  */
58
- export declare function iamProvider(config: IamConfig & {
60
+ declare function iamProvider(config: IamConfig & {
59
61
  redirectUri?: string;
60
62
  }): IamSocialProvider;
61
- /** @deprecated Use iamProvider instead */
62
- export { iamProvider as hanzoIamProvider };
63
- /** @deprecated Use iamProvider instead */
64
- export { iamProvider as hanzoIamSocialProvider };
65
- /** @deprecated Use IamSocialProvider instead */
66
- export type { IamSocialProvider as HanzoIamSocialProvider };
67
- //# sourceMappingURL=betterauth.d.ts.map
63
+
64
+ export { type IamSocialProvider as HanzoIamSocialProvider, type IamSocialProvider, iamProvider as hanzoIamProvider, iamProvider as hanzoIamSocialProvider, iamProvider };