@aura-stack/auth 0.1.0 → 0.2.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 (99) hide show
  1. package/dist/@types/index.d.ts +3 -4
  2. package/dist/@types/router.d.d.ts +3 -4
  3. package/dist/@types/utility.d.ts +1 -5
  4. package/dist/actions/callback/access-token.cjs +51 -41
  5. package/dist/actions/callback/access-token.d.ts +3 -4
  6. package/dist/actions/callback/access-token.js +4 -3
  7. package/dist/actions/callback/callback.cjs +115 -210
  8. package/dist/actions/callback/callback.d.ts +3 -4
  9. package/dist/actions/callback/callback.js +9 -10
  10. package/dist/actions/callback/userinfo.cjs +35 -22
  11. package/dist/actions/callback/userinfo.d.ts +3 -4
  12. package/dist/actions/callback/userinfo.js +6 -5
  13. package/dist/actions/csrfToken/csrfToken.cjs +34 -103
  14. package/dist/actions/csrfToken/csrfToken.js +6 -6
  15. package/dist/actions/index.cjs +234 -391
  16. package/dist/actions/index.d.ts +3 -4
  17. package/dist/actions/index.js +16 -17
  18. package/dist/actions/session/session.cjs +25 -109
  19. package/dist/actions/session/session.js +4 -5
  20. package/dist/actions/signIn/authorization.cjs +64 -55
  21. package/dist/actions/signIn/authorization.d.ts +3 -4
  22. package/dist/actions/signIn/authorization.js +5 -5
  23. package/dist/actions/signIn/signIn.cjs +84 -206
  24. package/dist/actions/signIn/signIn.d.ts +3 -4
  25. package/dist/actions/signIn/signIn.js +7 -9
  26. package/dist/actions/signOut/signOut.cjs +88 -234
  27. package/dist/actions/signOut/signOut.js +8 -9
  28. package/dist/assert.cjs +5 -0
  29. package/dist/assert.d.ts +9 -1
  30. package/dist/assert.js +3 -1
  31. package/dist/chunk-2RXNXMCZ.js +55 -0
  32. package/dist/{chunk-UJJ7R56J.js → chunk-4V4JNXVF.js} +13 -10
  33. package/dist/chunk-6R2YZ4AC.js +22 -0
  34. package/dist/{chunk-VFTYH33W.js → chunk-7H3OR6UU.js} +29 -9
  35. package/dist/{chunk-256KIVJL.js → chunk-CXLATHS5.js} +53 -9
  36. package/dist/{chunk-6SM22VVJ.js → chunk-EIL2FPSS.js} +5 -1
  37. package/dist/chunk-IMICRJ5U.js +197 -0
  38. package/dist/{chunk-EBPE35JT.js → chunk-IUYZQTJV.js} +0 -1
  39. package/dist/{chunk-GZU3RBTB.js → chunk-N2APGLXA.js} +19 -10
  40. package/dist/chunk-NEVKX6K2.js +70 -0
  41. package/dist/{chunk-XXJKNKGQ.js → chunk-PTJUYB33.js} +9 -13
  42. package/dist/chunk-QDO2KSRJ.js +35 -0
  43. package/dist/{chunk-CAKJT3KS.js → chunk-QEZL7EYN.js} +21 -17
  44. package/dist/chunk-RRLIF4PQ.js +55 -0
  45. package/dist/chunk-TLE4PXY3.js +39 -0
  46. package/dist/chunk-UEH3LVON.js +97 -0
  47. package/dist/{chunk-HMRKN75I.js → chunk-WD7AUHQ5.js} +12 -7
  48. package/dist/{chunk-RLT4RFKV.js → chunk-ZLR3LI6X.js} +19 -9
  49. package/dist/cookie.cjs +140 -99
  50. package/dist/cookie.d.ts +33 -43
  51. package/dist/cookie.js +10 -17
  52. package/dist/errors.cjs +85 -0
  53. package/dist/errors.d.ts +48 -0
  54. package/dist/errors.js +18 -0
  55. package/dist/{index-DpfbvTZ_.d.ts → index-EqsoyjrF.d.ts} +139 -57
  56. package/dist/index.cjs +427 -389
  57. package/dist/index.d.ts +4 -5
  58. package/dist/index.js +37 -26
  59. package/dist/jose.cjs +23 -12
  60. package/dist/jose.d.ts +4 -1
  61. package/dist/jose.js +5 -4
  62. package/dist/oauth/bitbucket.d.ts +3 -4
  63. package/dist/oauth/discord.cjs +0 -1
  64. package/dist/oauth/discord.d.ts +3 -4
  65. package/dist/oauth/discord.js +1 -1
  66. package/dist/oauth/figma.d.ts +3 -4
  67. package/dist/oauth/github.d.ts +3 -4
  68. package/dist/oauth/gitlab.d.ts +3 -4
  69. package/dist/oauth/index.cjs +132 -6
  70. package/dist/oauth/index.d.ts +3 -4
  71. package/dist/oauth/index.js +12 -5
  72. package/dist/oauth/spotify.d.ts +3 -4
  73. package/dist/{response.cjs → oauth/strava.cjs} +21 -9
  74. package/dist/oauth/strava.d.ts +6 -0
  75. package/dist/oauth/strava.js +6 -0
  76. package/dist/oauth/x.d.ts +3 -4
  77. package/dist/schemas.cjs +11 -5
  78. package/dist/schemas.d.ts +70 -67
  79. package/dist/schemas.js +3 -1
  80. package/dist/secure.cjs +27 -19
  81. package/dist/secure.d.ts +3 -4
  82. package/dist/secure.js +4 -3
  83. package/dist/utils.cjs +90 -15
  84. package/dist/utils.d.ts +11 -2
  85. package/dist/utils.js +8 -4
  86. package/package.json +5 -6
  87. package/dist/chunk-FJUDBLCP.js +0 -59
  88. package/dist/chunk-HGJ4TXY4.js +0 -137
  89. package/dist/chunk-JAPMIE6S.js +0 -10
  90. package/dist/chunk-LLR722CL.js +0 -96
  91. package/dist/chunk-SJPDVKUS.js +0 -112
  92. package/dist/chunk-SMQO5WD7.js +0 -30
  93. package/dist/chunk-UTDLUEEG.js +0 -31
  94. package/dist/chunk-ZV4BH47P.js +0 -154
  95. package/dist/error.cjs +0 -88
  96. package/dist/error.d.ts +0 -62
  97. package/dist/error.js +0 -16
  98. package/dist/response.d.ts +0 -10
  99. package/dist/response.js +0 -6
@@ -42,68 +42,53 @@ module.exports = __toCommonJS(actions_exports);
42
42
  var import_zod = __toESM(require("zod"), 1);
43
43
  var import_router2 = require("@aura-stack/router");
44
44
 
45
- // src/response.ts
46
- var AuraResponse = class extends Response {
47
- static json(body, init) {
48
- return Response.json(body, init);
49
- }
50
- };
51
-
52
45
  // src/secure.ts
53
46
  var import_node_crypto = __toESM(require("crypto"), 1);
54
47
 
55
48
  // src/utils.ts
56
49
  var import_router = require("@aura-stack/router");
57
50
 
58
- // src/error.ts
59
- var AuthError = class extends Error {
60
- constructor(type, message) {
61
- super(message);
62
- this.type = type;
63
- this.name = "AuthError";
51
+ // src/errors.ts
52
+ var OAuthProtocolError = class extends Error {
53
+ type = "OAUTH_PROTOCOL_ERROR";
54
+ error;
55
+ errorURI;
56
+ constructor(error, description, errorURI, options2) {
57
+ super(description, options2);
58
+ this.error = error;
59
+ this.errorURI = errorURI;
60
+ this.name = new.target.name;
61
+ Error.captureStackTrace(this, new.target);
64
62
  }
65
63
  };
66
- var InvalidCsrfTokenError = class extends AuthError {
67
- constructor(message = "The provided CSRF token is invalid or has expired") {
68
- super("invalid_csrf_token", message);
69
- this.name = "InvalidCsrfTokenError";
64
+ var AuthInternalError = class extends Error {
65
+ type = "AUTH_INTERNAL_ERROR";
66
+ code;
67
+ constructor(code, message, options2) {
68
+ super(message, options2);
69
+ this.code = code;
70
+ this.name = new.target.name;
71
+ Error.captureStackTrace(this, new.target);
70
72
  }
71
73
  };
72
- var InvalidRedirectToError = class extends AuthError {
73
- constructor(message = "The redirectTo parameter does not match the hosted origin.") {
74
- super("invalid_redirect_to", message);
75
- this.name = "InvalidRedirectToError";
74
+ var AuthSecurityError = class extends Error {
75
+ type = "AUTH_SECURITY_ERROR";
76
+ code;
77
+ constructor(code, message, options2) {
78
+ super(message, options2);
79
+ this.code = code;
80
+ this.name = new.target.name;
81
+ Error.captureStackTrace(this, new.target);
76
82
  }
77
83
  };
78
- var isAuthError = (error) => {
79
- return error instanceof AuthError;
84
+ var isNativeError = (error) => {
85
+ return error instanceof Error;
80
86
  };
81
- var throwAuthError = (error, message) => {
82
- if (error instanceof Error) {
83
- if (isAuthError(error)) {
84
- throw error;
85
- }
86
- throw new AuthError("invalid_request", error.message ?? message);
87
- }
87
+ var isOAuthProtocolError = (error) => {
88
+ return error instanceof OAuthProtocolError;
88
89
  };
89
- var ERROR_RESPONSE = {
90
- AUTHORIZATION: {
91
- INVALID_REQUEST: "invalid_request",
92
- UNAUTHORIZED_CLIENT: "unauthorized_client",
93
- ACCESS_DENIED: "access_denied",
94
- UNSUPPORTED_RESPONSE_TYPE: "unsupported_response_type",
95
- INVALID_SCOPE: "invalid_scope",
96
- SERVER_ERROR: "server_error",
97
- TEMPORARILY_UNAVAILABLE: "temporarily_unavailable"
98
- },
99
- ACCESS_TOKEN: {
100
- INVALID_REQUEST: "invalid_request",
101
- INVALID_CLIENT: "invalid_client",
102
- INVALID_GRANT: "invalid_grant",
103
- UNAUTHORIZED_CLIENT: "unauthorized_client",
104
- UNSUPPORTED_GRANT_TYPE: "unsupported_grant_type",
105
- INVALID_SCOPE: "invalid_scope"
106
- }
90
+ var isAuthSecurityError = (error) => {
91
+ return error instanceof AuthSecurityError;
107
92
  };
108
93
 
109
94
  // src/utils.ts
@@ -123,9 +108,9 @@ var equals = (a, b) => {
123
108
  if (a === null || b === null || a === void 0 || b === void 0) return false;
124
109
  return a === b;
125
110
  };
126
- var sanitizeURL = (url2) => {
111
+ var sanitizeURL = (url) => {
127
112
  try {
128
- let decodedURL = decodeURIComponent(url2).trim();
113
+ let decodedURL = decodeURIComponent(url).trim();
129
114
  const protocolMatch = decodedURL.match(/^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)/);
130
115
  let protocol = "";
131
116
  let rest = decodedURL;
@@ -153,7 +138,7 @@ var sanitizeURL = (url2) => {
153
138
  }
154
139
  return sanitized;
155
140
  } catch {
156
- return url2.trim();
141
+ return url.trim();
157
142
  }
158
143
  };
159
144
  var isValidRelativePath = (path) => {
@@ -166,10 +151,10 @@ var isValidRelativePath = (path) => {
166
151
  };
167
152
  var getNormalizedOriginPath = (path) => {
168
153
  try {
169
- const url2 = new URL(path);
170
- url2.hash = "";
171
- url2.search = "";
172
- return `${url2.origin}${url2.pathname}`;
154
+ const url = new URL(path);
155
+ url.hash = "";
156
+ url.search = "";
157
+ return `${url.origin}${url.pathname}`;
173
158
  } catch {
174
159
  return sanitizeURL(path);
175
160
  }
@@ -177,6 +162,31 @@ var getNormalizedOriginPath = (path) => {
177
162
  var toISOString = (date) => {
178
163
  return new Date(date).toISOString();
179
164
  };
165
+ var formatZodError = (error) => {
166
+ if (!error.issues || error.issues.length === 0) {
167
+ return {};
168
+ }
169
+ return error.issues.reduce((previous, issue) => {
170
+ const key = issue.path.join(".");
171
+ return {
172
+ ...previous,
173
+ [key]: {
174
+ code: issue.code,
175
+ message: issue.message
176
+ }
177
+ };
178
+ }, {});
179
+ };
180
+
181
+ // src/assert.ts
182
+ var isValidURL = (value) => {
183
+ if (value.includes("\r\n") || value.includes("\n") || value.includes("\r")) return false;
184
+ const regex = /^https?:\/\/(?:[a-zA-Z0-9._-]+|localhost|\[[0-9a-fA-F:]+\])(?::\d{1,5})?(?:\/[a-zA-Z0-9._~!$&'()*+,;=:@-]*)*\/?$/;
185
+ return regex.test(value);
186
+ };
187
+ var isJWTPayloadWithToken = (payload) => {
188
+ return typeof payload === "object" && payload !== null && "token" in payload && typeof payload?.token === "string";
189
+ };
180
190
 
181
191
  // src/secure.ts
182
192
  var generateSecure = (length = 32) => {
@@ -205,166 +215,35 @@ var createCSRF = async (jose, csrfCookie) => {
205
215
  };
206
216
  var verifyCSRF = async (jose, cookie, header) => {
207
217
  try {
208
- const { token: cookieToken } = await jose.verifyJWS(cookie);
209
- const { token: headerToken } = await jose.verifyJWS(header);
210
- const cookieBuffer = Buffer.from(cookieToken);
211
- const headerBuffer = Buffer.from(headerToken);
218
+ const cookiePayload = await jose.verifyJWS(cookie);
219
+ const headerPayload = await jose.verifyJWS(header);
220
+ if (!isJWTPayloadWithToken(cookiePayload)) {
221
+ throw new AuthSecurityError("CSRF_TOKEN_INVALID", "Cookie payload missing token field.");
222
+ }
223
+ if (!isJWTPayloadWithToken(headerPayload)) {
224
+ throw new AuthSecurityError("CSRF_TOKEN_INVALID", "Header payload missing token field.");
225
+ }
226
+ const cookieBuffer = Buffer.from(cookiePayload.token);
227
+ const headerBuffer = Buffer.from(headerPayload.token);
212
228
  if (!equals(headerBuffer.length, cookieBuffer.length)) {
213
- throw new InvalidCsrfTokenError();
229
+ throw new AuthSecurityError("CSRF_TOKEN_INVALID", "The CSRF tokens do not match.");
214
230
  }
215
231
  if (!import_node_crypto.default.timingSafeEqual(cookieBuffer, headerBuffer)) {
216
- throw new InvalidCsrfTokenError();
232
+ throw new AuthSecurityError("CSRF_TOKEN_INVALID", "The CSRF tokens do not match.");
217
233
  }
218
234
  return true;
219
235
  } catch {
220
- throw new InvalidCsrfTokenError();
221
- }
222
- };
223
-
224
- // src/cookie.ts
225
- var import_cookie = require("cookie");
226
-
227
- // src/assert.ts
228
- var isRequest = (value) => {
229
- return typeof Request !== "undefined" && value instanceof Request;
230
- };
231
- var isValidURL = (value) => {
232
- if (value.includes("\r\n") || value.includes("\n") || value.includes("\r")) return false;
233
- const regex = /^https?:\/\/(?:[a-zA-Z0-9._-]+|localhost|\[[0-9a-fA-F:]+\])(?::\d{1,5})?(?:\/[a-zA-Z0-9._~!$&'()*+,;=:@-]*)*\/?$/;
234
- return regex.test(value);
235
- };
236
-
237
- // src/cookie.ts
238
- var import_cookie2 = require("cookie");
239
- var COOKIE_NAME = "aura-auth";
240
- var defaultCookieOptions = {
241
- httpOnly: true,
242
- sameSite: "lax",
243
- path: "/",
244
- maxAge: 60 * 60 * 24 * 15
245
- };
246
- var defaultStandardCookieConfig = {
247
- secure: false,
248
- httpOnly: true,
249
- prefix: ""
250
- };
251
- var defaultSecureCookieConfig = {
252
- secure: true,
253
- prefix: "__Secure-"
254
- };
255
- var defaultHostCookieConfig = {
256
- secure: true,
257
- prefix: "__Host-",
258
- path: "/",
259
- domain: void 0
260
- };
261
- var expiredCookieOptions = {
262
- ...defaultCookieOptions,
263
- expires: /* @__PURE__ */ new Date(0),
264
- maxAge: 0
265
- };
266
- var defineDefaultCookieOptions = (options2) => {
267
- return {
268
- name: options2?.name ?? COOKIE_NAME,
269
- prefix: options2?.prefix ?? (options2?.secure ? "__Secure-" : ""),
270
- ...defaultCookieOptions,
271
- ...options2
272
- };
273
- };
274
- var setCookie = (cookieName, value, options2) => {
275
- const { prefix, name } = defineDefaultCookieOptions(options2);
276
- const cookieNameWithPrefix = `${prefix}${name}.${cookieName}`;
277
- return (0, import_cookie.serialize)(cookieNameWithPrefix, value, {
278
- ...defaultCookieOptions,
279
- ...options2
280
- });
281
- };
282
- var getCookie = (petition, cookie, options2, optional = false) => {
283
- const cookies = isRequest(petition) ? petition.headers.get("Cookie") : petition.headers.getSetCookie().join("; ");
284
- if (!cookies) {
285
- if (optional) {
286
- return "";
287
- }
288
- throw new AuthError("invalid_request", "No cookies found. There is no active session");
289
- }
290
- const { name, prefix } = defineDefaultCookieOptions(options2);
291
- const parsedCookies = (0, import_cookie.parse)(cookies);
292
- const value = parsedCookies[`${prefix}${name}.${cookie}`];
293
- if (value === void 0) {
294
- if (optional) {
295
- return "";
296
- }
297
- throw new AuthError("invalid_request", `Cookie "${cookie}" not found. There is no active session`);
298
- }
299
- return value;
300
- };
301
- var createSessionCookie = async (session, cookieOptions, jose) => {
302
- try {
303
- const encoded = await jose.encodeJWT(session);
304
- return setCookie("sessionToken", encoded, cookieOptions);
305
- } catch (error) {
306
- throw new AuthError("server_error", "Failed to create session cookie", { cause: error });
307
- }
308
- };
309
- var secureCookieOptions = (request, cookieOptions, trustedProxyHeaders) => {
310
- const name = cookieOptions.name ?? COOKIE_NAME;
311
- const isSecure = trustedProxyHeaders ? request.url.startsWith("https://") || request.headers.get("X-Forwarded-Proto") === "https" || request.headers.get("Forwarded")?.includes("proto=https") : request.url.startsWith("https://");
312
- if (!cookieOptions.options?.httpOnly) {
313
- console.warn(
314
- "[WARNING]: Cookie is configured without HttpOnly. This allows JavaScript access via document.cookie and increases XSS risk."
315
- );
316
- }
317
- if (cookieOptions.options?.domain === "*") {
318
- console.warn("[WARNING]: Cookie 'Domain' is set to '*', which is insecure. Avoid wildcard domains.");
319
- }
320
- if (!isSecure) {
321
- const options2 = cookieOptions.options;
322
- if (options2?.secure) {
323
- console.warn(
324
- "[WARNING]: The 'Secure' attribute will be disabled for this cookie. Serve over HTTPS to enforce Secure cookies."
325
- );
326
- }
327
- if (options2?.sameSite == "none") {
328
- console.warn("[WARNING]: SameSite=None without a secure connection can be blocked by browsers.");
329
- }
330
- if (process.env.NODE_ENV === "production") {
331
- console.warn("[WARNING]: In production, ensure cookies are served over HTTPS to maintain security.");
332
- }
333
- return {
334
- ...defaultCookieOptions,
335
- ...cookieOptions.options,
336
- sameSite: options2?.sameSite === "none" ? "lax" : options2?.sameSite ?? "lax",
337
- ...defaultStandardCookieConfig,
338
- name
339
- };
236
+ throw new AuthSecurityError("CSRF_TOKEN_INVALID", "The CSRF tokens do not match.");
340
237
  }
341
- return cookieOptions.strategy === "host" ? {
342
- ...defaultCookieOptions,
343
- ...cookieOptions.options,
344
- ...defaultHostCookieConfig,
345
- name
346
- } : { ...defaultCookieOptions, ...cookieOptions.options, ...defaultSecureCookieConfig, name };
347
- };
348
- var expireCookie = (name, options2) => {
349
- return setCookie(name, "", { ...options2, ...expiredCookieOptions });
350
- };
351
- var oauthCookie = (options2) => {
352
- return {
353
- ...options2,
354
- secure: options2.secure,
355
- httpOnly: options2.httpOnly,
356
- maxAge: 5 * 60,
357
- expires: new Date(Date.now() + 5 * 60 * 1e3)
358
- };
359
238
  };
360
239
 
361
240
  // src/schemas.ts
362
241
  var import_v4 = require("zod/v4");
363
242
  var OAuthProviderConfigSchema = (0, import_v4.object)({
364
- authorizeURL: (0, import_v4.url)(),
365
- accessToken: (0, import_v4.url)(),
243
+ authorizeURL: (0, import_v4.httpUrl)(),
244
+ accessToken: (0, import_v4.httpUrl)(),
366
245
  scope: (0, import_v4.string)().optional(),
367
- userInfo: (0, import_v4.url)(),
246
+ userInfo: (0, import_v4.httpUrl)(),
368
247
  responseType: (0, import_v4.enum)(["code", "token", "id_token"]),
369
248
  clientId: (0, import_v4.string)(),
370
249
  clientSecret: (0, import_v4.string)()
@@ -376,8 +255,8 @@ var OAuthAuthorization = OAuthProviderConfigSchema.extend({
376
255
  codeChallengeMethod: (0, import_v4.enum)(["plain", "S256"])
377
256
  });
378
257
  var OAuthAuthorizationResponse = (0, import_v4.object)({
379
- state: (0, import_v4.string)(),
380
- code: (0, import_v4.string)()
258
+ state: (0, import_v4.string)("Missing state parameter in the OAuth authorization response."),
259
+ code: (0, import_v4.string)("Missing code parameter in the OAuth authorization response.")
381
260
  });
382
261
  var OAuthAuthorizationErrorResponse = (0, import_v4.object)({
383
262
  error: (0, import_v4.enum)([
@@ -421,12 +300,17 @@ var OAuthErrorResponse = (0, import_v4.object)({
421
300
  error: (0, import_v4.string)(),
422
301
  error_description: (0, import_v4.string)().optional()
423
302
  });
303
+ var OAuthEnvSchema = (0, import_v4.object)({
304
+ clientId: import_v4.z.string().min(1, "OAuth Client ID is required in the environment variables."),
305
+ clientSecret: import_v4.z.string().min(1, "OAuth Client Secret is required in the environment variables.")
306
+ });
424
307
 
425
308
  // src/actions/signIn/authorization.ts
426
309
  var createAuthorizationURL = (oauthConfig, redirectURI, state, codeChallenge, codeChallengeMethod) => {
427
310
  const parsed = OAuthAuthorization.safeParse({ ...oauthConfig, redirectURI, state, codeChallenge, codeChallengeMethod });
428
311
  if (!parsed.success) {
429
- throw new AuthError(ERROR_RESPONSE.AUTHORIZATION.SERVER_ERROR, "Invalid OAuth configuration");
312
+ const msg = JSON.stringify(formatZodError(parsed.error), null, 2);
313
+ throw new AuthInternalError("INVALID_OAUTH_CONFIGURATION", msg);
430
314
  }
431
315
  const { authorizeURL, ...options2 } = parsed.data;
432
316
  const { userInfo, accessToken, clientSecret, ...required } = options2;
@@ -444,8 +328,8 @@ var getOriginURL = (request, trustedProxyHeaders) => {
444
328
  }
445
329
  };
446
330
  var createRedirectURI = (request, oauth, basePath, trustedProxyHeaders) => {
447
- const url2 = getOriginURL(request, trustedProxyHeaders);
448
- return `${url2.origin}${basePath}/callback/${oauth}`;
331
+ const url = getOriginURL(request, trustedProxyHeaders);
332
+ return `${url.origin}${basePath}/callback/${oauth}`;
449
333
  };
450
334
  var createRedirectTo = (request, redirectTo, trustedProxyHeaders) => {
451
335
  try {
@@ -459,15 +343,18 @@ var createRedirectTo = (request, redirectTo, trustedProxyHeaders) => {
459
343
  }
460
344
  const redirectToURL = new URL(sanitizeURL(getNormalizedOriginPath(redirectTo)));
461
345
  if (!isValidURL(redirectTo) || !equals(redirectToURL.origin, hostedURL.origin)) {
462
- throw new InvalidRedirectToError();
346
+ throw new AuthSecurityError(
347
+ "POTENTIAL_OPEN_REDIRECT_ATTACK_DETECTED",
348
+ "The redirectTo parameter does not match the hosted origin."
349
+ );
463
350
  }
464
351
  return sanitizeURL(redirectToURL.pathname);
465
352
  }
466
353
  if (referer) {
467
354
  const refererURL = new URL(sanitizeURL(referer));
468
355
  if (!isValidURL(referer) || !equals(refererURL.origin, hostedURL.origin)) {
469
- throw new AuthError(
470
- ERROR_RESPONSE.AUTHORIZATION.INVALID_REQUEST,
356
+ throw new AuthSecurityError(
357
+ "POTENTIAL_OPEN_REDIRECT_ATTACK_DETECTED",
471
358
  "The referer of the request does not match the hosted origin."
472
359
  );
473
360
  }
@@ -476,16 +363,16 @@ var createRedirectTo = (request, redirectTo, trustedProxyHeaders) => {
476
363
  if (origin) {
477
364
  const originURL = new URL(sanitizeURL(getNormalizedOriginPath(origin)));
478
365
  if (!isValidURL(origin) || !equals(originURL.origin, hostedURL.origin)) {
479
- throw new AuthError(ERROR_RESPONSE.AUTHORIZATION.INVALID_REQUEST, "Invalid origin (potential CSRF).");
366
+ throw new AuthSecurityError("POTENTIAL_OPEN_REDIRECT_ATTACK_DETECTED", "Invalid origin (potential CSRF).");
480
367
  }
481
368
  return sanitizeURL(originURL.pathname);
482
369
  }
483
370
  return "/";
484
371
  } catch (error) {
485
- if (isAuthError(error)) {
372
+ if (isAuthSecurityError(error)) {
486
373
  throw error;
487
374
  }
488
- throw new AuthError(ERROR_RESPONSE.AUTHORIZATION.INVALID_REQUEST, "Invalid origin (potential CSRF).");
375
+ throw new AuthSecurityError("POTENTIAL_OPEN_REDIRECT_ATTACK_DETECTED", "Invalid origin (potential CSRF).");
489
376
  }
490
377
  };
491
378
 
@@ -494,7 +381,7 @@ var signInConfig = (oauth) => {
494
381
  return (0, import_router2.createEndpointConfig)("/signIn/:oauth", {
495
382
  schemas: {
496
383
  params: import_zod.default.object({
497
- oauth: import_zod.default.enum(Object.keys(oauth)),
384
+ oauth: import_zod.default.enum(Object.keys(oauth), "The OAuth provider is not supported or invalid."),
498
385
  redirectTo: import_zod.default.string().optional()
499
386
  })
500
387
  }
@@ -507,52 +394,23 @@ var signInAction = (oauth) => {
507
394
  async (ctx) => {
508
395
  const {
509
396
  request,
397
+ headers: headersBuilder,
510
398
  params: { oauth: oauth2, redirectTo },
511
399
  context: { oauth: providers, cookies, trustedProxyHeaders, basePath }
512
400
  } = ctx;
513
- try {
514
- const cookieOptions = secureCookieOptions(request, cookies, trustedProxyHeaders);
515
- const state = generateSecure();
516
- const redirectURI = createRedirectURI(request, oauth2, basePath, trustedProxyHeaders);
517
- const stateCookie = setCookie("state", state, oauthCookie(cookieOptions));
518
- const redirectURICookie = setCookie("redirect_uri", redirectURI, oauthCookie(cookieOptions));
519
- const redirectToCookie = setCookie(
520
- "redirect_to",
521
- createRedirectTo(request, redirectTo, trustedProxyHeaders),
522
- oauthCookie(cookieOptions)
523
- );
524
- const { codeVerifier, codeChallenge, method } = await createPKCE();
525
- const codeVerifierCookie = setCookie("code_verifier", codeVerifier, oauthCookie(cookieOptions));
526
- const authorization = createAuthorizationURL(providers[oauth2], redirectURI, state, codeChallenge, method);
527
- const headers = new Headers();
528
- headers.set("Location", authorization);
529
- headers.append("Set-Cookie", stateCookie);
530
- headers.append("Set-Cookie", redirectURICookie);
531
- headers.append("Set-Cookie", redirectToCookie);
532
- headers.append("Set-Cookie", codeVerifierCookie);
533
- return Response.json(
534
- { oauth: oauth2 },
535
- {
536
- status: 302,
537
- headers
538
- }
539
- );
540
- } catch (error) {
541
- if (isAuthError(error)) {
542
- const { type, message } = error;
543
- return AuraResponse.json(
544
- { error: type, error_description: message },
545
- { status: import_router2.statusCode.BAD_REQUEST }
546
- );
401
+ const state = generateSecure();
402
+ const redirectURI = createRedirectURI(request, oauth2, basePath, trustedProxyHeaders);
403
+ const redirectToValue = createRedirectTo(request, redirectTo, trustedProxyHeaders);
404
+ const { codeVerifier, codeChallenge, method } = await createPKCE();
405
+ const authorization = createAuthorizationURL(providers[oauth2], redirectURI, state, codeChallenge, method);
406
+ const headers = headersBuilder.setHeader("Location", authorization).setCookie(cookies.state.name, state, cookies.state.attributes).setCookie(cookies.redirect_uri.name, redirectURI, cookies.redirect_uri.attributes).setCookie(cookies.redirect_to.name, redirectToValue, cookies.redirect_to.attributes).setCookie(cookies.code_verifier.name, codeVerifier, cookies.code_verifier.attributes).toHeaders();
407
+ return Response.json(
408
+ { oauth: oauth2 },
409
+ {
410
+ status: 302,
411
+ headers
547
412
  }
548
- return AuraResponse.json(
549
- {
550
- error: ERROR_RESPONSE.AUTHORIZATION.SERVER_ERROR,
551
- error_description: "An unexpected error occurred"
552
- },
553
- { status: import_router2.statusCode.INTERNAL_SERVER_ERROR }
554
- );
555
- }
413
+ );
556
414
  },
557
415
  signInConfig(oauth)
558
416
  );
@@ -593,11 +451,20 @@ var getUserInfo = async (oauthConfig, accessToken) => {
593
451
  const json = await response.json();
594
452
  const { success, data } = OAuthErrorResponse.safeParse(json);
595
453
  if (success) {
596
- throw new AuthError(data.error, data?.error_description ?? "An error occurred while fetching user information.");
454
+ throw new OAuthProtocolError(
455
+ data.error,
456
+ data?.error_description ?? "An error occurred while fetching user information."
457
+ );
597
458
  }
598
459
  return oauthConfig?.profile ? oauthConfig.profile(json) : getDefaultUserInfo(json);
599
460
  } catch (error) {
600
- throw throwAuthError(error, "Failed to retrieve userinfo");
461
+ if (isOAuthProtocolError(error)) {
462
+ throw error;
463
+ }
464
+ if (isNativeError(error)) {
465
+ throw new OAuthProtocolError("invalid_request", error.message, "", { cause: error });
466
+ }
467
+ throw new OAuthProtocolError("invalid_request", "Failed to fetch user information.", "", { cause: error });
601
468
  }
602
469
  };
603
470
 
@@ -605,7 +472,8 @@ var getUserInfo = async (oauthConfig, accessToken) => {
605
472
  var createAccessToken = async (oauthConfig, redirectURI, code, codeVerifier) => {
606
473
  const parsed = OAuthAccessToken.safeParse({ ...oauthConfig, redirectURI, code, codeVerifier });
607
474
  if (!parsed.success) {
608
- throw new AuthError(ERROR_RESPONSE.ACCESS_TOKEN.INVALID_REQUEST, "Invalid OAuth configuration");
475
+ const msg = JSON.stringify(formatZodError(parsed.error), null, 2);
476
+ throw new AuthInternalError("INVALID_OAUTH_CONFIGURATION", msg);
609
477
  }
610
478
  const { accessToken, clientId, clientSecret, code: codeParsed, redirectURI: redirectParsed } = parsed.data;
611
479
  try {
@@ -629,13 +497,55 @@ var createAccessToken = async (oauthConfig, redirectURI, code, codeVerifier) =>
629
497
  if (!token.success) {
630
498
  const { success, data } = OAuthAccessTokenErrorResponse.safeParse(json);
631
499
  if (!success) {
632
- throw new AuthError(ERROR_RESPONSE.ACCESS_TOKEN.INVALID_GRANT, "Invalid access token response format");
500
+ throw new OAuthProtocolError("INVALID_REQUEST", "Invalid access token response format");
633
501
  }
634
- throw new AuthError(data.error, data?.error_description ?? "Failed to retrieve access token");
502
+ throw new OAuthProtocolError(data.error, data?.error_description ?? "Failed to retrieve access token");
635
503
  }
636
504
  return token.data;
637
505
  } catch (error) {
638
- throw throwAuthError(error, "Failed to create access token");
506
+ throw error;
507
+ }
508
+ };
509
+
510
+ // src/cookie.ts
511
+ var import_cookie = require("@aura-stack/router/cookie");
512
+ var defaultCookieOptions = {
513
+ httpOnly: true,
514
+ sameSite: "lax",
515
+ path: "/",
516
+ maxAge: 60 * 60 * 24 * 15
517
+ };
518
+ var oauthCookieOptions = {
519
+ httpOnly: true,
520
+ maxAge: 5 * 60,
521
+ sameSite: "lax",
522
+ expires: new Date(Date.now() + 5 * 60 * 1e3)
523
+ };
524
+ var setCookie = (cookieName, value, options2) => {
525
+ return (0, import_cookie.serialize)(cookieName, value, options2);
526
+ };
527
+ var expiredCookieAttributes = {
528
+ ...defaultCookieOptions,
529
+ expires: /* @__PURE__ */ new Date(0),
530
+ maxAge: 0
531
+ };
532
+ var getCookie = (request, cookieName) => {
533
+ const cookies = request.headers.get("Cookie");
534
+ if (!cookies) {
535
+ throw new AuthInternalError("COOKIE_NOT_FOUND", "No cookies found. There is no active session");
536
+ }
537
+ const value = (0, import_cookie.parse)(cookies)[cookieName];
538
+ if (!value) {
539
+ throw new AuthInternalError("COOKIE_NOT_FOUND", `Cookie "${cookieName}" not found. There is no active session`);
540
+ }
541
+ return value;
542
+ };
543
+ var createSessionCookie = async (jose, session) => {
544
+ try {
545
+ const encoded = await jose.encodeJWT(session);
546
+ return encoded;
547
+ } catch (error) {
548
+ throw new AuthInternalError("INVALID_JWT_TOKEN", "Failed to create session cookie", { cause: error });
639
549
  }
640
550
  };
641
551
 
@@ -645,7 +555,7 @@ var callbackConfig = (oauth) => {
645
555
  schemas: {
646
556
  searchParams: OAuthAuthorizationResponse,
647
557
  params: import_zod2.default.object({
648
- oauth: import_zod2.default.enum(Object.keys(oauth))
558
+ oauth: import_zod2.default.enum(Object.keys(oauth), "The OAuth provider is not supported or invalid.")
649
559
  })
650
560
  },
651
561
  middlewares: [
@@ -653,7 +563,7 @@ var callbackConfig = (oauth) => {
653
563
  const response = OAuthAuthorizationErrorResponse.safeParse(ctx.searchParams);
654
564
  if (response.success) {
655
565
  const { error, error_description } = response.data;
656
- throw new AuthError(error, error_description ?? "OAuth Authorization Error");
566
+ throw new OAuthProtocolError(error, error_description ?? "OAuth Authorization Error");
657
567
  }
658
568
  return ctx;
659
569
  }
@@ -669,66 +579,32 @@ var callbackAction = (oauth) => {
669
579
  request,
670
580
  params: { oauth: oauth2 },
671
581
  searchParams: { code, state },
672
- context: { oauth: providers, cookies, jose, trustedProxyHeaders }
582
+ context: { oauth: providers, cookies, jose }
673
583
  } = ctx;
674
- try {
675
- const oauthConfig = providers[oauth2];
676
- const cookieOptions = secureCookieOptions(request, cookies, trustedProxyHeaders);
677
- const cookieState = getCookie(request, "state", cookieOptions);
678
- const cookieRedirectTo = getCookie(request, "redirect_to", cookieOptions);
679
- const cookieRedirectURI = getCookie(request, "redirect_uri", cookieOptions);
680
- const codeVerifier = getCookie(request, "code_verifier", cookieOptions);
681
- if (!equals(cookieState, state)) {
682
- throw new AuthError(ERROR_RESPONSE.ACCESS_TOKEN.INVALID_REQUEST, "Mismatching state");
683
- }
684
- const accessToken = await createAccessToken(oauthConfig, cookieRedirectURI, code, codeVerifier);
685
- const sanitized = sanitizeURL(cookieRedirectTo);
686
- if (!isValidRelativePath(sanitized)) {
687
- throw new AuthError(
688
- ERROR_RESPONSE.ACCESS_TOKEN.INVALID_REQUEST,
689
- "Invalid redirect path. Potential open redirect attack detected."
690
- );
691
- }
692
- const headers = new Headers(cacheControl);
693
- headers.set("Location", sanitized);
694
- const userInfo = await getUserInfo(oauthConfig, accessToken.access_token);
695
- const sessionCookie = await createSessionCookie(userInfo, cookieOptions, jose);
696
- const csrfToken = await createCSRF(jose);
697
- const csrfCookie = setCookie(
698
- "csrfToken",
699
- csrfToken,
700
- secureCookieOptions(
701
- request,
702
- {
703
- ...cookies,
704
- strategy: "host"
705
- },
706
- trustedProxyHeaders
707
- )
584
+ const oauthConfig = providers[oauth2];
585
+ const cookieState = getCookie(request, cookies.state.name);
586
+ const cookieRedirectTo = getCookie(request, cookies.redirect_to.name);
587
+ const cookieRedirectURI = getCookie(request, cookies.redirect_uri.name);
588
+ const codeVerifier = getCookie(request, cookies.code_verifier.name);
589
+ if (!equals(cookieState, state)) {
590
+ throw new AuthSecurityError(
591
+ "MISMATCHING_STATE",
592
+ "The provided state passed in the OAuth response does not match the stored state."
708
593
  );
709
- headers.set("Set-Cookie", sessionCookie);
710
- headers.append("Set-Cookie", expireCookie("state", cookieOptions));
711
- headers.append("Set-Cookie", expireCookie("redirect_uri", cookieOptions));
712
- headers.append("Set-Cookie", expireCookie("redirect_to", cookieOptions));
713
- headers.append("Set-Cookie", expireCookie("code_verifier", cookieOptions));
714
- headers.append("Set-Cookie", csrfCookie);
715
- return Response.json({ oauth: oauth2 }, { status: 302, headers });
716
- } catch (error) {
717
- if (isAuthError(error)) {
718
- const { type, message } = error;
719
- return AuraResponse.json(
720
- { error: type, error_description: message },
721
- { status: import_router3.statusCode.BAD_REQUEST }
722
- );
723
- }
724
- return AuraResponse.json(
725
- {
726
- error: ERROR_RESPONSE.ACCESS_TOKEN.INVALID_CLIENT,
727
- error_description: "An unexpected error occurred"
728
- },
729
- { status: import_router3.statusCode.INTERNAL_SERVER_ERROR }
594
+ }
595
+ const accessToken = await createAccessToken(oauthConfig, cookieRedirectURI, code, codeVerifier);
596
+ const sanitized = sanitizeURL(cookieRedirectTo);
597
+ if (!isValidRelativePath(sanitized)) {
598
+ throw new AuthSecurityError(
599
+ "POTENTIAL_OPEN_REDIRECT_ATTACK_DETECTED",
600
+ "Invalid redirect path. Potential open redirect attack detected."
730
601
  );
731
602
  }
603
+ const userInfo = await getUserInfo(oauthConfig, accessToken.access_token);
604
+ const sessionCookie = await createSessionCookie(jose, userInfo);
605
+ const csrfToken = await createCSRF(jose);
606
+ const headers = new import_router3.HeadersBuilder(cacheControl).setHeader("Location", sanitized).setCookie(cookies.sessionToken.name, sessionCookie, cookies.sessionToken.attributes).setCookie(cookies.csrfToken.name, csrfToken, cookies.csrfToken.attributes).setCookie(cookies.state.name, "", expiredCookieAttributes).setCookie(cookies.redirect_uri.name, "", expiredCookieAttributes).setCookie(cookies.redirect_to.name, "", expiredCookieAttributes).setCookie(cookies.code_verifier.name, "", expiredCookieAttributes).toHeaders();
607
+ return Response.json({ oauth: oauth2 }, { status: 302, headers });
732
608
  },
733
609
  callbackConfig(oauth)
734
610
  );
@@ -739,19 +615,16 @@ var import_router4 = require("@aura-stack/router");
739
615
  var sessionAction = (0, import_router4.createEndpoint)("GET", "/session", async (ctx) => {
740
616
  const {
741
617
  request,
742
- context: { cookies, jose, trustedProxyHeaders }
618
+ context: { jose, cookies }
743
619
  } = ctx;
744
- const cookieOptions = secureCookieOptions(request, cookies, trustedProxyHeaders);
745
620
  try {
746
- const session = getCookie(request, "sessionToken", cookieOptions);
621
+ const session = getCookie(request, cookies.sessionToken.name);
747
622
  const decoded = await jose.decodeJWT(session);
748
623
  const { exp, iat, jti, nbf, ...user } = decoded;
749
624
  const headers = new Headers(cacheControl);
750
625
  return Response.json({ user, expires: toISOString(exp * 1e3) }, { headers });
751
- } catch {
752
- const headers = new Headers(cacheControl);
753
- const sessionCookie = expireCookie("sessionToken", cookieOptions);
754
- headers.set("Set-Cookie", sessionCookie);
626
+ } catch (error) {
627
+ const headers = new import_router4.HeadersBuilder(cacheControl).setCookie(cookies.sessionToken.name, "", expiredCookieAttributes).toHeaders();
755
628
  return Response.json({ authenticated: false, message: "Unauthorized" }, { status: 401, headers });
756
629
  }
757
630
  });
@@ -775,83 +648,53 @@ var signOutAction = (0, import_router5.createEndpoint)(
775
648
  request,
776
649
  headers,
777
650
  searchParams: { redirectTo },
778
- context: { cookies, jose, trustedProxyHeaders }
651
+ context: { jose, cookies }
779
652
  } = ctx;
780
- try {
781
- const cookiesOptions = secureCookieOptions(request, cookies, trustedProxyHeaders);
782
- const session = getCookie(request, "sessionToken", cookiesOptions);
783
- const csrfToken = getCookie(request, "csrfToken", {
784
- ...cookiesOptions,
785
- prefix: cookiesOptions.secure ? "__Host-" : ""
786
- });
787
- const header = headers.get("X-CSRF-Token");
788
- if (!header || !session || !csrfToken) {
789
- throw new Error("Missing CSRF token or session token");
790
- }
791
- await verifyCSRF(jose, csrfToken, header);
792
- await jose.decodeJWT(session);
793
- const normalizedOriginPath = getNormalizedOriginPath(request.url);
794
- const location = createRedirectTo(
795
- new Request(normalizedOriginPath, {
796
- headers
797
- }),
798
- redirectTo
799
- );
800
- const responseHeaders = new Headers(cacheControl);
801
- responseHeaders.append("Set-Cookie", expireCookie("sessionToken", cookiesOptions));
802
- responseHeaders.append(
803
- "Set-Cookie",
804
- expireCookie("csrfToken", { ...cookiesOptions, prefix: cookiesOptions.secure ? "__Host-" : "" })
805
- );
806
- responseHeaders.append("Location", location);
807
- return Response.json(
808
- { message: "Signed out successfully" },
809
- { status: import_router5.statusCode.ACCEPTED, headers: responseHeaders }
810
- );
811
- } catch (error) {
812
- if (error instanceof InvalidCsrfTokenError) {
813
- return AuraResponse.json(
814
- {
815
- error: "invalid_csrf_token",
816
- error_description: "The provided CSRF token is invalid or has expired"
817
- },
818
- { status: import_router5.statusCode.UNAUTHORIZED }
819
- );
820
- }
821
- if (error instanceof InvalidRedirectToError) {
822
- const { type, message } = error;
823
- return AuraResponse.json(
824
- {
825
- error: type,
826
- error_description: message
827
- },
828
- { status: import_router5.statusCode.BAD_REQUEST }
829
- );
830
- }
831
- return AuraResponse.json(
832
- {
833
- error: "invalid_session_token",
834
- error_description: "The provided sessionToken is invalid or has already expired"
835
- },
836
- { status: import_router5.statusCode.UNAUTHORIZED }
837
- );
653
+ const session = headers.getCookie(cookies.sessionToken.name);
654
+ const csrfToken = headers.getCookie(cookies.csrfToken.name);
655
+ const header = headers.getHeader("X-CSRF-Token");
656
+ if (!session) {
657
+ throw new AuthSecurityError("SESSION_TOKEN_MISSING", "The sessionToken is missing.");
658
+ }
659
+ if (!csrfToken) {
660
+ throw new AuthSecurityError("CSRF_TOKEN_MISSING", "The CSRF token is missing.");
838
661
  }
662
+ if (!header) {
663
+ throw new AuthSecurityError("CSRF_TOKEN_MISSING", "The CSRF header is missing.");
664
+ }
665
+ await verifyCSRF(jose, csrfToken, header);
666
+ await jose.decodeJWT(session);
667
+ const normalizedOriginPath = getNormalizedOriginPath(request.url);
668
+ const location = createRedirectTo(
669
+ new Request(normalizedOriginPath, {
670
+ headers: headers.toHeaders()
671
+ }),
672
+ redirectTo
673
+ );
674
+ const headersList = new import_router5.HeadersBuilder(cacheControl).setHeader("Location", location).setCookie(cookies.csrfToken.name, "", expiredCookieAttributes).setCookie(cookies.sessionToken.name, "", expiredCookieAttributes).toHeaders();
675
+ return Response.json({ message: "Signed out successfully" }, { status: import_router5.statusCode.ACCEPTED, headers: headersList });
839
676
  },
840
677
  config
841
678
  );
842
679
 
843
680
  // src/actions/csrfToken/csrfToken.ts
844
681
  var import_router6 = require("@aura-stack/router");
682
+ var getCSRFToken = (request, cookieName) => {
683
+ try {
684
+ return getCookie(request, cookieName);
685
+ } catch {
686
+ return void 0;
687
+ }
688
+ };
845
689
  var csrfTokenAction = (0, import_router6.createEndpoint)("GET", "/csrfToken", async (ctx) => {
846
690
  const {
847
691
  request,
848
- context: { cookies, jose, trustedProxyHeaders }
692
+ context: { jose, cookies }
849
693
  } = ctx;
850
- const cookieOptions = secureCookieOptions(request, { ...cookies, strategy: "host" }, trustedProxyHeaders);
851
- const existingCSRFToken = getCookie(request, "csrfToken", cookieOptions, true);
852
- const csrfToken = await createCSRF(jose, existingCSRFToken);
694
+ const token = getCSRFToken(request, cookies.csrfToken.name);
695
+ const csrfToken = await createCSRF(jose, token);
853
696
  const headers = new Headers(cacheControl);
854
- headers.set("Set-Cookie", setCookie("csrfToken", csrfToken, cookieOptions));
697
+ headers.append("Set-Cookie", setCookie(cookies.csrfToken.name, csrfToken, cookies.csrfToken.attributes));
855
698
  return Response.json({ csrfToken }, { headers });
856
699
  });
857
700
  // Annotate the CommonJS export names for ESM import in node: