@aura-stack/auth 0.4.0-rc.5 → 0.4.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.
- package/dist/@types/index.d.ts +4 -3
- package/dist/@types/router.d.cjs +0 -17
- package/dist/@types/router.d.d.ts +3 -2
- package/dist/@types/router.d.js +0 -1
- package/dist/actions/callback/access-token.cjs +40 -25
- package/dist/actions/callback/access-token.d.ts +4 -3
- package/dist/actions/callback/access-token.js +3 -4
- package/dist/actions/callback/callback.cjs +287 -77
- package/dist/actions/callback/callback.d.ts +5 -26
- package/dist/actions/callback/callback.js +13 -10
- package/dist/actions/callback/userinfo.cjs +68 -7
- package/dist/actions/callback/userinfo.d.ts +4 -3
- package/dist/actions/callback/userinfo.js +8 -6
- package/dist/actions/csrfToken/csrfToken.cjs +63 -4
- package/dist/actions/csrfToken/csrfToken.d.ts +1 -3
- package/dist/actions/csrfToken/csrfToken.js +8 -6
- package/dist/actions/index.cjs +400 -175
- package/dist/actions/index.d.ts +3 -2
- package/dist/actions/index.js +21 -19
- package/dist/actions/session/session.cjs +40 -11
- package/dist/actions/session/session.d.ts +1 -3
- package/dist/actions/session/session.js +4 -4
- package/dist/actions/signIn/authorization.cjs +171 -132
- package/dist/actions/signIn/authorization.d.ts +21 -11
- package/dist/actions/signIn/authorization.js +8 -6
- package/dist/actions/signIn/signIn.cjs +220 -113
- package/dist/actions/signIn/signIn.d.ts +5 -25
- package/dist/actions/signIn/signIn.js +9 -7
- package/dist/actions/signOut/signOut.cjs +268 -119
- package/dist/actions/signOut/signOut.d.ts +1 -9
- package/dist/actions/signOut/signOut.js +10 -8
- package/dist/assert.cjs +117 -5
- package/dist/assert.d.ts +22 -3
- package/dist/assert.js +17 -3
- package/dist/chunk-4EKY7655.js +123 -0
- package/dist/chunk-4MYWAOLG.js +31 -0
- package/dist/chunk-4YHJ4IEQ.js +25 -0
- package/dist/chunk-54CZPKR4.js +25 -0
- package/dist/chunk-5LZ7TOM3.js +25 -0
- package/dist/{chunk-W6LG7BFW.js → chunk-5W4BRQYG.js} +24 -20
- package/dist/chunk-6MXFPFR3.js +143 -0
- package/dist/{chunk-3EUWD5BB.js → chunk-7QF22LHP.js} +13 -9
- package/dist/chunk-ALG3GIV4.js +95 -0
- package/dist/chunk-E6G5YCI6.js +25 -0
- package/dist/chunk-EBAMFRB7.js +34 -0
- package/dist/chunk-EEE7UM5T.js +25 -0
- package/dist/{chunk-TLE4PXY3.js → chunk-FRJFWTOY.js} +38 -7
- package/dist/chunk-FW4W3REU.js +25 -0
- package/dist/{chunk-HT4YLL7N.js → chunk-ICAZ4OVS.js} +10 -8
- package/dist/chunk-IPKO6UQN.js +25 -0
- package/dist/{chunk-YRCB5FLE.js → chunk-KJBAQZX2.js} +13 -0
- package/dist/chunk-KMMAZFSJ.js +25 -0
- package/dist/chunk-LDU7A2JE.js +25 -0
- package/dist/{chunk-N2APGLXA.js → chunk-NUDITUKX.js} +18 -16
- package/dist/chunk-OVHNRULD.js +33 -0
- package/dist/{chunk-JVFTCTTE.js → chunk-PHFH2MGS.js} +12 -9
- package/dist/chunk-QQVSRXGX.js +149 -0
- package/dist/chunk-TM5IPSNF.js +113 -0
- package/dist/{chunk-GA2SMTJO.js → chunk-TZB6MUXN.js} +33 -13
- package/dist/chunk-VNCNJKS2.js +267 -0
- package/dist/{chunk-IVET23KF.js → chunk-XGLBNXL4.js} +31 -14
- package/dist/chunk-XUP6KKNG.js +106 -0
- package/dist/cookie.cjs +24 -20
- package/dist/cookie.d.ts +4 -3
- package/dist/cookie.js +1 -1
- package/dist/env.cjs +56 -0
- package/dist/env.d.ts +7 -0
- package/dist/env.js +6 -0
- package/dist/errors.d.ts +4 -3
- package/dist/headers.cjs +28 -2
- package/dist/headers.d.ts +25 -1
- package/dist/headers.js +9 -3
- package/dist/{index-B8jeIElf.d.ts → index-CSyIJmCM.d.ts} +373 -45
- package/dist/index.cjs +1128 -483
- package/dist/index.d.ts +7 -67
- package/dist/index.js +83 -42
- package/dist/jose.cjs +62 -25
- package/dist/jose.d.ts +7 -5
- package/dist/jose.js +8 -6
- package/dist/logger.cjs +292 -0
- package/dist/logger.d.ts +8 -0
- package/dist/logger.js +8 -0
- package/dist/oauth/bitbucket.cjs +19 -15
- package/dist/oauth/bitbucket.d.ts +3 -2
- package/dist/oauth/bitbucket.js +1 -1
- package/dist/oauth/discord.cjs +27 -24
- package/dist/oauth/discord.d.ts +3 -2
- package/dist/oauth/discord.js +1 -1
- package/dist/oauth/figma.cjs +19 -16
- package/dist/oauth/figma.d.ts +3 -2
- package/dist/oauth/figma.js +1 -1
- package/dist/oauth/github.cjs +19 -8
- package/dist/oauth/github.d.ts +3 -2
- package/dist/oauth/github.js +1 -1
- package/dist/oauth/gitlab.cjs +19 -16
- package/dist/oauth/gitlab.d.ts +3 -2
- package/dist/oauth/gitlab.js +1 -1
- package/dist/oauth/index.cjs +266 -166
- package/dist/oauth/index.d.ts +3 -2
- package/dist/oauth/index.js +22 -21
- package/dist/oauth/mailchimp.cjs +19 -16
- package/dist/oauth/mailchimp.d.ts +3 -2
- package/dist/oauth/mailchimp.js +1 -1
- package/dist/oauth/pinterest.cjs +19 -16
- package/dist/oauth/pinterest.d.ts +3 -2
- package/dist/oauth/pinterest.js +1 -1
- package/dist/oauth/spotify.cjs +19 -16
- package/dist/oauth/spotify.d.ts +3 -2
- package/dist/oauth/spotify.js +1 -1
- package/dist/oauth/strava.cjs +19 -16
- package/dist/oauth/strava.d.ts +3 -2
- package/dist/oauth/strava.js +1 -1
- package/dist/oauth/x.cjs +19 -16
- package/dist/oauth/x.d.ts +3 -2
- package/dist/oauth/x.js +1 -1
- package/dist/schemas.cjs +16 -2
- package/dist/schemas.d.ts +17 -1
- package/dist/schemas.js +5 -3
- package/dist/secure.cjs +58 -16
- package/dist/secure.d.ts +4 -10
- package/dist/secure.js +5 -5
- package/dist/utils.cjs +94 -87
- package/dist/utils.d.ts +9 -39
- package/dist/utils.js +11 -9
- package/package.json +3 -4
- package/dist/chunk-42XB3YCW.js +0 -22
- package/dist/chunk-6R2YZ4AC.js +0 -22
- package/dist/chunk-A3N4PVAT.js +0 -70
- package/dist/chunk-B737EUJV.js +0 -22
- package/dist/chunk-CXLATHS5.js +0 -143
- package/dist/chunk-DIVDFNAP.js +0 -0
- package/dist/chunk-E3OXBRYF.js +0 -22
- package/dist/chunk-EIL2FPSS.js +0 -22
- package/dist/chunk-EMKJA2GJ.js +0 -89
- package/dist/chunk-FIPU4MLT.js +0 -21
- package/dist/chunk-FKRDCWBF.js +0 -22
- package/dist/chunk-HP34YGGJ.js +0 -22
- package/dist/chunk-IKHPGFCW.js +0 -14
- package/dist/chunk-IUYZQTJV.js +0 -30
- package/dist/chunk-KRNOMBXQ.js +0 -22
- package/dist/chunk-KSWLO5ZU.js +0 -102
- package/dist/chunk-N4SX7TZT.js +0 -96
- package/dist/chunk-STHEPPUZ.js +0 -11
package/dist/actions/index.cjs
CHANGED
|
@@ -49,9 +49,29 @@ var cacheControl = {
|
|
|
49
49
|
Expires: "0",
|
|
50
50
|
Vary: "Cookie"
|
|
51
51
|
};
|
|
52
|
+
var contentSecurityPolicy = {
|
|
53
|
+
"Content-Security-Policy": [
|
|
54
|
+
"default-src 'none'",
|
|
55
|
+
"script-src 'self'",
|
|
56
|
+
"frame-src 'none'",
|
|
57
|
+
"object-src 'none'",
|
|
58
|
+
"frame-ancestors 'none'",
|
|
59
|
+
"base-uri 'none'"
|
|
60
|
+
].join("; ")
|
|
61
|
+
};
|
|
62
|
+
var secureHeaders = {
|
|
63
|
+
"X-Content-Type-Options": "nosniff",
|
|
64
|
+
"X-Frame-Options": "DENY",
|
|
65
|
+
"Referrer-Policy": "strict-origin-when-cross-origin"
|
|
66
|
+
};
|
|
67
|
+
var secureApiHeaders = {
|
|
68
|
+
...cacheControl,
|
|
69
|
+
...contentSecurityPolicy,
|
|
70
|
+
...secureHeaders
|
|
71
|
+
};
|
|
52
72
|
|
|
53
73
|
// src/secure.ts
|
|
54
|
-
var
|
|
74
|
+
var import_crypto2 = __toESM(require("crypto"), 1);
|
|
55
75
|
|
|
56
76
|
// src/utils.ts
|
|
57
77
|
var import_router = require("@aura-stack/router");
|
|
@@ -95,9 +115,6 @@ var isNativeError = (error) => {
|
|
|
95
115
|
var isOAuthProtocolError = (error) => {
|
|
96
116
|
return error instanceof OAuthProtocolError;
|
|
97
117
|
};
|
|
98
|
-
var isAuthSecurityError = (error) => {
|
|
99
|
-
return error instanceof AuthSecurityError;
|
|
100
|
-
};
|
|
101
118
|
|
|
102
119
|
// src/utils.ts
|
|
103
120
|
var toSnakeCase = (str) => {
|
|
@@ -116,95 +133,175 @@ var equals = (a, b) => {
|
|
|
116
133
|
if (a === null || b === null || a === void 0 || b === void 0) return false;
|
|
117
134
|
return a === b;
|
|
118
135
|
};
|
|
119
|
-
var
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const protocolMatch = decodedURL.match(/^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)/);
|
|
123
|
-
let protocol = "";
|
|
124
|
-
let rest = decodedURL;
|
|
125
|
-
if (protocolMatch) {
|
|
126
|
-
protocol = protocolMatch[1];
|
|
127
|
-
rest = decodedURL.slice(protocol.length);
|
|
128
|
-
const slashIndex = rest.indexOf("/");
|
|
129
|
-
if (slashIndex === -1) {
|
|
130
|
-
return protocol + rest;
|
|
131
|
-
}
|
|
132
|
-
const domain = rest.slice(0, slashIndex);
|
|
133
|
-
let path = rest.slice(slashIndex).replace(/\/\.\.\//g, "/").replace(/\/\.\.$/, "").replace(/\.{2,}/g, "").replace(/\/{2,}/g, "/");
|
|
134
|
-
if (path !== "/" && path.endsWith("/")) {
|
|
135
|
-
path = path.replace(/\/+$/, "/");
|
|
136
|
-
} else if (path !== "/") {
|
|
137
|
-
path = path.replace(/\/+$/, "");
|
|
138
|
-
}
|
|
139
|
-
return protocol + domain + path;
|
|
140
|
-
}
|
|
141
|
-
let sanitized = decodedURL.replace(/\/\.\.\//g, "/").replace(/\/\.\.$/, "").replace(/\.{2,}/g, "").replace(/\/{2,}/g, "/");
|
|
142
|
-
if (sanitized !== "/" && sanitized.endsWith("/")) {
|
|
143
|
-
sanitized = sanitized.replace(/\/+$/, "/");
|
|
144
|
-
} else if (sanitized !== "/") {
|
|
145
|
-
sanitized = sanitized.replace(/\/+$/, "");
|
|
146
|
-
}
|
|
147
|
-
return sanitized;
|
|
148
|
-
} catch {
|
|
149
|
-
return url.trim();
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
var isValidRelativePath = (path) => {
|
|
153
|
-
if (!path || typeof path !== "string") return false;
|
|
154
|
-
if (!path.startsWith("/") || path.includes("://") || path.includes("\r") || path.includes("\n")) return false;
|
|
155
|
-
if (/[\x00-\x1F\x7F]/.test(path) || path.includes("\0")) return false;
|
|
156
|
-
const sanitized = sanitizeURL(path);
|
|
157
|
-
if (sanitized.includes("..")) return false;
|
|
158
|
-
return true;
|
|
159
|
-
};
|
|
160
|
-
var getNormalizedOriginPath = (path) => {
|
|
161
|
-
try {
|
|
162
|
-
const url = new URL(path);
|
|
163
|
-
url.hash = "";
|
|
164
|
-
url.search = "";
|
|
165
|
-
return `${url.origin}${url.pathname}`;
|
|
166
|
-
} catch {
|
|
167
|
-
return sanitizeURL(path);
|
|
168
|
-
}
|
|
136
|
+
var getBaseURL = (request) => {
|
|
137
|
+
const url = new URL(request.url);
|
|
138
|
+
return `${url.origin}${url.pathname}`;
|
|
169
139
|
};
|
|
170
140
|
var toISOString = (date) => {
|
|
171
141
|
return new Date(date).toISOString();
|
|
172
142
|
};
|
|
173
|
-
var
|
|
174
|
-
|
|
175
|
-
|
|
143
|
+
var extractPath = (url) => {
|
|
144
|
+
const pathRegex = /^https?:\/\/[a-zA-Z0-9_\-\.]+(:\d+)?(\/.*)$/;
|
|
145
|
+
const match = url.match(pathRegex);
|
|
146
|
+
return match && match[2] ? match[2] : "/";
|
|
147
|
+
};
|
|
148
|
+
var getErrorName = (error) => {
|
|
149
|
+
if (error instanceof Error) {
|
|
150
|
+
return error.name;
|
|
176
151
|
}
|
|
177
|
-
return error
|
|
178
|
-
const key = issue.path.join(".");
|
|
179
|
-
return {
|
|
180
|
-
...previous,
|
|
181
|
-
[key]: {
|
|
182
|
-
code: issue.code,
|
|
183
|
-
message: issue.message
|
|
184
|
-
}
|
|
185
|
-
};
|
|
186
|
-
}, {});
|
|
152
|
+
return typeof error === "string" ? error : "UnknownError";
|
|
187
153
|
};
|
|
188
154
|
|
|
189
155
|
// src/assert.ts
|
|
156
|
+
var import_crypto = require("crypto");
|
|
157
|
+
var unsafeChars = [
|
|
158
|
+
"<",
|
|
159
|
+
">",
|
|
160
|
+
'"',
|
|
161
|
+
"`",
|
|
162
|
+
" ",
|
|
163
|
+
"\r",
|
|
164
|
+
"\n",
|
|
165
|
+
" ",
|
|
166
|
+
"\\",
|
|
167
|
+
"%2F",
|
|
168
|
+
"%5C",
|
|
169
|
+
"%2f",
|
|
170
|
+
"%5c",
|
|
171
|
+
"\r\n",
|
|
172
|
+
"%0A",
|
|
173
|
+
"%0D",
|
|
174
|
+
"%0a",
|
|
175
|
+
"%0d",
|
|
176
|
+
"..",
|
|
177
|
+
"//",
|
|
178
|
+
"///",
|
|
179
|
+
"...",
|
|
180
|
+
"%20",
|
|
181
|
+
"\0"
|
|
182
|
+
];
|
|
190
183
|
var isValidURL = (value) => {
|
|
191
|
-
if (
|
|
192
|
-
|
|
193
|
-
|
|
184
|
+
if (!new RegExp(/^https?:\/\/[^/]/).test(value)) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
const match = value.match(/^(https?:\/\/)(.*)$/);
|
|
188
|
+
if (!match) return false;
|
|
189
|
+
const rest = match[2];
|
|
190
|
+
for (const char of unsafeChars) {
|
|
191
|
+
if (rest.includes(char)) return false;
|
|
192
|
+
}
|
|
193
|
+
const regex = /^https?:\/\/(?:[a-zA-Z0-9._-]+|localhost|\[[0-9a-fA-F:]+\])(?::\d{1,5})?(?:\/[a-zA-Z0-9._~!$&'()?#*+,;=:@-]*)*\/?$/;
|
|
194
|
+
return regex.test(match[0]);
|
|
194
195
|
};
|
|
195
196
|
var isJWTPayloadWithToken = (payload) => {
|
|
196
197
|
return typeof payload === "object" && payload !== null && "token" in payload && typeof payload?.token === "string";
|
|
197
198
|
};
|
|
199
|
+
var isRelativeURL = (value) => {
|
|
200
|
+
if (value.length > 100) return false;
|
|
201
|
+
for (const char of unsafeChars) {
|
|
202
|
+
if (value.includes(char)) return false;
|
|
203
|
+
}
|
|
204
|
+
const regex = /^\/[a-zA-Z0-9\-_\/.?&=#]*\/?$/;
|
|
205
|
+
return regex.test(value);
|
|
206
|
+
};
|
|
207
|
+
var isSameOrigin = (origin, expected) => {
|
|
208
|
+
const originURL = new URL(origin);
|
|
209
|
+
const expectedURL = new URL(expected);
|
|
210
|
+
return equals(originURL.origin, expectedURL.origin);
|
|
211
|
+
};
|
|
212
|
+
var patternToRegex = (pattern) => {
|
|
213
|
+
try {
|
|
214
|
+
if (pattern.length > 2048) return null;
|
|
215
|
+
pattern = pattern.replace(/\\/g, "");
|
|
216
|
+
const match = pattern.match(/^(https?):\/\/([a-zA-Z0-9.*-]{1,253})(?::(\d{1,5}|\*))?(?:\/.*)?$/);
|
|
217
|
+
if (!match) return null;
|
|
218
|
+
const [, protocol, host, port] = match;
|
|
219
|
+
const hasWildcard = host.includes("*");
|
|
220
|
+
if (hasWildcard && !host.startsWith("*.")) return null;
|
|
221
|
+
if (hasWildcard && host.slice(2).includes("*")) return null;
|
|
222
|
+
const domain = hasWildcard ? host.slice(2) : host;
|
|
223
|
+
const escapedDomain = domain.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
224
|
+
const hostRegex = hasWildcard ? `[^.]+\\.${escapedDomain}` : escapedDomain;
|
|
225
|
+
const portRegex = port === "*" ? ":\\d{1,5}" : port ? `:${port}` : "";
|
|
226
|
+
return new RegExp(`^${protocol}:\\/\\/${hostRegex}${portRegex}$`);
|
|
227
|
+
} catch {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
var isTrustedOrigin = (url, trustedOrigins) => {
|
|
232
|
+
if (!isValidURL(url) || trustedOrigins.length === 0) return false;
|
|
233
|
+
try {
|
|
234
|
+
const urlOrigin = new URL(url).origin;
|
|
235
|
+
for (const pattern of trustedOrigins) {
|
|
236
|
+
const regex = patternToRegex(pattern);
|
|
237
|
+
if (regex?.test(urlOrigin)) return true;
|
|
238
|
+
try {
|
|
239
|
+
if (isValidURL(pattern) && equals(new URL(pattern).origin, urlOrigin)) return true;
|
|
240
|
+
} catch {
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
} catch {
|
|
244
|
+
}
|
|
245
|
+
return false;
|
|
246
|
+
};
|
|
247
|
+
var safeEquals = (a, b) => {
|
|
248
|
+
const bufferA = Buffer.from(a);
|
|
249
|
+
const bufferB = Buffer.from(b);
|
|
250
|
+
if (bufferA.length !== bufferB.length) {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
return (0, import_crypto.timingSafeEqual)(bufferA, bufferB);
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
// src/env.ts
|
|
257
|
+
var import_meta = {};
|
|
258
|
+
var env = new Proxy({}, {
|
|
259
|
+
get(_, prop) {
|
|
260
|
+
if (typeof prop !== "string") return void 0;
|
|
261
|
+
const hasProperty = (process2) => {
|
|
262
|
+
return process2 && Object.prototype.hasOwnProperty.call(process2, prop);
|
|
263
|
+
};
|
|
264
|
+
try {
|
|
265
|
+
if (typeof process !== "undefined" && hasProperty(process.env)) {
|
|
266
|
+
return process.env[prop];
|
|
267
|
+
}
|
|
268
|
+
if (typeof import_meta !== "undefined" && hasProperty(import_meta.env)) {
|
|
269
|
+
return import_meta.env[prop];
|
|
270
|
+
}
|
|
271
|
+
if (typeof Deno !== "undefined" && Deno.env?.get) {
|
|
272
|
+
return Deno.env.get(prop);
|
|
273
|
+
}
|
|
274
|
+
if (typeof Bun !== "undefined" && hasProperty(Bun.env)) {
|
|
275
|
+
return Bun.env[prop];
|
|
276
|
+
}
|
|
277
|
+
const globalValue = globalThis[prop];
|
|
278
|
+
return typeof globalValue === "string" ? globalValue : void 0;
|
|
279
|
+
} catch {
|
|
280
|
+
return void 0;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// src/jose.ts
|
|
286
|
+
var import_jose = require("@aura-stack/jose");
|
|
287
|
+
var jwtVerificationOptions = {
|
|
288
|
+
algorithms: ["HS256"],
|
|
289
|
+
typ: "JWT"
|
|
290
|
+
};
|
|
198
291
|
|
|
199
292
|
// src/secure.ts
|
|
200
293
|
var generateSecure = (length = 32) => {
|
|
201
|
-
return
|
|
294
|
+
return import_crypto2.default.randomBytes(length).toString("base64url");
|
|
202
295
|
};
|
|
203
296
|
var createHash = (data, base = "hex") => {
|
|
204
|
-
return
|
|
297
|
+
return import_crypto2.default.createHash("sha256").update(data).digest().toString(base);
|
|
205
298
|
};
|
|
206
299
|
var createPKCE = async (verifier) => {
|
|
207
|
-
const
|
|
300
|
+
const byteLength = verifier ? void 0 : Math.floor(Math.random() * (96 - 32 + 1) + 32);
|
|
301
|
+
const codeVerifier = verifier ?? generateSecure(byteLength ?? 64);
|
|
302
|
+
if (codeVerifier.length < 43 || codeVerifier.length > 128) {
|
|
303
|
+
throw new AuthSecurityError("PKCE_VERIFIER_INVALID", "The code verifier must be between 43 and 128 characters in length.");
|
|
304
|
+
}
|
|
208
305
|
const codeChallenge = createHash(codeVerifier, "base64url");
|
|
209
306
|
return { codeVerifier, codeChallenge, method: "S256" };
|
|
210
307
|
};
|
|
@@ -212,7 +309,7 @@ var createCSRF = async (jose, csrfCookie) => {
|
|
|
212
309
|
try {
|
|
213
310
|
const token = generateSecure(32);
|
|
214
311
|
if (csrfCookie) {
|
|
215
|
-
await jose.verifyJWS(csrfCookie);
|
|
312
|
+
await jose.verifyJWS(csrfCookie, jwtVerificationOptions);
|
|
216
313
|
return csrfCookie;
|
|
217
314
|
}
|
|
218
315
|
return jose.signJWS({ token });
|
|
@@ -223,20 +320,18 @@ var createCSRF = async (jose, csrfCookie) => {
|
|
|
223
320
|
};
|
|
224
321
|
var verifyCSRF = async (jose, cookie, header) => {
|
|
225
322
|
try {
|
|
226
|
-
const cookiePayload = await jose.verifyJWS(cookie);
|
|
227
|
-
const headerPayload = await jose.verifyJWS(header);
|
|
323
|
+
const cookiePayload = await jose.verifyJWS(cookie, jwtVerificationOptions);
|
|
324
|
+
const headerPayload = await jose.verifyJWS(header, jwtVerificationOptions);
|
|
228
325
|
if (!isJWTPayloadWithToken(cookiePayload)) {
|
|
229
326
|
throw new AuthSecurityError("CSRF_TOKEN_INVALID", "Cookie payload missing token field.");
|
|
230
327
|
}
|
|
231
328
|
if (!isJWTPayloadWithToken(headerPayload)) {
|
|
232
329
|
throw new AuthSecurityError("CSRF_TOKEN_INVALID", "Header payload missing token field.");
|
|
233
330
|
}
|
|
234
|
-
|
|
235
|
-
const headerBuffer = Buffer.from(headerPayload.token);
|
|
236
|
-
if (!equals(headerBuffer.length, cookieBuffer.length)) {
|
|
331
|
+
if (!equals(cookiePayload.token.length, headerPayload.token.length)) {
|
|
237
332
|
throw new AuthSecurityError("CSRF_TOKEN_INVALID", "The CSRF tokens do not match.");
|
|
238
333
|
}
|
|
239
|
-
if (!
|
|
334
|
+
if (!safeEquals(cookiePayload.token, headerPayload.token)) {
|
|
240
335
|
throw new AuthSecurityError("CSRF_TOKEN_INVALID", "The CSRF tokens do not match.");
|
|
241
336
|
}
|
|
242
337
|
return true;
|
|
@@ -247,6 +342,18 @@ var verifyCSRF = async (jose, cookie, header) => {
|
|
|
247
342
|
|
|
248
343
|
// src/schemas.ts
|
|
249
344
|
var import_zod = require("zod");
|
|
345
|
+
var OAuthProviderCredentialsSchema = (0, import_zod.object)({
|
|
346
|
+
id: (0, import_zod.string)(),
|
|
347
|
+
name: (0, import_zod.string)(),
|
|
348
|
+
authorizeURL: (0, import_zod.string)().url(),
|
|
349
|
+
accessToken: (0, import_zod.string)().url(),
|
|
350
|
+
scope: (0, import_zod.string)(),
|
|
351
|
+
userInfo: (0, import_zod.string)().url(),
|
|
352
|
+
responseType: (0, import_zod.enum)(["code", "token", "id_token"]),
|
|
353
|
+
clientId: (0, import_zod.string)(),
|
|
354
|
+
clientSecret: (0, import_zod.string)(),
|
|
355
|
+
profile: import_zod.z.function().optional()
|
|
356
|
+
});
|
|
250
357
|
var OAuthProviderConfigSchema = (0, import_zod.object)({
|
|
251
358
|
authorizeURL: (0, import_zod.string)().url(),
|
|
252
359
|
accessToken: (0, import_zod.string)().url(),
|
|
@@ -314,73 +421,82 @@ var OAuthEnvSchema = (0, import_zod.object)({
|
|
|
314
421
|
});
|
|
315
422
|
|
|
316
423
|
// src/actions/signIn/authorization.ts
|
|
317
|
-
var createAuthorizationURL = (oauthConfig, redirectURI, state, codeChallenge, codeChallengeMethod) => {
|
|
424
|
+
var createAuthorizationURL = (oauthConfig, redirectURI, state, codeChallenge, codeChallengeMethod, logger) => {
|
|
318
425
|
const parsed = OAuthAuthorization.safeParse({ ...oauthConfig, redirectURI, state, codeChallenge, codeChallengeMethod });
|
|
319
426
|
if (!parsed.success) {
|
|
320
|
-
|
|
321
|
-
|
|
427
|
+
logger?.log("INVALID_OAUTH_CONFIGURATION", {
|
|
428
|
+
structuredData: {
|
|
429
|
+
scope: oauthConfig.scope,
|
|
430
|
+
redirect_uri: redirectURI,
|
|
431
|
+
has_state: Boolean(state),
|
|
432
|
+
has_code_challenge: Boolean(codeChallenge),
|
|
433
|
+
code_challenge_method: codeChallengeMethod
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
throw new AuthInternalError("INVALID_OAUTH_CONFIGURATION", "The OAuth provider configuration is invalid.");
|
|
322
437
|
}
|
|
323
438
|
const { authorizeURL, ...options2 } = parsed.data;
|
|
324
439
|
const { userInfo, accessToken, clientSecret, ...required } = options2;
|
|
325
440
|
const searchParams = new URLSearchParams(toCastCase(required));
|
|
326
441
|
return `${authorizeURL}?${searchParams}`;
|
|
327
442
|
};
|
|
328
|
-
var
|
|
443
|
+
var getTrustedOrigins = async (request, trustedOrigins) => {
|
|
444
|
+
if (!trustedOrigins) return [];
|
|
445
|
+
const raw = typeof trustedOrigins === "function" ? await trustedOrigins(request) : trustedOrigins;
|
|
446
|
+
return Array.isArray(raw) ? raw : typeof raw === "string" ? [raw] : [];
|
|
447
|
+
};
|
|
448
|
+
var getOriginURL = async (request, context) => {
|
|
329
449
|
const headers = request.headers;
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
450
|
+
let origin = new URL(request.url).origin;
|
|
451
|
+
const trustedOrigins = await getTrustedOrigins(request, context?.trustedOrigins);
|
|
452
|
+
trustedOrigins.push(origin);
|
|
453
|
+
if (context?.trustedProxyHeaders) {
|
|
454
|
+
const protocol = headers.get("Forwarded")?.match(/proto=([^;]+)/i)?.[1] ?? headers.get("X-Forwarded-Proto") ?? "http";
|
|
455
|
+
const host = headers.get("Host") ?? headers.get("Forwarded")?.match(/host=([^;]+)/i)?.[1] ?? headers.get("X-Forwarded-Host") ?? null;
|
|
456
|
+
origin = `${protocol}://${host}`;
|
|
457
|
+
}
|
|
458
|
+
if (!isTrustedOrigin(origin, trustedOrigins)) {
|
|
459
|
+
context?.logger?.log("UNTRUSTED_ORIGIN", { structuredData: { origin } });
|
|
460
|
+
throw new AuthInternalError("UNTRUSTED_ORIGIN", "The constructed origin URL is not trusted.");
|
|
336
461
|
}
|
|
462
|
+
return origin;
|
|
337
463
|
};
|
|
338
|
-
var createRedirectURI = (request, oauth,
|
|
339
|
-
const
|
|
340
|
-
return `${
|
|
464
|
+
var createRedirectURI = async (request, oauth, context) => {
|
|
465
|
+
const origin = await getOriginURL(request, context);
|
|
466
|
+
return `${origin}${context.basePath}/callback/${oauth}`;
|
|
341
467
|
};
|
|
342
|
-
var createRedirectTo = (request, redirectTo,
|
|
468
|
+
var createRedirectTo = async (request, redirectTo, context) => {
|
|
343
469
|
try {
|
|
344
470
|
const headers = request.headers;
|
|
345
|
-
const
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
if (
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
if (!isValidURL(referer) || !equals(refererURL.origin, hostedURL.origin)) {
|
|
364
|
-
throw new AuthSecurityError(
|
|
365
|
-
"POTENTIAL_OPEN_REDIRECT_ATTACK_DETECTED",
|
|
366
|
-
"The referer of the request does not match the hosted origin."
|
|
367
|
-
);
|
|
471
|
+
const requestOrigin = await getOriginURL(request, context);
|
|
472
|
+
const origins = await getTrustedOrigins(request, context?.trustedOrigins);
|
|
473
|
+
const validateURL = (url) => {
|
|
474
|
+
if (!isRelativeURL(url) && !isValidURL(url)) return "/";
|
|
475
|
+
if (isRelativeURL(url)) return url;
|
|
476
|
+
if (origins.length > 0) {
|
|
477
|
+
if (isTrustedOrigin(url, origins)) {
|
|
478
|
+
const urlOrigin = new URL(url).origin;
|
|
479
|
+
for (const pattern of origins) {
|
|
480
|
+
const regex = patternToRegex(pattern);
|
|
481
|
+
if (regex?.test(urlOrigin)) {
|
|
482
|
+
return isSameOrigin(url, request.url) ? extractPath(url) : url;
|
|
483
|
+
}
|
|
484
|
+
if (isValidURL(pattern) && equals(new URL(pattern).origin, urlOrigin)) return url;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
context?.logger?.log("OPEN_REDIRECT_ATTACK");
|
|
488
|
+
return "/";
|
|
368
489
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
if (origin) {
|
|
372
|
-
const originURL = new URL(sanitizeURL(getNormalizedOriginPath(origin)));
|
|
373
|
-
if (!isValidURL(origin) || !equals(originURL.origin, hostedURL.origin)) {
|
|
374
|
-
throw new AuthSecurityError("POTENTIAL_OPEN_REDIRECT_ATTACK_DETECTED", "Invalid origin (potential CSRF).");
|
|
490
|
+
if (isSameOrigin(url, requestOrigin)) {
|
|
491
|
+
return extractPath(url);
|
|
375
492
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
493
|
+
context?.logger?.log("OPEN_REDIRECT_ATTACK");
|
|
494
|
+
return "/";
|
|
495
|
+
};
|
|
496
|
+
return validateURL(redirectTo ?? headers.get("Referer") ?? headers.get("Origin") ?? "/");
|
|
379
497
|
} catch (error) {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
}
|
|
383
|
-
throw new AuthSecurityError("POTENTIAL_OPEN_REDIRECT_ATTACK_DETECTED", "Invalid origin (potential CSRF).");
|
|
498
|
+
context?.logger?.log("POTENTIAL_OPEN_REDIRECT_ATTACK_DETECTED");
|
|
499
|
+
return "/";
|
|
384
500
|
}
|
|
385
501
|
};
|
|
386
502
|
|
|
@@ -409,13 +525,17 @@ var signInAction = (oauth) => {
|
|
|
409
525
|
request,
|
|
410
526
|
params: { oauth: oauth2 },
|
|
411
527
|
searchParams: { redirectTo },
|
|
412
|
-
context
|
|
528
|
+
context
|
|
413
529
|
} = ctx;
|
|
530
|
+
const { oauth: providers, cookies, logger } = context;
|
|
414
531
|
const state = generateSecure();
|
|
415
|
-
const redirectURI = createRedirectURI(request, oauth2,
|
|
416
|
-
const redirectToValue = createRedirectTo(request, redirectTo,
|
|
532
|
+
const redirectURI = await createRedirectURI(request, oauth2, context);
|
|
533
|
+
const redirectToValue = await createRedirectTo(request, redirectTo, context);
|
|
417
534
|
const { codeVerifier, codeChallenge, method } = await createPKCE();
|
|
418
|
-
const authorization = createAuthorizationURL(providers[oauth2], redirectURI, state, codeChallenge, method);
|
|
535
|
+
const authorization = createAuthorizationURL(providers[oauth2], redirectURI, state, codeChallenge, method, logger);
|
|
536
|
+
logger?.log("SIGN_IN_INITIATED", {
|
|
537
|
+
structuredData: { oauth_provider: oauth2, code_challenge_method: method }
|
|
538
|
+
});
|
|
419
539
|
const headers = new import_router2.HeadersBuilder(cacheControl).setHeader("Location", authorization).setCookie(cookies.state.name, state, cookies.state.attributes).setCookie(cookies.redirectURI.name, redirectURI, cookies.redirectURI.attributes).setCookie(cookies.redirectTo.name, redirectToValue, cookies.redirectTo.attributes).setCookie(cookies.codeVerifier.name, codeVerifier, cookies.codeVerifier.attributes).toHeaders();
|
|
420
540
|
return Response.json(
|
|
421
541
|
{ oauth: oauth2 },
|
|
@@ -454,9 +574,14 @@ var getDefaultUserInfo = (profile) => {
|
|
|
454
574
|
image: profile?.image ?? profile?.picture
|
|
455
575
|
};
|
|
456
576
|
};
|
|
457
|
-
var getUserInfo = async (oauthConfig, accessToken) => {
|
|
577
|
+
var getUserInfo = async (oauthConfig, accessToken, logger) => {
|
|
458
578
|
const userinfoEndpoint = oauthConfig.userInfo;
|
|
459
579
|
try {
|
|
580
|
+
logger?.log("OAUTH_USERINFO_REQUEST_INITIATED", {
|
|
581
|
+
structuredData: {
|
|
582
|
+
endpoint: userinfoEndpoint
|
|
583
|
+
}
|
|
584
|
+
});
|
|
460
585
|
const response = await fetchAsync(userinfoEndpoint, {
|
|
461
586
|
method: "GET",
|
|
462
587
|
headers: {
|
|
@@ -464,35 +589,54 @@ var getUserInfo = async (oauthConfig, accessToken) => {
|
|
|
464
589
|
Authorization: `Bearer ${accessToken}`
|
|
465
590
|
}
|
|
466
591
|
});
|
|
592
|
+
if (!response.ok) {
|
|
593
|
+
logger?.log("OAUTH_USERINFO_INVALID_RESPONSE");
|
|
594
|
+
throw new OAuthProtocolError("INVALID_REQUEST", "Invalid userinfo response format");
|
|
595
|
+
}
|
|
467
596
|
const json = await response.json();
|
|
468
597
|
const { success, data } = OAuthErrorResponse.safeParse(json);
|
|
469
598
|
if (success) {
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
599
|
+
logger?.log("OAUTH_USERINFO_ERROR", {
|
|
600
|
+
message: "Error response received from OAuth userinfo endpoint",
|
|
601
|
+
structuredData: {
|
|
602
|
+
error: data.error,
|
|
603
|
+
error_description: data.error_description ?? ""
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
throw new OAuthProtocolError("INVALID_REQUEST", "An error was received from the OAuth userinfo endpoint.");
|
|
474
607
|
}
|
|
608
|
+
logger?.log("OAUTH_USERINFO_SUCCESS");
|
|
475
609
|
return oauthConfig?.profile ? oauthConfig.profile(json) : getDefaultUserInfo(json);
|
|
476
610
|
} catch (error) {
|
|
477
611
|
if (isOAuthProtocolError(error)) {
|
|
478
612
|
throw error;
|
|
479
613
|
}
|
|
614
|
+
logger?.log("OAUTH_USERINFO_REQUEST_FAILED");
|
|
480
615
|
if (isNativeError(error)) {
|
|
481
|
-
throw new OAuthProtocolError("
|
|
616
|
+
throw new OAuthProtocolError("SERVER_ERROR", "Failed to fetch user information from OAuth provider", "", {
|
|
617
|
+
cause: error
|
|
618
|
+
});
|
|
482
619
|
}
|
|
483
|
-
throw new OAuthProtocolError("
|
|
620
|
+
throw new OAuthProtocolError("SERVER_ERROR", "Failed to fetch user information", "", { cause: error });
|
|
484
621
|
}
|
|
485
622
|
};
|
|
486
623
|
|
|
487
624
|
// src/actions/callback/access-token.ts
|
|
488
|
-
var createAccessToken = async (oauthConfig, redirectURI, code, codeVerifier) => {
|
|
625
|
+
var createAccessToken = async (oauthConfig, redirectURI, code, codeVerifier, logger) => {
|
|
489
626
|
const parsed = OAuthAccessToken.safeParse({ ...oauthConfig, redirectURI, code, codeVerifier });
|
|
490
627
|
if (!parsed.success) {
|
|
491
|
-
|
|
492
|
-
throw new AuthInternalError("INVALID_OAUTH_CONFIGURATION",
|
|
628
|
+
logger?.log("INVALID_OAUTH_CONFIGURATION");
|
|
629
|
+
throw new AuthInternalError("INVALID_OAUTH_CONFIGURATION", "The OAuth provider configuration is invalid.");
|
|
493
630
|
}
|
|
494
631
|
const { accessToken, clientId, clientSecret, code: codeParsed, redirectURI: redirectParsed } = parsed.data;
|
|
495
632
|
try {
|
|
633
|
+
logger?.log("OAUTH_ACCESS_TOKEN_REQUEST_INITIATED", {
|
|
634
|
+
structuredData: {
|
|
635
|
+
has_client_id: Boolean(clientId),
|
|
636
|
+
redirect_uri: redirectParsed,
|
|
637
|
+
grant_type: "authorization_code"
|
|
638
|
+
}
|
|
639
|
+
});
|
|
496
640
|
const response = await fetchAsync(accessToken, {
|
|
497
641
|
method: "POST",
|
|
498
642
|
headers: {
|
|
@@ -508,17 +652,33 @@ var createAccessToken = async (oauthConfig, redirectURI, code, codeVerifier) =>
|
|
|
508
652
|
code_verifier: codeVerifier
|
|
509
653
|
}).toString()
|
|
510
654
|
});
|
|
655
|
+
if (!response.ok) {
|
|
656
|
+
logger?.log("INVALID_OAUTH_ACCESS_TOKEN_RESPONSE");
|
|
657
|
+
throw new OAuthProtocolError("invalid_request", "Invalid access token response");
|
|
658
|
+
}
|
|
511
659
|
const json = await response.json();
|
|
512
660
|
const token = OAuthAccessTokenResponse.safeParse(json);
|
|
513
661
|
if (!token.success) {
|
|
514
662
|
const { success, data } = OAuthAccessTokenErrorResponse.safeParse(json);
|
|
515
663
|
if (!success) {
|
|
516
|
-
|
|
664
|
+
logger?.log("INVALID_OAUTH_ACCESS_TOKEN_RESPONSE");
|
|
665
|
+
throw new OAuthProtocolError("invalid_request", "Invalid access token response format");
|
|
517
666
|
}
|
|
518
|
-
|
|
667
|
+
logger?.log("OAUTH_ACCESS_TOKEN_ERROR", {
|
|
668
|
+
structuredData: {
|
|
669
|
+
error: data.error,
|
|
670
|
+
error_description: data.error_description ?? ""
|
|
671
|
+
}
|
|
672
|
+
});
|
|
673
|
+
throw new OAuthProtocolError("INVALID_ACCESS_TOKEN", "Failed to retrieve access token");
|
|
519
674
|
}
|
|
675
|
+
logger?.log("OAUTH_ACCESS_TOKEN_SUCCESS");
|
|
520
676
|
return token.data;
|
|
521
677
|
} catch (error) {
|
|
678
|
+
logger?.log("OAUTH_ACCESS_TOKEN_REQUEST_FAILED");
|
|
679
|
+
if (error instanceof Error) {
|
|
680
|
+
throw new OAuthProtocolError("server_error", "Failed to communicate with OAuth provider", "", { cause: error });
|
|
681
|
+
}
|
|
522
682
|
throw error;
|
|
523
683
|
}
|
|
524
684
|
};
|
|
@@ -543,7 +703,8 @@ var setCookie = (cookieName, value, options2) => {
|
|
|
543
703
|
var expiredCookieAttributes = {
|
|
544
704
|
...defaultCookieOptions,
|
|
545
705
|
expires: /* @__PURE__ */ new Date(0),
|
|
546
|
-
maxAge: 0
|
|
706
|
+
maxAge: 0,
|
|
707
|
+
secure: true
|
|
547
708
|
};
|
|
548
709
|
var getCookie = (request, cookieName) => {
|
|
549
710
|
const cookies = request.headers.get("Cookie");
|
|
@@ -582,10 +743,23 @@ var callbackConfig = (oauth) => {
|
|
|
582
743
|
},
|
|
583
744
|
middlewares: [
|
|
584
745
|
(ctx) => {
|
|
585
|
-
const
|
|
746
|
+
const {
|
|
747
|
+
searchParams,
|
|
748
|
+
context: { logger }
|
|
749
|
+
} = ctx;
|
|
750
|
+
const response = OAuthAuthorizationErrorResponse.safeParse(searchParams);
|
|
586
751
|
if (response.success) {
|
|
587
752
|
const { error, error_description } = response.data;
|
|
588
|
-
|
|
753
|
+
const criticalAuthErrors = ["access_denied", "server_error"];
|
|
754
|
+
const severity = criticalAuthErrors.includes(error.toLowerCase()) ? "critical" : "warning";
|
|
755
|
+
logger?.log("OAUTH_AUTHORIZATION_ERROR", {
|
|
756
|
+
severity,
|
|
757
|
+
structuredData: {
|
|
758
|
+
error,
|
|
759
|
+
error_description: error_description ?? ""
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
throw new OAuthProtocolError(error, error_description || "OAuth Authorization Error");
|
|
589
763
|
}
|
|
590
764
|
return ctx;
|
|
591
765
|
}
|
|
@@ -601,31 +775,54 @@ var callbackAction = (oauth) => {
|
|
|
601
775
|
request,
|
|
602
776
|
params: { oauth: oauth2 },
|
|
603
777
|
searchParams: { code, state },
|
|
604
|
-
context
|
|
778
|
+
context
|
|
605
779
|
} = ctx;
|
|
780
|
+
const { oauth: providers, cookies, jose, logger, trustedOrigins } = context;
|
|
606
781
|
const oauthConfig = providers[oauth2];
|
|
607
782
|
const cookieState = getCookie(request, cookies.state.name);
|
|
783
|
+
const codeVerifier = getCookie(request, cookies.codeVerifier.name);
|
|
608
784
|
const cookieRedirectTo = getCookie(request, cookies.redirectTo.name);
|
|
609
785
|
const cookieRedirectURI = getCookie(request, cookies.redirectURI.name);
|
|
610
|
-
|
|
611
|
-
|
|
786
|
+
if (!safeEquals(cookieState, state)) {
|
|
787
|
+
logger?.log("MISMATCHING_STATE", {
|
|
788
|
+
structuredData: {
|
|
789
|
+
oauth_provider: oauth2
|
|
790
|
+
}
|
|
791
|
+
});
|
|
612
792
|
throw new AuthSecurityError(
|
|
613
793
|
"MISMATCHING_STATE",
|
|
614
794
|
"The provided state passed in the OAuth response does not match the stored state."
|
|
615
795
|
);
|
|
616
796
|
}
|
|
617
|
-
const accessToken = await createAccessToken(oauthConfig, cookieRedirectURI, code, codeVerifier);
|
|
618
|
-
const
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
797
|
+
const accessToken = await createAccessToken(oauthConfig, cookieRedirectURI, code, codeVerifier, logger);
|
|
798
|
+
const origins = await getTrustedOrigins(request, trustedOrigins);
|
|
799
|
+
const requestOrigin = await getOriginURL(request, context);
|
|
800
|
+
if (!isRelativeURL(cookieRedirectTo)) {
|
|
801
|
+
const isValid = origins.length > 0 ? isTrustedOrigin(cookieRedirectTo, origins) : isSameOrigin(cookieRedirectTo, requestOrigin);
|
|
802
|
+
if (!isValid) {
|
|
803
|
+
logger?.log("POTENTIAL_OPEN_REDIRECT_ATTACK_DETECTED", {
|
|
804
|
+
structuredData: {
|
|
805
|
+
redirect_path: cookieRedirectTo,
|
|
806
|
+
provider: oauth2,
|
|
807
|
+
has_trusted_origins: origins.length > 0,
|
|
808
|
+
request_origin: requestOrigin
|
|
809
|
+
}
|
|
810
|
+
});
|
|
811
|
+
throw new AuthSecurityError(
|
|
812
|
+
"POTENTIAL_OPEN_REDIRECT_ATTACK_DETECTED",
|
|
813
|
+
"Invalid redirect path. Potential open redirect attack detected."
|
|
814
|
+
);
|
|
815
|
+
}
|
|
624
816
|
}
|
|
625
|
-
const userInfo = await getUserInfo(oauthConfig, accessToken.access_token);
|
|
817
|
+
const userInfo = await getUserInfo(oauthConfig, accessToken.access_token, logger);
|
|
626
818
|
const sessionCookie = await createSessionCookie(jose, userInfo);
|
|
627
819
|
const csrfToken = await createCSRF(jose);
|
|
628
|
-
|
|
820
|
+
logger?.log("OAUTH_CALLBACK_SUCCESS", {
|
|
821
|
+
structuredData: {
|
|
822
|
+
provider: oauth2
|
|
823
|
+
}
|
|
824
|
+
});
|
|
825
|
+
const headers = new import_router3.HeadersBuilder(cacheControl).setHeader("Location", cookieRedirectTo).setCookie(cookies.sessionToken.name, sessionCookie, cookies.sessionToken.attributes).setCookie(cookies.csrfToken.name, csrfToken, cookies.csrfToken.attributes).setCookie(cookies.state.name, "", expiredCookieAttributes).setCookie(cookies.redirectURI.name, "", expiredCookieAttributes).setCookie(cookies.redirectTo.name, "", expiredCookieAttributes).setCookie(cookies.codeVerifier.name, "", expiredCookieAttributes).toHeaders();
|
|
629
826
|
return Response.json({ oauth: oauth2 }, { status: 302, headers });
|
|
630
827
|
},
|
|
631
828
|
callbackConfig(oauth)
|
|
@@ -637,16 +834,18 @@ var import_router4 = require("@aura-stack/router");
|
|
|
637
834
|
var sessionAction = (0, import_router4.createEndpoint)("GET", "/session", async (ctx) => {
|
|
638
835
|
const {
|
|
639
836
|
request,
|
|
640
|
-
context: { jose, cookies }
|
|
837
|
+
context: { jose, cookies, logger }
|
|
641
838
|
} = ctx;
|
|
642
839
|
try {
|
|
643
840
|
const session = getCookie(request, cookies.sessionToken.name);
|
|
644
841
|
const decoded = await jose.decodeJWT(session);
|
|
842
|
+
logger?.log("AUTH_SESSION_VALID");
|
|
645
843
|
const { exp, iat, jti, nbf, ...user } = decoded;
|
|
646
|
-
const headers = new Headers(
|
|
844
|
+
const headers = new Headers(secureApiHeaders);
|
|
647
845
|
return Response.json({ user, expires: toISOString(exp * 1e3) }, { headers });
|
|
648
846
|
} catch (error) {
|
|
649
|
-
|
|
847
|
+
logger?.log("AUTH_SESSION_INVALID", { structuredData: { error_type: getErrorName(error) } });
|
|
848
|
+
const headers = new import_router4.HeadersBuilder(secureApiHeaders).setCookie(cookies.sessionToken.name, "", expiredCookieAttributes).toHeaders();
|
|
650
849
|
return Response.json({ authenticated: false, message: "Unauthorized" }, { status: 401, headers });
|
|
651
850
|
}
|
|
652
851
|
});
|
|
@@ -670,30 +869,54 @@ var signOutAction = (0, import_router5.createEndpoint)(
|
|
|
670
869
|
request,
|
|
671
870
|
headers,
|
|
672
871
|
searchParams: { redirectTo },
|
|
673
|
-
context
|
|
872
|
+
context
|
|
674
873
|
} = ctx;
|
|
874
|
+
const { jose, cookies, logger } = context;
|
|
675
875
|
const session = headers.getCookie(cookies.sessionToken.name);
|
|
676
876
|
const csrfToken = headers.getCookie(cookies.csrfToken.name);
|
|
677
877
|
const header = headers.getHeader("X-CSRF-Token");
|
|
878
|
+
logger?.log("SIGN_OUT_ATTEMPT", {
|
|
879
|
+
structuredData: {
|
|
880
|
+
has_session: Boolean(session),
|
|
881
|
+
has_csrf_token: Boolean(csrfToken),
|
|
882
|
+
has_csrf_header: Boolean(header)
|
|
883
|
+
}
|
|
884
|
+
});
|
|
678
885
|
if (!session) {
|
|
886
|
+
logger?.log("SESSION_TOKEN_MISSING");
|
|
679
887
|
throw new AuthSecurityError("SESSION_TOKEN_MISSING", "The sessionToken is missing.");
|
|
680
888
|
}
|
|
681
889
|
if (!csrfToken) {
|
|
890
|
+
logger?.log("CSRF_TOKEN_MISSING");
|
|
682
891
|
throw new AuthSecurityError("CSRF_TOKEN_MISSING", "The CSRF token is missing.");
|
|
683
892
|
}
|
|
684
893
|
if (!header) {
|
|
685
|
-
|
|
894
|
+
logger?.log("CSRF_HEADER_MISSING");
|
|
895
|
+
throw new AuthSecurityError("CSRF_HEADER_MISSING", "The CSRF header is missing.");
|
|
896
|
+
}
|
|
897
|
+
try {
|
|
898
|
+
await verifyCSRF(jose, csrfToken, header);
|
|
899
|
+
} catch (error) {
|
|
900
|
+
logger?.log("CSRF_TOKEN_INVALID", { structuredData: { error_type: getErrorName(error) } });
|
|
901
|
+
throw new AuthSecurityError("CSRF_TOKEN_INVALID", "CSRF token verification failed");
|
|
902
|
+
}
|
|
903
|
+
logger?.log("SIGN_OUT_CSRF_VERIFIED");
|
|
904
|
+
try {
|
|
905
|
+
await jose.decodeJWT(session);
|
|
906
|
+
logger?.log("SIGN_OUT_SUCCESS");
|
|
907
|
+
} catch (error) {
|
|
908
|
+
logger?.log("INVALID_JWT_TOKEN", { structuredData: { error_type: getErrorName(error) } });
|
|
686
909
|
}
|
|
687
|
-
|
|
688
|
-
await
|
|
689
|
-
|
|
690
|
-
const location = createRedirectTo(
|
|
691
|
-
new Request(normalizedOriginPath, {
|
|
910
|
+
const baseURL = getBaseURL(request);
|
|
911
|
+
const location = await createRedirectTo(
|
|
912
|
+
new Request(baseURL, {
|
|
692
913
|
headers: headers.toHeaders()
|
|
693
914
|
}),
|
|
694
|
-
redirectTo
|
|
915
|
+
redirectTo,
|
|
916
|
+
context
|
|
695
917
|
);
|
|
696
|
-
|
|
918
|
+
logger?.log("SIGN_OUT_REDIRECT", { structuredData: { location } });
|
|
919
|
+
const headersList = new import_router5.HeadersBuilder(secureApiHeaders).setHeader("Location", location).setCookie(cookies.csrfToken.name, "", expiredCookieAttributes).setCookie(cookies.sessionToken.name, "", expiredCookieAttributes).toHeaders();
|
|
697
920
|
return Response.json({ message: "Signed out successfully" }, { status: import_router5.statusCode.ACCEPTED, headers: headersList });
|
|
698
921
|
},
|
|
699
922
|
config
|
|
@@ -711,11 +934,13 @@ var getCSRFToken = (request, cookieName) => {
|
|
|
711
934
|
var csrfTokenAction = (0, import_router6.createEndpoint)("GET", "/csrfToken", async (ctx) => {
|
|
712
935
|
const {
|
|
713
936
|
request,
|
|
714
|
-
context: { jose, cookies }
|
|
937
|
+
context: { jose, cookies, logger }
|
|
715
938
|
} = ctx;
|
|
716
939
|
const token = getCSRFToken(request, cookies.csrfToken.name);
|
|
940
|
+
logger?.log("CSRF_TOKEN_REQUESTED", { structuredData: { has_token: Boolean(token) } });
|
|
717
941
|
const csrfToken = await createCSRF(jose, token);
|
|
718
|
-
|
|
942
|
+
logger?.log("CSRF_TOKEN_ISSUED", { structuredData: { issued: Boolean(csrfToken) } });
|
|
943
|
+
const headers = new Headers(secureApiHeaders);
|
|
719
944
|
headers.append("Set-Cookie", setCookie(cookies.csrfToken.name, csrfToken, cookies.csrfToken.attributes));
|
|
720
945
|
return Response.json({ csrfToken }, { headers });
|
|
721
946
|
});
|