@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/index.cjs
CHANGED
|
@@ -30,23 +30,43 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
-
createAuth: () => createAuth
|
|
34
|
-
createClient: () => import_router7.createClient
|
|
33
|
+
createAuth: () => createAuth
|
|
35
34
|
});
|
|
36
35
|
module.exports = __toCommonJS(index_exports);
|
|
37
|
-
var import_config2 = require("dotenv/config");
|
|
38
36
|
var import_router7 = require("@aura-stack/router");
|
|
39
37
|
|
|
38
|
+
// src/env.ts
|
|
39
|
+
var import_meta = {};
|
|
40
|
+
var env = new Proxy({}, {
|
|
41
|
+
get(_, prop) {
|
|
42
|
+
if (typeof prop !== "string") return void 0;
|
|
43
|
+
const hasProperty = (process2) => {
|
|
44
|
+
return process2 && Object.prototype.hasOwnProperty.call(process2, prop);
|
|
45
|
+
};
|
|
46
|
+
try {
|
|
47
|
+
if (typeof process !== "undefined" && hasProperty(process.env)) {
|
|
48
|
+
return process.env[prop];
|
|
49
|
+
}
|
|
50
|
+
if (typeof import_meta !== "undefined" && hasProperty(import_meta.env)) {
|
|
51
|
+
return import_meta.env[prop];
|
|
52
|
+
}
|
|
53
|
+
if (typeof Deno !== "undefined" && Deno.env?.get) {
|
|
54
|
+
return Deno.env.get(prop);
|
|
55
|
+
}
|
|
56
|
+
if (typeof Bun !== "undefined" && hasProperty(Bun.env)) {
|
|
57
|
+
return Bun.env[prop];
|
|
58
|
+
}
|
|
59
|
+
const globalValue = globalThis[prop];
|
|
60
|
+
return typeof globalValue === "string" ? globalValue : void 0;
|
|
61
|
+
} catch {
|
|
62
|
+
return void 0;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
40
67
|
// src/jose.ts
|
|
41
|
-
var import_config = require("dotenv/config");
|
|
42
68
|
var import_jose = require("@aura-stack/jose");
|
|
43
69
|
|
|
44
|
-
// src/secure.ts
|
|
45
|
-
var import_crypto = __toESM(require("crypto"), 1);
|
|
46
|
-
|
|
47
|
-
// src/utils.ts
|
|
48
|
-
var import_router = require("@aura-stack/router");
|
|
49
|
-
|
|
50
70
|
// src/errors.ts
|
|
51
71
|
var OAuthProtocolError = class extends Error {
|
|
52
72
|
type = "OAUTH_PROTOCOL_ERROR";
|
|
@@ -93,194 +113,8 @@ var isAuthSecurityError = (error) => {
|
|
|
93
113
|
return error instanceof AuthSecurityError;
|
|
94
114
|
};
|
|
95
115
|
|
|
96
|
-
// src/utils.ts
|
|
97
|
-
var toSnakeCase = (str) => {
|
|
98
|
-
return str.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").toLowerCase().replace(/^_+/, "");
|
|
99
|
-
};
|
|
100
|
-
var toUpperCase = (str) => {
|
|
101
|
-
return str.toUpperCase();
|
|
102
|
-
};
|
|
103
|
-
var toCastCase = (obj, type = "snake") => {
|
|
104
|
-
return Object.entries(obj).reduce((previous, [key, value]) => {
|
|
105
|
-
const newKey = type === "snake" ? toSnakeCase(key) : toUpperCase(key);
|
|
106
|
-
return { ...previous, [newKey]: value };
|
|
107
|
-
}, {});
|
|
108
|
-
};
|
|
109
|
-
var equals = (a, b) => {
|
|
110
|
-
if (a === null || b === null || a === void 0 || b === void 0) return false;
|
|
111
|
-
return a === b;
|
|
112
|
-
};
|
|
113
|
-
var sanitizeURL = (url) => {
|
|
114
|
-
try {
|
|
115
|
-
let decodedURL = decodeURIComponent(url).trim();
|
|
116
|
-
const protocolMatch = decodedURL.match(/^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)/);
|
|
117
|
-
let protocol = "";
|
|
118
|
-
let rest = decodedURL;
|
|
119
|
-
if (protocolMatch) {
|
|
120
|
-
protocol = protocolMatch[1];
|
|
121
|
-
rest = decodedURL.slice(protocol.length);
|
|
122
|
-
const slashIndex = rest.indexOf("/");
|
|
123
|
-
if (slashIndex === -1) {
|
|
124
|
-
return protocol + rest;
|
|
125
|
-
}
|
|
126
|
-
const domain = rest.slice(0, slashIndex);
|
|
127
|
-
let path = rest.slice(slashIndex).replace(/\/\.\.\//g, "/").replace(/\/\.\.$/, "").replace(/\.{2,}/g, "").replace(/\/{2,}/g, "/");
|
|
128
|
-
if (path !== "/" && path.endsWith("/")) {
|
|
129
|
-
path = path.replace(/\/+$/, "/");
|
|
130
|
-
} else if (path !== "/") {
|
|
131
|
-
path = path.replace(/\/+$/, "");
|
|
132
|
-
}
|
|
133
|
-
return protocol + domain + path;
|
|
134
|
-
}
|
|
135
|
-
let sanitized = decodedURL.replace(/\/\.\.\//g, "/").replace(/\/\.\.$/, "").replace(/\.{2,}/g, "").replace(/\/{2,}/g, "/");
|
|
136
|
-
if (sanitized !== "/" && sanitized.endsWith("/")) {
|
|
137
|
-
sanitized = sanitized.replace(/\/+$/, "/");
|
|
138
|
-
} else if (sanitized !== "/") {
|
|
139
|
-
sanitized = sanitized.replace(/\/+$/, "");
|
|
140
|
-
}
|
|
141
|
-
return sanitized;
|
|
142
|
-
} catch {
|
|
143
|
-
return url.trim();
|
|
144
|
-
}
|
|
145
|
-
};
|
|
146
|
-
var isValidRelativePath = (path) => {
|
|
147
|
-
if (!path || typeof path !== "string") return false;
|
|
148
|
-
if (!path.startsWith("/") || path.includes("://") || path.includes("\r") || path.includes("\n")) return false;
|
|
149
|
-
if (/[\x00-\x1F\x7F]/.test(path) || path.includes("\0")) return false;
|
|
150
|
-
const sanitized = sanitizeURL(path);
|
|
151
|
-
if (sanitized.includes("..")) return false;
|
|
152
|
-
return true;
|
|
153
|
-
};
|
|
154
|
-
var onErrorHandler = (error) => {
|
|
155
|
-
if ((0, import_router.isRouterError)(error)) {
|
|
156
|
-
const { message, status, statusText } = error;
|
|
157
|
-
return Response.json({ type: "ROUTER_ERROR", code: "ROUTER_INTERNAL_ERROR", message }, { status, statusText });
|
|
158
|
-
}
|
|
159
|
-
if ((0, import_router.isInvalidZodSchemaError)(error)) {
|
|
160
|
-
return Response.json({ type: "ROUTER_ERROR", code: "INVALID_REQUEST", message: error.errors }, { status: 422 });
|
|
161
|
-
}
|
|
162
|
-
if (isOAuthProtocolError(error)) {
|
|
163
|
-
const { error: errorCode, message, type, errorURI } = error;
|
|
164
|
-
return Response.json(
|
|
165
|
-
{
|
|
166
|
-
type,
|
|
167
|
-
error: errorCode,
|
|
168
|
-
error_description: message,
|
|
169
|
-
error_uri: errorURI
|
|
170
|
-
},
|
|
171
|
-
{ status: 400 }
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
if (isAuthInternalError(error) || isAuthSecurityError(error)) {
|
|
175
|
-
const { type, code, message } = error;
|
|
176
|
-
return Response.json(
|
|
177
|
-
{
|
|
178
|
-
type,
|
|
179
|
-
code,
|
|
180
|
-
message
|
|
181
|
-
},
|
|
182
|
-
{ status: 400 }
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
return Response.json({ type: "SERVER_ERROR", code: "server_error", message: "An unexpected error occurred" }, { status: 500 });
|
|
186
|
-
};
|
|
187
|
-
var getNormalizedOriginPath = (path) => {
|
|
188
|
-
try {
|
|
189
|
-
const url = new URL(path);
|
|
190
|
-
url.hash = "";
|
|
191
|
-
url.search = "";
|
|
192
|
-
return `${url.origin}${url.pathname}`;
|
|
193
|
-
} catch {
|
|
194
|
-
return sanitizeURL(path);
|
|
195
|
-
}
|
|
196
|
-
};
|
|
197
|
-
var toISOString = (date) => {
|
|
198
|
-
return new Date(date).toISOString();
|
|
199
|
-
};
|
|
200
|
-
var useSecureCookies = (request, trustedProxyHeaders) => {
|
|
201
|
-
return trustedProxyHeaders ? request.url.startsWith("https://") || request.headers.get("X-Forwarded-Proto") === "https" || (request.headers.get("Forwarded")?.includes("proto=https") ?? false) : request.url.startsWith("https://");
|
|
202
|
-
};
|
|
203
|
-
var formatZodError = (error) => {
|
|
204
|
-
if (!error.issues || error.issues.length === 0) {
|
|
205
|
-
return {};
|
|
206
|
-
}
|
|
207
|
-
return error.issues.reduce((previous, issue) => {
|
|
208
|
-
const key = issue.path.join(".");
|
|
209
|
-
return {
|
|
210
|
-
...previous,
|
|
211
|
-
[key]: {
|
|
212
|
-
code: issue.code,
|
|
213
|
-
message: issue.message
|
|
214
|
-
}
|
|
215
|
-
};
|
|
216
|
-
}, {});
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
// src/assert.ts
|
|
220
|
-
var isValidURL = (value) => {
|
|
221
|
-
if (value.includes("\r\n") || value.includes("\n") || value.includes("\r")) return false;
|
|
222
|
-
const regex = /^https?:\/\/(?:[a-zA-Z0-9._-]+|localhost|\[[0-9a-fA-F:]+\])(?::\d{1,5})?(?:\/[a-zA-Z0-9._~!$&'()*+,;=:@-]*)*\/?$/;
|
|
223
|
-
return regex.test(value);
|
|
224
|
-
};
|
|
225
|
-
var isJWTPayloadWithToken = (payload) => {
|
|
226
|
-
return typeof payload === "object" && payload !== null && "token" in payload && typeof payload?.token === "string";
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
// src/secure.ts
|
|
230
|
-
var generateSecure = (length = 32) => {
|
|
231
|
-
return import_crypto.default.randomBytes(length).toString("base64url");
|
|
232
|
-
};
|
|
233
|
-
var createHash = (data, base = "hex") => {
|
|
234
|
-
return import_crypto.default.createHash("sha256").update(data).digest().toString(base);
|
|
235
|
-
};
|
|
236
|
-
var createPKCE = async (verifier) => {
|
|
237
|
-
const codeVerifier = verifier ?? generateSecure(86);
|
|
238
|
-
const codeChallenge = createHash(codeVerifier, "base64url");
|
|
239
|
-
return { codeVerifier, codeChallenge, method: "S256" };
|
|
240
|
-
};
|
|
241
|
-
var createCSRF = async (jose, csrfCookie) => {
|
|
242
|
-
try {
|
|
243
|
-
const token = generateSecure(32);
|
|
244
|
-
if (csrfCookie) {
|
|
245
|
-
await jose.verifyJWS(csrfCookie);
|
|
246
|
-
return csrfCookie;
|
|
247
|
-
}
|
|
248
|
-
return jose.signJWS({ token });
|
|
249
|
-
} catch {
|
|
250
|
-
const token = generateSecure(32);
|
|
251
|
-
return jose.signJWS({ token });
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
var verifyCSRF = async (jose, cookie, header) => {
|
|
255
|
-
try {
|
|
256
|
-
const cookiePayload = await jose.verifyJWS(cookie);
|
|
257
|
-
const headerPayload = await jose.verifyJWS(header);
|
|
258
|
-
if (!isJWTPayloadWithToken(cookiePayload)) {
|
|
259
|
-
throw new AuthSecurityError("CSRF_TOKEN_INVALID", "Cookie payload missing token field.");
|
|
260
|
-
}
|
|
261
|
-
if (!isJWTPayloadWithToken(headerPayload)) {
|
|
262
|
-
throw new AuthSecurityError("CSRF_TOKEN_INVALID", "Header payload missing token field.");
|
|
263
|
-
}
|
|
264
|
-
const cookieBuffer = Buffer.from(cookiePayload.token);
|
|
265
|
-
const headerBuffer = Buffer.from(headerPayload.token);
|
|
266
|
-
if (!equals(headerBuffer.length, cookieBuffer.length)) {
|
|
267
|
-
throw new AuthSecurityError("CSRF_TOKEN_INVALID", "The CSRF tokens do not match.");
|
|
268
|
-
}
|
|
269
|
-
if (!import_crypto.default.timingSafeEqual(cookieBuffer, headerBuffer)) {
|
|
270
|
-
throw new AuthSecurityError("CSRF_TOKEN_INVALID", "The CSRF tokens do not match.");
|
|
271
|
-
}
|
|
272
|
-
return true;
|
|
273
|
-
} catch {
|
|
274
|
-
throw new AuthSecurityError("CSRF_TOKEN_INVALID", "The CSRF tokens do not match.");
|
|
275
|
-
}
|
|
276
|
-
};
|
|
277
|
-
var createDerivedSalt = (secret) => {
|
|
278
|
-
return import_crypto.default.createHash("sha256").update(secret).update("aura-auth-salt").digest("hex");
|
|
279
|
-
};
|
|
280
|
-
|
|
281
116
|
// src/jose.ts
|
|
282
117
|
var createJoseInstance = (secret) => {
|
|
283
|
-
const env = process.env;
|
|
284
118
|
secret ??= env.AURA_AUTH_SECRET ?? env.AUTH_SECRET;
|
|
285
119
|
if (!secret) {
|
|
286
120
|
throw new AuthInternalError(
|
|
@@ -288,7 +122,22 @@ var createJoseInstance = (secret) => {
|
|
|
288
122
|
"AURA_AUTH_SECRET environment variable is not set and no secret was provided."
|
|
289
123
|
);
|
|
290
124
|
}
|
|
291
|
-
const salt = env.AURA_AUTH_SALT ?? env.AUTH_SALT
|
|
125
|
+
const salt = env.AURA_AUTH_SALT ?? env.AUTH_SALT;
|
|
126
|
+
if (!salt) {
|
|
127
|
+
throw new AuthInternalError(
|
|
128
|
+
"JOSE_INITIALIZATION_FAILED",
|
|
129
|
+
"AURA_AUTH_SALT or AUTH_SALT environment variable is not set. A salt value is required for key derivation."
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
(0, import_jose.createSecret)(salt);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
throw new AuthInternalError(
|
|
136
|
+
"INVALID_SALT_SECRET_VALUE",
|
|
137
|
+
"AURA_AUTH_SALT/AUTH_SALT is invalid. It must be at least 32 bytes long and meet entropy requirements.",
|
|
138
|
+
{ cause: error }
|
|
139
|
+
);
|
|
140
|
+
}
|
|
292
141
|
const { derivedKey: derivedSigningKey } = (0, import_jose.createDeriveKey)(secret, salt, "signing");
|
|
293
142
|
const { derivedKey: derivedEncryptionKey } = (0, import_jose.createDeriveKey)(secret, salt, "encryption");
|
|
294
143
|
const { derivedKey: derivedCsrfTokenKey } = (0, import_jose.createDeriveKey)(secret, salt, "csrfToken");
|
|
@@ -304,6 +153,10 @@ var createJoseInstance = (secret) => {
|
|
|
304
153
|
decryptJWE
|
|
305
154
|
};
|
|
306
155
|
};
|
|
156
|
+
var jwtVerificationOptions = {
|
|
157
|
+
algorithms: ["HS256"],
|
|
158
|
+
typ: "JWT"
|
|
159
|
+
};
|
|
307
160
|
|
|
308
161
|
// src/cookie.ts
|
|
309
162
|
var import_cookie = require("@aura-stack/router/cookie");
|
|
@@ -340,7 +193,8 @@ var setCookie = (cookieName, value, options2) => {
|
|
|
340
193
|
var expiredCookieAttributes = {
|
|
341
194
|
...defaultCookieOptions,
|
|
342
195
|
expires: /* @__PURE__ */ new Date(0),
|
|
343
|
-
maxAge: 0
|
|
196
|
+
maxAge: 0,
|
|
197
|
+
secure: true
|
|
344
198
|
};
|
|
345
199
|
var getCookie = (request, cookieName) => {
|
|
346
200
|
const cookies = request.headers.get("Cookie");
|
|
@@ -361,31 +215,27 @@ var createSessionCookie = async (jose, session) => {
|
|
|
361
215
|
throw new AuthInternalError("INVALID_JWT_TOKEN", "Failed to create session cookie", { cause: error });
|
|
362
216
|
}
|
|
363
217
|
};
|
|
364
|
-
var defineSecureCookieOptions = (useSecure, attributes, strategy) => {
|
|
218
|
+
var defineSecureCookieOptions = (useSecure, attributes, strategy, logger) => {
|
|
365
219
|
if (!attributes.httpOnly) {
|
|
366
|
-
|
|
367
|
-
"[WARNING]: Cookie is configured without HttpOnly. This allows JavaScript access via document.cookie and increases XSS risk."
|
|
368
|
-
);
|
|
220
|
+
logger?.log("COOKIE_HTTPONLY_DISABLED");
|
|
369
221
|
}
|
|
370
222
|
if (attributes.domain === "*") {
|
|
371
223
|
attributes.domain = void 0;
|
|
372
|
-
|
|
224
|
+
logger?.log("COOKIE_WILDCARD_DOMAIN");
|
|
373
225
|
}
|
|
374
226
|
if (!useSecure) {
|
|
375
227
|
if (attributes.secure) {
|
|
376
|
-
|
|
377
|
-
"[WARNING]: The 'Secure' attribute will be disabled for this cookie. Serve over HTTPS to enforce Secure cookies."
|
|
378
|
-
);
|
|
228
|
+
logger?.log("COOKIE_SECURE_DISABLED");
|
|
379
229
|
}
|
|
380
230
|
if (attributes.sameSite == "none") {
|
|
381
231
|
attributes.sameSite = "lax";
|
|
382
|
-
|
|
232
|
+
logger?.log("COOKIE_SAMESITE_NONE_WITHOUT_SECURE");
|
|
383
233
|
}
|
|
384
234
|
if (process.env.NODE_ENV === "production") {
|
|
385
|
-
|
|
235
|
+
logger?.log("COOKIE_INSECURE_IN_PRODUCTION");
|
|
386
236
|
}
|
|
387
237
|
if (strategy === "host") {
|
|
388
|
-
|
|
238
|
+
logger?.log("COOKIE_HOST_STRATEGY_INSECURE");
|
|
389
239
|
}
|
|
390
240
|
return {
|
|
391
241
|
...defaultCookieOptions,
|
|
@@ -399,7 +249,7 @@ var defineSecureCookieOptions = (useSecure, attributes, strategy) => {
|
|
|
399
249
|
...defaultHostCookieConfig
|
|
400
250
|
} : { ...defaultCookieOptions, ...attributes, ...defaultSecureCookieConfig };
|
|
401
251
|
};
|
|
402
|
-
var createCookieStore = (useSecure, prefix, overrides) => {
|
|
252
|
+
var createCookieStore = (useSecure, prefix, overrides, logger) => {
|
|
403
253
|
prefix ??= COOKIE_NAME;
|
|
404
254
|
const securePrefix = useSecure ? "__Secure-" : "";
|
|
405
255
|
const hostPrefix = useSecure ? "__Host-" : "";
|
|
@@ -412,7 +262,8 @@ var createCookieStore = (useSecure, prefix, overrides) => {
|
|
|
412
262
|
...defaultCookieOptions,
|
|
413
263
|
...overrides?.sessionToken?.attributes
|
|
414
264
|
},
|
|
415
|
-
overrides?.sessionToken?.attributes?.strategy ?? "secure"
|
|
265
|
+
overrides?.sessionToken?.attributes?.strategy ?? "secure",
|
|
266
|
+
logger
|
|
416
267
|
)
|
|
417
268
|
},
|
|
418
269
|
state: {
|
|
@@ -423,7 +274,8 @@ var createCookieStore = (useSecure, prefix, overrides) => {
|
|
|
423
274
|
...oauthCookieOptions,
|
|
424
275
|
...overrides?.state?.attributes
|
|
425
276
|
},
|
|
426
|
-
overrides?.state?.attributes?.strategy ?? "secure"
|
|
277
|
+
overrides?.state?.attributes?.strategy ?? "secure",
|
|
278
|
+
logger
|
|
427
279
|
)
|
|
428
280
|
},
|
|
429
281
|
csrfToken: {
|
|
@@ -432,9 +284,11 @@ var createCookieStore = (useSecure, prefix, overrides) => {
|
|
|
432
284
|
useSecure,
|
|
433
285
|
{
|
|
434
286
|
...overrides?.csrfToken?.attributes,
|
|
435
|
-
...defaultHostCookieConfig
|
|
287
|
+
...defaultHostCookieConfig,
|
|
288
|
+
sameSite: "strict"
|
|
436
289
|
},
|
|
437
|
-
overrides?.csrfToken?.attributes?.strategy ?? "host"
|
|
290
|
+
overrides?.csrfToken?.attributes?.strategy ?? "host",
|
|
291
|
+
logger
|
|
438
292
|
)
|
|
439
293
|
},
|
|
440
294
|
redirectTo: {
|
|
@@ -445,7 +299,8 @@ var createCookieStore = (useSecure, prefix, overrides) => {
|
|
|
445
299
|
...oauthCookieOptions,
|
|
446
300
|
...overrides?.redirectTo?.attributes
|
|
447
301
|
},
|
|
448
|
-
overrides?.redirectTo?.attributes?.strategy ?? "secure"
|
|
302
|
+
overrides?.redirectTo?.attributes?.strategy ?? "secure",
|
|
303
|
+
logger
|
|
449
304
|
)
|
|
450
305
|
},
|
|
451
306
|
redirectURI: {
|
|
@@ -456,7 +311,8 @@ var createCookieStore = (useSecure, prefix, overrides) => {
|
|
|
456
311
|
...oauthCookieOptions,
|
|
457
312
|
...overrides?.redirectURI?.attributes
|
|
458
313
|
},
|
|
459
|
-
overrides?.redirectURI?.attributes?.strategy ?? "secure"
|
|
314
|
+
overrides?.redirectURI?.attributes?.strategy ?? "secure",
|
|
315
|
+
logger
|
|
460
316
|
)
|
|
461
317
|
},
|
|
462
318
|
codeVerifier: {
|
|
@@ -467,203 +323,378 @@ var createCookieStore = (useSecure, prefix, overrides) => {
|
|
|
467
323
|
...oauthCookieOptions,
|
|
468
324
|
...overrides?.codeVerifier?.attributes
|
|
469
325
|
},
|
|
470
|
-
overrides?.codeVerifier?.attributes?.strategy ?? "secure"
|
|
326
|
+
overrides?.codeVerifier?.attributes?.strategy ?? "secure",
|
|
327
|
+
logger
|
|
471
328
|
)
|
|
472
329
|
}
|
|
473
330
|
};
|
|
474
331
|
};
|
|
475
332
|
|
|
476
|
-
// src/
|
|
477
|
-
var
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
authorizeURL: "https://github.com/login/oauth/authorize",
|
|
481
|
-
accessToken: "https://github.com/login/oauth/access_token",
|
|
482
|
-
userInfo: "https://api.github.com/user",
|
|
483
|
-
scope: "read:user user:email",
|
|
484
|
-
responseType: "code"
|
|
333
|
+
// src/utils.ts
|
|
334
|
+
var import_router = require("@aura-stack/router");
|
|
335
|
+
var toSnakeCase = (str) => {
|
|
336
|
+
return str.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").toLowerCase().replace(/^_+/, "");
|
|
485
337
|
};
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
338
|
+
var toUpperCase = (str) => {
|
|
339
|
+
return str.toUpperCase();
|
|
340
|
+
};
|
|
341
|
+
var toCastCase = (obj, type = "snake") => {
|
|
342
|
+
return Object.entries(obj).reduce((previous, [key, value]) => {
|
|
343
|
+
const newKey = type === "snake" ? toSnakeCase(key) : toUpperCase(key);
|
|
344
|
+
return { ...previous, [newKey]: value };
|
|
345
|
+
}, {});
|
|
346
|
+
};
|
|
347
|
+
var equals = (a, b) => {
|
|
348
|
+
if (a === null || b === null || a === void 0 || b === void 0) return false;
|
|
349
|
+
return a === b;
|
|
350
|
+
};
|
|
351
|
+
var createErrorHandler = (logger) => {
|
|
352
|
+
return (error) => {
|
|
353
|
+
if ((0, import_router.isRouterError)(error)) {
|
|
354
|
+
const { message, status, statusText } = error;
|
|
355
|
+
logger?.log("ROUTER_INTERNAL_ERROR");
|
|
356
|
+
return Response.json({ type: "ROUTER_ERROR", code: "ROUTER_INTERNAL_ERROR", message }, { status, statusText });
|
|
357
|
+
}
|
|
358
|
+
if ((0, import_router.isInvalidZodSchemaError)(error)) {
|
|
359
|
+
logger?.log("INVALID_REQUEST");
|
|
360
|
+
return Response.json({ type: "ROUTER_ERROR", code: "INVALID_REQUEST", message: error.errors }, { status: 422 });
|
|
361
|
+
}
|
|
362
|
+
if (isOAuthProtocolError(error)) {
|
|
363
|
+
const { error: errorCode, message, type, errorURI } = error;
|
|
364
|
+
logger?.log("OAUTH_PROTOCOL_ERROR", {
|
|
365
|
+
structuredData: {
|
|
366
|
+
error: errorCode,
|
|
367
|
+
error_description: message,
|
|
368
|
+
error_uri: errorURI ?? ""
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
return Response.json(
|
|
372
|
+
{
|
|
373
|
+
type,
|
|
374
|
+
message
|
|
375
|
+
},
|
|
376
|
+
{ status: 400 }
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
if (isAuthInternalError(error)) {
|
|
380
|
+
const { type, code, message } = error;
|
|
381
|
+
logger?.log("INVALID_OAUTH_CONFIGURATION", {
|
|
382
|
+
structuredData: {
|
|
383
|
+
error: code,
|
|
384
|
+
error_description: message
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
return Response.json(
|
|
388
|
+
{
|
|
389
|
+
type,
|
|
390
|
+
message
|
|
391
|
+
},
|
|
392
|
+
{ status: 400 }
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
if (isAuthSecurityError(error)) {
|
|
396
|
+
const { type, code, message } = error;
|
|
397
|
+
logger?.log("INVALID_OAUTH_CONFIGURATION", {
|
|
398
|
+
structuredData: {
|
|
399
|
+
error: code,
|
|
400
|
+
error_description: message
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
return Response.json(
|
|
404
|
+
{
|
|
405
|
+
type,
|
|
406
|
+
code,
|
|
407
|
+
message
|
|
408
|
+
},
|
|
409
|
+
{ status: 400 }
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
logger?.log("SERVER_ERROR");
|
|
413
|
+
return Response.json(
|
|
414
|
+
{ type: "SERVER_ERROR", code: "SERVER_ERROR", message: "An unexpected error occurred" },
|
|
415
|
+
{ status: 500 }
|
|
416
|
+
);
|
|
417
|
+
};
|
|
418
|
+
};
|
|
419
|
+
var getBaseURL = (request) => {
|
|
420
|
+
const url = new URL(request.url);
|
|
421
|
+
return `${url.origin}${url.pathname}`;
|
|
422
|
+
};
|
|
423
|
+
var toISOString = (date) => {
|
|
424
|
+
return new Date(date).toISOString();
|
|
425
|
+
};
|
|
426
|
+
var useSecureCookies = (request, trustedProxyHeaders) => {
|
|
427
|
+
return trustedProxyHeaders ? request.url.startsWith("https://") || request.headers.get("X-Forwarded-Proto") === "https" || (request.headers.get("Forwarded")?.includes("proto=https") ?? false) : request.url.startsWith("https://");
|
|
428
|
+
};
|
|
429
|
+
var formatZodError = (error) => {
|
|
430
|
+
if (!error.issues || error.issues.length === 0) {
|
|
431
|
+
return {};
|
|
432
|
+
}
|
|
433
|
+
return error.issues.reduce((previous, issue) => {
|
|
434
|
+
const key = issue.path.join(".");
|
|
497
435
|
return {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
436
|
+
...previous,
|
|
437
|
+
[key]: {
|
|
438
|
+
code: issue.code,
|
|
439
|
+
message: issue.message
|
|
440
|
+
}
|
|
501
441
|
};
|
|
442
|
+
}, {});
|
|
443
|
+
};
|
|
444
|
+
var extractPath = (url) => {
|
|
445
|
+
const pathRegex = /^https?:\/\/[a-zA-Z0-9_\-\.]+(:\d+)?(\/.*)$/;
|
|
446
|
+
const match = url.match(pathRegex);
|
|
447
|
+
return match && match[2] ? match[2] : "/";
|
|
448
|
+
};
|
|
449
|
+
var getErrorName = (error) => {
|
|
450
|
+
if (error instanceof Error) {
|
|
451
|
+
return error.name;
|
|
502
452
|
}
|
|
453
|
+
return typeof error === "string" ? error : "UnknownError";
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
// src/oauth/github.ts
|
|
457
|
+
var github = (options2) => {
|
|
458
|
+
return {
|
|
459
|
+
id: "github",
|
|
460
|
+
name: "GitHub",
|
|
461
|
+
authorizeURL: "https://github.com/login/oauth/authorize",
|
|
462
|
+
accessToken: "https://github.com/login/oauth/access_token",
|
|
463
|
+
userInfo: "https://api.github.com/user",
|
|
464
|
+
scope: "read:user user:email",
|
|
465
|
+
responseType: "code",
|
|
466
|
+
profile: (profile) => {
|
|
467
|
+
return {
|
|
468
|
+
sub: profile.id.toString(),
|
|
469
|
+
name: profile.name ?? profile.login,
|
|
470
|
+
email: profile.email ?? void 0,
|
|
471
|
+
image: profile.avatar_url
|
|
472
|
+
};
|
|
473
|
+
},
|
|
474
|
+
...options2
|
|
475
|
+
};
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
// src/oauth/bitbucket.ts
|
|
479
|
+
var bitbucket = (options2) => {
|
|
480
|
+
return {
|
|
481
|
+
id: "bitbucket",
|
|
482
|
+
name: "Bitbucket",
|
|
483
|
+
authorizeURL: "https://bitbucket.org/site/oauth2/authorize",
|
|
484
|
+
accessToken: "https://bitbucket.org/site/oauth2/access_token",
|
|
485
|
+
userInfo: "https://api.bitbucket.org/2.0/user",
|
|
486
|
+
scope: "account email",
|
|
487
|
+
responseType: "code",
|
|
488
|
+
profile(profile) {
|
|
489
|
+
return {
|
|
490
|
+
sub: profile.uuid ?? profile.account_id,
|
|
491
|
+
name: profile.display_name ?? profile.nickname,
|
|
492
|
+
image: profile.links.avatar?.href,
|
|
493
|
+
email: void 0
|
|
494
|
+
};
|
|
495
|
+
},
|
|
496
|
+
...options2
|
|
497
|
+
};
|
|
503
498
|
};
|
|
504
499
|
|
|
505
500
|
// src/oauth/figma.ts
|
|
506
|
-
var figma = {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
501
|
+
var figma = (options2) => {
|
|
502
|
+
return {
|
|
503
|
+
id: "figma",
|
|
504
|
+
name: "Figma",
|
|
505
|
+
authorizeURL: "https://www.figma.com/oauth",
|
|
506
|
+
accessToken: "https://api.figma.com/v1/oauth/token",
|
|
507
|
+
userInfo: "https://api.figma.com/v1/me",
|
|
508
|
+
scope: "current_user:read",
|
|
509
|
+
responseType: "code",
|
|
510
|
+
profile(profile) {
|
|
511
|
+
return {
|
|
512
|
+
sub: profile.id,
|
|
513
|
+
name: profile.handle,
|
|
514
|
+
email: profile.email,
|
|
515
|
+
image: profile.img_url
|
|
516
|
+
};
|
|
517
|
+
},
|
|
518
|
+
...options2
|
|
519
|
+
};
|
|
522
520
|
};
|
|
523
521
|
|
|
524
522
|
// src/oauth/discord.ts
|
|
525
|
-
var discord = {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
523
|
+
var discord = (options2) => {
|
|
524
|
+
return {
|
|
525
|
+
id: "discord",
|
|
526
|
+
name: "Discord",
|
|
527
|
+
authorizeURL: "https://discord.com/oauth2/authorize",
|
|
528
|
+
accessToken: "https://discord.com/api/oauth2/token",
|
|
529
|
+
userInfo: "https://discord.com/api/users/@me",
|
|
530
|
+
scope: "identify email",
|
|
531
|
+
responseType: "code",
|
|
532
|
+
profile(profile) {
|
|
533
|
+
let image = "";
|
|
534
|
+
if (profile.avatar === null) {
|
|
535
|
+
const index = profile.discriminator === "0" ? (BigInt(profile.id) >> 22n) % 6n : Number(profile.discriminator) % 5;
|
|
536
|
+
image = `https://cdn.discordapp.com/embed/avatars/${index}.png`;
|
|
537
|
+
} else {
|
|
538
|
+
const format = profile.avatar.startsWith("a_") ? "gif" : "png";
|
|
539
|
+
image = `https://cdn.discordapp.com/avatars/${profile.id}/${profile.avatar}.${format}`;
|
|
540
|
+
}
|
|
541
|
+
return {
|
|
542
|
+
sub: profile.id,
|
|
543
|
+
name: profile.global_name ?? profile.username,
|
|
544
|
+
email: profile.email ?? "",
|
|
545
|
+
image
|
|
546
|
+
};
|
|
547
|
+
},
|
|
548
|
+
...options2
|
|
549
|
+
};
|
|
549
550
|
};
|
|
550
551
|
|
|
551
552
|
// src/oauth/gitlab.ts
|
|
552
|
-
var gitlab = {
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
553
|
+
var gitlab = (options2) => {
|
|
554
|
+
return {
|
|
555
|
+
id: "gitlab",
|
|
556
|
+
name: "GitLab",
|
|
557
|
+
authorizeURL: "https://gitlab.com/oauth/authorize",
|
|
558
|
+
accessToken: "https://gitlab.com/oauth/token",
|
|
559
|
+
userInfo: "https://gitlab.com/api/v4/user",
|
|
560
|
+
scope: "read_user",
|
|
561
|
+
responseType: "code",
|
|
562
|
+
profile(profile) {
|
|
563
|
+
return {
|
|
564
|
+
sub: profile.id.toString(),
|
|
565
|
+
name: profile.name ?? profile.username,
|
|
566
|
+
email: profile.email,
|
|
567
|
+
image: profile.avatar_url
|
|
568
|
+
};
|
|
569
|
+
},
|
|
570
|
+
...options2
|
|
571
|
+
};
|
|
568
572
|
};
|
|
569
573
|
|
|
570
574
|
// src/oauth/spotify.ts
|
|
571
|
-
var spotify = {
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
575
|
+
var spotify = (options2) => {
|
|
576
|
+
return {
|
|
577
|
+
id: "spotify",
|
|
578
|
+
name: "Spotify",
|
|
579
|
+
authorizeURL: "https://accounts.spotify.com/authorize",
|
|
580
|
+
accessToken: "https://accounts.spotify.com/api/token",
|
|
581
|
+
userInfo: "https://api.spotify.com/v1/me",
|
|
582
|
+
scope: "user-read-private user-read-email",
|
|
583
|
+
responseType: "code",
|
|
584
|
+
profile(profile) {
|
|
585
|
+
return {
|
|
586
|
+
sub: profile.id,
|
|
587
|
+
name: profile.display_name,
|
|
588
|
+
email: profile.email,
|
|
589
|
+
image: profile.images[0]?.url ?? void 0
|
|
590
|
+
};
|
|
591
|
+
},
|
|
592
|
+
...options2
|
|
593
|
+
};
|
|
587
594
|
};
|
|
588
595
|
|
|
589
596
|
// src/oauth/x.ts
|
|
590
|
-
var x = {
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
597
|
+
var x = (options2) => {
|
|
598
|
+
return {
|
|
599
|
+
id: "x",
|
|
600
|
+
name: "X",
|
|
601
|
+
authorizeURL: "https://twitter.com/i/oauth2/authorize",
|
|
602
|
+
accessToken: "https://api.twitter.com/2/oauth2/token",
|
|
603
|
+
userInfo: "https://api.twitter.com/2/users/me?user.fields=profile_image_url",
|
|
604
|
+
scope: "tweet.read users.read offline.access",
|
|
605
|
+
responseType: "code",
|
|
606
|
+
profile(profile) {
|
|
607
|
+
return {
|
|
608
|
+
sub: profile.data.id,
|
|
609
|
+
name: profile.data.name,
|
|
610
|
+
image: profile.data.profile_image_url,
|
|
611
|
+
email: void 0
|
|
612
|
+
};
|
|
613
|
+
},
|
|
614
|
+
...options2
|
|
615
|
+
};
|
|
606
616
|
};
|
|
607
617
|
|
|
608
618
|
// src/oauth/strava.ts
|
|
609
|
-
var strava = {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
619
|
+
var strava = (options2) => {
|
|
620
|
+
return {
|
|
621
|
+
id: "strava",
|
|
622
|
+
name: "Strava",
|
|
623
|
+
authorizeURL: "https://www.strava.com/oauth/authorize",
|
|
624
|
+
accessToken: "https://www.strava.com/oauth/token",
|
|
625
|
+
userInfo: "https://www.strava.com/api/v3/athlete",
|
|
626
|
+
scope: "read",
|
|
627
|
+
responseType: "code",
|
|
628
|
+
profile(profile) {
|
|
629
|
+
return {
|
|
630
|
+
sub: profile.id.toString(),
|
|
631
|
+
name: `${profile.firstname} ${profile.lastname}`,
|
|
632
|
+
image: profile.profile,
|
|
633
|
+
email: void 0
|
|
634
|
+
};
|
|
635
|
+
},
|
|
636
|
+
...options2
|
|
637
|
+
};
|
|
625
638
|
};
|
|
626
639
|
|
|
627
640
|
// src/oauth/mailchimp.ts
|
|
628
|
-
var mailchimp = {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
641
|
+
var mailchimp = (options2) => {
|
|
642
|
+
return {
|
|
643
|
+
id: "mailchimp",
|
|
644
|
+
name: "Mailchimp",
|
|
645
|
+
authorizeURL: "https://login.mailchimp.com/oauth2/authorize",
|
|
646
|
+
accessToken: "https://login.mailchimp.com/oauth2/token",
|
|
647
|
+
userInfo: "https://login.mailchimp.com/oauth2/metadata",
|
|
648
|
+
scope: "",
|
|
649
|
+
responseType: "code",
|
|
650
|
+
profile(profile) {
|
|
651
|
+
return {
|
|
652
|
+
sub: profile.user_id,
|
|
653
|
+
name: profile.accountname,
|
|
654
|
+
email: profile.login.email,
|
|
655
|
+
image: profile.login.avatar
|
|
656
|
+
};
|
|
657
|
+
},
|
|
658
|
+
...options2
|
|
659
|
+
};
|
|
644
660
|
};
|
|
645
661
|
|
|
646
662
|
// src/oauth/pinterest.ts
|
|
647
|
-
var pinterest = {
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
+
var pinterest = (options2) => {
|
|
664
|
+
return {
|
|
665
|
+
id: "pinterest",
|
|
666
|
+
name: "Pinterest",
|
|
667
|
+
authorizeURL: "https://www.pinterest.com/oauth",
|
|
668
|
+
accessToken: "https://api.pinterest.com/v5/oauth/token",
|
|
669
|
+
userInfo: "https://api.pinterest.com/v5/user_account",
|
|
670
|
+
scope: "user_accounts:read",
|
|
671
|
+
responseType: "code",
|
|
672
|
+
profile(profile) {
|
|
673
|
+
return {
|
|
674
|
+
sub: profile.id,
|
|
675
|
+
name: profile.username,
|
|
676
|
+
image: profile.profile_image,
|
|
677
|
+
email: void 0
|
|
678
|
+
};
|
|
679
|
+
},
|
|
680
|
+
...options2
|
|
681
|
+
};
|
|
663
682
|
};
|
|
664
683
|
|
|
665
684
|
// src/schemas.ts
|
|
666
685
|
var import_zod = require("zod");
|
|
686
|
+
var OAuthProviderCredentialsSchema = (0, import_zod.object)({
|
|
687
|
+
id: (0, import_zod.string)(),
|
|
688
|
+
name: (0, import_zod.string)(),
|
|
689
|
+
authorizeURL: (0, import_zod.string)().url(),
|
|
690
|
+
accessToken: (0, import_zod.string)().url(),
|
|
691
|
+
scope: (0, import_zod.string)(),
|
|
692
|
+
userInfo: (0, import_zod.string)().url(),
|
|
693
|
+
responseType: (0, import_zod.enum)(["code", "token", "id_token"]),
|
|
694
|
+
clientId: (0, import_zod.string)(),
|
|
695
|
+
clientSecret: (0, import_zod.string)(),
|
|
696
|
+
profile: import_zod.z.function().optional()
|
|
697
|
+
});
|
|
667
698
|
var OAuthProviderConfigSchema = (0, import_zod.object)({
|
|
668
699
|
authorizeURL: (0, import_zod.string)().url(),
|
|
669
700
|
accessToken: (0, import_zod.string)().url(),
|
|
@@ -744,7 +775,6 @@ var builtInOAuthProviders = {
|
|
|
744
775
|
pinterest
|
|
745
776
|
};
|
|
746
777
|
var defineOAuthEnvironment = (oauth) => {
|
|
747
|
-
const env = process.env;
|
|
748
778
|
const clientIdSuffix = `${oauth.toUpperCase()}_CLIENT_ID`;
|
|
749
779
|
const clientSecretSuffix = `${oauth.toUpperCase()}_CLIENT_SECRET`;
|
|
750
780
|
const loadEnvs = OAuthEnvSchema.safeParse({
|
|
@@ -760,17 +790,38 @@ var defineOAuthEnvironment = (oauth) => {
|
|
|
760
790
|
var defineOAuthProviderConfig = (config2) => {
|
|
761
791
|
if (typeof config2 === "string") {
|
|
762
792
|
const definition = defineOAuthEnvironment(config2);
|
|
763
|
-
const oauthConfig = builtInOAuthProviders[config2];
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
793
|
+
const oauthConfig = builtInOAuthProviders[config2]();
|
|
794
|
+
const parsed2 = OAuthProviderCredentialsSchema.safeParse({ ...oauthConfig, ...definition });
|
|
795
|
+
if (!parsed2.success) {
|
|
796
|
+
const details = JSON.stringify(formatZodError(parsed2.error), null, 2);
|
|
797
|
+
throw new AuthInternalError(
|
|
798
|
+
"INVALID_OAUTH_PROVIDER_CONFIGURATION",
|
|
799
|
+
`Invalid configuration for OAuth provider "${config2}": ${details}`
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
return parsed2.data;
|
|
768
803
|
}
|
|
769
|
-
|
|
804
|
+
const hasCredentials = config2.clientId && config2.clientSecret;
|
|
805
|
+
const envConfig = hasCredentials ? {} : defineOAuthEnvironment(config2.id);
|
|
806
|
+
const parsed = OAuthProviderCredentialsSchema.safeParse({ ...envConfig, ...config2 });
|
|
807
|
+
if (!parsed.success) {
|
|
808
|
+
const details = JSON.stringify(formatZodError(parsed.error), null, 2);
|
|
809
|
+
throw new AuthInternalError(
|
|
810
|
+
"INVALID_OAUTH_PROVIDER_CONFIGURATION",
|
|
811
|
+
`Invalid configuration for OAuth provider "${config2.id}": ${details}`
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
return parsed.data;
|
|
770
815
|
};
|
|
771
816
|
var createBuiltInOAuthProviders = (oauth = []) => {
|
|
772
817
|
return oauth.reduce((previous, config2) => {
|
|
773
818
|
const oauthConfig = defineOAuthProviderConfig(config2);
|
|
819
|
+
if (oauthConfig.id in previous) {
|
|
820
|
+
throw new AuthInternalError(
|
|
821
|
+
"DUPLICATED_OAUTH_PROVIDER_ID",
|
|
822
|
+
`Duplicate OAuth provider id "${oauthConfig.id}" found. Each provider must have a unique id.`
|
|
823
|
+
);
|
|
824
|
+
}
|
|
774
825
|
return { ...previous, [oauthConfig.id]: oauthConfig };
|
|
775
826
|
}, {});
|
|
776
827
|
};
|
|
@@ -786,75 +837,259 @@ var cacheControl = {
|
|
|
786
837
|
Expires: "0",
|
|
787
838
|
Vary: "Cookie"
|
|
788
839
|
};
|
|
840
|
+
var contentSecurityPolicy = {
|
|
841
|
+
"Content-Security-Policy": [
|
|
842
|
+
"default-src 'none'",
|
|
843
|
+
"script-src 'self'",
|
|
844
|
+
"frame-src 'none'",
|
|
845
|
+
"object-src 'none'",
|
|
846
|
+
"frame-ancestors 'none'",
|
|
847
|
+
"base-uri 'none'"
|
|
848
|
+
].join("; ")
|
|
849
|
+
};
|
|
850
|
+
var secureHeaders = {
|
|
851
|
+
"X-Content-Type-Options": "nosniff",
|
|
852
|
+
"X-Frame-Options": "DENY",
|
|
853
|
+
"Referrer-Policy": "strict-origin-when-cross-origin"
|
|
854
|
+
};
|
|
855
|
+
var secureApiHeaders = {
|
|
856
|
+
...cacheControl,
|
|
857
|
+
...contentSecurityPolicy,
|
|
858
|
+
...secureHeaders
|
|
859
|
+
};
|
|
860
|
+
|
|
861
|
+
// src/secure.ts
|
|
862
|
+
var import_crypto2 = __toESM(require("crypto"), 1);
|
|
863
|
+
|
|
864
|
+
// src/assert.ts
|
|
865
|
+
var import_crypto = require("crypto");
|
|
866
|
+
var unsafeChars = [
|
|
867
|
+
"<",
|
|
868
|
+
">",
|
|
869
|
+
'"',
|
|
870
|
+
"`",
|
|
871
|
+
" ",
|
|
872
|
+
"\r",
|
|
873
|
+
"\n",
|
|
874
|
+
" ",
|
|
875
|
+
"\\",
|
|
876
|
+
"%2F",
|
|
877
|
+
"%5C",
|
|
878
|
+
"%2f",
|
|
879
|
+
"%5c",
|
|
880
|
+
"\r\n",
|
|
881
|
+
"%0A",
|
|
882
|
+
"%0D",
|
|
883
|
+
"%0a",
|
|
884
|
+
"%0d",
|
|
885
|
+
"..",
|
|
886
|
+
"//",
|
|
887
|
+
"///",
|
|
888
|
+
"...",
|
|
889
|
+
"%20",
|
|
890
|
+
"\0"
|
|
891
|
+
];
|
|
892
|
+
var isValidURL = (value) => {
|
|
893
|
+
if (!new RegExp(/^https?:\/\/[^/]/).test(value)) {
|
|
894
|
+
return false;
|
|
895
|
+
}
|
|
896
|
+
const match = value.match(/^(https?:\/\/)(.*)$/);
|
|
897
|
+
if (!match) return false;
|
|
898
|
+
const rest = match[2];
|
|
899
|
+
for (const char of unsafeChars) {
|
|
900
|
+
if (rest.includes(char)) return false;
|
|
901
|
+
}
|
|
902
|
+
const regex = /^https?:\/\/(?:[a-zA-Z0-9._-]+|localhost|\[[0-9a-fA-F:]+\])(?::\d{1,5})?(?:\/[a-zA-Z0-9._~!$&'()?#*+,;=:@-]*)*\/?$/;
|
|
903
|
+
return regex.test(match[0]);
|
|
904
|
+
};
|
|
905
|
+
var isJWTPayloadWithToken = (payload) => {
|
|
906
|
+
return typeof payload === "object" && payload !== null && "token" in payload && typeof payload?.token === "string";
|
|
907
|
+
};
|
|
908
|
+
var isRelativeURL = (value) => {
|
|
909
|
+
if (value.length > 100) return false;
|
|
910
|
+
for (const char of unsafeChars) {
|
|
911
|
+
if (value.includes(char)) return false;
|
|
912
|
+
}
|
|
913
|
+
const regex = /^\/[a-zA-Z0-9\-_\/.?&=#]*\/?$/;
|
|
914
|
+
return regex.test(value);
|
|
915
|
+
};
|
|
916
|
+
var isSameOrigin = (origin, expected) => {
|
|
917
|
+
const originURL = new URL(origin);
|
|
918
|
+
const expectedURL = new URL(expected);
|
|
919
|
+
return equals(originURL.origin, expectedURL.origin);
|
|
920
|
+
};
|
|
921
|
+
var patternToRegex = (pattern) => {
|
|
922
|
+
try {
|
|
923
|
+
if (pattern.length > 2048) return null;
|
|
924
|
+
pattern = pattern.replace(/\\/g, "");
|
|
925
|
+
const match = pattern.match(/^(https?):\/\/([a-zA-Z0-9.*-]{1,253})(?::(\d{1,5}|\*))?(?:\/.*)?$/);
|
|
926
|
+
if (!match) return null;
|
|
927
|
+
const [, protocol, host, port] = match;
|
|
928
|
+
const hasWildcard = host.includes("*");
|
|
929
|
+
if (hasWildcard && !host.startsWith("*.")) return null;
|
|
930
|
+
if (hasWildcard && host.slice(2).includes("*")) return null;
|
|
931
|
+
const domain = hasWildcard ? host.slice(2) : host;
|
|
932
|
+
const escapedDomain = domain.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
933
|
+
const hostRegex = hasWildcard ? `[^.]+\\.${escapedDomain}` : escapedDomain;
|
|
934
|
+
const portRegex = port === "*" ? ":\\d{1,5}" : port ? `:${port}` : "";
|
|
935
|
+
return new RegExp(`^${protocol}:\\/\\/${hostRegex}${portRegex}$`);
|
|
936
|
+
} catch {
|
|
937
|
+
return null;
|
|
938
|
+
}
|
|
939
|
+
};
|
|
940
|
+
var isTrustedOrigin = (url, trustedOrigins) => {
|
|
941
|
+
if (!isValidURL(url) || trustedOrigins.length === 0) return false;
|
|
942
|
+
try {
|
|
943
|
+
const urlOrigin = new URL(url).origin;
|
|
944
|
+
for (const pattern of trustedOrigins) {
|
|
945
|
+
const regex = patternToRegex(pattern);
|
|
946
|
+
if (regex?.test(urlOrigin)) return true;
|
|
947
|
+
try {
|
|
948
|
+
if (isValidURL(pattern) && equals(new URL(pattern).origin, urlOrigin)) return true;
|
|
949
|
+
} catch {
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
} catch {
|
|
953
|
+
}
|
|
954
|
+
return false;
|
|
955
|
+
};
|
|
956
|
+
var safeEquals = (a, b) => {
|
|
957
|
+
const bufferA = Buffer.from(a);
|
|
958
|
+
const bufferB = Buffer.from(b);
|
|
959
|
+
if (bufferA.length !== bufferB.length) {
|
|
960
|
+
return false;
|
|
961
|
+
}
|
|
962
|
+
return (0, import_crypto.timingSafeEqual)(bufferA, bufferB);
|
|
963
|
+
};
|
|
964
|
+
|
|
965
|
+
// src/secure.ts
|
|
966
|
+
var generateSecure = (length = 32) => {
|
|
967
|
+
return import_crypto2.default.randomBytes(length).toString("base64url");
|
|
968
|
+
};
|
|
969
|
+
var createHash = (data, base = "hex") => {
|
|
970
|
+
return import_crypto2.default.createHash("sha256").update(data).digest().toString(base);
|
|
971
|
+
};
|
|
972
|
+
var createPKCE = async (verifier) => {
|
|
973
|
+
const byteLength = verifier ? void 0 : Math.floor(Math.random() * (96 - 32 + 1) + 32);
|
|
974
|
+
const codeVerifier = verifier ?? generateSecure(byteLength ?? 64);
|
|
975
|
+
if (codeVerifier.length < 43 || codeVerifier.length > 128) {
|
|
976
|
+
throw new AuthSecurityError("PKCE_VERIFIER_INVALID", "The code verifier must be between 43 and 128 characters in length.");
|
|
977
|
+
}
|
|
978
|
+
const codeChallenge = createHash(codeVerifier, "base64url");
|
|
979
|
+
return { codeVerifier, codeChallenge, method: "S256" };
|
|
980
|
+
};
|
|
981
|
+
var createCSRF = async (jose, csrfCookie) => {
|
|
982
|
+
try {
|
|
983
|
+
const token = generateSecure(32);
|
|
984
|
+
if (csrfCookie) {
|
|
985
|
+
await jose.verifyJWS(csrfCookie, jwtVerificationOptions);
|
|
986
|
+
return csrfCookie;
|
|
987
|
+
}
|
|
988
|
+
return jose.signJWS({ token });
|
|
989
|
+
} catch {
|
|
990
|
+
const token = generateSecure(32);
|
|
991
|
+
return jose.signJWS({ token });
|
|
992
|
+
}
|
|
993
|
+
};
|
|
994
|
+
var verifyCSRF = async (jose, cookie, header) => {
|
|
995
|
+
try {
|
|
996
|
+
const cookiePayload = await jose.verifyJWS(cookie, jwtVerificationOptions);
|
|
997
|
+
const headerPayload = await jose.verifyJWS(header, jwtVerificationOptions);
|
|
998
|
+
if (!isJWTPayloadWithToken(cookiePayload)) {
|
|
999
|
+
throw new AuthSecurityError("CSRF_TOKEN_INVALID", "Cookie payload missing token field.");
|
|
1000
|
+
}
|
|
1001
|
+
if (!isJWTPayloadWithToken(headerPayload)) {
|
|
1002
|
+
throw new AuthSecurityError("CSRF_TOKEN_INVALID", "Header payload missing token field.");
|
|
1003
|
+
}
|
|
1004
|
+
if (!equals(cookiePayload.token.length, headerPayload.token.length)) {
|
|
1005
|
+
throw new AuthSecurityError("CSRF_TOKEN_INVALID", "The CSRF tokens do not match.");
|
|
1006
|
+
}
|
|
1007
|
+
if (!safeEquals(cookiePayload.token, headerPayload.token)) {
|
|
1008
|
+
throw new AuthSecurityError("CSRF_TOKEN_INVALID", "The CSRF tokens do not match.");
|
|
1009
|
+
}
|
|
1010
|
+
return true;
|
|
1011
|
+
} catch {
|
|
1012
|
+
throw new AuthSecurityError("CSRF_TOKEN_INVALID", "The CSRF tokens do not match.");
|
|
1013
|
+
}
|
|
1014
|
+
};
|
|
789
1015
|
|
|
790
1016
|
// src/actions/signIn/authorization.ts
|
|
791
|
-
var createAuthorizationURL = (oauthConfig, redirectURI, state, codeChallenge, codeChallengeMethod) => {
|
|
1017
|
+
var createAuthorizationURL = (oauthConfig, redirectURI, state, codeChallenge, codeChallengeMethod, logger) => {
|
|
792
1018
|
const parsed = OAuthAuthorization.safeParse({ ...oauthConfig, redirectURI, state, codeChallenge, codeChallengeMethod });
|
|
793
1019
|
if (!parsed.success) {
|
|
794
|
-
|
|
795
|
-
|
|
1020
|
+
logger?.log("INVALID_OAUTH_CONFIGURATION", {
|
|
1021
|
+
structuredData: {
|
|
1022
|
+
scope: oauthConfig.scope,
|
|
1023
|
+
redirect_uri: redirectURI,
|
|
1024
|
+
has_state: Boolean(state),
|
|
1025
|
+
has_code_challenge: Boolean(codeChallenge),
|
|
1026
|
+
code_challenge_method: codeChallengeMethod
|
|
1027
|
+
}
|
|
1028
|
+
});
|
|
1029
|
+
throw new AuthInternalError("INVALID_OAUTH_CONFIGURATION", "The OAuth provider configuration is invalid.");
|
|
796
1030
|
}
|
|
797
1031
|
const { authorizeURL, ...options2 } = parsed.data;
|
|
798
1032
|
const { userInfo, accessToken, clientSecret, ...required } = options2;
|
|
799
1033
|
const searchParams = new URLSearchParams(toCastCase(required));
|
|
800
1034
|
return `${authorizeURL}?${searchParams}`;
|
|
801
1035
|
};
|
|
802
|
-
var
|
|
1036
|
+
var getTrustedOrigins = async (request, trustedOrigins) => {
|
|
1037
|
+
if (!trustedOrigins) return [];
|
|
1038
|
+
const raw = typeof trustedOrigins === "function" ? await trustedOrigins(request) : trustedOrigins;
|
|
1039
|
+
return Array.isArray(raw) ? raw : typeof raw === "string" ? [raw] : [];
|
|
1040
|
+
};
|
|
1041
|
+
var getOriginURL = async (request, context) => {
|
|
803
1042
|
const headers = request.headers;
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
1043
|
+
let origin = new URL(request.url).origin;
|
|
1044
|
+
const trustedOrigins = await getTrustedOrigins(request, context?.trustedOrigins);
|
|
1045
|
+
trustedOrigins.push(origin);
|
|
1046
|
+
if (context?.trustedProxyHeaders) {
|
|
1047
|
+
const protocol = headers.get("Forwarded")?.match(/proto=([^;]+)/i)?.[1] ?? headers.get("X-Forwarded-Proto") ?? "http";
|
|
1048
|
+
const host = headers.get("Host") ?? headers.get("Forwarded")?.match(/host=([^;]+)/i)?.[1] ?? headers.get("X-Forwarded-Host") ?? null;
|
|
1049
|
+
origin = `${protocol}://${host}`;
|
|
1050
|
+
}
|
|
1051
|
+
if (!isTrustedOrigin(origin, trustedOrigins)) {
|
|
1052
|
+
context?.logger?.log("UNTRUSTED_ORIGIN", { structuredData: { origin } });
|
|
1053
|
+
throw new AuthInternalError("UNTRUSTED_ORIGIN", "The constructed origin URL is not trusted.");
|
|
810
1054
|
}
|
|
1055
|
+
return origin;
|
|
811
1056
|
};
|
|
812
|
-
var createRedirectURI = (request, oauth,
|
|
813
|
-
const
|
|
814
|
-
return `${
|
|
1057
|
+
var createRedirectURI = async (request, oauth, context) => {
|
|
1058
|
+
const origin = await getOriginURL(request, context);
|
|
1059
|
+
return `${origin}${context.basePath}/callback/${oauth}`;
|
|
815
1060
|
};
|
|
816
|
-
var createRedirectTo = (request, redirectTo,
|
|
1061
|
+
var createRedirectTo = async (request, redirectTo, context) => {
|
|
817
1062
|
try {
|
|
818
1063
|
const headers = request.headers;
|
|
819
|
-
const
|
|
820
|
-
const
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
if (
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
if (!isValidURL(referer) || !equals(refererURL.origin, hostedURL.origin)) {
|
|
838
|
-
throw new AuthSecurityError(
|
|
839
|
-
"POTENTIAL_OPEN_REDIRECT_ATTACK_DETECTED",
|
|
840
|
-
"The referer of the request does not match the hosted origin."
|
|
841
|
-
);
|
|
1064
|
+
const requestOrigin = await getOriginURL(request, context);
|
|
1065
|
+
const origins = await getTrustedOrigins(request, context?.trustedOrigins);
|
|
1066
|
+
const validateURL = (url) => {
|
|
1067
|
+
if (!isRelativeURL(url) && !isValidURL(url)) return "/";
|
|
1068
|
+
if (isRelativeURL(url)) return url;
|
|
1069
|
+
if (origins.length > 0) {
|
|
1070
|
+
if (isTrustedOrigin(url, origins)) {
|
|
1071
|
+
const urlOrigin = new URL(url).origin;
|
|
1072
|
+
for (const pattern of origins) {
|
|
1073
|
+
const regex = patternToRegex(pattern);
|
|
1074
|
+
if (regex?.test(urlOrigin)) {
|
|
1075
|
+
return isSameOrigin(url, request.url) ? extractPath(url) : url;
|
|
1076
|
+
}
|
|
1077
|
+
if (isValidURL(pattern) && equals(new URL(pattern).origin, urlOrigin)) return url;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
context?.logger?.log("OPEN_REDIRECT_ATTACK");
|
|
1081
|
+
return "/";
|
|
842
1082
|
}
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
if (origin) {
|
|
846
|
-
const originURL = new URL(sanitizeURL(getNormalizedOriginPath(origin)));
|
|
847
|
-
if (!isValidURL(origin) || !equals(originURL.origin, hostedURL.origin)) {
|
|
848
|
-
throw new AuthSecurityError("POTENTIAL_OPEN_REDIRECT_ATTACK_DETECTED", "Invalid origin (potential CSRF).");
|
|
1083
|
+
if (isSameOrigin(url, requestOrigin)) {
|
|
1084
|
+
return extractPath(url);
|
|
849
1085
|
}
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
1086
|
+
context?.logger?.log("OPEN_REDIRECT_ATTACK");
|
|
1087
|
+
return "/";
|
|
1088
|
+
};
|
|
1089
|
+
return validateURL(redirectTo ?? headers.get("Referer") ?? headers.get("Origin") ?? "/");
|
|
853
1090
|
} catch (error) {
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
}
|
|
857
|
-
throw new AuthSecurityError("POTENTIAL_OPEN_REDIRECT_ATTACK_DETECTED", "Invalid origin (potential CSRF).");
|
|
1091
|
+
context?.logger?.log("POTENTIAL_OPEN_REDIRECT_ATTACK_DETECTED");
|
|
1092
|
+
return "/";
|
|
858
1093
|
}
|
|
859
1094
|
};
|
|
860
1095
|
|
|
@@ -883,13 +1118,17 @@ var signInAction = (oauth) => {
|
|
|
883
1118
|
request,
|
|
884
1119
|
params: { oauth: oauth2 },
|
|
885
1120
|
searchParams: { redirectTo },
|
|
886
|
-
context
|
|
1121
|
+
context
|
|
887
1122
|
} = ctx;
|
|
1123
|
+
const { oauth: providers, cookies, logger } = context;
|
|
888
1124
|
const state = generateSecure();
|
|
889
|
-
const redirectURI = createRedirectURI(request, oauth2,
|
|
890
|
-
const redirectToValue = createRedirectTo(request, redirectTo,
|
|
1125
|
+
const redirectURI = await createRedirectURI(request, oauth2, context);
|
|
1126
|
+
const redirectToValue = await createRedirectTo(request, redirectTo, context);
|
|
891
1127
|
const { codeVerifier, codeChallenge, method } = await createPKCE();
|
|
892
|
-
const authorization = createAuthorizationURL(providers[oauth2], redirectURI, state, codeChallenge, method);
|
|
1128
|
+
const authorization = createAuthorizationURL(providers[oauth2], redirectURI, state, codeChallenge, method, logger);
|
|
1129
|
+
logger?.log("SIGN_IN_INITIATED", {
|
|
1130
|
+
structuredData: { oauth_provider: oauth2, code_challenge_method: method }
|
|
1131
|
+
});
|
|
893
1132
|
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();
|
|
894
1133
|
return Response.json(
|
|
895
1134
|
{ oauth: oauth2 },
|
|
@@ -928,9 +1167,14 @@ var getDefaultUserInfo = (profile) => {
|
|
|
928
1167
|
image: profile?.image ?? profile?.picture
|
|
929
1168
|
};
|
|
930
1169
|
};
|
|
931
|
-
var getUserInfo = async (oauthConfig, accessToken) => {
|
|
1170
|
+
var getUserInfo = async (oauthConfig, accessToken, logger) => {
|
|
932
1171
|
const userinfoEndpoint = oauthConfig.userInfo;
|
|
933
1172
|
try {
|
|
1173
|
+
logger?.log("OAUTH_USERINFO_REQUEST_INITIATED", {
|
|
1174
|
+
structuredData: {
|
|
1175
|
+
endpoint: userinfoEndpoint
|
|
1176
|
+
}
|
|
1177
|
+
});
|
|
934
1178
|
const response = await fetchAsync(userinfoEndpoint, {
|
|
935
1179
|
method: "GET",
|
|
936
1180
|
headers: {
|
|
@@ -938,35 +1182,54 @@ var getUserInfo = async (oauthConfig, accessToken) => {
|
|
|
938
1182
|
Authorization: `Bearer ${accessToken}`
|
|
939
1183
|
}
|
|
940
1184
|
});
|
|
1185
|
+
if (!response.ok) {
|
|
1186
|
+
logger?.log("OAUTH_USERINFO_INVALID_RESPONSE");
|
|
1187
|
+
throw new OAuthProtocolError("INVALID_REQUEST", "Invalid userinfo response format");
|
|
1188
|
+
}
|
|
941
1189
|
const json = await response.json();
|
|
942
1190
|
const { success, data } = OAuthErrorResponse.safeParse(json);
|
|
943
1191
|
if (success) {
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
1192
|
+
logger?.log("OAUTH_USERINFO_ERROR", {
|
|
1193
|
+
message: "Error response received from OAuth userinfo endpoint",
|
|
1194
|
+
structuredData: {
|
|
1195
|
+
error: data.error,
|
|
1196
|
+
error_description: data.error_description ?? ""
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1199
|
+
throw new OAuthProtocolError("INVALID_REQUEST", "An error was received from the OAuth userinfo endpoint.");
|
|
948
1200
|
}
|
|
1201
|
+
logger?.log("OAUTH_USERINFO_SUCCESS");
|
|
949
1202
|
return oauthConfig?.profile ? oauthConfig.profile(json) : getDefaultUserInfo(json);
|
|
950
1203
|
} catch (error) {
|
|
951
1204
|
if (isOAuthProtocolError(error)) {
|
|
952
1205
|
throw error;
|
|
953
1206
|
}
|
|
1207
|
+
logger?.log("OAUTH_USERINFO_REQUEST_FAILED");
|
|
954
1208
|
if (isNativeError(error)) {
|
|
955
|
-
throw new OAuthProtocolError("
|
|
1209
|
+
throw new OAuthProtocolError("SERVER_ERROR", "Failed to fetch user information from OAuth provider", "", {
|
|
1210
|
+
cause: error
|
|
1211
|
+
});
|
|
956
1212
|
}
|
|
957
|
-
throw new OAuthProtocolError("
|
|
1213
|
+
throw new OAuthProtocolError("SERVER_ERROR", "Failed to fetch user information", "", { cause: error });
|
|
958
1214
|
}
|
|
959
1215
|
};
|
|
960
1216
|
|
|
961
1217
|
// src/actions/callback/access-token.ts
|
|
962
|
-
var createAccessToken = async (oauthConfig, redirectURI, code, codeVerifier) => {
|
|
1218
|
+
var createAccessToken = async (oauthConfig, redirectURI, code, codeVerifier, logger) => {
|
|
963
1219
|
const parsed = OAuthAccessToken.safeParse({ ...oauthConfig, redirectURI, code, codeVerifier });
|
|
964
1220
|
if (!parsed.success) {
|
|
965
|
-
|
|
966
|
-
throw new AuthInternalError("INVALID_OAUTH_CONFIGURATION",
|
|
1221
|
+
logger?.log("INVALID_OAUTH_CONFIGURATION");
|
|
1222
|
+
throw new AuthInternalError("INVALID_OAUTH_CONFIGURATION", "The OAuth provider configuration is invalid.");
|
|
967
1223
|
}
|
|
968
1224
|
const { accessToken, clientId, clientSecret, code: codeParsed, redirectURI: redirectParsed } = parsed.data;
|
|
969
1225
|
try {
|
|
1226
|
+
logger?.log("OAUTH_ACCESS_TOKEN_REQUEST_INITIATED", {
|
|
1227
|
+
structuredData: {
|
|
1228
|
+
has_client_id: Boolean(clientId),
|
|
1229
|
+
redirect_uri: redirectParsed,
|
|
1230
|
+
grant_type: "authorization_code"
|
|
1231
|
+
}
|
|
1232
|
+
});
|
|
970
1233
|
const response = await fetchAsync(accessToken, {
|
|
971
1234
|
method: "POST",
|
|
972
1235
|
headers: {
|
|
@@ -982,17 +1245,33 @@ var createAccessToken = async (oauthConfig, redirectURI, code, codeVerifier) =>
|
|
|
982
1245
|
code_verifier: codeVerifier
|
|
983
1246
|
}).toString()
|
|
984
1247
|
});
|
|
1248
|
+
if (!response.ok) {
|
|
1249
|
+
logger?.log("INVALID_OAUTH_ACCESS_TOKEN_RESPONSE");
|
|
1250
|
+
throw new OAuthProtocolError("invalid_request", "Invalid access token response");
|
|
1251
|
+
}
|
|
985
1252
|
const json = await response.json();
|
|
986
1253
|
const token = OAuthAccessTokenResponse.safeParse(json);
|
|
987
1254
|
if (!token.success) {
|
|
988
1255
|
const { success, data } = OAuthAccessTokenErrorResponse.safeParse(json);
|
|
989
1256
|
if (!success) {
|
|
990
|
-
|
|
1257
|
+
logger?.log("INVALID_OAUTH_ACCESS_TOKEN_RESPONSE");
|
|
1258
|
+
throw new OAuthProtocolError("invalid_request", "Invalid access token response format");
|
|
991
1259
|
}
|
|
992
|
-
|
|
1260
|
+
logger?.log("OAUTH_ACCESS_TOKEN_ERROR", {
|
|
1261
|
+
structuredData: {
|
|
1262
|
+
error: data.error,
|
|
1263
|
+
error_description: data.error_description ?? ""
|
|
1264
|
+
}
|
|
1265
|
+
});
|
|
1266
|
+
throw new OAuthProtocolError("INVALID_ACCESS_TOKEN", "Failed to retrieve access token");
|
|
993
1267
|
}
|
|
1268
|
+
logger?.log("OAUTH_ACCESS_TOKEN_SUCCESS");
|
|
994
1269
|
return token.data;
|
|
995
1270
|
} catch (error) {
|
|
1271
|
+
logger?.log("OAUTH_ACCESS_TOKEN_REQUEST_FAILED");
|
|
1272
|
+
if (error instanceof Error) {
|
|
1273
|
+
throw new OAuthProtocolError("server_error", "Failed to communicate with OAuth provider", "", { cause: error });
|
|
1274
|
+
}
|
|
996
1275
|
throw error;
|
|
997
1276
|
}
|
|
998
1277
|
};
|
|
@@ -1014,10 +1293,23 @@ var callbackConfig = (oauth) => {
|
|
|
1014
1293
|
},
|
|
1015
1294
|
middlewares: [
|
|
1016
1295
|
(ctx) => {
|
|
1017
|
-
const
|
|
1296
|
+
const {
|
|
1297
|
+
searchParams,
|
|
1298
|
+
context: { logger }
|
|
1299
|
+
} = ctx;
|
|
1300
|
+
const response = OAuthAuthorizationErrorResponse.safeParse(searchParams);
|
|
1018
1301
|
if (response.success) {
|
|
1019
1302
|
const { error, error_description } = response.data;
|
|
1020
|
-
|
|
1303
|
+
const criticalAuthErrors = ["access_denied", "server_error"];
|
|
1304
|
+
const severity = criticalAuthErrors.includes(error.toLowerCase()) ? "critical" : "warning";
|
|
1305
|
+
logger?.log("OAUTH_AUTHORIZATION_ERROR", {
|
|
1306
|
+
severity,
|
|
1307
|
+
structuredData: {
|
|
1308
|
+
error,
|
|
1309
|
+
error_description: error_description ?? ""
|
|
1310
|
+
}
|
|
1311
|
+
});
|
|
1312
|
+
throw new OAuthProtocolError(error, error_description || "OAuth Authorization Error");
|
|
1021
1313
|
}
|
|
1022
1314
|
return ctx;
|
|
1023
1315
|
}
|
|
@@ -1033,31 +1325,54 @@ var callbackAction = (oauth) => {
|
|
|
1033
1325
|
request,
|
|
1034
1326
|
params: { oauth: oauth2 },
|
|
1035
1327
|
searchParams: { code, state },
|
|
1036
|
-
context
|
|
1328
|
+
context
|
|
1037
1329
|
} = ctx;
|
|
1330
|
+
const { oauth: providers, cookies, jose, logger, trustedOrigins } = context;
|
|
1038
1331
|
const oauthConfig = providers[oauth2];
|
|
1039
1332
|
const cookieState = getCookie(request, cookies.state.name);
|
|
1333
|
+
const codeVerifier = getCookie(request, cookies.codeVerifier.name);
|
|
1040
1334
|
const cookieRedirectTo = getCookie(request, cookies.redirectTo.name);
|
|
1041
1335
|
const cookieRedirectURI = getCookie(request, cookies.redirectURI.name);
|
|
1042
|
-
|
|
1043
|
-
|
|
1336
|
+
if (!safeEquals(cookieState, state)) {
|
|
1337
|
+
logger?.log("MISMATCHING_STATE", {
|
|
1338
|
+
structuredData: {
|
|
1339
|
+
oauth_provider: oauth2
|
|
1340
|
+
}
|
|
1341
|
+
});
|
|
1044
1342
|
throw new AuthSecurityError(
|
|
1045
1343
|
"MISMATCHING_STATE",
|
|
1046
1344
|
"The provided state passed in the OAuth response does not match the stored state."
|
|
1047
1345
|
);
|
|
1048
1346
|
}
|
|
1049
|
-
const accessToken = await createAccessToken(oauthConfig, cookieRedirectURI, code, codeVerifier);
|
|
1050
|
-
const
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1347
|
+
const accessToken = await createAccessToken(oauthConfig, cookieRedirectURI, code, codeVerifier, logger);
|
|
1348
|
+
const origins = await getTrustedOrigins(request, trustedOrigins);
|
|
1349
|
+
const requestOrigin = await getOriginURL(request, context);
|
|
1350
|
+
if (!isRelativeURL(cookieRedirectTo)) {
|
|
1351
|
+
const isValid = origins.length > 0 ? isTrustedOrigin(cookieRedirectTo, origins) : isSameOrigin(cookieRedirectTo, requestOrigin);
|
|
1352
|
+
if (!isValid) {
|
|
1353
|
+
logger?.log("POTENTIAL_OPEN_REDIRECT_ATTACK_DETECTED", {
|
|
1354
|
+
structuredData: {
|
|
1355
|
+
redirect_path: cookieRedirectTo,
|
|
1356
|
+
provider: oauth2,
|
|
1357
|
+
has_trusted_origins: origins.length > 0,
|
|
1358
|
+
request_origin: requestOrigin
|
|
1359
|
+
}
|
|
1360
|
+
});
|
|
1361
|
+
throw new AuthSecurityError(
|
|
1362
|
+
"POTENTIAL_OPEN_REDIRECT_ATTACK_DETECTED",
|
|
1363
|
+
"Invalid redirect path. Potential open redirect attack detected."
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1056
1366
|
}
|
|
1057
|
-
const userInfo = await getUserInfo(oauthConfig, accessToken.access_token);
|
|
1367
|
+
const userInfo = await getUserInfo(oauthConfig, accessToken.access_token, logger);
|
|
1058
1368
|
const sessionCookie = await createSessionCookie(jose, userInfo);
|
|
1059
1369
|
const csrfToken = await createCSRF(jose);
|
|
1060
|
-
|
|
1370
|
+
logger?.log("OAUTH_CALLBACK_SUCCESS", {
|
|
1371
|
+
structuredData: {
|
|
1372
|
+
provider: oauth2
|
|
1373
|
+
}
|
|
1374
|
+
});
|
|
1375
|
+
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();
|
|
1061
1376
|
return Response.json({ oauth: oauth2 }, { status: 302, headers });
|
|
1062
1377
|
},
|
|
1063
1378
|
callbackConfig(oauth)
|
|
@@ -1069,16 +1384,18 @@ var import_router4 = require("@aura-stack/router");
|
|
|
1069
1384
|
var sessionAction = (0, import_router4.createEndpoint)("GET", "/session", async (ctx) => {
|
|
1070
1385
|
const {
|
|
1071
1386
|
request,
|
|
1072
|
-
context: { jose, cookies }
|
|
1387
|
+
context: { jose, cookies, logger }
|
|
1073
1388
|
} = ctx;
|
|
1074
1389
|
try {
|
|
1075
1390
|
const session = getCookie(request, cookies.sessionToken.name);
|
|
1076
1391
|
const decoded = await jose.decodeJWT(session);
|
|
1392
|
+
logger?.log("AUTH_SESSION_VALID");
|
|
1077
1393
|
const { exp, iat, jti, nbf, ...user } = decoded;
|
|
1078
|
-
const headers = new Headers(
|
|
1394
|
+
const headers = new Headers(secureApiHeaders);
|
|
1079
1395
|
return Response.json({ user, expires: toISOString(exp * 1e3) }, { headers });
|
|
1080
1396
|
} catch (error) {
|
|
1081
|
-
|
|
1397
|
+
logger?.log("AUTH_SESSION_INVALID", { structuredData: { error_type: getErrorName(error) } });
|
|
1398
|
+
const headers = new import_router4.HeadersBuilder(secureApiHeaders).setCookie(cookies.sessionToken.name, "", expiredCookieAttributes).toHeaders();
|
|
1082
1399
|
return Response.json({ authenticated: false, message: "Unauthorized" }, { status: 401, headers });
|
|
1083
1400
|
}
|
|
1084
1401
|
});
|
|
@@ -1102,30 +1419,54 @@ var signOutAction = (0, import_router5.createEndpoint)(
|
|
|
1102
1419
|
request,
|
|
1103
1420
|
headers,
|
|
1104
1421
|
searchParams: { redirectTo },
|
|
1105
|
-
context
|
|
1422
|
+
context
|
|
1106
1423
|
} = ctx;
|
|
1424
|
+
const { jose, cookies, logger } = context;
|
|
1107
1425
|
const session = headers.getCookie(cookies.sessionToken.name);
|
|
1108
1426
|
const csrfToken = headers.getCookie(cookies.csrfToken.name);
|
|
1109
1427
|
const header = headers.getHeader("X-CSRF-Token");
|
|
1428
|
+
logger?.log("SIGN_OUT_ATTEMPT", {
|
|
1429
|
+
structuredData: {
|
|
1430
|
+
has_session: Boolean(session),
|
|
1431
|
+
has_csrf_token: Boolean(csrfToken),
|
|
1432
|
+
has_csrf_header: Boolean(header)
|
|
1433
|
+
}
|
|
1434
|
+
});
|
|
1110
1435
|
if (!session) {
|
|
1436
|
+
logger?.log("SESSION_TOKEN_MISSING");
|
|
1111
1437
|
throw new AuthSecurityError("SESSION_TOKEN_MISSING", "The sessionToken is missing.");
|
|
1112
1438
|
}
|
|
1113
1439
|
if (!csrfToken) {
|
|
1440
|
+
logger?.log("CSRF_TOKEN_MISSING");
|
|
1114
1441
|
throw new AuthSecurityError("CSRF_TOKEN_MISSING", "The CSRF token is missing.");
|
|
1115
1442
|
}
|
|
1116
1443
|
if (!header) {
|
|
1117
|
-
|
|
1444
|
+
logger?.log("CSRF_HEADER_MISSING");
|
|
1445
|
+
throw new AuthSecurityError("CSRF_HEADER_MISSING", "The CSRF header is missing.");
|
|
1446
|
+
}
|
|
1447
|
+
try {
|
|
1448
|
+
await verifyCSRF(jose, csrfToken, header);
|
|
1449
|
+
} catch (error) {
|
|
1450
|
+
logger?.log("CSRF_TOKEN_INVALID", { structuredData: { error_type: getErrorName(error) } });
|
|
1451
|
+
throw new AuthSecurityError("CSRF_TOKEN_INVALID", "CSRF token verification failed");
|
|
1118
1452
|
}
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1453
|
+
logger?.log("SIGN_OUT_CSRF_VERIFIED");
|
|
1454
|
+
try {
|
|
1455
|
+
await jose.decodeJWT(session);
|
|
1456
|
+
logger?.log("SIGN_OUT_SUCCESS");
|
|
1457
|
+
} catch (error) {
|
|
1458
|
+
logger?.log("INVALID_JWT_TOKEN", { structuredData: { error_type: getErrorName(error) } });
|
|
1459
|
+
}
|
|
1460
|
+
const baseURL = getBaseURL(request);
|
|
1461
|
+
const location = await createRedirectTo(
|
|
1462
|
+
new Request(baseURL, {
|
|
1124
1463
|
headers: headers.toHeaders()
|
|
1125
1464
|
}),
|
|
1126
|
-
redirectTo
|
|
1465
|
+
redirectTo,
|
|
1466
|
+
context
|
|
1127
1467
|
);
|
|
1128
|
-
|
|
1468
|
+
logger?.log("SIGN_OUT_REDIRECT", { structuredData: { location } });
|
|
1469
|
+
const headersList = new import_router5.HeadersBuilder(secureApiHeaders).setHeader("Location", location).setCookie(cookies.csrfToken.name, "", expiredCookieAttributes).setCookie(cookies.sessionToken.name, "", expiredCookieAttributes).toHeaders();
|
|
1129
1470
|
return Response.json({ message: "Signed out successfully" }, { status: import_router5.statusCode.ACCEPTED, headers: headersList });
|
|
1130
1471
|
},
|
|
1131
1472
|
config
|
|
@@ -1143,33 +1484,338 @@ var getCSRFToken = (request, cookieName) => {
|
|
|
1143
1484
|
var csrfTokenAction = (0, import_router6.createEndpoint)("GET", "/csrfToken", async (ctx) => {
|
|
1144
1485
|
const {
|
|
1145
1486
|
request,
|
|
1146
|
-
context: { jose, cookies }
|
|
1487
|
+
context: { jose, cookies, logger }
|
|
1147
1488
|
} = ctx;
|
|
1148
1489
|
const token = getCSRFToken(request, cookies.csrfToken.name);
|
|
1490
|
+
logger?.log("CSRF_TOKEN_REQUESTED", { structuredData: { has_token: Boolean(token) } });
|
|
1149
1491
|
const csrfToken = await createCSRF(jose, token);
|
|
1150
|
-
|
|
1492
|
+
logger?.log("CSRF_TOKEN_ISSUED", { structuredData: { issued: Boolean(csrfToken) } });
|
|
1493
|
+
const headers = new Headers(secureApiHeaders);
|
|
1151
1494
|
headers.append("Set-Cookie", setCookie(cookies.csrfToken.name, csrfToken, cookies.csrfToken.attributes));
|
|
1152
1495
|
return Response.json({ csrfToken }, { headers });
|
|
1153
1496
|
});
|
|
1154
1497
|
|
|
1498
|
+
// src/logger.ts
|
|
1499
|
+
var logMessages = {
|
|
1500
|
+
ROUTER_INTERNAL_ERROR: {
|
|
1501
|
+
facility: 10,
|
|
1502
|
+
severity: "error",
|
|
1503
|
+
msgId: "ROUTER_INTERNAL_ERROR",
|
|
1504
|
+
message: "Unhandled router error while processing the request"
|
|
1505
|
+
},
|
|
1506
|
+
INVALID_REQUEST: {
|
|
1507
|
+
facility: 10,
|
|
1508
|
+
severity: "warning",
|
|
1509
|
+
msgId: "INVALID_REQUEST",
|
|
1510
|
+
message: "Request validation failed against the expected schema"
|
|
1511
|
+
},
|
|
1512
|
+
SERVER_ERROR: {
|
|
1513
|
+
facility: 10,
|
|
1514
|
+
severity: "error",
|
|
1515
|
+
msgId: "SERVER_ERROR",
|
|
1516
|
+
message: "Unexpected internal server error during authentication"
|
|
1517
|
+
},
|
|
1518
|
+
OAUTH_PROTOCOL_ERROR: {
|
|
1519
|
+
facility: 10,
|
|
1520
|
+
severity: "warning",
|
|
1521
|
+
msgId: "OAUTH_PROTOCOL_ERROR",
|
|
1522
|
+
message: "OAuth provider returned an invalid or unexpected protocol response"
|
|
1523
|
+
},
|
|
1524
|
+
OAUTH_AUTHORIZATION_ERROR: {
|
|
1525
|
+
facility: 10,
|
|
1526
|
+
severity: "error",
|
|
1527
|
+
msgId: "OAUTH_AUTHORIZATION_ERROR",
|
|
1528
|
+
message: "OAuth authorization request was rejected or failed"
|
|
1529
|
+
},
|
|
1530
|
+
INVALID_OAUTH_CONFIGURATION: {
|
|
1531
|
+
facility: 10,
|
|
1532
|
+
severity: "error",
|
|
1533
|
+
msgId: "INVALID_OAUTH_CONFIGURATION",
|
|
1534
|
+
message: "The OAuth provider configuration is invalid or incomplete"
|
|
1535
|
+
},
|
|
1536
|
+
OAUTH_ACCESS_TOKEN_REQUEST_INITIATED: {
|
|
1537
|
+
facility: 10,
|
|
1538
|
+
severity: "debug",
|
|
1539
|
+
msgId: "OAUTH_ACCESS_TOKEN_REQUEST_INITIATED",
|
|
1540
|
+
message: "Starting OAuth access token request to the provider"
|
|
1541
|
+
},
|
|
1542
|
+
INVALID_OAUTH_ACCESS_TOKEN_RESPONSE: {
|
|
1543
|
+
facility: 10,
|
|
1544
|
+
severity: "error",
|
|
1545
|
+
msgId: "INVALID_OAUTH_ACCESS_TOKEN_RESPONSE",
|
|
1546
|
+
message: "OAuth access token endpoint returned an invalid or malformed response"
|
|
1547
|
+
},
|
|
1548
|
+
OAUTH_ACCESS_TOKEN_ERROR: {
|
|
1549
|
+
facility: 10,
|
|
1550
|
+
severity: "error",
|
|
1551
|
+
msgId: "OAUTH_ACCESS_TOKEN_ERROR",
|
|
1552
|
+
message: "OAuth access token endpoint returned an error response"
|
|
1553
|
+
},
|
|
1554
|
+
OAUTH_ACCESS_TOKEN_SUCCESS: {
|
|
1555
|
+
facility: 10,
|
|
1556
|
+
severity: "info",
|
|
1557
|
+
msgId: "OAUTH_ACCESS_TOKEN_SUCCESS",
|
|
1558
|
+
message: "Successfully retrieved OAuth access token from the provider"
|
|
1559
|
+
},
|
|
1560
|
+
OAUTH_ACCESS_TOKEN_REQUEST_FAILED: {
|
|
1561
|
+
facility: 10,
|
|
1562
|
+
severity: "error",
|
|
1563
|
+
msgId: "OAUTH_ACCESS_TOKEN_REQUEST_FAILED",
|
|
1564
|
+
message: "Network or server error while requesting OAuth access token"
|
|
1565
|
+
},
|
|
1566
|
+
OAUTH_USERINFO_REQUEST_INITIATED: {
|
|
1567
|
+
facility: 10,
|
|
1568
|
+
severity: "debug",
|
|
1569
|
+
msgId: "OAUTH_USERINFO_REQUEST_INITIATED",
|
|
1570
|
+
message: "Starting OAuth userinfo request to the provider"
|
|
1571
|
+
},
|
|
1572
|
+
OAUTH_USERINFO_INVALID_RESPONSE: {
|
|
1573
|
+
facility: 10,
|
|
1574
|
+
severity: "error",
|
|
1575
|
+
msgId: "OAUTH_USERINFO_INVALID_RESPONSE",
|
|
1576
|
+
message: "OAuth userinfo endpoint returned an invalid or malformed response"
|
|
1577
|
+
},
|
|
1578
|
+
OAUTH_USERINFO_ERROR: {
|
|
1579
|
+
facility: 10,
|
|
1580
|
+
severity: "error",
|
|
1581
|
+
msgId: "OAUTH_USERINFO_ERROR",
|
|
1582
|
+
message: "OAuth userinfo endpoint returned an error response"
|
|
1583
|
+
},
|
|
1584
|
+
OAUTH_USERINFO_SUCCESS: {
|
|
1585
|
+
facility: 10,
|
|
1586
|
+
severity: "info",
|
|
1587
|
+
msgId: "OAUTH_USERINFO_SUCCESS",
|
|
1588
|
+
message: "Successfully retrieved user information from the OAuth provider"
|
|
1589
|
+
},
|
|
1590
|
+
OAUTH_USERINFO_REQUEST_FAILED: {
|
|
1591
|
+
facility: 10,
|
|
1592
|
+
severity: "error",
|
|
1593
|
+
msgId: "OAUTH_USERINFO_REQUEST_FAILED",
|
|
1594
|
+
message: "Network or server error while requesting user information from the OAuth provider"
|
|
1595
|
+
},
|
|
1596
|
+
OAUTH_CALLBACK_SUCCESS: {
|
|
1597
|
+
facility: 4,
|
|
1598
|
+
severity: "info",
|
|
1599
|
+
msgId: "OAUTH_CALLBACK_SUCCESS",
|
|
1600
|
+
message: "OAuth callback completed successfully and session was created"
|
|
1601
|
+
},
|
|
1602
|
+
MISMATCHING_STATE: {
|
|
1603
|
+
facility: 4,
|
|
1604
|
+
severity: "critical",
|
|
1605
|
+
msgId: "MISMATCHING_STATE",
|
|
1606
|
+
message: "OAuth response state parameter does not match the stored state value"
|
|
1607
|
+
},
|
|
1608
|
+
POTENTIAL_OPEN_REDIRECT_ATTACK_DETECTED: {
|
|
1609
|
+
facility: 4,
|
|
1610
|
+
severity: "critical",
|
|
1611
|
+
msgId: "POTENTIAL_OPEN_REDIRECT_ATTACK_DETECTED",
|
|
1612
|
+
message: "Blocked redirect to untrusted or external URL (potential open redirect attack)"
|
|
1613
|
+
},
|
|
1614
|
+
OPEN_REDIRECT_ATTACK: {
|
|
1615
|
+
facility: 4,
|
|
1616
|
+
severity: "warning",
|
|
1617
|
+
msgId: "OPEN_REDIRECT_ATTACK",
|
|
1618
|
+
message: "Detected redirect target that does not match the trusted origin"
|
|
1619
|
+
},
|
|
1620
|
+
SESSION_TOKEN_MISSING: {
|
|
1621
|
+
facility: 4,
|
|
1622
|
+
severity: "warning",
|
|
1623
|
+
msgId: "SESSION_TOKEN_MISSING",
|
|
1624
|
+
message: "Session cookie is missing from the request"
|
|
1625
|
+
},
|
|
1626
|
+
CSRF_TOKEN_MISSING: {
|
|
1627
|
+
facility: 4,
|
|
1628
|
+
severity: "warning",
|
|
1629
|
+
msgId: "CSRF_TOKEN_MISSING",
|
|
1630
|
+
message: "CSRF token cookie is missing from the request"
|
|
1631
|
+
},
|
|
1632
|
+
CSRF_HEADER_MISSING: {
|
|
1633
|
+
facility: 4,
|
|
1634
|
+
severity: "warning",
|
|
1635
|
+
msgId: "CSRF_HEADER_MISSING",
|
|
1636
|
+
message: "CSRF header is missing from the request"
|
|
1637
|
+
},
|
|
1638
|
+
CSRF_TOKEN_INVALID: {
|
|
1639
|
+
facility: 4,
|
|
1640
|
+
severity: "error",
|
|
1641
|
+
msgId: "CSRF_TOKEN_INVALID",
|
|
1642
|
+
message: "CSRF token verification failed or token is invalid"
|
|
1643
|
+
},
|
|
1644
|
+
SIGN_IN_INITIATED: {
|
|
1645
|
+
facility: 4,
|
|
1646
|
+
severity: "info",
|
|
1647
|
+
msgId: "SIGN_IN_INITIATED",
|
|
1648
|
+
message: "Starting OAuth sign-in flow for the selected provider"
|
|
1649
|
+
},
|
|
1650
|
+
SIGN_OUT_ATTEMPT: {
|
|
1651
|
+
facility: 4,
|
|
1652
|
+
severity: "debug",
|
|
1653
|
+
msgId: "SIGN_OUT_ATTEMPT",
|
|
1654
|
+
message: "Received sign-out request from client"
|
|
1655
|
+
},
|
|
1656
|
+
SIGN_OUT_CSRF_VERIFIED: {
|
|
1657
|
+
facility: 4,
|
|
1658
|
+
severity: "info",
|
|
1659
|
+
msgId: "SIGN_OUT_CSRF_VERIFIED",
|
|
1660
|
+
message: "CSRF token was successfully verified during sign-out"
|
|
1661
|
+
},
|
|
1662
|
+
SIGN_OUT_SUCCESS: {
|
|
1663
|
+
facility: 4,
|
|
1664
|
+
severity: "info",
|
|
1665
|
+
msgId: "SIGN_OUT_SUCCESS",
|
|
1666
|
+
message: "User session was cleared and sign-out completed successfully"
|
|
1667
|
+
},
|
|
1668
|
+
SIGN_OUT_REDIRECT: {
|
|
1669
|
+
facility: 4,
|
|
1670
|
+
severity: "debug",
|
|
1671
|
+
msgId: "SIGN_OUT_REDIRECT",
|
|
1672
|
+
message: "Redirecting client after successful sign-out"
|
|
1673
|
+
},
|
|
1674
|
+
AUTH_SESSION_VALID: {
|
|
1675
|
+
facility: 4,
|
|
1676
|
+
severity: "info",
|
|
1677
|
+
msgId: "AUTH_SESSION_VALID",
|
|
1678
|
+
message: "Session token is valid and user session was returned"
|
|
1679
|
+
},
|
|
1680
|
+
AUTH_SESSION_INVALID: {
|
|
1681
|
+
facility: 4,
|
|
1682
|
+
severity: "notice",
|
|
1683
|
+
msgId: "AUTH_SESSION_INVALID",
|
|
1684
|
+
message: "Session token is missing, expired, or invalid"
|
|
1685
|
+
},
|
|
1686
|
+
INVALID_JWT_TOKEN: {
|
|
1687
|
+
facility: 4,
|
|
1688
|
+
severity: "warning",
|
|
1689
|
+
msgId: "INVALID_JWT_TOKEN",
|
|
1690
|
+
message: "JWT session token failed validation during sign-out"
|
|
1691
|
+
},
|
|
1692
|
+
CSRF_TOKEN_REQUESTED: {
|
|
1693
|
+
facility: 4,
|
|
1694
|
+
severity: "debug",
|
|
1695
|
+
msgId: "CSRF_TOKEN_REQUESTED",
|
|
1696
|
+
message: "Client requested a CSRF token"
|
|
1697
|
+
},
|
|
1698
|
+
CSRF_TOKEN_ISSUED: {
|
|
1699
|
+
facility: 4,
|
|
1700
|
+
severity: "debug",
|
|
1701
|
+
msgId: "CSRF_TOKEN_ISSUED",
|
|
1702
|
+
message: "Issued a new CSRF token to the client"
|
|
1703
|
+
},
|
|
1704
|
+
INVALID_URL: {
|
|
1705
|
+
facility: 10,
|
|
1706
|
+
severity: "error",
|
|
1707
|
+
msgId: "INVALID_URL",
|
|
1708
|
+
message: "Derived origin URL is invalid or malformed"
|
|
1709
|
+
},
|
|
1710
|
+
COOKIE_HTTPONLY_DISABLED: {
|
|
1711
|
+
facility: 10,
|
|
1712
|
+
severity: "critical",
|
|
1713
|
+
msgId: "COOKIE_HTTPONLY_DISABLED",
|
|
1714
|
+
message: "Cookie is configured without HttpOnly. This allows JavaScript access via document.cookie and increases XSS exposure."
|
|
1715
|
+
},
|
|
1716
|
+
COOKIE_WILDCARD_DOMAIN: {
|
|
1717
|
+
facility: 10,
|
|
1718
|
+
severity: "critical",
|
|
1719
|
+
msgId: "COOKIE_WILDCARD_DOMAIN",
|
|
1720
|
+
message: "Cookie 'Domain' is set to a wildcard, which is insecure and should be avoided."
|
|
1721
|
+
},
|
|
1722
|
+
COOKIE_SECURE_DISABLED: {
|
|
1723
|
+
facility: 10,
|
|
1724
|
+
severity: "warning",
|
|
1725
|
+
msgId: "COOKIE_SECURE_DISABLED",
|
|
1726
|
+
message: "Cookie is configured with 'Secure' but the request is not HTTPS. The 'Secure' attribute will be ignored by the browser."
|
|
1727
|
+
},
|
|
1728
|
+
COOKIE_SAMESITE_NONE_WITHOUT_SECURE: {
|
|
1729
|
+
facility: 10,
|
|
1730
|
+
severity: "warning",
|
|
1731
|
+
msgId: "COOKIE_SAMESITE_NONE_WITHOUT_SECURE",
|
|
1732
|
+
message: "Cookie uses SameSite=None without Secure. Falling back to SameSite=Lax for safer defaults."
|
|
1733
|
+
},
|
|
1734
|
+
COOKIE_INSECURE_IN_PRODUCTION: {
|
|
1735
|
+
facility: 10,
|
|
1736
|
+
severity: "critical",
|
|
1737
|
+
msgId: "COOKIE_INSECURE_IN_PRODUCTION",
|
|
1738
|
+
message: "Cookies are being served over an insecure connection in production, which is a serious security risk."
|
|
1739
|
+
},
|
|
1740
|
+
COOKIE_HOST_STRATEGY_INSECURE: {
|
|
1741
|
+
facility: 10,
|
|
1742
|
+
severity: "critical",
|
|
1743
|
+
msgId: "COOKIE_HOST_STRATEGY_INSECURE",
|
|
1744
|
+
message: "__Host- cookies require a secure HTTPS context. Falling back to standard cookie settings."
|
|
1745
|
+
},
|
|
1746
|
+
UNTRUSTED_ORIGIN: {
|
|
1747
|
+
facility: 10,
|
|
1748
|
+
severity: "error",
|
|
1749
|
+
msgId: "UNTRUSTED_ORIGIN",
|
|
1750
|
+
message: "The constructed origin URL is not trusted."
|
|
1751
|
+
}
|
|
1752
|
+
};
|
|
1753
|
+
var createLogEntry = (key, overrides) => {
|
|
1754
|
+
const message = logMessages[key];
|
|
1755
|
+
return {
|
|
1756
|
+
...message,
|
|
1757
|
+
...overrides
|
|
1758
|
+
};
|
|
1759
|
+
};
|
|
1760
|
+
|
|
1155
1761
|
// src/index.ts
|
|
1762
|
+
var logLevelToSeverity = {
|
|
1763
|
+
debug: ["debug", "info", "notice", "warning", "error", "critical", "alert", "emergency"],
|
|
1764
|
+
info: ["info", "notice", "warning", "error", "critical", "alert", "emergency"],
|
|
1765
|
+
warn: ["warning", "error", "critical", "alert", "emergency"],
|
|
1766
|
+
error: ["error", "critical", "alert", "emergency"]
|
|
1767
|
+
};
|
|
1768
|
+
var createLoggerProxy = (logger) => {
|
|
1769
|
+
if (!logger) return void 0;
|
|
1770
|
+
const level = logger.level;
|
|
1771
|
+
const allowedSeverities = logLevelToSeverity[level] ?? [];
|
|
1772
|
+
const internalLogger = {
|
|
1773
|
+
level,
|
|
1774
|
+
log(key, overrides) {
|
|
1775
|
+
const entry = createLogEntry(key, overrides);
|
|
1776
|
+
if (!allowedSeverities.includes(entry.severity)) return entry;
|
|
1777
|
+
logger.log({
|
|
1778
|
+
timestamp: entry.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1779
|
+
appName: entry.appName ?? "aura-auth",
|
|
1780
|
+
hostname: entry.hostname ?? "aura-auth",
|
|
1781
|
+
...entry
|
|
1782
|
+
});
|
|
1783
|
+
return entry;
|
|
1784
|
+
}
|
|
1785
|
+
};
|
|
1786
|
+
return internalLogger;
|
|
1787
|
+
};
|
|
1156
1788
|
var createInternalConfig = (authConfig) => {
|
|
1157
1789
|
const useSecure = authConfig?.trustedProxyHeaders ?? false;
|
|
1790
|
+
const logger = authConfig?.logger;
|
|
1791
|
+
const internalLogger = createLoggerProxy(logger);
|
|
1158
1792
|
return {
|
|
1159
1793
|
basePath: authConfig?.basePath ?? "/auth",
|
|
1160
|
-
onError:
|
|
1794
|
+
onError: createErrorHandler(internalLogger),
|
|
1161
1795
|
context: {
|
|
1162
1796
|
oauth: createBuiltInOAuthProviders(authConfig?.oauth),
|
|
1163
|
-
cookies: createCookieStore(
|
|
1797
|
+
cookies: createCookieStore(
|
|
1798
|
+
useSecure,
|
|
1799
|
+
authConfig?.cookies?.prefix,
|
|
1800
|
+
authConfig?.cookies?.overrides ?? {},
|
|
1801
|
+
internalLogger
|
|
1802
|
+
),
|
|
1164
1803
|
jose: createJoseInstance(authConfig?.secret),
|
|
1165
1804
|
secret: authConfig?.secret,
|
|
1166
1805
|
basePath: authConfig?.basePath ?? "/auth",
|
|
1167
|
-
trustedProxyHeaders: useSecure
|
|
1806
|
+
trustedProxyHeaders: useSecure,
|
|
1807
|
+
trustedOrigins: authConfig?.trustedOrigins,
|
|
1808
|
+
logger: internalLogger
|
|
1168
1809
|
},
|
|
1169
1810
|
middlewares: [
|
|
1170
1811
|
(ctx) => {
|
|
1171
1812
|
const useSecure2 = useSecureCookies(ctx.request, ctx.context.trustedProxyHeaders);
|
|
1172
|
-
const cookies = createCookieStore(
|
|
1813
|
+
const cookies = createCookieStore(
|
|
1814
|
+
useSecure2,
|
|
1815
|
+
authConfig?.cookies?.prefix,
|
|
1816
|
+
authConfig?.cookies?.overrides ?? {},
|
|
1817
|
+
internalLogger
|
|
1818
|
+
);
|
|
1173
1819
|
ctx.context.cookies = cookies;
|
|
1174
1820
|
return ctx;
|
|
1175
1821
|
}
|
|
@@ -1189,6 +1835,5 @@ var createAuth = (authConfig) => {
|
|
|
1189
1835
|
};
|
|
1190
1836
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1191
1837
|
0 && (module.exports = {
|
|
1192
|
-
createAuth
|
|
1193
|
-
createClient
|
|
1838
|
+
createAuth
|
|
1194
1839
|
});
|