@rpcbase/auth 0.165.0 → 0.167.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.
Files changed (66) hide show
  1. package/dist/handler-0nKR-P5B.js +84 -0
  2. package/dist/handler-0nKR-P5B.js.map +1 -0
  3. package/dist/handler-B8jPw9zh.js +74 -0
  4. package/dist/handler-B8jPw9zh.js.map +1 -0
  5. package/dist/handler-BGNM_dz1.js +19 -0
  6. package/dist/handler-BGNM_dz1.js.map +1 -0
  7. package/dist/handler-BoWTfKWi.js +56 -0
  8. package/dist/handler-BoWTfKWi.js.map +1 -0
  9. package/dist/handler-ClyVm_Us.js +92 -0
  10. package/dist/handler-ClyVm_Us.js.map +1 -0
  11. package/dist/handler-DNxEmrtP.js +67 -0
  12. package/dist/{handler-B_lItxOC.js.map → handler-DNxEmrtP.js.map} +1 -1
  13. package/dist/handler-DsPS6JXV.js +70 -0
  14. package/dist/handler-DsPS6JXV.js.map +1 -0
  15. package/dist/handler-GKdEwnBO.js +75 -0
  16. package/dist/handler-GKdEwnBO.js.map +1 -0
  17. package/dist/handler-IBGVDW5w.js +125 -0
  18. package/dist/handler-IBGVDW5w.js.map +1 -0
  19. package/dist/handler-J2OwFTyf.js +51 -0
  20. package/dist/handler-J2OwFTyf.js.map +1 -0
  21. package/dist/index.js +770 -756
  22. package/dist/index.js.map +1 -1
  23. package/dist/middleware-BT3Uput1.js +18 -0
  24. package/dist/middleware-BT3Uput1.js.map +1 -0
  25. package/dist/oauth/index.js +636 -761
  26. package/dist/oauth/index.js.map +1 -1
  27. package/dist/routes.js +19 -9
  28. package/dist/routes.js.map +1 -1
  29. package/dist/schemas-BgOpQqUv.js +3988 -0
  30. package/dist/schemas-BgOpQqUv.js.map +1 -0
  31. package/dist/session-BQ06Yo3C.js +20 -0
  32. package/dist/session-BQ06Yo3C.js.map +1 -0
  33. package/dist/sign-in-BG2ND3cO.js +18 -0
  34. package/dist/sign-in-BG2ND3cO.js.map +1 -0
  35. package/dist/sign-up-BCVi8UC9.js +18 -0
  36. package/dist/sign-up-BCVi8UC9.js.map +1 -0
  37. package/package.json +1 -1
  38. package/dist/handler-BBSeVt7x.js +0 -153
  39. package/dist/handler-BBSeVt7x.js.map +0 -1
  40. package/dist/handler-BKSik1j0.js +0 -83
  41. package/dist/handler-BKSik1j0.js.map +0 -1
  42. package/dist/handler-BXvh_Nx4.js +0 -95
  43. package/dist/handler-BXvh_Nx4.js.map +0 -1
  44. package/dist/handler-B_lItxOC.js +0 -66
  45. package/dist/handler-BuwKXMOu.js +0 -24
  46. package/dist/handler-BuwKXMOu.js.map +0 -1
  47. package/dist/handler-C-hPxnHG.js +0 -68
  48. package/dist/handler-C-hPxnHG.js.map +0 -1
  49. package/dist/handler-CoHpAhui.js +0 -81
  50. package/dist/handler-CoHpAhui.js.map +0 -1
  51. package/dist/handler-DfZwgoA4.js +0 -94
  52. package/dist/handler-DfZwgoA4.js.map +0 -1
  53. package/dist/handler-Eyjkxj9l.js +0 -58
  54. package/dist/handler-Eyjkxj9l.js.map +0 -1
  55. package/dist/handler-U3df_2Al.js +0 -59
  56. package/dist/handler-U3df_2Al.js.map +0 -1
  57. package/dist/index-1_ZKABa_.js +0 -20
  58. package/dist/index-1_ZKABa_.js.map +0 -1
  59. package/dist/index-D94-1Zwh.js +0 -20
  60. package/dist/index-D94-1Zwh.js.map +0 -1
  61. package/dist/middleware-8IfSkEEy.js +0 -25
  62. package/dist/middleware-8IfSkEEy.js.map +0 -1
  63. package/dist/schemas--f80aXK7.js +0 -4329
  64. package/dist/schemas--f80aXK7.js.map +0 -1
  65. package/dist/session-DoTNxRRA.js +0 -20
  66. package/dist/session-DoTNxRRA.js.map +0 -1
@@ -1,787 +1,662 @@
1
+ import { n as completeAuthSignIn } from "../session-BQ06Yo3C.js";
1
2
  import crypto from "crypto";
2
3
  import { models } from "@rpcbase/db";
3
4
  import { hashPasswordForStorage } from "@rpcbase/server";
4
- import { c as completeAuthSignIn } from "../session-DoTNxRRA.js";
5
- const STORE_KEY = /* @__PURE__ */ Symbol.for("@rpcbase/auth/oauthProviders");
6
- const getStore = () => {
7
- const anyGlobal = globalThis;
8
- const existing = anyGlobal[STORE_KEY];
9
- if (existing && typeof existing === "object" && existing.providers && typeof existing.providers === "object") {
10
- return existing;
11
- }
12
- const created = {
13
- providers: {}
14
- };
15
- anyGlobal[STORE_KEY] = created;
16
- return created;
5
+ //#region src/oauth/config.ts
6
+ var STORE_KEY = Symbol.for("@rpcbase/auth/oauthProviders");
7
+ var getStore = () => {
8
+ const anyGlobal = globalThis;
9
+ const existing = anyGlobal[STORE_KEY];
10
+ if (existing && typeof existing === "object" && existing.providers && typeof existing.providers === "object") return existing;
11
+ const created = { providers: {} };
12
+ anyGlobal[STORE_KEY] = created;
13
+ return created;
17
14
  };
18
- const normalizeProviders = (providers) => {
19
- const result = {};
20
- const isSafeRedirectPath2 = (candidate) => {
21
- if (!candidate.startsWith("/")) return false;
22
- if (candidate.startsWith("//")) return false;
23
- return true;
24
- };
25
- for (const [providerIdRaw, value] of Object.entries(providers)) {
26
- const providerId = providerIdRaw.trim();
27
- if (!providerId) continue;
28
- const issuer = typeof value?.issuer === "string" ? value.issuer.trim() : "";
29
- const clientId = typeof value?.clientId === "string" ? value.clientId.trim() : "";
30
- if (!issuer) throw new Error(`oauth provider "${providerId}" missing issuer`);
31
- if (!clientId) throw new Error(`oauth provider "${providerId}" missing clientId`);
32
- const clientSecret = typeof value?.clientSecret === "string" && value.clientSecret.trim() ? value.clientSecret : void 0;
33
- const scope = typeof value?.scope === "string" ? value.scope.trim() : "";
34
- if (!scope) throw new Error(`oauth provider "${providerId}" missing scope`);
35
- const callbackPath = typeof value?.callbackPath === "string" ? value.callbackPath.trim() : "";
36
- if (!callbackPath) throw new Error(`oauth provider "${providerId}" missing callbackPath`);
37
- if (!isSafeRedirectPath2(callbackPath)) throw new Error(`oauth provider "${providerId}" has invalid callbackPath`);
38
- result[providerId] = {
39
- issuer,
40
- clientId,
41
- clientSecret,
42
- scope,
43
- callbackPath
44
- };
45
- }
46
- return result;
15
+ var normalizeProviders = (providers) => {
16
+ const result = {};
17
+ const isSafeRedirectPath = (candidate) => {
18
+ if (!candidate.startsWith("/")) return false;
19
+ if (candidate.startsWith("//")) return false;
20
+ return true;
21
+ };
22
+ for (const [providerIdRaw, value] of Object.entries(providers)) {
23
+ const providerId = providerIdRaw.trim();
24
+ if (!providerId) continue;
25
+ const issuer = typeof value?.issuer === "string" ? value.issuer.trim() : "";
26
+ const clientId = typeof value?.clientId === "string" ? value.clientId.trim() : "";
27
+ if (!issuer) throw new Error(`oauth provider "${providerId}" missing issuer`);
28
+ if (!clientId) throw new Error(`oauth provider "${providerId}" missing clientId`);
29
+ const clientSecret = typeof value?.clientSecret === "string" && value.clientSecret.trim() ? value.clientSecret : void 0;
30
+ const scope = typeof value?.scope === "string" ? value.scope.trim() : "";
31
+ if (!scope) throw new Error(`oauth provider "${providerId}" missing scope`);
32
+ const callbackPath = typeof value?.callbackPath === "string" ? value.callbackPath.trim() : "";
33
+ if (!callbackPath) throw new Error(`oauth provider "${providerId}" missing callbackPath`);
34
+ if (!isSafeRedirectPath(callbackPath)) throw new Error(`oauth provider "${providerId}" has invalid callbackPath`);
35
+ result[providerId] = {
36
+ issuer,
37
+ clientId,
38
+ clientSecret,
39
+ scope,
40
+ callbackPath
41
+ };
42
+ }
43
+ return result;
47
44
  };
48
- const configureOAuthProviders = (providers) => {
49
- const store = getStore();
50
- const normalized = normalizeProviders(providers);
51
- store.providers = {
52
- ...store.providers,
53
- ...normalized
54
- };
45
+ var configureOAuthProviders = (providers) => {
46
+ const store = getStore();
47
+ const normalized = normalizeProviders(providers);
48
+ store.providers = {
49
+ ...store.providers,
50
+ ...normalized
51
+ };
55
52
  };
56
- const setOAuthProviders = (providers) => {
57
- const store = getStore();
58
- store.providers = normalizeProviders(providers);
53
+ var setOAuthProviders = (providers) => {
54
+ const store = getStore();
55
+ store.providers = normalizeProviders(providers);
59
56
  };
60
- const getOAuthProviderConfig = (providerId) => {
61
- const store = getStore();
62
- return store.providers[providerId] ?? null;
57
+ var getOAuthProviderConfig = (providerId) => {
58
+ return getStore().providers[providerId] ?? null;
63
59
  };
64
- const decodeBase64Url = (value) => {
65
- const padded = value.replace(/-/g, "+").replace(/_/g, "/") + "===".slice((value.length + 3) % 4);
66
- return Buffer.from(padded, "base64").toString("utf8");
60
+ //#endregion
61
+ //#region src/oauth/jwt.ts
62
+ var decodeBase64Url = (value) => {
63
+ const padded = value.replace(/-/g, "+").replace(/_/g, "/") + "===".slice((value.length + 3) % 4);
64
+ return Buffer.from(padded, "base64").toString("utf8");
67
65
  };
68
- const decodeJwtPayload = (token) => {
69
- const parts = token.split(".");
70
- if (parts.length < 2) return null;
71
- try {
72
- const json = decodeBase64Url(parts[1]);
73
- const parsed = JSON.parse(json);
74
- if (!parsed || typeof parsed !== "object") return null;
75
- return parsed;
76
- } catch {
77
- return null;
78
- }
66
+ var decodeJwtPayload = (token) => {
67
+ const parts = token.split(".");
68
+ if (parts.length < 2) return null;
69
+ try {
70
+ const json = decodeBase64Url(parts[1]);
71
+ const parsed = JSON.parse(json);
72
+ if (!parsed || typeof parsed !== "object") return null;
73
+ return parsed;
74
+ } catch {
75
+ return null;
76
+ }
79
77
  };
80
- const cache = /* @__PURE__ */ new Map();
81
- const normalizeIssuer = (raw) => raw.trim().replace(/\/+$/, "");
82
- const getOidcWellKnown = async (issuerRaw) => {
83
- const issuer = normalizeIssuer(issuerRaw);
84
- if (!issuer) {
85
- throw new Error("OIDC issuer is required");
86
- }
87
- const existing = cache.get(issuer);
88
- if (existing) {
89
- return existing;
90
- }
91
- const loader = (async () => {
92
- const url = new URL("/.well-known/openid-configuration", issuer);
93
- const response = await fetch(url, {
94
- headers: {
95
- Accept: "application/json"
96
- }
97
- });
98
- if (!response.ok) {
99
- const body = await response.text().catch(() => "");
100
- throw new Error(`Failed to fetch OIDC discovery document: ${response.status} ${body}`);
101
- }
102
- const json = await response.json().catch(() => null);
103
- if (!json || typeof json !== "object") {
104
- throw new Error("Invalid OIDC discovery document");
105
- }
106
- const authorizationEndpoint = typeof json.authorization_endpoint === "string" ? json.authorization_endpoint : "";
107
- const tokenEndpoint = typeof json.token_endpoint === "string" ? json.token_endpoint : "";
108
- const issuerValue = typeof json.issuer === "string" ? json.issuer : issuer;
109
- const userinfoEndpoint = typeof json.userinfo_endpoint === "string" ? json.userinfo_endpoint : void 0;
110
- const jwksUri = typeof json.jwks_uri === "string" ? json.jwks_uri : void 0;
111
- if (!authorizationEndpoint || !tokenEndpoint) {
112
- throw new Error("OIDC discovery document missing required endpoints");
113
- }
114
- return {
115
- issuer: issuerValue,
116
- authorization_endpoint: authorizationEndpoint,
117
- token_endpoint: tokenEndpoint,
118
- userinfo_endpoint: userinfoEndpoint,
119
- jwks_uri: jwksUri
120
- };
121
- })();
122
- cache.set(issuer, loader);
123
- try {
124
- return await loader;
125
- } catch (err) {
126
- cache.delete(issuer);
127
- throw err;
128
- }
78
+ //#endregion
79
+ //#region src/oauth/oidc.ts
80
+ var cache = /* @__PURE__ */ new Map();
81
+ var normalizeIssuer = (raw) => raw.trim().replace(/\/+$/, "");
82
+ var getOidcWellKnown = async (issuerRaw) => {
83
+ const issuer = normalizeIssuer(issuerRaw);
84
+ if (!issuer) throw new Error("OIDC issuer is required");
85
+ const existing = cache.get(issuer);
86
+ if (existing) return existing;
87
+ const loader = (async () => {
88
+ const url = new URL("/.well-known/openid-configuration", issuer);
89
+ const response = await fetch(url, { headers: { Accept: "application/json" } });
90
+ if (!response.ok) {
91
+ const body = await response.text().catch(() => "");
92
+ throw new Error(`Failed to fetch OIDC discovery document: ${response.status} ${body}`);
93
+ }
94
+ const json = await response.json().catch(() => null);
95
+ if (!json || typeof json !== "object") throw new Error("Invalid OIDC discovery document");
96
+ const authorizationEndpoint = typeof json.authorization_endpoint === "string" ? json.authorization_endpoint : "";
97
+ const tokenEndpoint = typeof json.token_endpoint === "string" ? json.token_endpoint : "";
98
+ const issuerValue = typeof json.issuer === "string" ? json.issuer : issuer;
99
+ const userinfoEndpoint = typeof json.userinfo_endpoint === "string" ? json.userinfo_endpoint : void 0;
100
+ const jwksUri = typeof json.jwks_uri === "string" ? json.jwks_uri : void 0;
101
+ if (!authorizationEndpoint || !tokenEndpoint) throw new Error("OIDC discovery document missing required endpoints");
102
+ return {
103
+ issuer: issuerValue,
104
+ authorization_endpoint: authorizationEndpoint,
105
+ token_endpoint: tokenEndpoint,
106
+ userinfo_endpoint: userinfoEndpoint,
107
+ jwks_uri: jwksUri
108
+ };
109
+ })();
110
+ cache.set(issuer, loader);
111
+ try {
112
+ return await loader;
113
+ } catch (err) {
114
+ cache.delete(issuer);
115
+ throw err;
116
+ }
129
117
  };
130
- const base64UrlEncode$1 = (input) => input.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
131
- const generatePkcePair = () => {
132
- const verifier = base64UrlEncode$1(crypto.randomBytes(32));
133
- const challenge = base64UrlEncode$1(crypto.createHash("sha256").update(verifier).digest());
134
- return {
135
- verifier,
136
- challenge
137
- };
118
+ //#endregion
119
+ //#region src/oauth/pkce.ts
120
+ var base64UrlEncode$1 = (input) => input.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
121
+ var generatePkcePair = () => {
122
+ const verifier = base64UrlEncode$1(crypto.randomBytes(32));
123
+ return {
124
+ verifier,
125
+ challenge: base64UrlEncode$1(crypto.createHash("sha256").update(verifier).digest())
126
+ };
138
127
  };
139
- const RESERVED_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
140
- const isSafeOAuthProviderId = (value) => {
141
- const providerId = value.trim();
142
- if (!providerId) return false;
143
- if (providerId.length > 128) return false;
144
- if (RESERVED_KEYS.has(providerId)) return false;
145
- return /^[a-zA-Z0-9_-]+$/.test(providerId);
128
+ //#endregion
129
+ //#region src/oauth/providerId.ts
130
+ var RESERVED_KEYS = /* @__PURE__ */ new Set([
131
+ "__proto__",
132
+ "prototype",
133
+ "constructor"
134
+ ]);
135
+ var isSafeOAuthProviderId = (value) => {
136
+ const providerId = value.trim();
137
+ if (!providerId) return false;
138
+ if (providerId.length > 128) return false;
139
+ if (RESERVED_KEYS.has(providerId)) return false;
140
+ return /^[a-zA-Z0-9_-]+$/.test(providerId);
146
141
  };
147
- const OAUTH_REQUEST_TTL_MS = 10 * 60 * 1e3;
148
- const OAUTH_STATE_COOKIE_NAME = "rb_oauth_state";
149
- const getQueryString = (value) => {
150
- if (typeof value === "string") return value;
151
- if (Array.isArray(value) && typeof value[0] === "string") return value[0];
152
- return null;
142
+ //#endregion
143
+ //#region src/oauth/lib.ts
144
+ var OAUTH_REQUEST_TTL_MS = 600 * 1e3;
145
+ var OAUTH_STATE_COOKIE_NAME = "rb_oauth_state";
146
+ var getQueryString = (value) => {
147
+ if (typeof value === "string") return value;
148
+ if (Array.isArray(value) && typeof value[0] === "string") return value[0];
149
+ return null;
153
150
  };
154
- const getCookieValue = (ctx, name) => {
155
- const header = ctx.req.headers?.cookie;
156
- if (typeof header !== "string" || !header) return null;
157
- for (const part of header.split(";")) {
158
- const [rawKey, ...rest] = part.split("=");
159
- const key = rawKey?.trim();
160
- if (!key || key !== name) continue;
161
- const rawValue = rest.join("=").trim();
162
- if (!rawValue) return "";
163
- try {
164
- return decodeURIComponent(rawValue);
165
- } catch {
166
- return rawValue;
167
- }
168
- }
169
- return null;
151
+ var getCookieValue = (ctx, name) => {
152
+ const header = ctx.req.headers?.cookie;
153
+ if (typeof header !== "string" || !header) return null;
154
+ for (const part of header.split(";")) {
155
+ const [rawKey, ...rest] = part.split("=");
156
+ const key = rawKey?.trim();
157
+ if (!key || key !== name) continue;
158
+ const rawValue = rest.join("=").trim();
159
+ if (!rawValue) return "";
160
+ try {
161
+ return decodeURIComponent(rawValue);
162
+ } catch {
163
+ return rawValue;
164
+ }
165
+ }
166
+ return null;
170
167
  };
171
- const isSafeRedirectPath = (candidate) => {
172
- if (!candidate.startsWith("/")) return false;
173
- if (candidate.startsWith("//")) return false;
174
- return true;
168
+ var isSafeRedirectPath = (candidate) => {
169
+ if (!candidate.startsWith("/")) return false;
170
+ if (candidate.startsWith("//")) return false;
171
+ return true;
175
172
  };
176
- const getRequestOrigin = (ctx) => {
177
- const protoHeader = ctx.req.headers["x-forwarded-proto"];
178
- const hostHeader = ctx.req.headers["x-forwarded-host"];
179
- const protocol = typeof protoHeader === "string" ? protoHeader.split(",")[0].trim() : ctx.req.protocol;
180
- const host = typeof hostHeader === "string" ? hostHeader.split(",")[0].trim() : ctx.req.get("host");
181
- return protocol && host ? `${protocol}://${host}` : "";
173
+ var getRequestOrigin = (ctx) => {
174
+ const protoHeader = ctx.req.headers["x-forwarded-proto"];
175
+ const hostHeader = ctx.req.headers["x-forwarded-host"];
176
+ const protocol = typeof protoHeader === "string" ? protoHeader.split(",")[0].trim() : ctx.req.protocol;
177
+ const host = typeof hostHeader === "string" ? hostHeader.split(",")[0].trim() : ctx.req.get("host");
178
+ return protocol && host ? `${protocol}://${host}` : "";
182
179
  };
183
- const resolveCallbackPath = (callbackPath) => {
184
- const trimmed = callbackPath.trim();
185
- return trimmed && isSafeRedirectPath(trimmed) ? trimmed : null;
180
+ var resolveCallbackPath = (callbackPath) => {
181
+ const trimmed = callbackPath.trim();
182
+ return trimmed && isSafeRedirectPath(trimmed) ? trimmed : null;
186
183
  };
187
- const readTextBody = async (req) => await new Promise((resolve, reject) => {
188
- let body = "";
189
- if (typeof req.setEncoding === "function") {
190
- req.setEncoding("utf8");
191
- }
192
- req.on("data", (chunk) => {
193
- body += String(chunk);
194
- if (body.length > 32768) {
195
- reject(new Error("request_body_too_large"));
196
- }
197
- });
198
- req.on("end", () => resolve(body));
199
- req.on("error", reject);
184
+ var readTextBody = async (req) => await new Promise((resolve, reject) => {
185
+ let body = "";
186
+ if (typeof req.setEncoding === "function") req.setEncoding("utf8");
187
+ req.on("data", (chunk) => {
188
+ body += String(chunk);
189
+ if (body.length > 32768) reject(/* @__PURE__ */ new Error("request_body_too_large"));
190
+ });
191
+ req.on("end", () => resolve(body));
192
+ req.on("error", reject);
200
193
  });
201
- const getFormPostParams = async (ctx) => {
202
- if (ctx.req.method !== "POST") return null;
203
- const contentType = typeof ctx.req.headers["content-type"] === "string" ? ctx.req.headers["content-type"] : "";
204
- if (!contentType.includes("application/x-www-form-urlencoded")) return null;
205
- const body = await readTextBody(ctx.req);
206
- const params = new URLSearchParams(body);
207
- const result = {};
208
- for (const [key, value] of params.entries()) {
209
- result[key] = value;
210
- }
211
- return result;
194
+ var getFormPostParams = async (ctx) => {
195
+ if (ctx.req.method !== "POST") return null;
196
+ if (!(typeof ctx.req.headers["content-type"] === "string" ? ctx.req.headers["content-type"] : "").includes("application/x-www-form-urlencoded")) return null;
197
+ const body = await readTextBody(ctx.req);
198
+ const params = new URLSearchParams(body);
199
+ const result = {};
200
+ for (const [key, value] of params.entries()) result[key] = value;
201
+ return result;
212
202
  };
213
- const resolveProviderId = (ctx, providerIdOverride) => (providerIdOverride ?? String(ctx.req.params?.provider ?? "")).trim();
214
- const RESERVED_AUTH_PARAM_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
215
- const normalizeAuthorizationParams = (value) => {
216
- if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
217
- const result = {};
218
- for (const [keyRaw, valueRaw] of Object.entries(value)) {
219
- const key = keyRaw.trim();
220
- if (!key) continue;
221
- if (RESERVED_AUTH_PARAM_KEYS.has(key)) continue;
222
- const val = typeof valueRaw === "string" ? valueRaw.trim() : "";
223
- if (!val) continue;
224
- result[key] = val;
225
- }
226
- return Object.keys(result).length ? result : void 0;
203
+ var resolveProviderId = (ctx, providerIdOverride) => (providerIdOverride ?? String(ctx.req.params?.provider ?? "")).trim();
204
+ var RESERVED_AUTH_PARAM_KEYS = /* @__PURE__ */ new Set([
205
+ "__proto__",
206
+ "prototype",
207
+ "constructor"
208
+ ]);
209
+ var normalizeAuthorizationParams = (value) => {
210
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
211
+ const result = {};
212
+ for (const [keyRaw, valueRaw] of Object.entries(value)) {
213
+ const key = keyRaw.trim();
214
+ if (!key) continue;
215
+ if (RESERVED_AUTH_PARAM_KEYS.has(key)) continue;
216
+ const val = typeof valueRaw === "string" ? valueRaw.trim() : "";
217
+ if (!val) continue;
218
+ result[key] = val;
219
+ }
220
+ return Object.keys(result).length ? result : void 0;
227
221
  };
228
- const getOAuthStartRedirectUrl = async ({
229
- ctx,
230
- providerId: providerIdOverride,
231
- returnTo,
232
- authorizationParams
233
- }) => {
234
- const providerId = resolveProviderId(ctx, providerIdOverride);
235
- if (!providerId) {
236
- return {
237
- success: false,
238
- error: "missing_provider",
239
- statusCode: 400
240
- };
241
- }
242
- if (!isSafeOAuthProviderId(providerId)) {
243
- return {
244
- success: false,
245
- error: "invalid_provider",
246
- statusCode: 400
247
- };
248
- }
249
- const provider = getOAuthProviderConfig(providerId);
250
- if (!provider) {
251
- return {
252
- success: false,
253
- error: "unknown_provider",
254
- statusCode: 404
255
- };
256
- }
257
- const origin = getRequestOrigin(ctx);
258
- if (!origin) {
259
- return {
260
- success: false,
261
- error: "origin_unavailable",
262
- statusCode: 500
263
- };
264
- }
265
- const isHttps = origin.startsWith("https://");
266
- const callbackPath = resolveCallbackPath(provider.callbackPath);
267
- if (!callbackPath) {
268
- return {
269
- success: false,
270
- error: "invalid_callback_path",
271
- statusCode: 500
272
- };
273
- }
274
- const state = crypto.randomBytes(16).toString("hex");
275
- const {
276
- verifier,
277
- challenge
278
- } = generatePkcePair();
279
- const safeReturnTo = typeof returnTo === "string" && isSafeRedirectPath(returnTo) ? returnTo : void 0;
280
- try {
281
- const OAuthRequest = await models.getGlobal("RBOAuthRequest", ctx);
282
- const now = /* @__PURE__ */ new Date();
283
- await OAuthRequest.create({
284
- _id: state,
285
- providerId,
286
- codeVerifier: verifier,
287
- returnTo: safeReturnTo,
288
- createdAt: now,
289
- expiresAt: new Date(now.getTime() + OAUTH_REQUEST_TTL_MS)
290
- });
291
- } catch (err) {
292
- console.warn("oauth::failed_to_store_request", err);
293
- return {
294
- success: false,
295
- error: "oauth_request_store_failed",
296
- statusCode: 500
297
- };
298
- }
299
- try {
300
- ctx.res.cookie(OAUTH_STATE_COOKIE_NAME, `${providerId}:${state}`, {
301
- httpOnly: true,
302
- secure: isHttps,
303
- sameSite: isHttps ? "none" : "lax",
304
- path: callbackPath,
305
- maxAge: OAUTH_REQUEST_TTL_MS
306
- });
307
- } catch (err) {
308
- console.warn("oauth::failed_to_set_state_cookie", err);
309
- return {
310
- success: false,
311
- error: "oauth_cookie_set_failed",
312
- statusCode: 500
313
- };
314
- }
315
- const oidc = await getOidcWellKnown(provider.issuer);
316
- const redirectUri = `${origin}${callbackPath}`;
317
- const url = new URL(oidc.authorization_endpoint);
318
- url.searchParams.set("client_id", provider.clientId);
319
- url.searchParams.set("redirect_uri", redirectUri);
320
- url.searchParams.set("response_type", "code");
321
- url.searchParams.set("scope", provider.scope);
322
- url.searchParams.set("state", state);
323
- url.searchParams.set("code_challenge", challenge);
324
- url.searchParams.set("code_challenge_method", "S256");
325
- const extraAuthorizationParams = normalizeAuthorizationParams(authorizationParams);
326
- for (const [key, value] of Object.entries(extraAuthorizationParams ?? {})) {
327
- if (url.searchParams.has(key)) continue;
328
- url.searchParams.set(key, value);
329
- }
330
- return {
331
- success: true,
332
- redirectUrl: url.toString()
333
- };
222
+ var getOAuthStartRedirectUrl = async ({ ctx, providerId: providerIdOverride, returnTo, authorizationParams }) => {
223
+ const providerId = resolveProviderId(ctx, providerIdOverride);
224
+ if (!providerId) return {
225
+ success: false,
226
+ error: "missing_provider",
227
+ statusCode: 400
228
+ };
229
+ if (!isSafeOAuthProviderId(providerId)) return {
230
+ success: false,
231
+ error: "invalid_provider",
232
+ statusCode: 400
233
+ };
234
+ const provider = getOAuthProviderConfig(providerId);
235
+ if (!provider) return {
236
+ success: false,
237
+ error: "unknown_provider",
238
+ statusCode: 404
239
+ };
240
+ const origin = getRequestOrigin(ctx);
241
+ if (!origin) return {
242
+ success: false,
243
+ error: "origin_unavailable",
244
+ statusCode: 500
245
+ };
246
+ const isHttps = origin.startsWith("https://");
247
+ const callbackPath = resolveCallbackPath(provider.callbackPath);
248
+ if (!callbackPath) return {
249
+ success: false,
250
+ error: "invalid_callback_path",
251
+ statusCode: 500
252
+ };
253
+ const state = crypto.randomBytes(16).toString("hex");
254
+ const { verifier, challenge } = generatePkcePair();
255
+ const safeReturnTo = typeof returnTo === "string" && isSafeRedirectPath(returnTo) ? returnTo : void 0;
256
+ try {
257
+ const OAuthRequest = await models.getGlobal("RBOAuthRequest", ctx);
258
+ const now = /* @__PURE__ */ new Date();
259
+ await OAuthRequest.create({
260
+ _id: state,
261
+ providerId,
262
+ codeVerifier: verifier,
263
+ returnTo: safeReturnTo,
264
+ createdAt: now,
265
+ expiresAt: new Date(now.getTime() + OAUTH_REQUEST_TTL_MS)
266
+ });
267
+ } catch (err) {
268
+ console.warn("oauth::failed_to_store_request", err);
269
+ return {
270
+ success: false,
271
+ error: "oauth_request_store_failed",
272
+ statusCode: 500
273
+ };
274
+ }
275
+ try {
276
+ ctx.res.cookie(OAUTH_STATE_COOKIE_NAME, `${providerId}:${state}`, {
277
+ httpOnly: true,
278
+ secure: isHttps,
279
+ sameSite: isHttps ? "none" : "lax",
280
+ path: callbackPath,
281
+ maxAge: OAUTH_REQUEST_TTL_MS
282
+ });
283
+ } catch (err) {
284
+ console.warn("oauth::failed_to_set_state_cookie", err);
285
+ return {
286
+ success: false,
287
+ error: "oauth_cookie_set_failed",
288
+ statusCode: 500
289
+ };
290
+ }
291
+ const oidc = await getOidcWellKnown(provider.issuer);
292
+ const redirectUri = `${origin}${callbackPath}`;
293
+ const url = new URL(oidc.authorization_endpoint);
294
+ url.searchParams.set("client_id", provider.clientId);
295
+ url.searchParams.set("redirect_uri", redirectUri);
296
+ url.searchParams.set("response_type", "code");
297
+ url.searchParams.set("scope", provider.scope);
298
+ url.searchParams.set("state", state);
299
+ url.searchParams.set("code_challenge", challenge);
300
+ url.searchParams.set("code_challenge_method", "S256");
301
+ const extraAuthorizationParams = normalizeAuthorizationParams(authorizationParams);
302
+ for (const [key, value] of Object.entries(extraAuthorizationParams ?? {})) {
303
+ if (url.searchParams.has(key)) continue;
304
+ url.searchParams.set(key, value);
305
+ }
306
+ return {
307
+ success: true,
308
+ redirectUrl: url.toString()
309
+ };
334
310
  };
335
- const OAUTH_SUCCESS_REDIRECT_PATH = "/onboarding";
336
- const AUTH_ERROR_REDIRECT_BASE = "/auth/sign-in";
337
- const processOAuthCallback = async ({
338
- ctx,
339
- providerId: providerIdOverride,
340
- createUserOnFirstSignIn,
341
- missingUserRedirectPath,
342
- successRedirectPath
343
- }) => {
344
- const providerId = resolveProviderId(ctx, providerIdOverride);
345
- if (!providerId) {
346
- return {
347
- success: false,
348
- type: "error",
349
- error: "missing_provider",
350
- redirectPath: `${AUTH_ERROR_REDIRECT_BASE}?error=missing_provider`
351
- };
352
- }
353
- if (!isSafeOAuthProviderId(providerId)) {
354
- return {
355
- success: false,
356
- type: "error",
357
- error: "invalid_provider",
358
- redirectPath: `${AUTH_ERROR_REDIRECT_BASE}?error=invalid_provider`
359
- };
360
- }
361
- const provider = getOAuthProviderConfig(providerId);
362
- if (!provider) {
363
- return {
364
- success: false,
365
- type: "error",
366
- error: "unknown_provider",
367
- redirectPath: `${AUTH_ERROR_REDIRECT_BASE}?error=unknown_provider`
368
- };
369
- }
370
- const origin = getRequestOrigin(ctx);
371
- if (!origin) {
372
- return {
373
- success: false,
374
- type: "error",
375
- error: "origin_unavailable",
376
- redirectPath: `${AUTH_ERROR_REDIRECT_BASE}?error=origin_unavailable`
377
- };
378
- }
379
- const isHttps = origin.startsWith("https://");
380
- const callbackPath = resolveCallbackPath(provider.callbackPath);
381
- if (!callbackPath) {
382
- return {
383
- success: false,
384
- type: "error",
385
- error: "invalid_callback_path",
386
- redirectPath: `${AUTH_ERROR_REDIRECT_BASE}?error=invalid_callback_path`
387
- };
388
- }
389
- const bodyParams = await getFormPostParams(ctx);
390
- const code = (getQueryString(bodyParams?.code) ?? getQueryString(ctx.req.query?.code))?.trim() ?? "";
391
- const state = (getQueryString(bodyParams?.state) ?? getQueryString(ctx.req.query?.state))?.trim() ?? "";
392
- const oauthUserRaw = (getQueryString(bodyParams?.user) ?? getQueryString(ctx.req.query?.user))?.trim() ?? "";
393
- if (!code || !state) {
394
- return {
395
- success: false,
396
- type: "error",
397
- error: "missing_code_or_state",
398
- redirectPath: `${AUTH_ERROR_REDIRECT_BASE}?error=missing_code_or_state`
399
- };
400
- }
401
- const stateCookieValue = getCookieValue(ctx, OAUTH_STATE_COOKIE_NAME);
402
- if (stateCookieValue !== `${providerId}:${state}`) {
403
- return {
404
- success: false,
405
- type: "error",
406
- error: "invalid_state",
407
- redirectPath: `${AUTH_ERROR_REDIRECT_BASE}?error=invalid_state`
408
- };
409
- }
410
- let oauthRequest = null;
411
- let OAuthRequestModel = null;
412
- try {
413
- OAuthRequestModel = await models.getGlobal("RBOAuthRequest", ctx);
414
- oauthRequest = await OAuthRequestModel.findOne({
415
- _id: state,
416
- providerId
417
- }).lean();
418
- } catch (err) {
419
- console.warn("oauth::failed_to_load_request", err);
420
- return {
421
- success: false,
422
- type: "error",
423
- error: "oauth_request_load_failed",
424
- redirectPath: `${AUTH_ERROR_REDIRECT_BASE}?error=oauth_request_load_failed`
425
- };
426
- }
427
- if (!oauthRequest) {
428
- return {
429
- success: false,
430
- type: "error",
431
- error: "invalid_state",
432
- redirectPath: `${AUTH_ERROR_REDIRECT_BASE}?error=invalid_state`
433
- };
434
- }
435
- const returnTo = typeof oauthRequest.returnTo === "string" ? oauthRequest.returnTo : void 0;
436
- const codeVerifier = typeof oauthRequest.codeVerifier === "string" ? oauthRequest.codeVerifier : "";
437
- if (!codeVerifier) {
438
- return {
439
- success: false,
440
- type: "error",
441
- error: "missing_code_verifier",
442
- redirectPath: `${AUTH_ERROR_REDIRECT_BASE}?error=missing_code_verifier`
443
- };
444
- }
445
- const oidc = await getOidcWellKnown(provider.issuer);
446
- const redirectUri = `${origin}${callbackPath}`;
447
- const tokenBody = new URLSearchParams();
448
- tokenBody.set("grant_type", "authorization_code");
449
- tokenBody.set("code", code);
450
- tokenBody.set("redirect_uri", redirectUri);
451
- tokenBody.set("client_id", provider.clientId);
452
- tokenBody.set("code_verifier", codeVerifier);
453
- if (provider.clientSecret) {
454
- tokenBody.set("client_secret", provider.clientSecret);
455
- }
456
- const tokenResponse = await fetch(oidc.token_endpoint, {
457
- method: "POST",
458
- headers: {
459
- "Content-Type": "application/x-www-form-urlencoded",
460
- Accept: "application/json"
461
- },
462
- body: tokenBody.toString()
463
- });
464
- const tokenJson = await tokenResponse.json().catch(() => null);
465
- if (!tokenResponse.ok || !tokenJson || typeof tokenJson !== "object") {
466
- const body = tokenJson ? JSON.stringify(tokenJson) : "";
467
- console.warn("oauth::token_exchange failed", tokenResponse.status, body);
468
- return {
469
- success: false,
470
- type: "error",
471
- error: "token_exchange_failed",
472
- redirectPath: `${AUTH_ERROR_REDIRECT_BASE}?error=token_exchange_failed`
473
- };
474
- }
475
- const accessToken = typeof tokenJson.access_token === "string" ? tokenJson.access_token : "";
476
- const refreshToken = typeof tokenJson.refresh_token === "string" ? tokenJson.refresh_token : void 0;
477
- const idToken = typeof tokenJson.id_token === "string" ? tokenJson.id_token : void 0;
478
- const scope = typeof tokenJson.scope === "string" ? tokenJson.scope : void 0;
479
- const tokenType = typeof tokenJson.token_type === "string" ? tokenJson.token_type : void 0;
480
- const expiresIn = typeof tokenJson.expires_in === "number" ? tokenJson.expires_in : typeof tokenJson.expires_in === "string" ? Number(tokenJson.expires_in) : void 0;
481
- const expiresInSeconds = typeof expiresIn === "number" && Number.isFinite(expiresIn) ? expiresIn : null;
482
- const expiresAt = expiresInSeconds !== null ? new Date(Date.now() + expiresInSeconds * 1e3) : void 0;
483
- let userInfo = null;
484
- if (oidc.userinfo_endpoint && accessToken) {
485
- const userInfoResponse = await fetch(oidc.userinfo_endpoint, {
486
- headers: {
487
- Authorization: `Bearer ${accessToken}`,
488
- Accept: "application/json"
489
- }
490
- });
491
- if (userInfoResponse.ok) {
492
- userInfo = await userInfoResponse.json().catch(() => null);
493
- }
494
- }
495
- const idTokenPayload = idToken ? decodeJwtPayload(idToken) : null;
496
- const accessTokenPayload = decodeJwtPayload(accessToken);
497
- const subject = typeof userInfo?.sub === "string" ? userInfo.sub : typeof idTokenPayload?.sub === "string" ? idTokenPayload.sub : typeof accessTokenPayload?.sub === "string" ? accessTokenPayload.sub : "";
498
- if (!subject) {
499
- return {
500
- success: false,
501
- type: "error",
502
- error: "missing_subject",
503
- redirectPath: `${AUTH_ERROR_REDIRECT_BASE}?error=missing_subject`
504
- };
505
- }
506
- const email = typeof userInfo?.email === "string" ? userInfo.email : typeof idTokenPayload?.email === "string" ? idTokenPayload.email : void 0;
507
- const name = typeof userInfo?.name === "string" ? userInfo.name : typeof idTokenPayload?.name === "string" ? idTokenPayload.name : void 0;
508
- let oauthUser = null;
509
- if (oauthUserRaw) {
510
- try {
511
- oauthUser = JSON.parse(oauthUserRaw);
512
- } catch {
513
- oauthUser = null;
514
- }
515
- }
516
- const oauthUserRecord = oauthUser && typeof oauthUser === "object" ? oauthUser : null;
517
- const oauthUserEmail = typeof oauthUserRecord?.email === "string" ? oauthUserRecord.email.trim() : "";
518
- const oauthUserNameRecord = oauthUserRecord?.name && typeof oauthUserRecord.name === "object" ? oauthUserRecord.name : null;
519
- const oauthUserNameFirst = typeof oauthUserNameRecord?.firstName === "string" ? oauthUserNameRecord.firstName.trim() : "";
520
- const oauthUserNameLast = typeof oauthUserNameRecord?.lastName === "string" ? oauthUserNameRecord.lastName.trim() : "";
521
- const oauthUserName = [oauthUserNameFirst, oauthUserNameLast].filter(Boolean).join(" ").trim();
522
- const resolvedEmail = email ?? (oauthUserEmail || void 0);
523
- const resolvedName = name ?? (oauthUserName || void 0);
524
- const [User, Tenant] = await Promise.all([models.getGlobal("RBUser", ctx), models.getGlobal("RBTenant", ctx)]);
525
- const subjectQueryKey = `oauthProviders.${providerId}.subject`;
526
- let user = await User.findOne({
527
- [subjectQueryKey]: subject
528
- });
529
- if (!user && resolvedEmail) {
530
- user = await User.findOne({
531
- email: resolvedEmail
532
- });
533
- }
534
- const shouldCreateUser = createUserOnFirstSignIn ?? true;
535
- if (!user && !shouldCreateUser) {
536
- const redirectPath2 = missingUserRedirectPath;
537
- const result = {
538
- success: false,
539
- type: "missing_user",
540
- providerId,
541
- subject,
542
- email: resolvedEmail,
543
- name: resolvedName
544
- };
545
- if (redirectPath2 && isSafeRedirectPath(redirectPath2)) {
546
- const url = new URL(redirectPath2, origin);
547
- url.searchParams.set("oauth_provider", providerId);
548
- if (resolvedEmail) url.searchParams.set("email", resolvedEmail);
549
- if (resolvedName) url.searchParams.set("name", resolvedName);
550
- result.redirectPath = `${url.pathname}${url.search}`;
551
- }
552
- return result;
553
- }
554
- if (!ctx.req.session) {
555
- return {
556
- success: false,
557
- type: "error",
558
- error: "session_unavailable",
559
- redirectPath: `${AUTH_ERROR_REDIRECT_BASE}?error=session_unavailable`
560
- };
561
- }
562
- const now = /* @__PURE__ */ new Date();
563
- let providerCreatedAt;
564
- const oauthProviders = user?.get("oauthProviders");
565
- const existingProvider = oauthProviders?.get(providerId);
566
- if (existingProvider?.createdAt instanceof Date) {
567
- providerCreatedAt = existingProvider.createdAt;
568
- }
569
- const oauthProviderPayload = {
570
- subject,
571
- email: resolvedEmail,
572
- name: resolvedName,
573
- accessToken,
574
- refreshToken,
575
- idToken,
576
- scope,
577
- tokenType,
578
- expiresAt,
579
- rawUserInfo: userInfo ?? void 0,
580
- createdAt: providerCreatedAt ?? now,
581
- updatedAt: now
582
- };
583
- if (!user) {
584
- const tenantId2 = crypto.randomUUID();
585
- const password = await hashPasswordForStorage(crypto.randomBytes(32).toString("hex"));
586
- user = new User({
587
- email: resolvedEmail,
588
- name: resolvedName,
589
- password,
590
- tenants: [tenantId2],
591
- tenantRoles: {
592
- [tenantId2]: ["owner"]
593
- },
594
- oauthProviders: {
595
- [providerId]: oauthProviderPayload
596
- }
597
- });
598
- await user.save();
599
- try {
600
- await Tenant.create({
601
- tenantId: tenantId2,
602
- name: resolvedEmail || subject
603
- });
604
- } catch (err) {
605
- console.warn("oauth::failed_to_create_tenant", err);
606
- }
607
- } else {
608
- const setFields = {
609
- [`oauthProviders.${providerId}`]: oauthProviderPayload
610
- };
611
- if (!user.email && resolvedEmail) {
612
- setFields.email = resolvedEmail;
613
- }
614
- if (!user.name && resolvedName) {
615
- setFields.name = resolvedName;
616
- }
617
- await User.updateOne({
618
- _id: user._id
619
- }, {
620
- $set: setFields
621
- });
622
- }
623
- const signedInTenants = (user.tenants || []).map(String);
624
- const tenantId = signedInTenants[0];
625
- if (!tenantId) {
626
- return {
627
- success: false,
628
- type: "error",
629
- error: "tenant_unavailable",
630
- redirectPath: `${AUTH_ERROR_REDIRECT_BASE}?error=tenant_unavailable`
631
- };
632
- }
633
- const tenantRolesMap = user.get("tenantRoles");
634
- const tenantRoles = tenantRolesMap ? Object.fromEntries(tenantRolesMap.entries()) : void 0;
635
- if (!completeAuthSignIn(ctx, {
636
- userId: user._id.toString(),
637
- currentTenantId: tenantId,
638
- signedInTenants: signedInTenants.length ? signedInTenants : [tenantId],
639
- tenantRoles
640
- })) {
641
- return {
642
- success: false,
643
- type: "error",
644
- error: "session_unavailable",
645
- redirectPath: `${AUTH_ERROR_REDIRECT_BASE}?error=session_unavailable`
646
- };
647
- }
648
- try {
649
- if (OAuthRequestModel) {
650
- await OAuthRequestModel.deleteOne({
651
- _id: state,
652
- providerId
653
- });
654
- }
655
- } catch (err) {
656
- console.warn("oauth::failed_to_delete_request", err);
657
- }
658
- try {
659
- ctx.res.clearCookie(OAUTH_STATE_COOKIE_NAME, {
660
- httpOnly: true,
661
- secure: isHttps,
662
- sameSite: isHttps ? "none" : "lax",
663
- path: callbackPath
664
- });
665
- } catch (err) {
666
- console.warn("oauth::failed_to_clear_state_cookie", err);
667
- }
668
- const redirectPath = returnTo && isSafeRedirectPath(returnTo) ? returnTo : successRedirectPath && isSafeRedirectPath(successRedirectPath) ? successRedirectPath : OAUTH_SUCCESS_REDIRECT_PATH;
669
- return {
670
- success: true,
671
- type: "signed_in",
672
- redirectPath,
673
- userId: user._id.toString(),
674
- tenantId,
675
- signedInTenants: signedInTenants.length ? signedInTenants : [tenantId]
676
- };
311
+ var OAUTH_SUCCESS_REDIRECT_PATH = "/onboarding";
312
+ var AUTH_ERROR_REDIRECT_BASE = "/auth/sign-in";
313
+ var processOAuthCallback = async ({ ctx, providerId: providerIdOverride, createUserOnFirstSignIn, missingUserRedirectPath, successRedirectPath }) => {
314
+ const providerId = resolveProviderId(ctx, providerIdOverride);
315
+ if (!providerId) return {
316
+ success: false,
317
+ type: "error",
318
+ error: "missing_provider",
319
+ redirectPath: `${AUTH_ERROR_REDIRECT_BASE}?error=missing_provider`
320
+ };
321
+ if (!isSafeOAuthProviderId(providerId)) return {
322
+ success: false,
323
+ type: "error",
324
+ error: "invalid_provider",
325
+ redirectPath: `${AUTH_ERROR_REDIRECT_BASE}?error=invalid_provider`
326
+ };
327
+ const provider = getOAuthProviderConfig(providerId);
328
+ if (!provider) return {
329
+ success: false,
330
+ type: "error",
331
+ error: "unknown_provider",
332
+ redirectPath: `${AUTH_ERROR_REDIRECT_BASE}?error=unknown_provider`
333
+ };
334
+ const origin = getRequestOrigin(ctx);
335
+ if (!origin) return {
336
+ success: false,
337
+ type: "error",
338
+ error: "origin_unavailable",
339
+ redirectPath: `${AUTH_ERROR_REDIRECT_BASE}?error=origin_unavailable`
340
+ };
341
+ const isHttps = origin.startsWith("https://");
342
+ const callbackPath = resolveCallbackPath(provider.callbackPath);
343
+ if (!callbackPath) return {
344
+ success: false,
345
+ type: "error",
346
+ error: "invalid_callback_path",
347
+ redirectPath: `${AUTH_ERROR_REDIRECT_BASE}?error=invalid_callback_path`
348
+ };
349
+ const bodyParams = await getFormPostParams(ctx);
350
+ const code = (getQueryString(bodyParams?.code) ?? getQueryString(ctx.req.query?.code))?.trim() ?? "";
351
+ const state = (getQueryString(bodyParams?.state) ?? getQueryString(ctx.req.query?.state))?.trim() ?? "";
352
+ const oauthUserRaw = (getQueryString(bodyParams?.user) ?? getQueryString(ctx.req.query?.user))?.trim() ?? "";
353
+ if (!code || !state) return {
354
+ success: false,
355
+ type: "error",
356
+ error: "missing_code_or_state",
357
+ redirectPath: `${AUTH_ERROR_REDIRECT_BASE}?error=missing_code_or_state`
358
+ };
359
+ if (getCookieValue(ctx, OAUTH_STATE_COOKIE_NAME) !== `${providerId}:${state}`) return {
360
+ success: false,
361
+ type: "error",
362
+ error: "invalid_state",
363
+ redirectPath: `${AUTH_ERROR_REDIRECT_BASE}?error=invalid_state`
364
+ };
365
+ let oauthRequest = null;
366
+ let OAuthRequestModel = null;
367
+ try {
368
+ OAuthRequestModel = await models.getGlobal("RBOAuthRequest", ctx);
369
+ oauthRequest = await OAuthRequestModel.findOne({
370
+ _id: state,
371
+ providerId
372
+ }).lean();
373
+ } catch (err) {
374
+ console.warn("oauth::failed_to_load_request", err);
375
+ return {
376
+ success: false,
377
+ type: "error",
378
+ error: "oauth_request_load_failed",
379
+ redirectPath: `${AUTH_ERROR_REDIRECT_BASE}?error=oauth_request_load_failed`
380
+ };
381
+ }
382
+ if (!oauthRequest) return {
383
+ success: false,
384
+ type: "error",
385
+ error: "invalid_state",
386
+ redirectPath: `${AUTH_ERROR_REDIRECT_BASE}?error=invalid_state`
387
+ };
388
+ const returnTo = typeof oauthRequest.returnTo === "string" ? oauthRequest.returnTo : void 0;
389
+ const codeVerifier = typeof oauthRequest.codeVerifier === "string" ? oauthRequest.codeVerifier : "";
390
+ if (!codeVerifier) return {
391
+ success: false,
392
+ type: "error",
393
+ error: "missing_code_verifier",
394
+ redirectPath: `${AUTH_ERROR_REDIRECT_BASE}?error=missing_code_verifier`
395
+ };
396
+ const oidc = await getOidcWellKnown(provider.issuer);
397
+ const redirectUri = `${origin}${callbackPath}`;
398
+ const tokenBody = new URLSearchParams();
399
+ tokenBody.set("grant_type", "authorization_code");
400
+ tokenBody.set("code", code);
401
+ tokenBody.set("redirect_uri", redirectUri);
402
+ tokenBody.set("client_id", provider.clientId);
403
+ tokenBody.set("code_verifier", codeVerifier);
404
+ if (provider.clientSecret) tokenBody.set("client_secret", provider.clientSecret);
405
+ const tokenResponse = await fetch(oidc.token_endpoint, {
406
+ method: "POST",
407
+ headers: {
408
+ "Content-Type": "application/x-www-form-urlencoded",
409
+ Accept: "application/json"
410
+ },
411
+ body: tokenBody.toString()
412
+ });
413
+ const tokenJson = await tokenResponse.json().catch(() => null);
414
+ if (!tokenResponse.ok || !tokenJson || typeof tokenJson !== "object") {
415
+ const body = tokenJson ? JSON.stringify(tokenJson) : "";
416
+ console.warn("oauth::token_exchange failed", tokenResponse.status, body);
417
+ return {
418
+ success: false,
419
+ type: "error",
420
+ error: "token_exchange_failed",
421
+ redirectPath: `${AUTH_ERROR_REDIRECT_BASE}?error=token_exchange_failed`
422
+ };
423
+ }
424
+ const accessToken = typeof tokenJson.access_token === "string" ? tokenJson.access_token : "";
425
+ const refreshToken = typeof tokenJson.refresh_token === "string" ? tokenJson.refresh_token : void 0;
426
+ const idToken = typeof tokenJson.id_token === "string" ? tokenJson.id_token : void 0;
427
+ const scope = typeof tokenJson.scope === "string" ? tokenJson.scope : void 0;
428
+ const tokenType = typeof tokenJson.token_type === "string" ? tokenJson.token_type : void 0;
429
+ const expiresIn = typeof tokenJson.expires_in === "number" ? tokenJson.expires_in : typeof tokenJson.expires_in === "string" ? Number(tokenJson.expires_in) : void 0;
430
+ const expiresInSeconds = typeof expiresIn === "number" && Number.isFinite(expiresIn) ? expiresIn : null;
431
+ const expiresAt = expiresInSeconds !== null ? new Date(Date.now() + expiresInSeconds * 1e3) : void 0;
432
+ let userInfo = null;
433
+ if (oidc.userinfo_endpoint && accessToken) {
434
+ const userInfoResponse = await fetch(oidc.userinfo_endpoint, { headers: {
435
+ Authorization: `Bearer ${accessToken}`,
436
+ Accept: "application/json"
437
+ } });
438
+ if (userInfoResponse.ok) userInfo = await userInfoResponse.json().catch(() => null);
439
+ }
440
+ const idTokenPayload = idToken ? decodeJwtPayload(idToken) : null;
441
+ const accessTokenPayload = decodeJwtPayload(accessToken);
442
+ const subject = typeof userInfo?.sub === "string" ? userInfo.sub : typeof idTokenPayload?.sub === "string" ? idTokenPayload.sub : typeof accessTokenPayload?.sub === "string" ? accessTokenPayload.sub : "";
443
+ if (!subject) return {
444
+ success: false,
445
+ type: "error",
446
+ error: "missing_subject",
447
+ redirectPath: `${AUTH_ERROR_REDIRECT_BASE}?error=missing_subject`
448
+ };
449
+ const email = typeof userInfo?.email === "string" ? userInfo.email : typeof idTokenPayload?.email === "string" ? idTokenPayload.email : void 0;
450
+ const name = typeof userInfo?.name === "string" ? userInfo.name : typeof idTokenPayload?.name === "string" ? idTokenPayload.name : void 0;
451
+ let oauthUser = null;
452
+ if (oauthUserRaw) try {
453
+ oauthUser = JSON.parse(oauthUserRaw);
454
+ } catch {
455
+ oauthUser = null;
456
+ }
457
+ const oauthUserRecord = oauthUser && typeof oauthUser === "object" ? oauthUser : null;
458
+ const oauthUserEmail = typeof oauthUserRecord?.email === "string" ? oauthUserRecord.email.trim() : "";
459
+ const oauthUserNameRecord = oauthUserRecord?.name && typeof oauthUserRecord.name === "object" ? oauthUserRecord.name : null;
460
+ const oauthUserName = [typeof oauthUserNameRecord?.firstName === "string" ? oauthUserNameRecord.firstName.trim() : "", typeof oauthUserNameRecord?.lastName === "string" ? oauthUserNameRecord.lastName.trim() : ""].filter(Boolean).join(" ").trim();
461
+ const resolvedEmail = email ?? (oauthUserEmail || void 0);
462
+ const resolvedName = name ?? (oauthUserName || void 0);
463
+ const [User, Tenant] = await Promise.all([models.getGlobal("RBUser", ctx), models.getGlobal("RBTenant", ctx)]);
464
+ const subjectQueryKey = `oauthProviders.${providerId}.subject`;
465
+ let user = await User.findOne({ [subjectQueryKey]: subject });
466
+ if (!user && resolvedEmail) user = await User.findOne({ email: resolvedEmail });
467
+ if (!user && !(createUserOnFirstSignIn ?? true)) {
468
+ const redirectPath = missingUserRedirectPath;
469
+ const result = {
470
+ success: false,
471
+ type: "missing_user",
472
+ providerId,
473
+ subject,
474
+ email: resolvedEmail,
475
+ name: resolvedName
476
+ };
477
+ if (redirectPath && isSafeRedirectPath(redirectPath)) {
478
+ const url = new URL(redirectPath, origin);
479
+ url.searchParams.set("oauth_provider", providerId);
480
+ if (resolvedEmail) url.searchParams.set("email", resolvedEmail);
481
+ if (resolvedName) url.searchParams.set("name", resolvedName);
482
+ result.redirectPath = `${url.pathname}${url.search}`;
483
+ }
484
+ return result;
485
+ }
486
+ if (!ctx.req.session) return {
487
+ success: false,
488
+ type: "error",
489
+ error: "session_unavailable",
490
+ redirectPath: `${AUTH_ERROR_REDIRECT_BASE}?error=session_unavailable`
491
+ };
492
+ const now = /* @__PURE__ */ new Date();
493
+ let providerCreatedAt;
494
+ const existingProvider = (user?.get("oauthProviders"))?.get(providerId);
495
+ if (existingProvider?.createdAt instanceof Date) providerCreatedAt = existingProvider.createdAt;
496
+ const oauthProviderPayload = {
497
+ subject,
498
+ email: resolvedEmail,
499
+ name: resolvedName,
500
+ accessToken,
501
+ refreshToken,
502
+ idToken,
503
+ scope,
504
+ tokenType,
505
+ expiresAt,
506
+ rawUserInfo: userInfo ?? void 0,
507
+ createdAt: providerCreatedAt ?? now,
508
+ updatedAt: now
509
+ };
510
+ if (!user) {
511
+ const tenantId = crypto.randomUUID();
512
+ user = new User({
513
+ email: resolvedEmail,
514
+ name: resolvedName,
515
+ password: await hashPasswordForStorage(crypto.randomBytes(32).toString("hex")),
516
+ tenants: [tenantId],
517
+ tenantRoles: { [tenantId]: ["owner"] },
518
+ oauthProviders: { [providerId]: oauthProviderPayload }
519
+ });
520
+ await user.save();
521
+ try {
522
+ await Tenant.create({
523
+ tenantId,
524
+ name: resolvedEmail || subject
525
+ });
526
+ } catch (err) {
527
+ console.warn("oauth::failed_to_create_tenant", err);
528
+ }
529
+ } else {
530
+ const setFields = { [`oauthProviders.${providerId}`]: oauthProviderPayload };
531
+ if (!user.email && resolvedEmail) setFields.email = resolvedEmail;
532
+ if (!user.name && resolvedName) setFields.name = resolvedName;
533
+ await User.updateOne({ _id: user._id }, { $set: setFields });
534
+ }
535
+ const signedInTenants = (user.tenants || []).map(String);
536
+ const tenantId = signedInTenants[0];
537
+ if (!tenantId) return {
538
+ success: false,
539
+ type: "error",
540
+ error: "tenant_unavailable",
541
+ redirectPath: `${AUTH_ERROR_REDIRECT_BASE}?error=tenant_unavailable`
542
+ };
543
+ const tenantRolesMap = user.get("tenantRoles");
544
+ const tenantRoles = tenantRolesMap ? Object.fromEntries(tenantRolesMap.entries()) : void 0;
545
+ if (!completeAuthSignIn(ctx, {
546
+ userId: user._id.toString(),
547
+ currentTenantId: tenantId,
548
+ signedInTenants: signedInTenants.length ? signedInTenants : [tenantId],
549
+ tenantRoles
550
+ })) return {
551
+ success: false,
552
+ type: "error",
553
+ error: "session_unavailable",
554
+ redirectPath: `${AUTH_ERROR_REDIRECT_BASE}?error=session_unavailable`
555
+ };
556
+ try {
557
+ if (OAuthRequestModel) await OAuthRequestModel.deleteOne({
558
+ _id: state,
559
+ providerId
560
+ });
561
+ } catch (err) {
562
+ console.warn("oauth::failed_to_delete_request", err);
563
+ }
564
+ try {
565
+ ctx.res.clearCookie(OAUTH_STATE_COOKIE_NAME, {
566
+ httpOnly: true,
567
+ secure: isHttps,
568
+ sameSite: isHttps ? "none" : "lax",
569
+ path: callbackPath
570
+ });
571
+ } catch (err) {
572
+ console.warn("oauth::failed_to_clear_state_cookie", err);
573
+ }
574
+ return {
575
+ success: true,
576
+ type: "signed_in",
577
+ redirectPath: returnTo && isSafeRedirectPath(returnTo) ? returnTo : successRedirectPath && isSafeRedirectPath(successRedirectPath) ? successRedirectPath : OAUTH_SUCCESS_REDIRECT_PATH,
578
+ userId: user._id.toString(),
579
+ tenantId,
580
+ signedInTenants: signedInTenants.length ? signedInTenants : [tenantId]
581
+ };
677
582
  };
678
- const createOAuthStartHandler = ({
679
- providerId,
680
- getAuthorizationParams
681
- } = {}) => async (_payload, ctx) => {
682
- const resolvedProviderId = resolveProviderId(ctx, providerId);
683
- const providerAuthorizationParams = resolvedProviderId && getAuthorizationParams ? getAuthorizationParams(resolvedProviderId) : void 0;
684
- const returnTo = getQueryString(ctx.req.query?.returnTo)?.trim();
685
- const result = await getOAuthStartRedirectUrl({
686
- ctx,
687
- providerId: resolvedProviderId,
688
- returnTo,
689
- authorizationParams: providerAuthorizationParams
690
- });
691
- if (!result.success) {
692
- ctx.res.status(result.statusCode);
693
- return {
694
- success: false,
695
- error: result.error
696
- };
697
- }
698
- ctx.res.redirect(result.redirectUrl);
699
- return {
700
- success: true
701
- };
583
+ var createOAuthStartHandler = ({ providerId, getAuthorizationParams } = {}) => async (_payload, ctx) => {
584
+ const resolvedProviderId = resolveProviderId(ctx, providerId);
585
+ const providerAuthorizationParams = resolvedProviderId && getAuthorizationParams ? getAuthorizationParams(resolvedProviderId) : void 0;
586
+ const returnTo = getQueryString(ctx.req.query?.returnTo)?.trim();
587
+ const result = await getOAuthStartRedirectUrl({
588
+ ctx,
589
+ providerId: resolvedProviderId,
590
+ returnTo,
591
+ authorizationParams: providerAuthorizationParams
592
+ });
593
+ if (!result.success) {
594
+ ctx.res.status(result.statusCode);
595
+ return {
596
+ success: false,
597
+ error: result.error
598
+ };
599
+ }
600
+ ctx.res.redirect(result.redirectUrl);
601
+ return { success: true };
702
602
  };
703
- const createOAuthCallbackHandler = ({
704
- providerId,
705
- createUserOnFirstSignIn,
706
- missingUserRedirectPath,
707
- successRedirectPath
708
- } = {}) => async (_payload, ctx) => {
709
- const result = await processOAuthCallback({
710
- ctx,
711
- providerId,
712
- createUserOnFirstSignIn,
713
- missingUserRedirectPath,
714
- successRedirectPath
715
- });
716
- if (result.type === "error") {
717
- ctx.res.redirect(result.redirectPath);
718
- return {
719
- success: false,
720
- error: result.error
721
- };
722
- }
723
- if (result.type === "missing_user") {
724
- if (result.redirectPath) {
725
- ctx.res.redirect(result.redirectPath);
726
- } else {
727
- ctx.res.redirect("/auth/sign-up");
728
- }
729
- return {
730
- success: false,
731
- error: "user_not_found"
732
- };
733
- }
734
- ctx.res.redirect(result.redirectPath);
735
- return {
736
- success: true
737
- };
603
+ var createOAuthCallbackHandler = ({ providerId, createUserOnFirstSignIn, missingUserRedirectPath, successRedirectPath } = {}) => async (_payload, ctx) => {
604
+ const result = await processOAuthCallback({
605
+ ctx,
606
+ providerId,
607
+ createUserOnFirstSignIn,
608
+ missingUserRedirectPath,
609
+ successRedirectPath
610
+ });
611
+ if (result.type === "error") {
612
+ ctx.res.redirect(result.redirectPath);
613
+ return {
614
+ success: false,
615
+ error: result.error
616
+ };
617
+ }
618
+ if (result.type === "missing_user") {
619
+ if (result.redirectPath) ctx.res.redirect(result.redirectPath);
620
+ else ctx.res.redirect("/auth/sign-up");
621
+ return {
622
+ success: false,
623
+ error: "user_not_found"
624
+ };
625
+ }
626
+ ctx.res.redirect(result.redirectPath);
627
+ return { success: true };
738
628
  };
739
- const base64UrlEncode = (input) => Buffer.from(input).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
740
- const createAppleClientSecret = ({
741
- teamId,
742
- clientId,
743
- keyId,
744
- privateKeyPem,
745
- now = /* @__PURE__ */ new Date(),
746
- expiresInSeconds = 10 * 60
747
- }) => {
748
- const teamIdValue = teamId.trim();
749
- const clientIdValue = clientId.trim();
750
- const keyIdValue = keyId.trim();
751
- const privateKeyPemValue = privateKeyPem.trim();
752
- if (!teamIdValue) throw new Error("teamId is required");
753
- if (!clientIdValue) throw new Error("clientId is required");
754
- if (!keyIdValue) throw new Error("keyId is required");
755
- if (!privateKeyPemValue) throw new Error("privateKeyPem is required");
756
- const nowSeconds = Math.floor(now.getTime() / 1e3);
757
- const expiresIn = Number.isFinite(expiresInSeconds) ? expiresInSeconds : 10 * 60;
758
- const exp = nowSeconds + Math.max(60, Math.min(expiresIn, 60 * 60 * 24 * 180));
759
- const header = {
760
- alg: "ES256",
761
- kid: keyIdValue,
762
- typ: "JWT"
763
- };
764
- const payload = {
765
- iss: teamIdValue,
766
- iat: nowSeconds,
767
- exp,
768
- aud: "https://appleid.apple.com",
769
- sub: clientIdValue
770
- };
771
- const signingInput = `${base64UrlEncode(JSON.stringify(header))}.${base64UrlEncode(JSON.stringify(payload))}`;
772
- const signature = crypto.sign("sha256", Buffer.from(signingInput), {
773
- key: privateKeyPemValue,
774
- dsaEncoding: "ieee-p1363"
775
- });
776
- return `${signingInput}.${base64UrlEncode(signature)}`;
629
+ var base64UrlEncode = (input) => Buffer.from(input).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
630
+ var createAppleClientSecret = ({ teamId, clientId, keyId, privateKeyPem, now = /* @__PURE__ */ new Date(), expiresInSeconds = 600 }) => {
631
+ const teamIdValue = teamId.trim();
632
+ const clientIdValue = clientId.trim();
633
+ const keyIdValue = keyId.trim();
634
+ const privateKeyPemValue = privateKeyPem.trim();
635
+ if (!teamIdValue) throw new Error("teamId is required");
636
+ if (!clientIdValue) throw new Error("clientId is required");
637
+ if (!keyIdValue) throw new Error("keyId is required");
638
+ if (!privateKeyPemValue) throw new Error("privateKeyPem is required");
639
+ const nowSeconds = Math.floor(now.getTime() / 1e3);
640
+ const exp = nowSeconds + Math.max(60, Math.min(Number.isFinite(expiresInSeconds) ? expiresInSeconds : 600, 3600 * 24 * 180));
641
+ const header = {
642
+ alg: "ES256",
643
+ kid: keyIdValue,
644
+ typ: "JWT"
645
+ };
646
+ const payload = {
647
+ iss: teamIdValue,
648
+ iat: nowSeconds,
649
+ exp,
650
+ aud: "https://appleid.apple.com",
651
+ sub: clientIdValue
652
+ };
653
+ const signingInput = `${base64UrlEncode(JSON.stringify(header))}.${base64UrlEncode(JSON.stringify(payload))}`;
654
+ return `${signingInput}.${base64UrlEncode(crypto.sign("sha256", Buffer.from(signingInput), {
655
+ key: privateKeyPemValue,
656
+ dsaEncoding: "ieee-p1363"
657
+ }))}`;
777
658
  };
778
- export {
779
- configureOAuthProviders,
780
- createAppleClientSecret,
781
- createOAuthCallbackHandler,
782
- createOAuthStartHandler,
783
- getOAuthStartRedirectUrl,
784
- processOAuthCallback,
785
- setOAuthProviders
786
- };
787
- //# sourceMappingURL=index.js.map
659
+ //#endregion
660
+ export { configureOAuthProviders, createAppleClientSecret, createOAuthCallbackHandler, createOAuthStartHandler, getOAuthStartRedirectUrl, processOAuthCallback, setOAuthProviders };
661
+
662
+ //# sourceMappingURL=index.js.map