@fitzzero/quickdraw-core 5.1.1 → 5.1.3
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/README.md +1308 -219
- package/dist/{admin-DKwkIh5i.d.ts → admin-CS7JB_UH.d.ts} +2 -2
- package/dist/{adminSchemas-2DUA_hZ8.d.ts → adminSchemas-29cGgrg5.d.ts} +6 -4
- package/dist/{auth-BhWqdyb-.d.ts → auth-DtdYjrZ9.d.ts} +51 -21
- package/dist/{caller-CXTFswBO.d.ts → caller-BTwGl7vm.d.ts} +296 -33
- package/dist/chunk-2FDSKLBO.js +82 -0
- package/dist/chunk-2FDSKLBO.js.map +1 -0
- package/dist/{chunk-RT6OILOI.js → chunk-4D2DOGAW.js} +2 -14
- package/dist/chunk-4D2DOGAW.js.map +1 -0
- package/dist/{chunk-OECZPRPW.js → chunk-64I6MYBC.js} +162 -775
- package/dist/chunk-64I6MYBC.js.map +1 -0
- package/dist/chunk-7473HK24.js +17 -0
- package/dist/chunk-7473HK24.js.map +1 -0
- package/dist/chunk-7Y5ROPXL.js +33 -0
- package/dist/chunk-7Y5ROPXL.js.map +1 -0
- package/dist/chunk-A2RBQD66.js +7 -0
- package/dist/chunk-A2RBQD66.js.map +1 -0
- package/dist/chunk-AK23XSWD.js +16 -0
- package/dist/chunk-AK23XSWD.js.map +1 -0
- package/dist/chunk-ASAMUGHG.js +651 -0
- package/dist/chunk-ASAMUGHG.js.map +1 -0
- package/dist/{chunk-B2P7AZ25.js → chunk-CYE4UBOB.js} +6 -4
- package/dist/chunk-CYE4UBOB.js.map +1 -0
- package/dist/chunk-DKE6QO26.js +136 -0
- package/dist/chunk-DKE6QO26.js.map +1 -0
- package/dist/chunk-DNP65TD5.js +832 -0
- package/dist/chunk-DNP65TD5.js.map +1 -0
- package/dist/{chunk-H23KEFBA.js → chunk-DUBU3BB3.js} +30 -1
- package/dist/chunk-DUBU3BB3.js.map +1 -0
- package/dist/chunk-GYLWEJHW.js +19 -0
- package/dist/chunk-GYLWEJHW.js.map +1 -0
- package/dist/{chunk-SHEGLZEP.js → chunk-I3JCFS7H.js} +34 -13
- package/dist/chunk-I3JCFS7H.js.map +1 -0
- package/dist/{chunk-X5CWPX7H.js → chunk-IIQ3WMJK.js} +13 -2
- package/dist/chunk-IIQ3WMJK.js.map +1 -0
- package/dist/{chunk-RZTP4IEW.js → chunk-JVTONQFI.js} +6339 -4226
- package/dist/chunk-JVTONQFI.js.map +1 -0
- package/dist/{chunk-QEKVYG5F.js → chunk-KKQANRLG.js} +2 -2
- package/dist/chunk-KKQANRLG.js.map +1 -0
- package/dist/{chunk-CDKG4TFA.js → chunk-MPQAT55F.js} +38 -205
- package/dist/chunk-MPQAT55F.js.map +1 -0
- package/dist/chunk-NO4M6PO5.js +101 -0
- package/dist/chunk-NO4M6PO5.js.map +1 -0
- package/dist/{chunk-TBMKJ26R.js → chunk-NW6U45HX.js} +3 -1
- package/dist/chunk-NW6U45HX.js.map +1 -0
- package/dist/chunk-NX2V6SIL.js +202 -0
- package/dist/chunk-NX2V6SIL.js.map +1 -0
- package/dist/{chunk-ZIVPHYRG.js → chunk-NZHA4TZD.js} +72 -175
- package/dist/chunk-NZHA4TZD.js.map +1 -0
- package/dist/{chunk-2PWMUVC7.js → chunk-OCZJ4EYV.js} +518 -27
- package/dist/chunk-OCZJ4EYV.js.map +1 -0
- package/dist/{chunk-REX5YPHX.js → chunk-PHCURHC7.js} +76 -21
- package/dist/chunk-PHCURHC7.js.map +1 -0
- package/dist/{chunk-4YO5H5ON.js → chunk-Q4OGQM5I.js} +4 -10
- package/dist/chunk-Q4OGQM5I.js.map +1 -0
- package/dist/chunk-QSU47XEX.js +13 -0
- package/dist/chunk-QSU47XEX.js.map +1 -0
- package/dist/{chunk-DD4SKCNO.js → chunk-RZXJTDH5.js} +13 -2
- package/dist/chunk-RZXJTDH5.js.map +1 -0
- package/dist/{chunk-Z2LZ7IZ7.js → chunk-SN3OZVEQ.js} +5 -31
- package/dist/chunk-SN3OZVEQ.js.map +1 -0
- package/dist/{chunk-4DIQHASK.js → chunk-SQ5VUG77.js} +28 -8
- package/dist/chunk-SQ5VUG77.js.map +1 -0
- package/dist/chunk-STH5DLQP.js +1192 -0
- package/dist/chunk-STH5DLQP.js.map +1 -0
- package/dist/{chunk-QCHKWIRS.js → chunk-VZFJTKIW.js} +13 -13
- package/dist/{chunk-QCHKWIRS.js.map → chunk-VZFJTKIW.js.map} +1 -1
- package/dist/{chunk-VSQBHPWH.js → chunk-X4ZU4QH5.js} +54 -22
- package/dist/chunk-X4ZU4QH5.js.map +1 -0
- package/dist/chunk-XD7SRDUY.js +188 -0
- package/dist/chunk-XD7SRDUY.js.map +1 -0
- package/dist/chunk-XH2IZVLT.js +62 -0
- package/dist/chunk-XH2IZVLT.js.map +1 -0
- package/dist/chunk-Z7I6SONO.js +825 -0
- package/dist/chunk-Z7I6SONO.js.map +1 -0
- package/dist/cli/quickdraw-docs.js +132 -112
- package/dist/cli/quickdraw-docs.js.map +1 -1
- package/dist/client/core.d.ts +387 -0
- package/dist/client/core.js +109 -0
- package/dist/client/index.d.ts +52 -203
- package/dist/client/index.js +59 -218
- package/dist/client/index.js.map +1 -1
- package/dist/{clientTypes-Bc-G8n-U.d.ts → clientTypes-CUUNJwWs.d.ts} +3 -3
- package/dist/{connection-Cr1BU07W.d.ts → connection-2EeQ_Et0.d.ts} +22 -11
- package/dist/{createServer-CP9eDHZZ.d.ts → createServer-Bx5HOcYK.d.ts} +67 -17
- package/dist/{createTestApp-UKVGEmOf.d.ts → createTestApp-DAMANT79.d.ts} +46 -13
- package/dist/{envelope-Cp3YcVxC.d.ts → envelope-BMBaxppV.d.ts} +216 -10
- package/dist/{http-Djj2Inuu.d.ts → http-YzLAar3w.d.ts} +76 -60
- package/dist/index.d.ts +8 -7
- package/dist/index.js +82 -25
- package/dist/index.js.map +1 -1
- package/dist/{infer-Teo1xpsd.d.ts → infer-Br62cnyW.d.ts} +82 -11
- package/dist/logger-Yz93KzdV.d.ts +30 -0
- package/dist/{metrics-BdBu5o8G.d.ts → metrics-CTVpw0gu.d.ts} +2 -2
- package/dist/origins-kvpxnVcW.d.ts +8 -0
- package/dist/parser.js +2 -1
- package/dist/prisma/index.d.ts +14 -7
- package/dist/prisma/index.js +279 -70
- package/dist/prisma/index.js.map +1 -1
- package/dist/{rateLimit-ZMIZJUZF.js → rateLimit-5RKBFLUR.js} +2 -2
- package/dist/rateLimit-5RKBFLUR.js.map +1 -0
- package/dist/remote-N7OUAGOW.js +15 -0
- package/dist/remote-N7OUAGOW.js.map +1 -0
- package/dist/server/auth/index.d.ts +82 -33
- package/dist/server/auth/index.js +277 -270
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/express/index.d.ts +9 -2
- package/dist/server/express/index.js +1 -1
- package/dist/server/index.d.ts +268 -84
- package/dist/server/index.js +664 -418
- package/dist/server/index.js.map +1 -1
- package/dist/server/mcp/index.d.ts +89 -27
- package/dist/server/mcp/index.js +281 -62
- package/dist/server/mcp/index.js.map +1 -1
- package/dist/server/otel.d.ts +3 -3
- package/dist/{sharing-DVa8W-yR.d.ts → sharing-BrsmlDv_.d.ts} +3 -3
- package/dist/testing/client.d.ts +14 -14
- package/dist/testing/client.js +19 -10
- package/dist/testing/client.js.map +1 -1
- package/dist/testing/index.d.ts +50 -13
- package/dist/testing/index.js +113 -21
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/mock.d.ts +5 -5
- package/dist/testing/mock.js +17 -9
- package/dist/tracked-BR5b2O-U.d.ts +43 -0
- package/dist/{types-Cu0vmx7c.d.ts → types-CLRCQGnO.d.ts} +4 -3
- package/dist/types-tMagHbjy.d.ts +151 -0
- package/dist/utils/index.d.ts +16 -4
- package/dist/utils/index.js +6 -3
- package/package.json +5 -1
- package/dist/chunk-2PWMUVC7.js.map +0 -1
- package/dist/chunk-4DIQHASK.js.map +0 -1
- package/dist/chunk-4YO5H5ON.js.map +0 -1
- package/dist/chunk-B2P7AZ25.js.map +0 -1
- package/dist/chunk-CDKG4TFA.js.map +0 -1
- package/dist/chunk-DD4SKCNO.js.map +0 -1
- package/dist/chunk-H23KEFBA.js.map +0 -1
- package/dist/chunk-OECZPRPW.js.map +0 -1
- package/dist/chunk-QEKVYG5F.js.map +0 -1
- package/dist/chunk-REX5YPHX.js.map +0 -1
- package/dist/chunk-RT6OILOI.js.map +0 -1
- package/dist/chunk-RZTP4IEW.js.map +0 -1
- package/dist/chunk-SHEGLZEP.js.map +0 -1
- package/dist/chunk-TBMKJ26R.js.map +0 -1
- package/dist/chunk-VSQBHPWH.js.map +0 -1
- package/dist/chunk-X5CWPX7H.js.map +0 -1
- package/dist/chunk-YSWH776Y.js +0 -13
- package/dist/chunk-YSWH776Y.js.map +0 -1
- package/dist/chunk-Z2LZ7IZ7.js.map +0 -1
- package/dist/chunk-ZIVPHYRG.js.map +0 -1
- package/dist/logger-BY28acW-.d.ts +0 -16
- /package/dist/{rateLimit-ZMIZJUZF.js.map → client/core.js.map} +0 -0
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OAUTH_RETURN_ORIGIN_COOKIE,
|
|
3
|
+
originAllowlist,
|
|
4
|
+
rememberRouteOrigins,
|
|
5
|
+
routeOriginsOf,
|
|
6
|
+
validateRedirectOrigin
|
|
7
|
+
} from "../../chunk-NO4M6PO5.js";
|
|
1
8
|
import {
|
|
2
9
|
HOST_SESSION_COOKIE,
|
|
3
10
|
SESSION_COOKIE,
|
|
4
11
|
clearSessionCookie,
|
|
12
|
+
clearStaleHostSession,
|
|
5
13
|
cookieDomainFromEnv,
|
|
6
14
|
cookieToken,
|
|
7
15
|
cookiesOf,
|
|
@@ -11,25 +19,29 @@ import {
|
|
|
11
19
|
httpCredentialOf,
|
|
12
20
|
isJsonRequest,
|
|
13
21
|
isSecureSessionRequest,
|
|
22
|
+
markSocketAuth,
|
|
14
23
|
readJsonInput,
|
|
24
|
+
readVerifiedJWT,
|
|
15
25
|
recordSocketSession,
|
|
16
26
|
sessionCookieNameFor,
|
|
17
27
|
sessionCookieNamesFor,
|
|
18
28
|
setSessionCookie,
|
|
19
|
-
tokenOf,
|
|
20
29
|
transportCookieNaming,
|
|
21
30
|
verifyJWT
|
|
22
|
-
} from "../../chunk-
|
|
23
|
-
import "../../chunk-
|
|
24
|
-
import "../../chunk-Z2LZ7IZ7.js";
|
|
31
|
+
} from "../../chunk-PHCURHC7.js";
|
|
32
|
+
import "../../chunk-DUBU3BB3.js";
|
|
25
33
|
import {
|
|
26
|
-
consoleLogger
|
|
27
|
-
|
|
34
|
+
consoleLogger,
|
|
35
|
+
errorText
|
|
36
|
+
} from "../../chunk-DKE6QO26.js";
|
|
37
|
+
import "../../chunk-SN3OZVEQ.js";
|
|
38
|
+
import "../../chunk-7Y5ROPXL.js";
|
|
28
39
|
import {
|
|
29
40
|
INTERNAL_MESSAGE,
|
|
30
41
|
QuickdrawError,
|
|
31
42
|
httpStatus
|
|
32
|
-
} from "../../chunk-
|
|
43
|
+
} from "../../chunk-Q4OGQM5I.js";
|
|
44
|
+
import "../../chunk-QSU47XEX.js";
|
|
33
45
|
|
|
34
46
|
// src/server/auth/oauth.ts
|
|
35
47
|
function createOAuthURL(provider, config, state) {
|
|
@@ -128,38 +140,15 @@ var googleProvider = {
|
|
|
128
140
|
// src/server/auth/mock.ts
|
|
129
141
|
import { randomBytes } from "crypto";
|
|
130
142
|
|
|
131
|
-
// src/server/auth/
|
|
132
|
-
var
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (!origin) return null;
|
|
141
|
-
const clientUrl = options.clientUrl ?? process.env.CLIENT_URL;
|
|
142
|
-
let parsedOrigin;
|
|
143
|
-
try {
|
|
144
|
-
parsedOrigin = new URL(origin);
|
|
145
|
-
} catch {
|
|
146
|
-
return null;
|
|
147
|
-
}
|
|
148
|
-
const cleanOrigin = parsedOrigin.origin;
|
|
149
|
-
if (clientUrl && cleanOrigin === clientUrl) {
|
|
150
|
-
return cleanOrigin;
|
|
151
|
-
}
|
|
152
|
-
const extra = options.extraAllowedOrigins ?? envExtraOrigins();
|
|
153
|
-
if (extra.includes(cleanOrigin)) {
|
|
154
|
-
return cleanOrigin;
|
|
155
|
-
}
|
|
156
|
-
if (options.allowedPatterns?.some((pattern) => pattern.test(cleanOrigin))) {
|
|
157
|
-
return cleanOrigin;
|
|
158
|
-
}
|
|
159
|
-
if ((options.allowLocalhostInDev ?? true) && process.env.NODE_ENV !== "production" && LOCALHOST_REGEX.test(cleanOrigin)) {
|
|
160
|
-
return cleanOrigin;
|
|
161
|
-
}
|
|
162
|
-
return null;
|
|
143
|
+
// src/server/auth/devSignIns.ts
|
|
144
|
+
var DEVELOPMENT_ENVS = /* @__PURE__ */ new Set(["development", "test"]);
|
|
145
|
+
function developmentSignInsAllowed() {
|
|
146
|
+
const env = process.env.NODE_ENV;
|
|
147
|
+
return env !== void 0 && DEVELOPMENT_ENVS.has(env);
|
|
148
|
+
}
|
|
149
|
+
function nodeEnvLabel() {
|
|
150
|
+
const env = process.env.NODE_ENV;
|
|
151
|
+
return env === void 0 ? "unset" : JSON.stringify(env);
|
|
163
152
|
}
|
|
164
153
|
|
|
165
154
|
// src/server/auth/mock.ts
|
|
@@ -168,7 +157,7 @@ var CODE_TTL_MS = 5 * 60 * 1e3;
|
|
|
168
157
|
var TOKEN_TTL_MS = 60 * 60 * 1e3;
|
|
169
158
|
var DEV_PRIVATE_ORIGIN_REGEX = /^http:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0|10\.\d{1,3}\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3}|172\.(?:1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}):\d+$/;
|
|
170
159
|
function isMockOAuthEnabled() {
|
|
171
|
-
return process.env.ENABLE_MOCK_OAUTH === "true" &&
|
|
160
|
+
return process.env.ENABLE_MOCK_OAUTH === "true" && developmentSignInsAllowed();
|
|
172
161
|
}
|
|
173
162
|
function createMockOAuthProvider(apiBaseUrl, options = {}) {
|
|
174
163
|
const prefix = options.pathPrefix ?? DEFAULT_MOCK_PATH_PREFIX;
|
|
@@ -269,7 +258,7 @@ function sweep(store) {
|
|
|
269
258
|
}
|
|
270
259
|
}
|
|
271
260
|
function guard(res) {
|
|
272
|
-
if (
|
|
261
|
+
if (!developmentSignInsAllowed()) {
|
|
273
262
|
res.status(404).json({ error: "Not found" });
|
|
274
263
|
return false;
|
|
275
264
|
}
|
|
@@ -356,7 +345,9 @@ function registerMockOAuthProvider(router, options) {
|
|
|
356
345
|
const logger = options.logger ?? consoleLogger;
|
|
357
346
|
if (!isMockOAuthEnabled()) {
|
|
358
347
|
if (process.env.ENABLE_MOCK_OAUTH === "true") {
|
|
359
|
-
logger.error(
|
|
348
|
+
logger.error(
|
|
349
|
+
`Mock OAuth is enabled but NODE_ENV is ${nodeEnvLabel()}: the mock runs only when NODE_ENV is development or test, so its routes are not mounted; set NODE_ENV=development to use it`
|
|
350
|
+
);
|
|
360
351
|
}
|
|
361
352
|
return false;
|
|
362
353
|
}
|
|
@@ -370,7 +361,9 @@ function registerMockOAuthProvider(router, options) {
|
|
|
370
361
|
router.get(`${endpoints.prefix}/authorize`, authorizeHandler(endpoints));
|
|
371
362
|
router.post(`${endpoints.prefix}/token`, tokenHandler(endpoints));
|
|
372
363
|
router.get(`${endpoints.prefix}/userinfo`, userinfoHandler(endpoints));
|
|
373
|
-
logger.
|
|
364
|
+
logger.warn(
|
|
365
|
+
`Mock OAuth provider mounted at ${endpoints.prefix}: anyone who reaches it signs in as any user it lists (NODE_ENV=${process.env.NODE_ENV ?? ""}; development and tests only)`
|
|
366
|
+
);
|
|
374
367
|
return true;
|
|
375
368
|
}
|
|
376
369
|
|
|
@@ -401,6 +394,14 @@ function sendJson(res, status, body) {
|
|
|
401
394
|
function refuse(res, code, message) {
|
|
402
395
|
sendJson(res, httpStatus(code), { error: code, message });
|
|
403
396
|
}
|
|
397
|
+
function failRoute(logger, req, res, error) {
|
|
398
|
+
logger.error("An auth route failed", {
|
|
399
|
+
category: "quickdraw.auth",
|
|
400
|
+
path: pathOf(req),
|
|
401
|
+
error: errorText(error)
|
|
402
|
+
});
|
|
403
|
+
refuse(res, "INTERNAL", INTERNAL_MESSAGE);
|
|
404
|
+
}
|
|
404
405
|
function redirect(res, location) {
|
|
405
406
|
noStore(res, 302);
|
|
406
407
|
res.setHeader("location", location);
|
|
@@ -411,67 +412,8 @@ function noContent(res) {
|
|
|
411
412
|
res.end();
|
|
412
413
|
}
|
|
413
414
|
|
|
414
|
-
// src/server/auth/routes/origins.ts
|
|
415
|
-
function exactOrigin(entry, owner) {
|
|
416
|
-
let url;
|
|
417
|
-
try {
|
|
418
|
-
url = typeof entry === "string" ? new URL(entry) : void 0;
|
|
419
|
-
} catch {
|
|
420
|
-
url = void 0;
|
|
421
|
-
}
|
|
422
|
-
const bare = url !== void 0 && (url.protocol === "https:" || url.protocol === "http:") && url.username === "" && url.password === "" && url.pathname === "/" && url.search === "" && url.hash === "";
|
|
423
|
-
if (url === void 0 || !bare) {
|
|
424
|
-
throw new TypeError(
|
|
425
|
-
`${owner}: allowedOrigins entries are origins such as "https://app.example.com" or anchored patterns; got ${String(entry)}`
|
|
426
|
-
);
|
|
427
|
-
}
|
|
428
|
-
return url.origin;
|
|
429
|
-
}
|
|
430
|
-
function anchoredPattern(pattern, owner) {
|
|
431
|
-
const { source } = pattern;
|
|
432
|
-
const anchored = source.startsWith("^") && source.endsWith("$") && !source.endsWith("\\$");
|
|
433
|
-
if (!anchored || pattern.global || pattern.sticky) {
|
|
434
|
-
throw new TypeError(
|
|
435
|
-
`${owner}: an allowedOrigins pattern must match whole origins, anchored with ^ and $ and without the g or y flag; got ${String(pattern)}`
|
|
436
|
-
);
|
|
437
|
-
}
|
|
438
|
-
return new RegExp(`^(?:${source})$`, pattern.flags);
|
|
439
|
-
}
|
|
440
|
-
function originAllowlist(allowedOrigins, owner, allowEmpty = false) {
|
|
441
|
-
if (!Array.isArray(allowedOrigins) || allowedOrigins.length === 0 && !allowEmpty) {
|
|
442
|
-
throw new TypeError(`${owner}: allowedOrigins must list the web app's origins`);
|
|
443
|
-
}
|
|
444
|
-
const exact = [];
|
|
445
|
-
const patterns = [];
|
|
446
|
-
for (const entry of allowedOrigins) {
|
|
447
|
-
if (entry instanceof RegExp) {
|
|
448
|
-
patterns.push(anchoredPattern(entry, owner));
|
|
449
|
-
} else {
|
|
450
|
-
exact.push(exactOrigin(entry, owner));
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
const options = {
|
|
454
|
-
// An empty client URL, not undefined: undefined falls back to process.env.CLIENT_URL.
|
|
455
|
-
clientUrl: "",
|
|
456
|
-
extraAllowedOrigins: exact,
|
|
457
|
-
allowedPatterns: patterns,
|
|
458
|
-
allowLocalhostInDev: false
|
|
459
|
-
};
|
|
460
|
-
return Object.freeze({
|
|
461
|
-
allowed: (value) => typeof value === "string" && value !== "" ? validateRedirectOrigin(value, options) : null,
|
|
462
|
-
fallback: exact[0]
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
var ROUTE_ORIGINS = /* @__PURE__ */ new WeakMap();
|
|
466
|
-
function rememberRouteOrigins(sessions, origins) {
|
|
467
|
-
ROUTE_ORIGINS.set(sessions, origins);
|
|
468
|
-
}
|
|
469
|
-
function routeOriginsOf(sessions) {
|
|
470
|
-
return ROUTE_ORIGINS.get(sessions);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
415
|
// src/server/auth/routes/state.ts
|
|
474
|
-
import { randomBytes as randomBytes2, timingSafeEqual } from "crypto";
|
|
416
|
+
import { createHmac, randomBytes as randomBytes2, timingSafeEqual } from "crypto";
|
|
475
417
|
var OAUTH_STATE_COOKIE = "qd_oauth";
|
|
476
418
|
var HOST_OAUTH_STATE_COOKIE = "__Host-qd_oauth";
|
|
477
419
|
var OAUTH_STATE_TTL_MS = 10 * 60 * 1e3;
|
|
@@ -479,16 +421,30 @@ var MAX_REDEEMED = 1e4;
|
|
|
479
421
|
function newState() {
|
|
480
422
|
return randomBytes2(32).toString("base64url");
|
|
481
423
|
}
|
|
482
|
-
function
|
|
483
|
-
return
|
|
424
|
+
function oauthStateKey(jwtSecret) {
|
|
425
|
+
return createHmac("sha256", jwtSecret).update("quickdraw:oauth-state").digest();
|
|
426
|
+
}
|
|
427
|
+
function macOf(payload, key) {
|
|
428
|
+
return createHmac("sha256", key).update(payload).digest();
|
|
429
|
+
}
|
|
430
|
+
function encodePending(pending, key) {
|
|
431
|
+
const payload = Buffer.from(JSON.stringify(pending), "utf8").toString("base64url");
|
|
432
|
+
return `${payload}.${macOf(payload, key).toString("base64url")}`;
|
|
484
433
|
}
|
|
485
|
-
function decodePending(raw) {
|
|
486
|
-
|
|
434
|
+
function decodePending(raw, key) {
|
|
435
|
+
const dot = raw === void 0 ? -1 : raw.lastIndexOf(".");
|
|
436
|
+
if (raw === void 0 || dot <= 0) {
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
const payload = raw.slice(0, dot);
|
|
440
|
+
const mac = Buffer.from(raw.slice(dot + 1), "base64url");
|
|
441
|
+
const expected = macOf(payload, key);
|
|
442
|
+
if (mac.length !== expected.length || !timingSafeEqual(mac, expected)) {
|
|
487
443
|
return null;
|
|
488
444
|
}
|
|
489
445
|
let value;
|
|
490
446
|
try {
|
|
491
|
-
value = JSON.parse(Buffer.from(
|
|
447
|
+
value = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
492
448
|
} catch {
|
|
493
449
|
return null;
|
|
494
450
|
}
|
|
@@ -564,7 +520,7 @@ async function issueSession(keys, userId, meta, ttlMs = DEFAULT_SESSION_TTL_MS)
|
|
|
564
520
|
}
|
|
565
521
|
async function liveSession(keys, token) {
|
|
566
522
|
checkSessionKeys(keys, "liveSession");
|
|
567
|
-
const payload = await
|
|
523
|
+
const payload = await readVerifiedJWT(token, keys.jwtSecret);
|
|
568
524
|
if (payload === null || typeof payload.userId !== "string" || payload.userId === "" || payload.sid === void 0) {
|
|
569
525
|
return null;
|
|
570
526
|
}
|
|
@@ -641,9 +597,9 @@ function warnOnCookieDomain(cookie, logger) {
|
|
|
641
597
|
if (cookie.name !== void 0 || cookie.domain !== void 0 === transportsSeeDomain) {
|
|
642
598
|
return;
|
|
643
599
|
}
|
|
644
|
-
const why = cookie.domain === void 0 ? 'cookie.domain is "" while COOKIE_DOMAIN is set, so over HTTPS the routes set __Host-session
|
|
600
|
+
const why = cookie.domain === void 0 ? 'cookie.domain is "" while COOKIE_DOMAIN is set, so over HTTPS the routes set __Host-session, but socketAuth and the HTTP transport also take a "session" cookie there, which any site under COOKIE_DOMAIN can set for a browser that holds no __Host-session. Unset COOKIE_DOMAIN' : `cookie.domain is set and COOKIE_DOMAIN is not, so the routes set "session" on every request, which socketAuth and the HTTP transport read over HTTPS only under that name. Set COOKIE_DOMAIN to the cookie's domain instead`;
|
|
645
601
|
logger.warn(
|
|
646
|
-
`${OWNER}: ${why}
|
|
602
|
+
`${OWNER}: ${why}, or name the cookie in all three places: cookie.name, socketAuth's cookieName and createServer's http.cookieName.`,
|
|
647
603
|
{ category: "quickdraw.auth" }
|
|
648
604
|
);
|
|
649
605
|
}
|
|
@@ -704,8 +660,9 @@ function routeSettings(options) {
|
|
|
704
660
|
const cookie = cookieOf(options.cookie);
|
|
705
661
|
const logger = options.logger ?? consoleLogger;
|
|
706
662
|
const publicUrl = publicUrlOf(options.publicUrl);
|
|
663
|
+
const keys = checkSessionKeys(options, OWNER);
|
|
707
664
|
const settings = Object.freeze({
|
|
708
|
-
keys
|
|
665
|
+
keys,
|
|
709
666
|
origins: originAllowlist(options.allowedOrigins, OWNER),
|
|
710
667
|
publicUrl,
|
|
711
668
|
basePath: basePathOf(options.basePath),
|
|
@@ -716,6 +673,7 @@ function routeSettings(options) {
|
|
|
716
673
|
onRevoke: options.onRevoke,
|
|
717
674
|
logger,
|
|
718
675
|
redeemed: redeemedStates(),
|
|
676
|
+
stateKey: oauthStateKey(keys.jwtSecret),
|
|
719
677
|
watchHost: hostWatcher(publicUrl, logger)
|
|
720
678
|
});
|
|
721
679
|
warnOnCookieDomain(cookie, logger);
|
|
@@ -772,16 +730,25 @@ function setSession(res, settings, req, token, pageOrigin) {
|
|
|
772
730
|
const from = fromPage(req, pageOrigin);
|
|
773
731
|
const name = sessionCookieName(settings, from);
|
|
774
732
|
res.cookie(name, token, sessionCookieSettings(settings, from, name, true));
|
|
733
|
+
if (settings.cookie.name === void 0) {
|
|
734
|
+
clearStaleHostSession(res, req, name, settings.cookie.sameSite);
|
|
735
|
+
}
|
|
775
736
|
}
|
|
776
737
|
function clearSession(res, settings, req) {
|
|
777
738
|
const name = sessionCookieName(settings, req);
|
|
778
739
|
res.clearCookie(name, sessionCookieSettings(settings, req, name, false));
|
|
740
|
+
if (settings.cookie.name === void 0) {
|
|
741
|
+
clearStaleHostSession(res, req, name, settings.cookie.sameSite);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
function stateSecure(settings, req) {
|
|
745
|
+
return secureFor(settings, req) || settings.publicUrl.startsWith("https:");
|
|
779
746
|
}
|
|
780
747
|
function stateCookieName(settings, req) {
|
|
781
|
-
return
|
|
748
|
+
return stateSecure(settings, req) ? HOST_OAUTH_STATE_COOKIE : OAUTH_STATE_COOKIE;
|
|
782
749
|
}
|
|
783
750
|
function stateCookieOf(settings, req, live = false) {
|
|
784
|
-
const secure =
|
|
751
|
+
const secure = stateSecure(settings, req);
|
|
785
752
|
return {
|
|
786
753
|
httpOnly: true,
|
|
787
754
|
secure,
|
|
@@ -800,6 +767,151 @@ function issueFor(settings, userId, provider, req) {
|
|
|
800
767
|
return issueSession(settings.keys, userId, meta, settings.cookie.ttlMs);
|
|
801
768
|
}
|
|
802
769
|
|
|
770
|
+
// src/server/auth/routes/socketAuth.ts
|
|
771
|
+
function handshakeOf(request) {
|
|
772
|
+
const { handshake } = request.socket;
|
|
773
|
+
return { headers: request.headers, secure: handshake?.secure === true };
|
|
774
|
+
}
|
|
775
|
+
function credentialOf(request, naming) {
|
|
776
|
+
if (request.transport === "http") {
|
|
777
|
+
const { sessionCookie } = request;
|
|
778
|
+
if (typeof sessionCookie === "string" && sessionCookie !== "") {
|
|
779
|
+
return { token: sessionCookie, checkOrigin: true };
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
const { token } = request.auth;
|
|
783
|
+
if (typeof token === "string" && token !== "") {
|
|
784
|
+
return { token, checkOrigin: request.transport === "http" && request.credential === "cookie" };
|
|
785
|
+
}
|
|
786
|
+
if (request.transport === "http") {
|
|
787
|
+
return null;
|
|
788
|
+
}
|
|
789
|
+
const names = sessionCookieNamesFor(handshakeOf(request), naming);
|
|
790
|
+
const cookie = cookieToken(cookiesOf({ headers: request.headers }), names);
|
|
791
|
+
return cookie === null ? null : { token: cookie, checkOrigin: true };
|
|
792
|
+
}
|
|
793
|
+
function originAccepted(headers, origins, allowMissing) {
|
|
794
|
+
const { origin } = headers;
|
|
795
|
+
if (origin !== void 0) {
|
|
796
|
+
return origins.allowed(origin) !== null;
|
|
797
|
+
}
|
|
798
|
+
return allowMissing || headers["sec-fetch-site"] === "same-origin";
|
|
799
|
+
}
|
|
800
|
+
function httpOriginAccepted(headers, origins) {
|
|
801
|
+
const { origin } = headers;
|
|
802
|
+
if (origin !== void 0) {
|
|
803
|
+
return origins.allowed(origin) !== null;
|
|
804
|
+
}
|
|
805
|
+
const site = headers["sec-fetch-site"];
|
|
806
|
+
return site === void 0 || site === "same-origin";
|
|
807
|
+
}
|
|
808
|
+
var compiled = /* @__PURE__ */ new WeakMap();
|
|
809
|
+
function cookieOriginAllowed(request, allowedOrigins, options = {}) {
|
|
810
|
+
let origins = compiled.get(allowedOrigins);
|
|
811
|
+
if (origins === void 0) {
|
|
812
|
+
origins = originAllowlist(allowedOrigins, "cookieOriginAllowed", true);
|
|
813
|
+
compiled.set(allowedOrigins, origins);
|
|
814
|
+
}
|
|
815
|
+
return request.transport === "socket" ? originAccepted(request.headers, origins, options.allowMissingOrigin === true) : httpOriginAccepted(request.headers, origins);
|
|
816
|
+
}
|
|
817
|
+
function httpCookieOriginAllowed(headers, origins) {
|
|
818
|
+
return httpOriginAccepted(headers, origins);
|
|
819
|
+
}
|
|
820
|
+
var COOKIE_REFUSED = "This page's origin may not use the session cookie; send it from an allowed origin or use a bearer token";
|
|
821
|
+
function checkCookieOrigin(request, origins, allowMissing) {
|
|
822
|
+
if (request.transport === "http") {
|
|
823
|
+
if (!httpOriginAccepted(request.headers, origins)) {
|
|
824
|
+
throw new QuickdrawError("FORBIDDEN", COOKIE_REFUSED);
|
|
825
|
+
}
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
if (!originAccepted(request.headers, origins, allowMissing)) {
|
|
829
|
+
throw refused("This page's origin may not use the session cookie on a socket");
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
function refused(message) {
|
|
833
|
+
return new QuickdrawError("UNAUTHENTICATED", message);
|
|
834
|
+
}
|
|
835
|
+
function checkDevCredentials(devCredentials) {
|
|
836
|
+
if (devCredentials === void 0) {
|
|
837
|
+
return void 0;
|
|
838
|
+
}
|
|
839
|
+
if (typeof devCredentials !== "function") {
|
|
840
|
+
throw new TypeError("socketAuth: devCredentials must be a function of the user id");
|
|
841
|
+
}
|
|
842
|
+
if (!developmentSignInsAllowed()) {
|
|
843
|
+
throw new TypeError(
|
|
844
|
+
`socketAuth: devCredentials signs sockets in by a user id alone, so it runs only when NODE_ENV is development or test (NODE_ENV is ${nodeEnvLabel()}); set NODE_ENV=development to use it`
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
return devCredentials;
|
|
848
|
+
}
|
|
849
|
+
function devUserOf(request) {
|
|
850
|
+
const { token, userId } = request.auth;
|
|
851
|
+
const tokenless = token === void 0 || token === null || token === "";
|
|
852
|
+
return request.transport === "socket" && tokenless && typeof userId === "string" && userId !== "" ? userId : void 0;
|
|
853
|
+
}
|
|
854
|
+
async function devPrincipal(devCredentials, userId) {
|
|
855
|
+
if (!developmentSignInsAllowed()) {
|
|
856
|
+
throw refused("Development credentials are refused unless NODE_ENV is development or test");
|
|
857
|
+
}
|
|
858
|
+
const principal = await devCredentials(userId);
|
|
859
|
+
if (principal === null || principal === void 0) {
|
|
860
|
+
throw refused("No development user has that id");
|
|
861
|
+
}
|
|
862
|
+
if (principal.userId !== userId) {
|
|
863
|
+
throw new TypeError("socketAuth: devCredentials must return the principal of the user named");
|
|
864
|
+
}
|
|
865
|
+
return principal;
|
|
866
|
+
}
|
|
867
|
+
async function principalOfSession(session, loadPrincipal, owner) {
|
|
868
|
+
if (loadPrincipal === void 0) {
|
|
869
|
+
return { userId: session.userId, kind: "user" };
|
|
870
|
+
}
|
|
871
|
+
const principal = await loadPrincipal(session.userId, session);
|
|
872
|
+
if (principal === null || principal === void 0) {
|
|
873
|
+
throw refused("The session's user has no principal");
|
|
874
|
+
}
|
|
875
|
+
if (principal.userId !== session.userId) {
|
|
876
|
+
throw new TypeError(`${owner}: loadPrincipal must return the principal of the session's user`);
|
|
877
|
+
}
|
|
878
|
+
return principal;
|
|
879
|
+
}
|
|
880
|
+
function socketAuth(options) {
|
|
881
|
+
const keys = checkSessionKeys(options, "socketAuth");
|
|
882
|
+
const origins = originAllowlist(options.allowedOrigins, "socketAuth", true);
|
|
883
|
+
const naming = transportCookieNaming(options.cookieName);
|
|
884
|
+
const allowMissing = options.allowMissingOrigin === true;
|
|
885
|
+
const { loadPrincipal } = options;
|
|
886
|
+
if (loadPrincipal !== void 0 && typeof loadPrincipal !== "function") {
|
|
887
|
+
throw new TypeError("socketAuth: loadPrincipal must be a function");
|
|
888
|
+
}
|
|
889
|
+
const devCredentials = checkDevCredentials(options.devCredentials);
|
|
890
|
+
const authenticate = async (request) => {
|
|
891
|
+
const devUser = devCredentials === void 0 ? void 0 : devUserOf(request);
|
|
892
|
+
if (devCredentials !== void 0 && devUser !== void 0) {
|
|
893
|
+
return await devPrincipal(devCredentials, devUser);
|
|
894
|
+
}
|
|
895
|
+
const credential = credentialOf(request, naming);
|
|
896
|
+
if (credential === null) {
|
|
897
|
+
return null;
|
|
898
|
+
}
|
|
899
|
+
if (credential.checkOrigin) {
|
|
900
|
+
checkCookieOrigin(request, origins, allowMissing);
|
|
901
|
+
}
|
|
902
|
+
const session = await liveSession(keys, credential.token);
|
|
903
|
+
if (session === null) {
|
|
904
|
+
throw refused("The session is not valid");
|
|
905
|
+
}
|
|
906
|
+
if (request.transport === "socket") {
|
|
907
|
+
recordSocketSession(request.socket, session.id, session.expiresAt);
|
|
908
|
+
}
|
|
909
|
+
return await principalOfSession(session, loadPrincipal, "socketAuth");
|
|
910
|
+
};
|
|
911
|
+
markSocketAuth(authenticate, { devCredentials: devCredentials !== void 0 });
|
|
912
|
+
return authenticate;
|
|
913
|
+
}
|
|
914
|
+
|
|
803
915
|
// src/server/auth/routes/guest.ts
|
|
804
916
|
var GUEST_MAX_BODY_BYTES = 16384;
|
|
805
917
|
function guest(options) {
|
|
@@ -832,7 +944,7 @@ async function createGuest(settings, provider, req, res) {
|
|
|
832
944
|
}
|
|
833
945
|
settings.logger.error("Creating a guest user failed", {
|
|
834
946
|
category: "quickdraw.auth",
|
|
835
|
-
error:
|
|
947
|
+
error: errorText(error)
|
|
836
948
|
});
|
|
837
949
|
refuse(res, "INTERNAL", INTERNAL_MESSAGE);
|
|
838
950
|
return null;
|
|
@@ -840,6 +952,10 @@ async function createGuest(settings, provider, req, res) {
|
|
|
840
952
|
}
|
|
841
953
|
function guestRoute(settings, provider) {
|
|
842
954
|
return async (req, res) => {
|
|
955
|
+
if (!httpCookieOriginAllowed(req.headers, settings.origins)) {
|
|
956
|
+
refuse(res, "FORBIDDEN", COOKIE_REFUSED);
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
843
959
|
const user = await createGuest(settings, provider, req, res);
|
|
844
960
|
if (user === null) {
|
|
845
961
|
return;
|
|
@@ -880,7 +996,7 @@ async function loadLimiters(option) {
|
|
|
880
996
|
if (option.signIn !== void 0 && option.session !== void 0 && option.providers !== void 0) {
|
|
881
997
|
return option;
|
|
882
998
|
}
|
|
883
|
-
const presets = await import("../../rateLimit-
|
|
999
|
+
const presets = await import("../../rateLimit-5RKBFLUR.js");
|
|
884
1000
|
return {
|
|
885
1001
|
signIn: option.signIn ?? presets.createAuthLimiter({ max: SIGN_IN_LIMIT }),
|
|
886
1002
|
session: option.session ?? presets.createAuthStatusLimiter(),
|
|
@@ -944,9 +1060,6 @@ function mockServes() {
|
|
|
944
1060
|
|
|
945
1061
|
// src/server/auth/routes/oauth.ts
|
|
946
1062
|
var CATEGORY = "quickdraw.auth";
|
|
947
|
-
function messageOf(error) {
|
|
948
|
-
return error instanceof Error ? error.message : String(error);
|
|
949
|
-
}
|
|
950
1063
|
function startRoute(settings, flow) {
|
|
951
1064
|
return (req, res) => {
|
|
952
1065
|
if (flow.enabled?.() === false) {
|
|
@@ -961,7 +1074,10 @@ function startRoute(settings, flow) {
|
|
|
961
1074
|
}
|
|
962
1075
|
const state = newState();
|
|
963
1076
|
const location = flow.authorizeUrl(state, redirectUriOf(settings, flow.id));
|
|
964
|
-
const pending = encodePending(
|
|
1077
|
+
const pending = encodePending(
|
|
1078
|
+
{ state, provider: flow.id, origin, issuedAt: Date.now() },
|
|
1079
|
+
settings.stateKey
|
|
1080
|
+
);
|
|
965
1081
|
res.cookie(stateCookieName(settings, req), pending, stateCookieOf(settings, req, true));
|
|
966
1082
|
redirect(res, location);
|
|
967
1083
|
};
|
|
@@ -974,7 +1090,7 @@ async function userFor(settings, flow, code) {
|
|
|
974
1090
|
settings.logger.warn("A sign-in's code exchange failed", {
|
|
975
1091
|
category: CATEGORY,
|
|
976
1092
|
provider: flow.id,
|
|
977
|
-
error:
|
|
1093
|
+
error: errorText(error)
|
|
978
1094
|
});
|
|
979
1095
|
return { error: "failed" };
|
|
980
1096
|
}
|
|
@@ -989,7 +1105,7 @@ async function userFor(settings, flow, code) {
|
|
|
989
1105
|
settings.logger.error("onLogin failed", {
|
|
990
1106
|
category: CATEGORY,
|
|
991
1107
|
provider: flow.id,
|
|
992
|
-
error:
|
|
1108
|
+
error: errorText(error)
|
|
993
1109
|
});
|
|
994
1110
|
return { error: "failed" };
|
|
995
1111
|
}
|
|
@@ -1012,7 +1128,7 @@ async function complete(settings, flow, req, res, origin) {
|
|
|
1012
1128
|
} catch (error) {
|
|
1013
1129
|
settings.logger.error("Creating a session failed", {
|
|
1014
1130
|
category: CATEGORY,
|
|
1015
|
-
error:
|
|
1131
|
+
error: errorText(error)
|
|
1016
1132
|
});
|
|
1017
1133
|
redirect(res, landingOf(settings, origin, "failed"));
|
|
1018
1134
|
return;
|
|
@@ -1028,7 +1144,7 @@ function callbackRoute(settings, flow) {
|
|
|
1028
1144
|
return;
|
|
1029
1145
|
}
|
|
1030
1146
|
const name = stateCookieName(settings, req);
|
|
1031
|
-
const pending = decodePending(cookiesOf(req)[name]);
|
|
1147
|
+
const pending = decodePending(cookiesOf(req)[name], settings.stateKey);
|
|
1032
1148
|
res.clearCookie(name, stateCookieOf(settings, req));
|
|
1033
1149
|
if (pending === null) {
|
|
1034
1150
|
const { fallback } = settings.origins;
|
|
@@ -1060,13 +1176,24 @@ function callbackRoute(settings, flow) {
|
|
|
1060
1176
|
|
|
1061
1177
|
// src/server/auth/routes/session.ts
|
|
1062
1178
|
var NOT_SIGNED_IN = "Not signed in";
|
|
1063
|
-
function
|
|
1064
|
-
const
|
|
1179
|
+
function tokenFor(settings, req, res) {
|
|
1180
|
+
const credential = httpCredentialOf(req, sessionCookieNaming(settings));
|
|
1181
|
+
if (credential?.from === "cookie" && !httpCookieOriginAllowed(req.headers, settings.origins)) {
|
|
1182
|
+
refuse(res, "FORBIDDEN", COOKIE_REFUSED);
|
|
1183
|
+
return void 0;
|
|
1184
|
+
}
|
|
1185
|
+
return credential?.token ?? null;
|
|
1186
|
+
}
|
|
1187
|
+
function sessionOf(settings, token) {
|
|
1065
1188
|
return token === null ? Promise.resolve(null) : liveSession(settings.keys, token);
|
|
1066
1189
|
}
|
|
1067
1190
|
function meRoute(settings) {
|
|
1068
1191
|
return async (req, res) => {
|
|
1069
|
-
const
|
|
1192
|
+
const token = tokenFor(settings, req, res);
|
|
1193
|
+
if (token === void 0) {
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
const session = await sessionOf(settings, token);
|
|
1070
1197
|
if (session === null) {
|
|
1071
1198
|
refuse(res, "UNAUTHENTICATED", NOT_SIGNED_IN);
|
|
1072
1199
|
return;
|
|
@@ -1081,14 +1208,17 @@ async function revoked(settings, userId, sessionId) {
|
|
|
1081
1208
|
settings.logger.error("onRevoke failed", {
|
|
1082
1209
|
category: "quickdraw.auth",
|
|
1083
1210
|
userId,
|
|
1084
|
-
error:
|
|
1211
|
+
error: errorText(error)
|
|
1085
1212
|
});
|
|
1086
1213
|
}
|
|
1087
1214
|
}
|
|
1088
1215
|
function logoutRoute(settings) {
|
|
1089
1216
|
return async (req, res) => {
|
|
1090
|
-
const token =
|
|
1091
|
-
|
|
1217
|
+
const token = tokenFor(settings, req, res);
|
|
1218
|
+
if (token === void 0) {
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
const payload = token === null ? null : await readVerifiedJWT(token, settings.keys.jwtSecret);
|
|
1092
1222
|
if (payload?.sid !== void 0) {
|
|
1093
1223
|
await settings.keys.sessions.revoke(payload.sid);
|
|
1094
1224
|
settings.logger.info("Signed out", { category: "quickdraw.auth", userId: payload.userId });
|
|
@@ -1100,7 +1230,11 @@ function logoutRoute(settings) {
|
|
|
1100
1230
|
}
|
|
1101
1231
|
function logoutAllRoute(settings) {
|
|
1102
1232
|
return async (req, res) => {
|
|
1103
|
-
const
|
|
1233
|
+
const token = tokenFor(settings, req, res);
|
|
1234
|
+
if (token === void 0) {
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
const session = await sessionOf(settings, token);
|
|
1104
1238
|
clearSession(res, settings, req);
|
|
1105
1239
|
if (session === null) {
|
|
1106
1240
|
refuse(res, "UNAUTHENTICATED", NOT_SIGNED_IN);
|
|
@@ -1187,7 +1321,7 @@ function routeTable(settings, listed) {
|
|
|
1187
1321
|
const mounted = providers.filter((provider) => addProvider(table, settings, provider));
|
|
1188
1322
|
if (mounted.length === 0) {
|
|
1189
1323
|
settings.logger.warn(
|
|
1190
|
-
"createAuthRoutes: no provider can sign anyone in (the mock is mounted only while ENABLE_MOCK_OAUTH is set
|
|
1324
|
+
"createAuthRoutes: no provider can sign anyone in (the mock is mounted only while ENABLE_MOCK_OAUTH is set and NODE_ENV is development or test): every sign-in route answers 404",
|
|
1191
1325
|
{ category: "quickdraw.auth" }
|
|
1192
1326
|
);
|
|
1193
1327
|
}
|
|
@@ -1216,12 +1350,7 @@ async function serve(route, req, res) {
|
|
|
1216
1350
|
}
|
|
1217
1351
|
function dispatch(settings, limiters, route, req, res) {
|
|
1218
1352
|
const fail = (error) => {
|
|
1219
|
-
settings.logger
|
|
1220
|
-
category: "quickdraw.auth",
|
|
1221
|
-
path: pathOf(req),
|
|
1222
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1223
|
-
});
|
|
1224
|
-
refuse(res, "INTERNAL", INTERNAL_MESSAGE);
|
|
1353
|
+
failRoute(settings.logger, req, res, error);
|
|
1225
1354
|
};
|
|
1226
1355
|
if (limiters === null) {
|
|
1227
1356
|
refuse(res, "INTERNAL", "The auth routes' rate limiters are unavailable");
|
|
@@ -1270,151 +1399,28 @@ function createAuthRoutes(options) {
|
|
|
1270
1399
|
return Object.assign(routes, { providers: () => servedProviders(mounted) });
|
|
1271
1400
|
}
|
|
1272
1401
|
|
|
1273
|
-
// src/server/auth/routes/
|
|
1274
|
-
function
|
|
1275
|
-
const
|
|
1276
|
-
return { headers: request.headers, secure: handshake?.secure === true };
|
|
1277
|
-
}
|
|
1278
|
-
function credentialOf(request, naming) {
|
|
1279
|
-
const { token } = request.auth;
|
|
1280
|
-
if (typeof token === "string" && token !== "") {
|
|
1281
|
-
return { token, checkOrigin: request.transport === "http" && request.credential === "cookie" };
|
|
1282
|
-
}
|
|
1283
|
-
if (request.transport === "http") {
|
|
1284
|
-
return null;
|
|
1285
|
-
}
|
|
1286
|
-
const names = sessionCookieNamesFor(handshakeOf(request), naming);
|
|
1287
|
-
const cookie = cookieToken(cookiesOf({ headers: request.headers }), names);
|
|
1288
|
-
return cookie === null ? null : { token: cookie, checkOrigin: true };
|
|
1289
|
-
}
|
|
1290
|
-
function originAccepted(headers, origins, allowMissing) {
|
|
1291
|
-
const { origin } = headers;
|
|
1292
|
-
if (origin !== void 0) {
|
|
1293
|
-
return origins.allowed(origin) !== null;
|
|
1294
|
-
}
|
|
1295
|
-
return allowMissing || headers["sec-fetch-site"] === "same-origin";
|
|
1296
|
-
}
|
|
1297
|
-
function httpOriginAccepted(headers, origins) {
|
|
1298
|
-
const { origin } = headers;
|
|
1299
|
-
if (origin !== void 0) {
|
|
1300
|
-
return origins.allowed(origin) !== null;
|
|
1301
|
-
}
|
|
1302
|
-
const site = headers["sec-fetch-site"];
|
|
1303
|
-
return site === void 0 || site === "same-origin";
|
|
1304
|
-
}
|
|
1305
|
-
var compiled = /* @__PURE__ */ new WeakMap();
|
|
1306
|
-
function cookieOriginAllowed(request, allowedOrigins, options = {}) {
|
|
1307
|
-
let origins = compiled.get(allowedOrigins);
|
|
1308
|
-
if (origins === void 0) {
|
|
1309
|
-
origins = originAllowlist(allowedOrigins, "cookieOriginAllowed", true);
|
|
1310
|
-
compiled.set(allowedOrigins, origins);
|
|
1311
|
-
}
|
|
1312
|
-
return request.transport === "socket" ? originAccepted(request.headers, origins, options.allowMissingOrigin === true) : httpOriginAccepted(request.headers, origins);
|
|
1313
|
-
}
|
|
1314
|
-
function httpCookieOriginAllowed(headers, origins) {
|
|
1315
|
-
return httpOriginAccepted(headers, origins);
|
|
1316
|
-
}
|
|
1317
|
-
function checkCookieOrigin(request, origins, allowMissing) {
|
|
1318
|
-
if (request.transport === "http") {
|
|
1319
|
-
if (!httpOriginAccepted(request.headers, origins)) {
|
|
1320
|
-
throw new QuickdrawError(
|
|
1321
|
-
"FORBIDDEN",
|
|
1322
|
-
"This page's origin may not use the session cookie; send it from an allowed origin or use a bearer token"
|
|
1323
|
-
);
|
|
1324
|
-
}
|
|
1325
|
-
return;
|
|
1326
|
-
}
|
|
1327
|
-
if (!originAccepted(request.headers, origins, allowMissing)) {
|
|
1328
|
-
throw refused("This page's origin may not use the session cookie on a socket");
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
function refused(message) {
|
|
1332
|
-
return new QuickdrawError("UNAUTHENTICATED", message);
|
|
1333
|
-
}
|
|
1334
|
-
function production() {
|
|
1335
|
-
return process.env.NODE_ENV === "production";
|
|
1336
|
-
}
|
|
1337
|
-
function checkDevCredentials(devCredentials) {
|
|
1338
|
-
if (devCredentials === void 0) {
|
|
1339
|
-
return void 0;
|
|
1340
|
-
}
|
|
1341
|
-
if (typeof devCredentials !== "function") {
|
|
1342
|
-
throw new TypeError("socketAuth: devCredentials must be a function of the user id");
|
|
1343
|
-
}
|
|
1344
|
-
if (production()) {
|
|
1345
|
-
throw new TypeError(
|
|
1346
|
-
"socketAuth: devCredentials signs sockets in by a user id alone, so it cannot be used when NODE_ENV is production"
|
|
1347
|
-
);
|
|
1348
|
-
}
|
|
1349
|
-
return devCredentials;
|
|
1350
|
-
}
|
|
1351
|
-
function devUserOf(request) {
|
|
1352
|
-
const { token, userId } = request.auth;
|
|
1353
|
-
const tokenless = token === void 0 || token === null || token === "";
|
|
1354
|
-
return request.transport === "socket" && tokenless && typeof userId === "string" && userId !== "" ? userId : void 0;
|
|
1355
|
-
}
|
|
1356
|
-
async function devPrincipal(devCredentials, userId) {
|
|
1357
|
-
if (production()) {
|
|
1358
|
-
throw refused("Development credentials are refused in production");
|
|
1359
|
-
}
|
|
1360
|
-
const principal = await devCredentials(userId);
|
|
1361
|
-
if (principal === null || principal === void 0) {
|
|
1362
|
-
throw refused("No development user has that id");
|
|
1363
|
-
}
|
|
1364
|
-
if (principal.userId !== userId) {
|
|
1365
|
-
throw new TypeError("socketAuth: devCredentials must return the principal of the user named");
|
|
1366
|
-
}
|
|
1367
|
-
return principal;
|
|
1368
|
-
}
|
|
1369
|
-
function socketAuth(options) {
|
|
1370
|
-
const keys = checkSessionKeys(options, "socketAuth");
|
|
1371
|
-
const origins = originAllowlist(options.allowedOrigins, "socketAuth", true);
|
|
1372
|
-
const naming = transportCookieNaming(options.cookieName);
|
|
1373
|
-
const allowMissing = options.allowMissingOrigin === true;
|
|
1402
|
+
// src/server/auth/routes/bearerAuth.ts
|
|
1403
|
+
function bearerAuth(options) {
|
|
1404
|
+
const keys = checkSessionKeys(options, "bearerAuth");
|
|
1374
1405
|
const { loadPrincipal } = options;
|
|
1375
1406
|
if (loadPrincipal !== void 0 && typeof loadPrincipal !== "function") {
|
|
1376
|
-
throw new TypeError("
|
|
1407
|
+
throw new TypeError("bearerAuth: loadPrincipal must be a function");
|
|
1377
1408
|
}
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
const devUser = devCredentials === void 0 ? void 0 : devUserOf(request);
|
|
1381
|
-
if (devCredentials !== void 0 && devUser !== void 0) {
|
|
1382
|
-
return await devPrincipal(devCredentials, devUser);
|
|
1383
|
-
}
|
|
1384
|
-
const credential = credentialOf(request, naming);
|
|
1385
|
-
if (credential === null) {
|
|
1409
|
+
return async (token) => {
|
|
1410
|
+
if (typeof token !== "string" || token === "") {
|
|
1386
1411
|
return null;
|
|
1387
1412
|
}
|
|
1388
|
-
|
|
1389
|
-
checkCookieOrigin(request, origins, allowMissing);
|
|
1390
|
-
}
|
|
1391
|
-
const session = await liveSession(keys, credential.token);
|
|
1413
|
+
const session = await liveSession(keys, token);
|
|
1392
1414
|
if (session === null) {
|
|
1393
|
-
throw
|
|
1394
|
-
}
|
|
1395
|
-
if (request.transport === "socket") {
|
|
1396
|
-
recordSocketSession(request.socket, session.id);
|
|
1397
|
-
}
|
|
1398
|
-
if (loadPrincipal === void 0) {
|
|
1399
|
-
return { userId: session.userId, kind: "user" };
|
|
1415
|
+
throw new QuickdrawError("UNAUTHENTICATED", "The session is not valid");
|
|
1400
1416
|
}
|
|
1401
|
-
|
|
1402
|
-
if (principal === null || principal === void 0) {
|
|
1403
|
-
throw refused("The session's user has no principal");
|
|
1404
|
-
}
|
|
1405
|
-
if (principal.userId !== session.userId) {
|
|
1406
|
-
throw new TypeError(
|
|
1407
|
-
"socketAuth: loadPrincipal must return the principal of the session's user"
|
|
1408
|
-
);
|
|
1409
|
-
}
|
|
1410
|
-
return principal;
|
|
1417
|
+
return await principalOfSession(session, loadPrincipal, "bearerAuth");
|
|
1411
1418
|
};
|
|
1412
1419
|
}
|
|
1413
1420
|
|
|
1414
1421
|
// src/server/auth/routes/requireSession.ts
|
|
1415
1422
|
var SESSIONS = /* @__PURE__ */ new WeakMap();
|
|
1416
1423
|
var NO_ORIGINS = originAllowlist([], "requireSession", true);
|
|
1417
|
-
var COOKIE_REFUSED = "This page's origin may not use the session cookie; send it from an allowed origin or use a bearer token";
|
|
1418
1424
|
var NO_LIST = "requireSession has no origin list, so no page may use the session cookie: pass it the sessions object createAuthRoutes was given (the same store object: a second store over the same table is another), or pass allowedOrigins";
|
|
1419
1425
|
function requireSession(keys, options = {}) {
|
|
1420
1426
|
checkSessionKeys(keys, "requireSession");
|
|
@@ -1625,6 +1631,7 @@ export {
|
|
|
1625
1631
|
OAUTH_STATE_COOKIE,
|
|
1626
1632
|
OAUTH_STATE_TTL_MS,
|
|
1627
1633
|
SESSION_COOKIE,
|
|
1634
|
+
bearerAuth,
|
|
1628
1635
|
clearSessionCookie,
|
|
1629
1636
|
cookieOriginAllowed,
|
|
1630
1637
|
createAuthRoutes,
|