@server/next 0.46.0 → 0.47.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/index.d.ts +94 -53
  2. package/index.js +747 -816
  3. package/package.json +3 -3
package/index.js CHANGED
@@ -43,7 +43,6 @@ ServerError_default.extend({
43
43
  status: 400,
44
44
  message: "The route param '{param}' tries to climb the path ('{value}'). If this route legitimately receives paths, set security: { traversalProtection: false }"
45
45
  },
46
- AUTH_ARGON_NEEDED: "Argon2 is needed for the auth module, please install it with 'npm i argon2'",
47
46
  AUTH_INVALID_TOKEN: { status: 401, message: "Invalid Authorization token" },
48
47
  AUTH_NO_CODE: {
49
48
  status: 400,
@@ -53,32 +52,7 @@ ServerError_default.extend({
53
52
  status: 401,
54
53
  message: "Invalid authorization header {type}, must send 'Bearer {TOKEN}' (with space)"
55
54
  },
56
- AUTH_INVALID_STATE: { status: 403, message: "Invalid OAuth state" },
57
- AUTH_NO_PROVIDER: "No provider passed to the option 'auth.providers'",
58
- AUTH_INVALID_PROVIDER: {
59
- status: 401,
60
- message: "Invalid provider '{provider}', valid ones are: '{valid}'"
61
- },
62
- AUTH_NO_SESSION: { status: 401, message: "Invalid session" },
63
- AUTH_NO_USER: {
64
- status: 401,
65
- message: "Credentials do not correspond to a user"
66
- },
67
- AUTH_INVALID_USER: {
68
- status: 500,
69
- message: "{callback} must return a user with an 'id' and an 'email'"
70
- },
71
- LOGIN_NO_EMAIL: "The email is required to log in",
72
- LOGIN_INVALID_EMAIL: "The email you wrote is not correct",
73
- LOGIN_NO_PASSWORD: "The email is required to log in",
74
- LOGIN_INVALID_PASSWORD: "The password you wrote is not correct",
75
- LOGIN_WRONG_ACCOUNT: "That email does not correspond to any account",
76
- LOGIN_WRONG_PASSWORD: "That is not the valid password",
77
- REGISTER_NO_EMAIL: "Email needed",
78
- REGISTER_INVALID_EMAIL: "The email you wrote is not correct",
79
- REGISTER_NO_PASSWORD: "Password needed",
80
- REGISTER_INVALID_PASSWORD: "The password you wrote is not correct",
81
- REGISTER_EMAIL_EXISTS: "Email is already registered"
55
+ AUTH_INVALID_STATE: { status: 403, message: "Invalid OAuth state" }
82
56
  });
83
57
  var errors_default = ServerError_default;
84
58
 
@@ -744,23 +718,6 @@ function clientIp(headers2, opts = {}) {
744
718
  return normalize(remoteAddress);
745
719
  }
746
720
 
747
- // src/helpers/store.ts
748
- import kv from "polystore";
749
- function isStore(source) {
750
- const store = source;
751
- return Boolean(
752
- store && typeof store.prefix === "function" && typeof store.get === "function" && typeof store.set === "function"
753
- );
754
- }
755
- function toStore(source) {
756
- if (isStore(source)) return source;
757
- return kv(source);
758
- }
759
- function toStoreExpiring(source, expires) {
760
- if (isStore(source)) return source;
761
- return kv(source).expires(expires);
762
- }
763
-
764
721
  // src/helpers/disposition.ts
765
722
  var encodeExt = (name) => encodeURIComponent(name).replace(
766
723
  /['()*]/g,
@@ -984,13 +941,6 @@ var json = (...args) => r().json(...args);
984
941
  var file = (...args) => r().file(...args);
985
942
  var redirect = (...args) => r().redirect(...args);
986
943
 
987
- // src/auth/assertUser.ts
988
- function assertUser(user, callback3) {
989
- if (!user || typeof user !== "object" || user.id == null || !user.email) {
990
- throw ServerError_default.AUTH_INVALID_USER({ callback: callback3 });
991
- }
992
- }
993
-
994
944
  // src/helpers/jwt.ts
995
945
  var enc = new TextEncoder();
996
946
  var dec = new TextDecoder();
@@ -1017,13 +967,13 @@ var hmacKey = (secret) => crypto.subtle.importKey(
1017
967
  );
1018
968
  async function signJwt(payload, secret, expires) {
1019
969
  const now = Math.floor(Date.now() / 1e3);
1020
- const claims = {
970
+ const claims2 = {
1021
971
  iat: now,
1022
972
  ...expires ? { exp: now + expires } : {},
1023
973
  ...payload
1024
974
  };
1025
975
  const head = b64url(JSON.stringify({ alg: "HS256", typ: "JWT" }));
1026
- const body = b64url(JSON.stringify(claims));
976
+ const body = b64url(JSON.stringify(claims2));
1027
977
  const data = `${head}.${body}`;
1028
978
  const key = await hmacKey(secret);
1029
979
  const sig = await crypto.subtle.sign("HMAC", key, enc.encode(data));
@@ -1062,595 +1012,746 @@ async function verifyJwt(token, secret) {
1062
1012
  return payload;
1063
1013
  }
1064
1014
 
1065
- // src/auth/findSessionId.ts
1066
- var validateToken = (authorization) => {
1067
- const [type2, id] = authorization.trim().split(" ");
1068
- if (type2?.toLowerCase() !== "bearer") {
1015
+ // src/auth/credential.ts
1016
+ var NAME = "session";
1017
+ var inCookie = (s) => s === "session" || s === "cookie";
1018
+ var isSigned = (s) => s === "cookie" || s === "jwt";
1019
+ var UNITS2 = { s: 1, m: 60, h: 3600, d: 86400, w: 604800 };
1020
+ function seconds(expires) {
1021
+ const match = /^(\d+)([smhdw])$/.exec(expires);
1022
+ if (!match) throw new Error(`Invalid \`expires\`: "${expires}"`);
1023
+ return Number(match[1]) * UNITS2[match[2]];
1024
+ }
1025
+ var bearer = (ctx) => {
1026
+ const header = ctx.headers.authorization;
1027
+ if (!header) return;
1028
+ const [type2, token] = header.trim().split(" ");
1029
+ if (type2?.toLowerCase() !== "bearer" || !token) {
1069
1030
  throw ServerError_default.AUTH_INVALID_HEADER({ type: type2 });
1070
1031
  }
1071
- if (id?.length !== 16) {
1072
- throw ServerError_default.AUTH_INVALID_TOKEN();
1032
+ return token;
1033
+ };
1034
+ async function read(ctx, strategies) {
1035
+ for (const strategy of strategies) {
1036
+ const token = inCookie(strategy) ? ctx.cookies[NAME] : bearer(ctx);
1037
+ if (!token) continue;
1038
+ const payload = await verifyJwt(token, ctx.options.secrets);
1039
+ if (!payload) throw ServerError_default.AUTH_INVALID_TOKEN();
1040
+ return { payload, strategy };
1041
+ }
1042
+ }
1043
+ var meta = (payload, strategy) => ({
1044
+ issuedAt: new Date(payload.iat * 1e3),
1045
+ expiresAt: payload.exp ? new Date(payload.exp * 1e3) : void 0,
1046
+ strategy,
1047
+ provider: payload.provider
1048
+ });
1049
+ var issue = (ctx, payload, expires) => signJwt(payload, ctx.options.secrets[0], seconds(expires));
1050
+
1051
+ // src/auth/providers/index.ts
1052
+ import {
1053
+ AmazonCognito,
1054
+ AniList,
1055
+ Apple,
1056
+ Atlassian,
1057
+ Auth0,
1058
+ Authentik,
1059
+ Autodesk,
1060
+ BattleNet,
1061
+ Bitbucket,
1062
+ Box,
1063
+ Bungie,
1064
+ Coinbase,
1065
+ Discord,
1066
+ DonationAlerts,
1067
+ Dribbble,
1068
+ Dropbox,
1069
+ Etsy,
1070
+ EpicGames,
1071
+ Facebook,
1072
+ Figma,
1073
+ Gitea,
1074
+ GitHub,
1075
+ GitLab,
1076
+ Google,
1077
+ Intuit,
1078
+ Kakao,
1079
+ Kick,
1080
+ KeyCloak,
1081
+ Lichess,
1082
+ Line,
1083
+ Linear,
1084
+ LinkedIn,
1085
+ Mastodon,
1086
+ MercadoLibre,
1087
+ MercadoPago,
1088
+ MicrosoftEntraId,
1089
+ MyAnimeList,
1090
+ Naver,
1091
+ Notion,
1092
+ Okta,
1093
+ Osu,
1094
+ Patreon,
1095
+ Polar,
1096
+ Reddit,
1097
+ Roblox,
1098
+ Salesforce,
1099
+ Shikimori,
1100
+ Slack,
1101
+ Spotify,
1102
+ StartGG,
1103
+ Strava,
1104
+ TikTok,
1105
+ Tiltify,
1106
+ Tumblr,
1107
+ Twitch,
1108
+ Twitter,
1109
+ VK,
1110
+ Withings,
1111
+ WorkOS,
1112
+ Yahoo,
1113
+ Yandex,
1114
+ Zoom,
1115
+ FortyTwo
1116
+ } from "antarctic";
1117
+
1118
+ // src/auth/providers/oauth.ts
1119
+ var credentials = (name, options) => ({
1120
+ id: options.id ?? env[`${name.toUpperCase()}_ID`],
1121
+ secret: options.secret ?? env[`${name.toUpperCase()}_SECRET`]
1122
+ });
1123
+ var passthrough = (options) => {
1124
+ const { id, secret, scope, issuer, ...rest } = options;
1125
+ return rest;
1126
+ };
1127
+ var scopeOf = (options, fallback) => {
1128
+ const scope = options.scope ?? fallback;
1129
+ return Array.isArray(scope) ? scope.join(" ") : scope;
1130
+ };
1131
+ var search = (base, params) => {
1132
+ const query = new URLSearchParams();
1133
+ for (const [key, value] of Object.entries(params)) {
1134
+ if (value) query.set(key, String(value));
1135
+ }
1136
+ return `${base}?${query}`;
1137
+ };
1138
+
1139
+ // src/auth/providers/antarctic.ts
1140
+ var nowhere = {
1141
+ get: async () => null,
1142
+ set: async () => {
1143
+ },
1144
+ del: async () => {
1073
1145
  }
1074
- return id;
1075
1146
  };
1076
- function findSessionId(ctx) {
1077
- if (ctx.options.auth?.strategy.includes("token")) {
1078
- if (!ctx.headers.authorization) return;
1079
- return validateToken(ctx.headers.authorization);
1080
- }
1081
- return ctx.cookies.session || void 0;
1082
- }
1083
-
1084
- // src/auth/finishLogin.ts
1085
- async function finishLogin(ctx, input, opts = {}) {
1086
- const settings = ctx.options.auth;
1087
- const { strategy, onLogin, onUser, onToken } = settings;
1088
- const key = String(input.key);
1089
- const auth2 = {
1090
- user: key,
1091
- provider: input.provider,
1092
- created: (/* @__PURE__ */ new Date()).toISOString().replace(/\.[0-9]*/, "")
1147
+ function antarcticProvider(name, Client) {
1148
+ const client = (ctx, options) => {
1149
+ const { id, secret } = credentials(name, options);
1150
+ if (!id) throw new Error(`${name.toUpperCase()}_ID is not set`);
1151
+ return new Client({
1152
+ // Whatever that provider needs beyond the standard four: Auth0 takes a
1153
+ // `domain`, Keycloak a `realm`, Gitea a `baseURL`, Mastodon an
1154
+ // `instance`. Unknown keys go straight through.
1155
+ ...passthrough(options),
1156
+ clientId: id,
1157
+ clientSecret: secret,
1158
+ redirectURI: `${ctx.url.origin}/auth/callback/${name}`,
1159
+ scopes: options.scope ? Array.isArray(options.scope) ? options.scope : options.scope.split(" ") : void 0,
1160
+ store: nowhere
1161
+ });
1093
1162
  };
1094
- const loginUser = {
1095
- ...input.user,
1096
- provider: input.provider,
1097
- strategy
1163
+ return {
1164
+ async authorize(ctx, options) {
1165
+ const { url, state, payload } = await client(
1166
+ ctx,
1167
+ options
1168
+ ).getAuthorizationURL();
1169
+ return { url: String(url), state, payload };
1170
+ },
1171
+ async exchange(ctx, options, code, pending) {
1172
+ const user = await client(ctx, options).getUser(
1173
+ { code, state: pending.state },
1174
+ pending
1175
+ );
1176
+ return {
1177
+ provider: name,
1178
+ id: String(user.id),
1179
+ email: user.email ?? "",
1180
+ name: user.name ?? void 0,
1181
+ avatar: user.image ?? void 0,
1182
+ accessToken: user.accessToken,
1183
+ refreshToken: user.refreshToken ?? void 0,
1184
+ raw: user.raw ?? {}
1185
+ };
1186
+ }
1098
1187
  };
1099
- const existingUser = await settings.users.get(key) ?? null;
1100
- const user = onLogin ? await onLogin(loginUser, existingUser, ctx) : { ...existingUser ?? {}, ...loginUser };
1101
- assertUser(user, "onLogin");
1102
- await settings.users.set(key, user);
1103
- if (strategy.includes("jwt")) {
1104
- const payload = {
1105
- ...await onToken(user, ctx),
1106
- provider: input.provider
1107
- };
1108
- assertUser(payload, "onToken");
1109
- const token = await signJwt(
1110
- payload,
1111
- ctx.options.secrets[0],
1112
- 7 * 24 * 60 * 60
1113
- );
1114
- const exposed = await onUser(payload, ctx);
1115
- assertUser(exposed, "onUser");
1116
- return status(201).json({ ...exposed, token });
1117
- }
1118
- const prev = findSessionId(ctx);
1119
- if (prev) await settings.sessions.del(prev);
1120
- const id = createId();
1121
- await settings.sessions.set(id, auth2);
1122
- if (strategy.includes("token")) {
1123
- const exposed = await onUser(user, ctx);
1124
- assertUser(exposed, "onUser");
1125
- return status(201).json({ ...exposed, token: id });
1126
- }
1127
- if (strategy.includes("cookie")) {
1128
- const reply = cookies("session", {
1129
- value: id,
1130
- path: "/",
1131
- httpOnly: true,
1132
- secure: ctx.platform.production,
1133
- sameSite: "Lax"
1188
+ }
1189
+
1190
+ // src/auth/providers/index.ts
1191
+ var CLASSES = {
1192
+ amazoncognito: AmazonCognito,
1193
+ anilist: AniList,
1194
+ apple: Apple,
1195
+ atlassian: Atlassian,
1196
+ auth0: Auth0,
1197
+ authentik: Authentik,
1198
+ autodesk: Autodesk,
1199
+ battlenet: BattleNet,
1200
+ bitbucket: Bitbucket,
1201
+ box: Box,
1202
+ bungie: Bungie,
1203
+ coinbase: Coinbase,
1204
+ discord: Discord,
1205
+ donationalerts: DonationAlerts,
1206
+ dribbble: Dribbble,
1207
+ dropbox: Dropbox,
1208
+ etsy: Etsy,
1209
+ epicgames: EpicGames,
1210
+ facebook: Facebook,
1211
+ figma: Figma,
1212
+ gitea: Gitea,
1213
+ github: GitHub,
1214
+ gitlab: GitLab,
1215
+ google: Google,
1216
+ intuit: Intuit,
1217
+ kakao: Kakao,
1218
+ kick: Kick,
1219
+ keycloak: KeyCloak,
1220
+ lichess: Lichess,
1221
+ line: Line,
1222
+ linear: Linear,
1223
+ linkedin: LinkedIn,
1224
+ mastodon: Mastodon,
1225
+ mercadolibre: MercadoLibre,
1226
+ mercadopago: MercadoPago,
1227
+ microsoftentraid: MicrosoftEntraId,
1228
+ myanimelist: MyAnimeList,
1229
+ naver: Naver,
1230
+ notion: Notion,
1231
+ okta: Okta,
1232
+ osu: Osu,
1233
+ patreon: Patreon,
1234
+ polar: Polar,
1235
+ reddit: Reddit,
1236
+ roblox: Roblox,
1237
+ salesforce: Salesforce,
1238
+ shikimori: Shikimori,
1239
+ slack: Slack,
1240
+ spotify: Spotify,
1241
+ startgg: StartGG,
1242
+ strava: Strava,
1243
+ tiktok: TikTok,
1244
+ tiltify: Tiltify,
1245
+ tumblr: Tumblr,
1246
+ twitch: Twitch,
1247
+ twitter: Twitter,
1248
+ vk: VK,
1249
+ withings: Withings,
1250
+ workos: WorkOS,
1251
+ yahoo: Yahoo,
1252
+ yandex: Yandex,
1253
+ zoom: Zoom,
1254
+ fortytwo: FortyTwo
1255
+ };
1256
+ var ALIASES = {
1257
+ cognito: "amazoncognito",
1258
+ entra: "microsoftentraid",
1259
+ microsoft: "microsoftentraid"
1260
+ };
1261
+ var providers = Object.fromEntries(
1262
+ Object.entries(CLASSES).map(([name, Client]) => [
1263
+ name,
1264
+ antarcticProvider(name, Client)
1265
+ ])
1266
+ );
1267
+ for (const [alias, target2] of Object.entries(ALIASES)) {
1268
+ providers[alias] = antarcticProvider(alias, CLASSES[target2]);
1269
+ }
1270
+ var ISSUERS = {
1271
+ paypal: "https://www.paypal.com"
1272
+ };
1273
+ var providers_default = providers;
1274
+
1275
+ // src/auth/providers/oidc.ts
1276
+ var discovered = /* @__PURE__ */ new Map();
1277
+ function discover(issuer) {
1278
+ let doc = discovered.get(issuer);
1279
+ if (!doc) {
1280
+ const url = `${issuer.replace(/\/$/, "")}/.well-known/openid-configuration`;
1281
+ doc = fetch(url).then((r2) => {
1282
+ if (!r2.ok) throw new Error(`Cannot reach the OIDC issuer at ${url}`);
1283
+ return r2.json();
1134
1284
  });
1135
- if (opts.json) {
1136
- const exposed = await onUser(user, ctx);
1137
- assertUser(exposed, "onUser");
1138
- return reply.status(201).json(exposed);
1139
- }
1140
- return reply.redirect(settings.redirect);
1285
+ doc.catch(() => discovered.delete(issuer));
1286
+ discovered.set(issuer, doc);
1141
1287
  }
1142
- throw new Error("Unknown auth type");
1288
+ return doc;
1289
+ }
1290
+ var claims = (token) => {
1291
+ const body = token.split(".")[1];
1292
+ if (!body) throw new Error("The issuer returned no usable id_token");
1293
+ let b64 = body.replace(/-/g, "+").replace(/_/g, "/");
1294
+ b64 += "=".repeat((4 - b64.length % 4) % 4);
1295
+ return JSON.parse(atob(b64));
1296
+ };
1297
+ function oidcProvider(name) {
1298
+ return {
1299
+ async authorize(ctx, options) {
1300
+ const doc = await discover(options.issuer);
1301
+ const state = createId();
1302
+ const url = search(doc.authorization_endpoint, {
1303
+ client_id: credentials(name, options).id,
1304
+ response_type: "code",
1305
+ scope: scopeOf(options, "openid email profile"),
1306
+ redirect_uri: `${ctx.url.origin}/auth/callback/${name}`,
1307
+ state,
1308
+ ...passthrough(options)
1309
+ });
1310
+ return { url, state };
1311
+ },
1312
+ async exchange(ctx, options, code) {
1313
+ const doc = await discover(options.issuer);
1314
+ const { id, secret } = credentials(name, options);
1315
+ const body = new URLSearchParams({
1316
+ client_id: id,
1317
+ client_secret: secret,
1318
+ code,
1319
+ grant_type: "authorization_code"
1320
+ });
1321
+ body.set("redirect_uri", `${ctx.url.origin}/auth/callback/${name}`);
1322
+ const res = await fetch(doc.token_endpoint, {
1323
+ method: "POST",
1324
+ headers: {
1325
+ accept: "application/json",
1326
+ "content-type": "application/x-www-form-urlencoded"
1327
+ },
1328
+ body
1329
+ });
1330
+ if (!res.ok) throw new Error(`${name}: token exchange failed`);
1331
+ const token = await res.json();
1332
+ const raw = claims(token.id_token);
1333
+ return {
1334
+ provider: name,
1335
+ id: String(raw.sub),
1336
+ email: raw.email,
1337
+ name: raw.name,
1338
+ avatar: raw.picture,
1339
+ accessToken: token.access_token,
1340
+ refreshToken: token.refresh_token,
1341
+ raw
1342
+ };
1343
+ }
1344
+ };
1143
1345
  }
1144
1346
 
1145
1347
  // src/auth/state.ts
1146
- var NAME = "oauth_state";
1147
- function startState(ctx, crossSite = false) {
1148
- const state = createId();
1348
+ var NAME2 = "oauth_state";
1349
+ var EXPIRES = "10m";
1350
+ async function startState(ctx, pending) {
1351
+ const value = await signJwt(pending, ctx.options.secrets[0], 10 * 60);
1149
1352
  return {
1150
- state,
1151
- cookie: {
1152
- value: state,
1153
- path: "/",
1154
- expires: "10m",
1155
- httpOnly: true,
1156
- secure: crossSite || ctx.platform.production,
1157
- sameSite: crossSite ? "None" : "Lax"
1158
- }
1353
+ value,
1354
+ path: "/",
1355
+ expires: EXPIRES,
1356
+ httpOnly: true,
1357
+ secure: ctx.platform.production,
1358
+ sameSite: "Lax"
1159
1359
  };
1160
1360
  }
1161
- function checkState(ctx, received) {
1162
- const expected = ctx.cookies[NAME];
1163
- if (!expected || !received || expected !== received) {
1361
+ async function readState(ctx, received) {
1362
+ const cookie = ctx.cookies[NAME2];
1363
+ if (!cookie || !received) throw ServerError_default.AUTH_INVALID_STATE();
1364
+ const pending = await verifyJwt(cookie, ctx.options.secrets);
1365
+ if (!pending || pending.state !== received) {
1164
1366
  throw ServerError_default.AUTH_INVALID_STATE();
1165
1367
  }
1166
- }
1167
- function clearState() {
1168
- return createCookies(NAME, { value: null });
1368
+ return pending;
1169
1369
  }
1170
1370
 
1171
- // src/auth/providers/apple.ts
1172
- var AUTHORIZE = "https://appleid.apple.com/auth/authorize";
1173
- var TOKEN = "https://appleid.apple.com/auth/token";
1174
- var b64url2 = (data) => {
1175
- const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
1176
- let bin = "";
1177
- for (const byte of bytes) bin += String.fromCharCode(byte);
1178
- return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1179
- };
1180
- var b64urlJson = (segment) => {
1181
- let b64 = segment.replace(/-/g, "+").replace(/_/g, "/");
1182
- b64 += "=".repeat((4 - b64.length % 4) % 4);
1183
- const bytes = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
1184
- return JSON.parse(new TextDecoder().decode(bytes));
1185
- };
1186
- var clientSecret = async () => {
1187
- const now = Math.floor(Date.now() / 1e3);
1188
- const header = { alg: "ES256", kid: env.APPLE_KEY_ID, typ: "JWT" };
1189
- const payload = {
1190
- iss: env.APPLE_TEAM_ID,
1191
- iat: now,
1192
- exp: now + 3600,
1193
- aud: "https://appleid.apple.com",
1194
- sub: env.APPLE_ID
1195
- };
1196
- const data = `${b64url2(JSON.stringify(header))}.${b64url2(JSON.stringify(payload))}`;
1197
- const pem = String(env.APPLE_PRIVATE_KEY).replace(/-----[^-]+-----/g, "").replace(/\s+/g, "");
1198
- const der = Uint8Array.from(atob(pem), (c) => c.charCodeAt(0));
1199
- const key = await crypto.subtle.importKey(
1200
- "pkcs8",
1201
- der,
1202
- { name: "ECDSA", namedCurve: "P-256" },
1203
- false,
1204
- ["sign"]
1205
- );
1206
- const sig = await crypto.subtle.sign(
1207
- { name: "ECDSA", hash: "SHA-256" },
1208
- key,
1209
- new TextEncoder().encode(data)
1210
- );
1211
- return `${data}.${b64url2(new Uint8Array(sig))}`;
1212
- };
1213
- var login = (ctx) => {
1214
- const { state, cookie } = startState(ctx, true);
1215
- const params = new URLSearchParams({
1216
- client_id: env.APPLE_ID,
1217
- redirect_uri: `${ctx.url.origin}/auth/callback/apple`,
1218
- response_type: "code",
1219
- scope: "name email",
1220
- // Requesting scopes forces Apple to POST the result back (form_post)
1221
- response_mode: "form_post",
1222
- state
1223
- });
1224
- return cookies("oauth_state", cookie).redirect(`${AUTHORIZE}?${params}`);
1225
- };
1226
- var exchange = async (code, redirectUri, user) => {
1227
- const params = new URLSearchParams({
1228
- client_id: env.APPLE_ID,
1229
- client_secret: await clientSecret(),
1230
- code: code ?? "",
1231
- grant_type: "authorization_code"
1232
- });
1233
- if (redirectUri) params.set("redirect_uri", redirectUri);
1234
- const tokenRes = await fetch(TOKEN, {
1235
- method: "POST",
1236
- headers: {
1237
- accept: "application/json",
1238
- "content-type": "application/x-www-form-urlencoded"
1239
- },
1240
- body: params
1241
- });
1242
- if (!tokenRes.ok) throw new Error("apple: token exchange failed");
1243
- const token = await tokenRes.json();
1244
- const claims = b64urlJson(token.id_token.split(".")[1]);
1245
- let name;
1246
- if (user) {
1247
- const parsed = JSON.parse(user).name;
1248
- if (parsed) name = `${parsed.firstName} ${parsed.lastName}`.trim();
1249
- }
1250
- return { ...claims, name };
1251
- };
1252
- var finish = async (ctx, raw, opts) => {
1253
- const { onProfile } = ctx.options.auth;
1254
- const profile = onProfile ? await onProfile(raw, "apple") : { id: raw.sub, name: raw.name, email: raw.email };
1255
- assertUser(profile, "onProfile");
1256
- return finishLogin(
1257
- ctx,
1258
- {
1259
- provider: "apple",
1260
- key: profile.id,
1261
- email: profile.email,
1262
- user: profile
1263
- },
1264
- opts
1265
- );
1266
- };
1267
- var callback = async (ctx) => {
1268
- const body = ctx.body || {};
1269
- checkState(ctx, body.state);
1270
- const url = `${ctx.url.origin}/auth/callback/apple`;
1271
- const raw = await exchange(body.code, url, body.user);
1272
- const res = await finish(ctx, raw);
1273
- res.headers.append("set-cookie", clearState());
1274
- return res;
1275
- };
1276
- var verify = async (ctx) => {
1277
- const { code, redirect_uri, user } = ctx.body ?? {};
1278
- if (!code) throw ServerError_default.AUTH_NO_CODE();
1279
- const raw = await exchange(code, redirect_uri, user);
1280
- return finish(ctx, raw, { json: true });
1281
- };
1282
- var apple_default = { login, callback, verify };
1283
-
1284
- // src/auth/providers/oauth.ts
1371
+ // src/auth/flow.ts
1372
+ var SPEC = { schema: { tags: "auth" } };
1285
1373
  var wantsJson = (ctx) => String(ctx.headers.accept || "").includes("application/json");
1286
- var clientParams = (source) => ({
1287
- redirect_uri: source.redirect_uri,
1288
- state: source.state,
1289
- code_challenge: source.code_challenge,
1290
- code_challenge_method: source.code_challenge ? "S256" : void 0
1291
- });
1292
- function oauthProvider(config2) {
1293
- const KEY = config2.name.toUpperCase();
1294
- const callbackUrl = (ctx) => `${ctx.url.origin}/auth/callback/${config2.name}`;
1295
- const authorizeUrl2 = (params) => {
1296
- const search = new URLSearchParams({
1297
- client_id: env[`${KEY}_ID`],
1298
- response_type: "code",
1299
- scope: config2.scope
1300
- });
1301
- for (const [key, value] of Object.entries(params)) {
1302
- if (value) search.set(key, value);
1374
+ function parseProviders(given) {
1375
+ const map2 = typeof given === "string" ? { [given]: {} } : Array.isArray(given) ? Object.fromEntries(given.map((name) => [name, {}])) : { ...given };
1376
+ const out = [];
1377
+ for (const [name, raw] of Object.entries(map2)) {
1378
+ const options = typeof raw === "string" ? { issuer: raw } : { ...raw };
1379
+ if (!options.issuer && !providers_default[name] && ISSUERS[name]) {
1380
+ options.issuer = ISSUERS[name];
1381
+ }
1382
+ if (options.issuer) {
1383
+ out.push({ name, options, provider: oidcProvider(name) });
1384
+ } else if (providers_default[name]) {
1385
+ out.push({ name, options, provider: providers_default[name] });
1386
+ } else {
1387
+ throw new Error(
1388
+ `Unknown provider "${name}". Give it an \`issuer\` to use any OIDC provider, or pick one of "${Object.keys(providers_default).join('", "')}".`
1389
+ );
1303
1390
  }
1304
- return `${config2.authorizeUrl}?${search}`;
1305
- };
1306
- const login3 = (ctx) => {
1307
- if (wantsJson(ctx)) {
1308
- return json({ url: authorizeUrl2(clientParams(ctx.url.query)) });
1391
+ }
1392
+ if (!out.length) throw new Error("Auth needs at least one provider");
1393
+ return out;
1394
+ }
1395
+ var target = async (where, fallback, user, ctx) => typeof where === "function" ? where(user, ctx) : where ?? fallback;
1396
+ function entry(config2) {
1397
+ const list = parseProviders(config2.providers);
1398
+ const strategies = Array.isArray(config2.strategy) ? config2.strategy : [config2.strategy ?? "session"];
1399
+ for (const one of strategies) {
1400
+ if (!["session", "cookie", "token", "jwt"].includes(one)) {
1401
+ throw new Error(
1402
+ `Unknown strategy "${one}"; it takes 'session', 'cookie', 'token' or 'jwt'.`
1403
+ );
1309
1404
  }
1310
- const { state, cookie } = startState(ctx);
1311
- const url = authorizeUrl2({ redirect_uri: callbackUrl(ctx), state });
1312
- return cookies("oauth_state", cookie).redirect(url);
1313
- };
1314
- const exchange2 = async (ctx, code, extra) => {
1315
- const body = new URLSearchParams({
1316
- client_id: env[`${KEY}_ID`],
1317
- client_secret: env[`${KEY}_SECRET`],
1318
- code,
1319
- grant_type: "authorization_code"
1320
- });
1321
- for (const [key, value] of Object.entries(extra)) {
1322
- if (value) body.set(key, value);
1323
- }
1324
- const tokenRes = await fetch(config2.tokenUrl, {
1325
- method: "POST",
1326
- headers: {
1327
- accept: "application/json",
1328
- "content-type": "application/x-www-form-urlencoded"
1329
- },
1330
- body
1331
- });
1332
- if (!tokenRes.ok) throw new Error(`${config2.name}: token exchange failed`);
1333
- const token = await tokenRes.json();
1334
- const profileRes = await fetch(config2.profileUrl, {
1335
- headers: {
1336
- accept: "application/json",
1337
- authorization: `Bearer ${token.access_token}`
1405
+ }
1406
+ const expires = config2.expires ?? "30d";
1407
+ const { onLogin, getUser, toPublicUser, onLogout } = config2;
1408
+ if (onLogin && !getUser) {
1409
+ throw new Error("`onLogin` needs a `getUser`: something has to resolve the id it returns.");
1410
+ }
1411
+ for (const strategy of strategies) {
1412
+ if (isSigned(strategy)) {
1413
+ if (getUser && !toPublicUser) {
1414
+ throw new Error(
1415
+ `The \`${strategy}\` strategy signs the user into the credential, so it needs a \`toPublicUser\` to say what goes in. Signing the whole row would publish whatever else is on it.`
1416
+ );
1338
1417
  }
1339
- });
1340
- if (!profileRes.ok) throw new Error(`${config2.name}: profile fetch failed`);
1341
- return profileRes.json();
1342
- };
1343
- const finish3 = async (ctx, raw, opts) => {
1344
- const { onProfile } = ctx.options.auth;
1345
- const profile = onProfile ? await onProfile(raw, config2.name) : config2.profile(raw);
1346
- assertUser(profile, "onProfile");
1347
- return finishLogin(
1348
- ctx,
1349
- {
1350
- provider: config2.name,
1351
- key: profile.id,
1352
- email: profile.email,
1353
- user: profile
1354
- },
1355
- opts
1356
- );
1357
- };
1358
- const callback3 = async (ctx) => {
1359
- checkState(ctx, ctx.url.query.state);
1360
- const raw = await exchange2(ctx, ctx.url.query.code, {
1361
- redirect_uri: callbackUrl(ctx)
1362
- });
1363
- const res = await finish3(ctx, raw);
1364
- res.headers.append("set-cookie", clearState());
1365
- return res;
1366
- };
1367
- const verify4 = async (ctx) => {
1368
- const { code, redirect_uri, code_verifier } = ctx.body ?? {};
1369
- if (!code) throw ServerError_default.AUTH_NO_CODE();
1370
- const raw = await exchange2(ctx, code, { redirect_uri, code_verifier });
1371
- return finish3(ctx, raw, { json: true });
1372
- };
1373
- return { login: login3, callback: callback3, verify: verify4 };
1374
- }
1375
-
1376
- // src/auth/providers/discord.ts
1377
- var discord_default = oauthProvider({
1378
- name: "discord",
1379
- authorizeUrl: "https://discord.com/oauth2/authorize",
1380
- tokenUrl: "https://discord.com/api/oauth2/token",
1381
- profileUrl: "https://discord.com/api/users/@me",
1382
- scope: "identify email",
1383
- profile: (p) => ({
1384
- id: p.id,
1385
- email: p.email,
1386
- name: p.global_name || p.username,
1387
- picture: p.avatar ? `https://cdn.discordapp.com/avatars/${p.id}/${p.avatar}.png` : void 0
1388
- })
1389
- });
1390
-
1391
- // src/auth/updateUser.ts
1392
- async function updateUser(user, auth2, store) {
1393
- if (auth2.provider === "email") {
1394
- return await store.set(auth2.email, user);
1395
- }
1396
- }
1397
-
1398
- // src/auth/providers/email.ts
1399
- async function emailLogin(ctx) {
1400
- const { email, password } = ctx.body;
1401
- if (!email) throw ServerError_default.LOGIN_NO_EMAIL();
1402
- if (!/@/.test(email)) throw ServerError_default.LOGIN_INVALID_EMAIL();
1403
- if (!password) throw ServerError_default.LOGIN_NO_PASSWORD();
1404
- if (password.length < 8) throw ServerError_default.LOGIN_INVALID_PASSWORD();
1405
- const users = ctx.options.auth.users;
1406
- if (!await users.has(email)) throw ServerError_default.LOGIN_WRONG_EMAIL();
1407
- const user = await users.get(email);
1408
- const isValid = await verify2(password, user.password);
1409
- if (!isValid) throw ServerError_default.LOGIN_WRONG_PASSWORD();
1410
- return finishLogin(ctx, {
1411
- provider: "email",
1412
- key: user.email,
1413
- email: user.email,
1414
- user
1415
- });
1416
- }
1417
- async function emailRegister(ctx) {
1418
- const { email, password, ...data } = ctx.body;
1419
- if (!email) throw ServerError_default.REGISTER_NO_EMAIL();
1420
- if (!/@/.test(email)) throw ServerError_default.REGISTER_INVALID_EMAIL();
1421
- if (!password) throw ServerError_default.REGISTER_NO_PASSWORD();
1422
- if (password.length < 8) throw ServerError_default.REGISTER_INVALID_PASSWORD();
1423
- const users = ctx.options.auth.users;
1424
- if (await users.has(email)) throw ServerError_default.REGISTER_EMAIL_EXISTS();
1425
- const time = (/* @__PURE__ */ new Date()).toISOString().replace(/\.[0-9]*/, "");
1426
- const user = {
1427
- id: createId(email),
1428
- strategy: ctx.options.auth.strategy,
1429
- provider: "email",
1430
- email,
1431
- password: await hash2(password),
1432
- time,
1433
- ...data
1434
- };
1435
- return finishLogin(ctx, {
1436
- provider: "email",
1437
- key: email,
1418
+ } else if (!getUser) {
1419
+ throw new Error(
1420
+ `The \`${strategy}\` strategy puts an id in the credential, so it needs a \`getUser\` to resolve it. With no database, use \`cookie\` or \`jwt\`.`
1421
+ );
1422
+ }
1423
+ }
1424
+ const publicProfile = ({ id, email, name, avatar }) => ({
1425
+ id,
1438
1426
  email,
1439
- user
1427
+ name,
1428
+ avatar
1440
1429
  });
1441
- }
1442
- async function emailResetPassword() {
1443
- }
1444
- async function emailUpdatePassword(ctx) {
1445
- const passwords = ctx.body;
1446
- const fullUser = await ctx.options.auth.users.get(ctx.user.email);
1447
- if (!fullUser) throw ServerError_default.AUTH_NO_USER();
1448
- const isValid = await verify2(passwords.previous, fullUser.password);
1449
- if (!isValid) throw ServerError_default.LOGIN_WRONG_PASSWORD();
1450
- fullUser.password = await hash2(passwords.updated);
1451
- await updateUser(fullUser, ctx.user, ctx.options.auth.users);
1452
- return 200;
1453
- }
1454
- var email_default = {
1455
- login: emailLogin,
1456
- register: emailRegister,
1457
- reset: emailResetPassword,
1458
- password: emailUpdatePassword
1459
- };
1460
-
1461
- // src/auth/providers/facebook.ts
1462
- var facebook_default = oauthProvider({
1463
- name: "facebook",
1464
- authorizeUrl: "https://www.facebook.com/v18.0/dialog/oauth",
1465
- tokenUrl: "https://graph.facebook.com/v18.0/oauth/access_token",
1466
- profileUrl: "https://graph.facebook.com/me?fields=id,name,email,picture",
1467
- scope: "email public_profile",
1468
- profile: (p) => ({
1469
- id: p.id,
1470
- email: p.email,
1471
- name: p.name,
1472
- picture: p.picture?.data?.url
1473
- })
1474
- });
1475
-
1476
- // src/auth/providers/github.ts
1477
- var AUTHORIZE2 = "https://github.com/login/oauth/authorize";
1478
- var oauth = async (code, extra) => {
1479
- const fch = async (url, { body, headers: headers2 = {}, ...rest } = {}) => {
1480
- headers2.accept = "application/json";
1481
- headers2["content-type"] = "application/json";
1482
- const res2 = await fetch(url, { ...rest, body, headers: headers2 });
1483
- if (!res2.ok) throw new Error("Invalid request");
1484
- return res2.json();
1485
- };
1486
- const params = {
1487
- client_id: env.GITHUB_ID,
1488
- client_secret: env.GITHUB_SECRET,
1489
- code
1430
+ const redirects = typeof config2.redirect === "object" ? config2.redirect : {};
1431
+ const loginTo = typeof config2.redirect === "object" ? redirects.login : config2.redirect;
1432
+ const finish = async (ctx, profile) => {
1433
+ const payload = getUser ? await (async () => {
1434
+ const id = await onLogin(profile, ctx);
1435
+ if (id === void 0 || id === null) {
1436
+ throw new Error("`onLogin` must return the id the credential points at");
1437
+ }
1438
+ if (!isSigned(strategies[0])) return { sub: String(id) };
1439
+ const user2 = await getUser(String(id), ctx);
1440
+ return { user: await toPublicUser(user2) };
1441
+ })() : { user: publicProfile(profile) };
1442
+ const signed = { ...payload, provider: profile.provider };
1443
+ const token = await issue(ctx, signed, expires);
1444
+ const user = signed.user ?? await getUser(signed.sub, ctx);
1445
+ const to = await target(loginTo, "/", user, ctx);
1446
+ if (inCookie(strategies[0])) {
1447
+ return cookies("session", {
1448
+ value: token,
1449
+ path: "/",
1450
+ expires,
1451
+ httpOnly: true,
1452
+ secure: ctx.platform.production,
1453
+ sameSite: "Lax"
1454
+ }).redirect(to);
1455
+ }
1456
+ return redirect(`${to}#token=${token}`);
1490
1457
  };
1491
- for (const [key, value] of Object.entries(extra)) {
1492
- if (value) params[key] = value;
1493
- }
1494
- const res = await fch("https://github.com/login/oauth/access_token", {
1495
- method: "post",
1496
- body: JSON.stringify(params)
1497
- });
1498
- return (path) => {
1499
- return fch(`https://api.github.com${path}`, {
1500
- headers: { Authorization: `Bearer ${res.access_token}` }
1501
- });
1458
+ return {
1459
+ name: "flow",
1460
+ async user(ctx) {
1461
+ const found = await read(ctx, strategies);
1462
+ if (!found) return;
1463
+ const { payload, strategy } = found;
1464
+ ctx.auth = meta(payload, strategy);
1465
+ if (payload.user) return payload.user;
1466
+ if (!payload.sub) return;
1467
+ return getUser(payload.sub, ctx);
1468
+ },
1469
+ routes(app) {
1470
+ for (const { name, options, provider } of list) {
1471
+ app.get(`/auth/login/${name}`, SPEC, async (ctx) => {
1472
+ const { url, state, payload } = await provider.authorize(ctx, options);
1473
+ const cookie = await startState(ctx, { state, payload });
1474
+ if (wantsJson(ctx)) {
1475
+ return cookies(NAME2, cookie).json({ url });
1476
+ }
1477
+ return cookies(NAME2, cookie).redirect(url);
1478
+ });
1479
+ const callback = async (ctx) => {
1480
+ const query = ctx.url.query;
1481
+ if (query.error) {
1482
+ const to = await target(redirects.error, "/", null, ctx);
1483
+ return redirect(`${to}?error=${encodeURIComponent(query.error)}`);
1484
+ }
1485
+ const pending = await readState(ctx, query.state);
1486
+ if (!query.code) throw ServerError_default.AUTH_NO_CODE();
1487
+ let res;
1488
+ try {
1489
+ const profile = await provider.exchange(
1490
+ ctx,
1491
+ options,
1492
+ query.code,
1493
+ pending
1494
+ );
1495
+ res = await finish(ctx, profile);
1496
+ } catch (error) {
1497
+ const to = await target(redirects.error, "/", null, ctx);
1498
+ const message = error.message;
1499
+ res = redirect(`${to}?error=${encodeURIComponent(message)}`);
1500
+ }
1501
+ res.headers.append(
1502
+ "set-cookie",
1503
+ `${NAME2}=; Path=/; Max-Age=0; HttpOnly`
1504
+ );
1505
+ return res;
1506
+ };
1507
+ app.get(`/auth/callback/${name}`, SPEC, callback);
1508
+ }
1509
+ app.post("/auth/logout", SPEC, async (ctx) => {
1510
+ const found = await read(ctx, strategies).catch(() => void 0);
1511
+ if (onLogout && found?.payload.sub) {
1512
+ await onLogout(found.payload.sub, ctx);
1513
+ }
1514
+ const to = await target(redirects.logout, "/", null, ctx);
1515
+ if (!inCookie(strategies[0])) return status(204);
1516
+ return cookies("session", { value: null }).redirect(to);
1517
+ });
1518
+ }
1502
1519
  };
1520
+ }
1521
+
1522
+ // src/auth/verify.ts
1523
+ var enc2 = new TextEncoder();
1524
+ var dec2 = new TextDecoder();
1525
+ var unb64url2 = (seg) => {
1526
+ let b64 = seg.replace(/-/g, "+").replace(/_/g, "/");
1527
+ b64 += "=".repeat((4 - b64.length % 4) % 4);
1528
+ const bin = atob(b64);
1529
+ const bytes = new Uint8Array(bin.length);
1530
+ for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
1531
+ return bytes;
1503
1532
  };
1504
- var authorizeUrl = (params) => {
1505
- const search = new URLSearchParams({
1506
- client_id: env.GITHUB_ID,
1507
- scope: "user:email"
1508
- });
1509
- for (const [key, value] of Object.entries(params)) {
1510
- if (value) search.set(key, value);
1511
- }
1512
- return `${AUTHORIZE2}?${search}`;
1533
+ var ALGS = {
1534
+ RS256: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
1535
+ RS384: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-384" },
1536
+ RS512: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-512" },
1537
+ ES256: { name: "ECDSA", namedCurve: "P-256", hash: "SHA-256" },
1538
+ ES384: { name: "ECDSA", namedCurve: "P-384", hash: "SHA-384" }
1513
1539
  };
1514
- var login2 = (ctx) => {
1515
- if (wantsJson(ctx)) {
1516
- return json({ url: authorizeUrl(clientParams(ctx.url.query)) });
1540
+ var cache2 = /* @__PURE__ */ new Map();
1541
+ function keysOf(issuer, refresh = false) {
1542
+ let entry3 = cache2.get(issuer);
1543
+ if (!entry3 || refresh && Date.now() - entry3.at > 6e4) {
1544
+ const keys = (async () => {
1545
+ const url = `${issuer.replace(/\/$/, "")}/.well-known/openid-configuration`;
1546
+ const discovery = await fetch(url).then((r2) => r2.json());
1547
+ const set = await fetch(discovery.jwks_uri).then((r2) => r2.json());
1548
+ const out = /* @__PURE__ */ new Map();
1549
+ for (const jwk of set.keys ?? []) {
1550
+ const algorithm = ALGS[jwk.alg];
1551
+ if (!algorithm) continue;
1552
+ out.set(
1553
+ jwk.kid,
1554
+ await crypto.subtle.importKey("jwk", jwk, algorithm, false, ["verify"])
1555
+ );
1556
+ }
1557
+ return out;
1558
+ })();
1559
+ keys.catch(() => cache2.delete(issuer));
1560
+ entry3 = { at: Date.now(), keys };
1561
+ cache2.set(issuer, entry3);
1517
1562
  }
1518
- const { state, cookie } = startState(ctx);
1519
- return cookies("oauth_state", cookie).redirect(authorizeUrl({ state }));
1520
- };
1521
- var getUserProfile = async (code, extra = {}) => {
1522
- const api = await oauth(code, extra);
1523
- const [profile, emails] = await Promise.all([
1524
- api("/user"),
1525
- api("/user/emails")
1526
- ]);
1527
- const email = emails.sort((a) => a.primary ? -1 : 1)[0]?.email;
1528
- return { ...profile, email };
1563
+ return entry3.keys;
1564
+ }
1565
+ var bearer2 = (ctx) => {
1566
+ const header = ctx.headers.authorization;
1567
+ if (!header) return;
1568
+ const [type2, token] = header.trim().split(" ");
1569
+ if (type2?.toLowerCase() !== "bearer" || !token) {
1570
+ throw ServerError_default.AUTH_INVALID_HEADER({ type: type2 });
1571
+ }
1572
+ return token;
1529
1573
  };
1530
- var defaultProfile = (raw) => ({
1531
- id: raw.id,
1532
- name: raw.name,
1533
- email: raw.email,
1534
- picture: raw.avatar_url,
1535
- location: raw.location,
1536
- created: raw.created_at
1537
- });
1538
- var finish2 = async (ctx, raw, opts) => {
1539
- const { onProfile } = ctx.options.auth;
1540
- const profile = onProfile ? await onProfile(raw, "github") : defaultProfile(raw);
1541
- assertUser(profile, "onProfile");
1542
- return finishLogin(
1543
- ctx,
1544
- {
1545
- provider: "github",
1546
- key: profile.id,
1547
- email: profile.email,
1548
- user: profile
1549
- },
1550
- opts
1574
+ function entry2(options) {
1575
+ const { verify: issuer, audience } = options;
1576
+ const claimNames = options.audienceClaim ? Array.isArray(options.audienceClaim) ? options.audienceClaim : [options.audienceClaim] : ["aud"];
1577
+ if (!audience) {
1578
+ throw new Error(
1579
+ "`verify` needs an `audience`: one issuer serves many applications, and without it a token minted for another one is accepted here."
1580
+ );
1581
+ }
1582
+ const allowed = Array.isArray(audience) ? audience : [audience];
1583
+ return {
1584
+ name: `verify:${issuer}`,
1585
+ async user(ctx) {
1586
+ const token = options.cookie ? ctx.cookies[options.cookie] : bearer2(ctx);
1587
+ if (!token) return;
1588
+ const claims2 = await check(token, issuer, allowed, claimNames);
1589
+ ctx.auth = {
1590
+ issuedAt: new Date((claims2.iat ?? 0) * 1e3),
1591
+ expiresAt: claims2.exp ? new Date(claims2.exp * 1e3) : void 0,
1592
+ strategy: options.cookie ? "cookie" : "jwt",
1593
+ provider: issuer
1594
+ };
1595
+ if (!options.getUser) return claims2;
1596
+ return options.getUser(claims2.sub, ctx);
1597
+ }
1598
+ };
1599
+ }
1600
+ async function check(token, issuer, allowed, claimNames) {
1601
+ const parts = token.split(".");
1602
+ if (parts.length !== 3) throw ServerError_default.AUTH_INVALID_TOKEN();
1603
+ const [head, body, sig] = parts;
1604
+ let header;
1605
+ let claims2;
1606
+ try {
1607
+ header = JSON.parse(dec2.decode(unb64url2(head)));
1608
+ claims2 = JSON.parse(dec2.decode(unb64url2(body)));
1609
+ } catch {
1610
+ throw ServerError_default.AUTH_INVALID_TOKEN();
1611
+ }
1612
+ const algorithm = ALGS[header?.alg];
1613
+ if (!algorithm) throw ServerError_default.AUTH_INVALID_TOKEN();
1614
+ let key = (await keysOf(issuer)).get(header.kid);
1615
+ if (!key) key = (await keysOf(issuer, true)).get(header.kid);
1616
+ if (!key) throw ServerError_default.AUTH_INVALID_TOKEN();
1617
+ const ok = await crypto.subtle.verify(
1618
+ algorithm.name === "ECDSA" ? { name: "ECDSA", hash: algorithm.hash } : algorithm,
1619
+ key,
1620
+ unb64url2(sig),
1621
+ enc2.encode(`${head}.${body}`)
1551
1622
  );
1552
- };
1553
- var callback2 = async (ctx) => {
1554
- checkState(ctx, ctx.url.query.state);
1555
- const raw = await getUserProfile(ctx.url.query.code);
1556
- const res = await finish2(ctx, raw);
1557
- res.headers.append("set-cookie", clearState());
1558
- return res;
1559
- };
1560
- var verify3 = async (ctx) => {
1561
- const { code, redirect_uri, code_verifier } = ctx.body ?? {};
1562
- if (!code) throw ServerError_default.AUTH_NO_CODE();
1563
- const raw = await getUserProfile(code, { redirect_uri, code_verifier });
1564
- return finish2(ctx, raw, { json: true });
1565
- };
1566
- var github_default = { login: login2, callback: callback2, verify: verify3 };
1567
-
1568
- // src/auth/providers/google.ts
1569
- var google_default = oauthProvider({
1570
- name: "google",
1571
- authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
1572
- tokenUrl: "https://oauth2.googleapis.com/token",
1573
- profileUrl: "https://openidconnect.googleapis.com/v1/userinfo",
1574
- scope: "openid email profile",
1575
- profile: (p) => ({
1576
- id: p.sub,
1577
- email: p.email,
1578
- name: p.name,
1579
- picture: p.picture
1580
- })
1581
- });
1582
-
1583
- // src/auth/providers/microsoft.ts
1584
- var microsoft_default = oauthProvider({
1585
- name: "microsoft",
1586
- authorizeUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
1587
- tokenUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/token",
1588
- profileUrl: "https://graph.microsoft.com/v1.0/me",
1589
- scope: "openid email profile User.Read",
1590
- profile: (p) => ({
1591
- // Personal accounts expose `userPrincipalName` rather than `mail`
1592
- id: p.id,
1593
- email: p.mail || p.userPrincipalName,
1594
- name: p.displayName
1595
- })
1596
- });
1623
+ if (!ok) throw ServerError_default.AUTH_INVALID_TOKEN();
1624
+ const now = Math.floor(Date.now() / 1e3);
1625
+ if (claims2.exp && now >= claims2.exp) throw ServerError_default.AUTH_INVALID_TOKEN();
1626
+ if (claims2.nbf && now < claims2.nbf) throw ServerError_default.AUTH_INVALID_TOKEN();
1627
+ if (claims2.iss !== issuer) throw ServerError_default.AUTH_INVALID_TOKEN();
1628
+ const name = claimNames.find((one) => claims2[one] !== void 0);
1629
+ if (!name) throw ServerError_default.AUTH_INVALID_TOKEN();
1630
+ const value = claims2[name];
1631
+ const aud = Array.isArray(value) ? value : [value];
1632
+ if (!aud.some((one) => allowed.includes(one))) {
1633
+ throw ServerError_default.AUTH_INVALID_TOKEN();
1634
+ }
1635
+ return claims2;
1636
+ }
1597
1637
 
1598
- // src/auth/providers/index.ts
1599
- var providers_default = {
1600
- apple: apple_default,
1601
- discord: discord_default,
1602
- email: email_default,
1603
- facebook: facebook_default,
1604
- github: github_default,
1605
- google: google_default,
1606
- microsoft: microsoft_default
1638
+ // src/auth/vendors.ts
1639
+ var VENDORS = {
1640
+ clerk: {
1641
+ cookie: "__session",
1642
+ // Clerk session tokens carry no `aud`: the authorized party (your
1643
+ // frontend origin) is in `azp`, which is what their own SDK checks
1644
+ audience: "your frontend origin, like https://app.example.com",
1645
+ claim: "azp",
1646
+ docs: "https://clerk.com/docs/backend-requests/resources/session-tokens"
1647
+ },
1648
+ firebase: {
1649
+ // The client SDK holds the token and sends it as a header, so no cookie.
1650
+ // Both halves are the project id: the issuer is per-project, and it is
1651
+ // what Firebase puts in `aud`.
1652
+ audience: "your Firebase project id",
1653
+ docs: "https://firebase.google.com/docs/auth/admin/verify-id-tokens"
1654
+ },
1655
+ // Google Cloud Identity Platform is the same service, and the same tokens,
1656
+ // under its enterprise name
1657
+ gcip: {
1658
+ audience: "your Google Cloud project id",
1659
+ docs: "https://cloud.google.com/identity-platform/docs/how-to-verify-tokens"
1660
+ },
1661
+ supabase: {
1662
+ audience: '"authenticated"',
1663
+ docs: "https://supabase.com/docs/guides/auth/jwts"
1664
+ }
1607
1665
  };
1666
+ var vendors_default = VENDORS;
1608
1667
 
1609
- // src/auth/parseAuthOptions.ts
1610
- var defaultRedirect = "/user";
1611
- function defaultOnUser(fullUser) {
1612
- const { password: _password, ...user } = fullUser;
1613
- return user;
1614
- }
1615
- var available = Object.keys(providers_default);
1616
- function parseAuthOptions(auth2) {
1668
+ // src/auth/parse.ts
1669
+ function parseAuth(auth2) {
1617
1670
  if (!auth2) return null;
1618
- if (typeof auth2 === "string") {
1619
- const [strategy2, provider] = auth2.split(":");
1620
- auth2 = { strategy: strategy2, providers: provider ? [provider] : [] };
1671
+ const list = Array.isArray(auth2) ? auth2 : [auth2];
1672
+ return list.flatMap((one) => toEntry(one));
1673
+ }
1674
+ function vendorEntry(strategy, name) {
1675
+ const vendor = vendors_default[name];
1676
+ const KEY = name.toUpperCase();
1677
+ if (strategy !== "jwt" && strategy !== "cookie") {
1678
+ throw new Error(
1679
+ `"${strategy}:${name}" is not possible: ${name} issues a signed token, and "${strategy}" means an opaque id resolved through a \`getUser\` of yours. Use "jwt:${name}", or "cookie:${name}" for a same-origin app.`
1680
+ );
1621
1681
  }
1622
- if (!auth2.strategy?.length) {
1623
- throw new Error("Auth options needs a strategy");
1682
+ if (strategy === "cookie" && !vendor.cookie) {
1683
+ throw new Error(
1684
+ `"cookie:${name}" is not possible: ${name} does not store its token in a cookie with a fixed name. Use "jwt:${name}", or name the cookie yourself with { verify, audience, cookie }.`
1685
+ );
1624
1686
  }
1625
- const strategy = auth2.strategy;
1626
- const list = Array.isArray(auth2.providers) ? auth2.providers : auth2.providers ? [auth2.providers] : [];
1627
- if (!list.length) {
1628
- throw new Error("Auth options needs a provider");
1687
+ const issuer = globalThis.env[`${KEY}_ISSUER`];
1688
+ if (!issuer) {
1689
+ throw new Error(
1690
+ `${KEY}_ISSUER is not set, and it differs per account, so it cannot be guessed. See ${vendor.docs}`
1691
+ );
1629
1692
  }
1630
- const invalid = list.find((p) => !available.includes(p));
1631
- if (invalid) {
1693
+ const audience = globalThis.env[`${KEY}_AUDIENCE`];
1694
+ if (!audience) {
1632
1695
  throw new Error(
1633
- `Provider "${invalid}" not found, available ones are "${available.join('", "')}"`
1696
+ `${KEY}_AUDIENCE is not set. It should be ${vendor.audience}. One issuer serves many applications, all signed with the same keys, so without it a token minted for another one is accepted here.`
1634
1697
  );
1635
1698
  }
1636
- const redirect2 = auth2.redirect || defaultRedirect;
1637
- const { onProfile, onLogin, onLogout } = auth2;
1638
- const onUser = auth2.onUser || defaultOnUser;
1639
- const onToken = auth2.onToken || defaultOnUser;
1640
- const users = auth2.users ? toStore(auth2.users) : null;
1641
- const sessions = auth2.sessions ? toStoreExpiring(auth2.sessions, "1w") : null;
1642
- return {
1643
- strategy,
1644
- providers: list,
1645
- redirect: redirect2,
1646
- onProfile,
1647
- onLogin,
1648
- onUser,
1649
- onToken,
1650
- onLogout,
1651
- users,
1652
- sessions
1653
- };
1699
+ return entry2({
1700
+ verify: issuer,
1701
+ audience,
1702
+ ...vendor.claim ? { audienceClaim: vendor.claim } : {},
1703
+ ...strategy === "cookie" ? { cookie: vendor.cookie } : {}
1704
+ });
1705
+ }
1706
+ function toEntry(auth2) {
1707
+ if (typeof auth2 === "string") {
1708
+ const [strategy, name] = auth2.split(":");
1709
+ if (!name) {
1710
+ throw new Error(
1711
+ `Invalid auth "${auth2}": the string form is "<strategy>:<name>", like "cookie:github" to log people in, or "jwt:clerk" to check a token a vendor issued.`
1712
+ );
1713
+ }
1714
+ if (vendors_default[name]) return [vendorEntry(strategy, name)];
1715
+ return [entry({ strategy, providers: name })];
1716
+ }
1717
+ if (typeof auth2 === "function") {
1718
+ return [{ name: "function", user: async (ctx) => auth2(ctx) }];
1719
+ }
1720
+ if (auth2 && typeof auth2 === "object") {
1721
+ if ("verify" in auth2) return [entry2(auth2)];
1722
+ if ("providers" in auth2) return [entry(auth2)];
1723
+ if ("handler" in auth2) {
1724
+ const instance = auth2;
1725
+ const path = (instance.path ?? "/api/auth").replace(/\/$/, "");
1726
+ const raw = { parser: "stream" };
1727
+ const forward = (ctx) => instance.handler(
1728
+ new Request(ctx.url.href, {
1729
+ method: ctx.method,
1730
+ headers: ctx.headers,
1731
+ body: ctx.body,
1732
+ // Required by fetch whenever a body is a stream
1733
+ ...ctx.body ? { duplex: "half" } : {}
1734
+ })
1735
+ );
1736
+ return [
1737
+ {
1738
+ name: `instance:${path}`,
1739
+ user: async (ctx) => instance.user?.(ctx),
1740
+ routes: (app) => {
1741
+ const wildcard = `${path}/*`;
1742
+ app.get(wildcard, raw, forward);
1743
+ app.post(wildcard, raw, forward);
1744
+ app.put(wildcard, raw, forward);
1745
+ app.patch(wildcard, raw, forward);
1746
+ app.delete(wildcard, raw, forward);
1747
+ }
1748
+ }
1749
+ ];
1750
+ }
1751
+ }
1752
+ throw new Error(
1753
+ "Invalid `auth`: it takes a string, a function, `{ providers }`, `{ verify, audience }`, a library instance, or an array of those."
1754
+ );
1654
1755
  }
1655
1756
 
1656
1757
  // src/helpers/color.ts
@@ -1715,16 +1816,16 @@ var STATUS_TEXT = {
1715
1816
  502: "Bad Gateway",
1716
1817
  503: "Service Unavailable"
1717
1818
  };
1718
- var UNITS2 = ["b", "kb", "mb", "gb", "tb"];
1819
+ var UNITS3 = ["b", "kb", "mb", "gb", "tb"];
1719
1820
  function formatBytes(bytes) {
1720
1821
  if (!bytes || bytes < 0) return "0b";
1721
1822
  const i = Math.min(
1722
1823
  Math.floor(Math.log(bytes) / Math.log(1024)),
1723
- UNITS2.length - 1
1824
+ UNITS3.length - 1
1724
1825
  );
1725
1826
  const value = bytes / 1024 ** i;
1726
1827
  const rounded = i === 0 ? Math.round(value) : Math.round(value * 10) / 10;
1727
- return `${rounded}${UNITS2[i]}`;
1828
+ return `${rounded}${UNITS3[i]}`;
1728
1829
  }
1729
1830
  var SCOPE_COLORS = {
1730
1831
  start: "green",
@@ -1909,31 +2010,13 @@ function config(options = {}) {
1909
2010
  settings.uploads = resolveUploads(options.uploads);
1910
2011
  const production = env2.NODE_ENV === "production";
1911
2012
  if (options.auth || env2.AUTH) {
1912
- settings.auth = parseAuthOptions(
2013
+ settings.auth = parseAuth(
1913
2014
  options.auth || env2.AUTH || null
1914
2015
  );
1915
2016
  }
1916
- if (settings.auth) {
1917
- if (!settings.auth.users) {
1918
- if (production) {
1919
- throw new Error(
1920
- "Auth in production needs a persistent `users` store, like auth: { ..., users: kv(redis).prefix('user:') }."
1921
- );
1922
- }
1923
- settings.auth.users = toStore(/* @__PURE__ */ new Map());
1924
- }
1925
- if (!settings.auth.sessions && !settings.auth.strategy.includes("jwt")) {
1926
- if (production) {
1927
- throw new Error(
1928
- "Auth in production needs a persistent `sessions` store, like auth: { ..., sessions: kv(redis).prefix('session:') }."
1929
- );
1930
- }
1931
- settings.auth.sessions = toStoreExpiring(/* @__PURE__ */ new Map(), "1w");
1932
- }
1933
- }
1934
- if (settings.auth?.strategy.includes("jwt") && settings.secrets[0].startsWith("unsafe-")) {
2017
+ if (settings.auth?.some((one) => one.name === "flow") && settings.secrets[0].startsWith("unsafe-")) {
1935
2018
  console.warn(
1936
- "[server:auth] jwt strategy with no SECRETS set: tokens are signed with a random per-process secret, so they break on restart and across instances. Set the SECRETS environment variable (or the `secrets` option)."
2019
+ "[server:auth] auth with no SECRETS set: credentials are signed with a random per-process secret, so they break on restart and across instances. Set the SECRETS environment variable (or the `secrets` option)."
1937
2020
  );
1938
2021
  }
1939
2022
  if (options.openapi) {
@@ -1950,7 +2033,8 @@ function config(options = {}) {
1950
2033
  settings.onResponse = options.onResponse;
1951
2034
  const loc = (v) => typeof v === "string" ? v : "enabled";
1952
2035
  if (settings.auth) {
1953
- log.message("auth", `${settings.auth.providers.join(", ")} auth enabled`);
2036
+ const names = settings.auth.map((one) => one.name).join(", ");
2037
+ log.message("auth", ` enabled`);
1954
2038
  }
1955
2039
  if (settings.public) log.message("public", loc(options.public));
1956
2040
  if (settings.uploads) log.message("uploads", loc(options.uploads));
@@ -2166,9 +2250,9 @@ async function validateResponse(out, options) {
2166
2250
  if (out?.constructor !== Object && !Array.isArray(out)) return out;
2167
2251
  return await run(options.response, out, "response");
2168
2252
  }
2169
- function replace2(target, values) {
2170
- for (const key of Object.keys(target)) delete target[key];
2171
- Object.assign(target, values);
2253
+ function replace2(target2, values) {
2254
+ for (const key of Object.keys(target2)) delete target2[key];
2255
+ Object.assign(target2, values);
2172
2256
  }
2173
2257
 
2174
2258
  // src/helpers/handleRequest.ts
@@ -2236,37 +2320,6 @@ async function getResponse(app, ctx) {
2236
2320
  }
2237
2321
  }
2238
2322
 
2239
- // src/helpers/hash.ts
2240
- import * as crypto2 from "crypto";
2241
- import { getRandomValues } from "crypto";
2242
- import { promisify } from "util";
2243
- async function hash2(password) {
2244
- if ("Bun" in globalThis) {
2245
- return await Bun.password.hash(password, {
2246
- algorithm: "argon2id",
2247
- memoryCost: 65536,
2248
- timeCost: 3
2249
- });
2250
- }
2251
- if (!("argon2" in crypto2)) {
2252
- throw new Error(
2253
- "Password hashing needs argon2: run on Bun, or on Node 24+ where node:crypto provides it."
2254
- );
2255
- }
2256
- const nonce = getRandomValues(new Uint8Array(32));
2257
- const argon23 = promisify(crypto2.argon2);
2258
- const buf = await argon23("argon2id", {
2259
- message: Buffer.from(password),
2260
- nonce,
2261
- parallelism: 1,
2262
- tagLength: 32,
2263
- memory: 65536,
2264
- passes: 3
2265
- });
2266
- const b64 = (bytes) => Buffer.from(bytes).toString("base64").replace(/=+$/, "");
2267
- return `$argon2id$v=19$m=65536,t=3,p=1$${b64(nonce)}$${b64(buf)}`;
2268
- }
2269
-
2270
2323
  // src/helpers/iteratorAsyncToReadable.ts
2271
2324
  function iteratorAsyncToReadable(asyncGenerator) {
2272
2325
  let cancelled = false;
@@ -2357,142 +2410,19 @@ function toWeb(nodeStream) {
2357
2410
  });
2358
2411
  }
2359
2412
 
2360
- // src/helpers/verify.ts
2361
- import * as crypto3 from "crypto";
2362
- async function verify2(password, hash3) {
2363
- if ("Bun" in globalThis) {
2364
- return Bun.password.verify(password, hash3, "argon2id");
2365
- }
2366
- const match = /^\$argon2(id|i|d)\$v=(\d+)\$m=(\d+),t=(\d+),p=(\d+)\$([^$]+)\$([^$]+)$/.exec(
2367
- hash3
2368
- );
2369
- if (!match) throw new Error("Invalid Argon2 hash format");
2370
- const [, variant, , memory, passes, parallelism, saltB64, hashB64] = match;
2371
- const nonce = Buffer.from(saltB64, "base64");
2372
- const expected = Buffer.from(hashB64, "base64");
2373
- return new Promise((resolve, reject) => {
2374
- crypto3.argon2(
2375
- `argon2${variant}`,
2376
- {
2377
- message: password,
2378
- nonce,
2379
- memory: parseInt(memory, 10),
2380
- passes: parseInt(passes, 10),
2381
- parallelism: parseInt(parallelism, 10),
2382
- tagLength: expected.length
2383
- },
2384
- (err, derivedKey) => {
2385
- if (err) return reject(err);
2386
- if (derivedKey.length !== expected.length) return resolve(false);
2387
- resolve(crypto3.timingSafeEqual(derivedKey, expected));
2388
- }
2389
- );
2390
- });
2391
- }
2392
-
2393
- // src/auth/getUser.ts
2394
- async function getJwtUser(ctx) {
2395
- const header = ctx.headers.authorization;
2396
- if (!header) return;
2397
- const [type2, token] = header.trim().split(" ");
2398
- if (type2?.toLowerCase() !== "bearer" || !token) {
2399
- throw ServerError_default.AUTH_INVALID_HEADER({ type: type2 });
2400
- }
2401
- const payload = await verifyJwt(token, ctx.options.secrets);
2402
- if (!payload) throw ServerError_default.AUTH_INVALID_TOKEN();
2403
- const { iat, exp, ...claims } = payload;
2404
- if (!claims.id || !claims.email) throw ServerError_default.AUTH_INVALID_TOKEN();
2405
- if (!ctx.options.auth.providers.includes(claims.provider)) {
2406
- throw ServerError_default.AUTH_INVALID_PROVIDER({
2407
- provider: claims.provider,
2408
- valid: ctx.options.auth.providers
2409
- });
2410
- }
2411
- const exposed = await ctx.options.auth.onUser(claims, ctx);
2412
- assertUser(exposed, "onUser");
2413
- return exposed;
2414
- }
2415
- async function getAuthSession(ctx) {
2416
- const id = findSessionId(ctx);
2417
- if (!id) return;
2418
- const session = await ctx.options.auth.sessions.get(id);
2419
- return session?.user ? session : void 0;
2420
- }
2421
- async function getUser(ctx) {
2422
- if (!ctx.options.auth) return;
2423
- const options = ctx.options.auth;
2424
- if (options.strategy.includes("jwt")) return getJwtUser(ctx);
2425
- const auth2 = await getAuthSession(ctx);
2426
- if (!auth2) return;
2427
- if (!options.providers.includes(auth2.provider)) {
2428
- throw ServerError_default.AUTH_INVALID_PROVIDER({
2429
- provider: auth2.provider,
2430
- valid: options.providers
2431
- });
2432
- }
2433
- const user = await options.users.get(auth2.user);
2434
- if (!user) throw ServerError_default.AUTH_NO_USER();
2435
- const exposed = await options.onUser(user, ctx);
2436
- assertUser(exposed, "onUser");
2437
- return exposed;
2438
- }
2439
-
2440
- // src/auth/logout.ts
2441
- async function logout(ctx) {
2442
- const { strategy } = ctx.options.auth;
2443
- if (!strategy.includes("jwt")) {
2444
- const prev = findSessionId(ctx);
2445
- if (prev) await ctx.options.auth.sessions.del(prev);
2446
- }
2447
- if (ctx.options.auth.onLogout) await ctx.options.auth.onLogout(ctx);
2448
- if (strategy.includes("token") || strategy.includes("jwt")) {
2449
- return { token: null };
2450
- }
2451
- if (strategy.includes("cookie")) {
2452
- return cookies({ session: null }).redirect("/");
2453
- }
2454
- throw new Error("Unknown auth type");
2455
- }
2456
-
2457
2413
  // src/auth/index.ts
2458
- var oauth2 = [
2459
- "github",
2460
- "google",
2461
- "microsoft",
2462
- "discord",
2463
- "facebook"
2464
- ];
2465
2414
  function auth(app) {
2415
+ const entries = app.settings.auth;
2466
2416
  app.use(async function middle(ctx) {
2467
- ctx.user = await getUser(ctx);
2417
+ for (const entry3 of entries) {
2418
+ const user = await entry3.user(ctx);
2419
+ if (user) {
2420
+ ctx.user = user;
2421
+ return;
2422
+ }
2423
+ }
2468
2424
  });
2469
- const spec = { schema: { tags: "auth" } };
2470
- app.post("/auth/logout", spec, logout);
2471
- const enabled = app.settings.auth.providers;
2472
- for (const name of oauth2) {
2473
- if (!enabled.includes(name)) continue;
2474
- const key = name.toUpperCase();
2475
- if (!env[`${key}_ID`]) throw new Error(`${key}_ID not defined`);
2476
- if (!env[`${key}_SECRET`]) throw new Error(`${key}_SECRET not defined`);
2477
- app.get(`/auth/login/${name}`, spec, providers_default[name].login);
2478
- app.get(`/auth/callback/${name}`, spec, providers_default[name].callback);
2479
- app.post(`/auth/verify/${name}`, spec, providers_default[name].verify);
2480
- }
2481
- if (enabled.includes("apple")) {
2482
- const keys = ["APPLE_ID", "APPLE_TEAM_ID", "APPLE_KEY_ID", "APPLE_PRIVATE_KEY"];
2483
- for (const key of keys) {
2484
- if (!env[key]) throw new Error(`${key} not defined`);
2485
- }
2486
- app.get("/auth/login/apple", spec, providers_default.apple.login);
2487
- app.post("/auth/callback/apple", spec, providers_default.apple.callback);
2488
- app.post("/auth/verify/apple", spec, providers_default.apple.verify);
2489
- }
2490
- if (enabled.includes("email")) {
2491
- app.post("/auth/register/email", spec, providers_default.email.register);
2492
- app.post("/auth/login/email", spec, providers_default.email.login);
2493
- app.put("/auth/password/email", spec, providers_default.email.password);
2494
- app.put("/auth/reset/email", spec, providers_default.email.reset);
2495
- }
2425
+ for (const entry3 of entries) entry3.routes?.(app);
2496
2426
  }
2497
2427
 
2498
2428
  // src/helpers/parseRange.ts
@@ -2528,35 +2458,35 @@ async function assets(ctx) {
2528
2458
  const key = ctx.url.pathname.replace(/^\/+/, "");
2529
2459
  const file2 = ctx.options.public.file(key);
2530
2460
  const info = file2.info?.bind(file2);
2531
- const meta = info ? await info() : null;
2532
- if (info ? !meta : !await file2.exists()) return;
2461
+ const meta2 = info ? await info() : null;
2462
+ if (info ? !meta2 : !await file2.exists()) return;
2533
2463
  const ext = ctx.url.pathname.split(".").pop()?.toLowerCase();
2534
- const ctype = ext && mimes_default[ext] || meta?.type || ext;
2464
+ const ctype = ext && mimes_default[ext] || meta2?.type || ext;
2535
2465
  const headers2 = { "cache-control": CACHE_CONTROL };
2536
2466
  let tag;
2537
- if (meta) {
2538
- const stamp = meta.modified ? meta.modified.getTime() : 0;
2539
- tag = `W/"${meta.size.toString(16)}-${stamp.toString(16)}"`;
2467
+ if (meta2) {
2468
+ const stamp = meta2.modified ? meta2.modified.getTime() : 0;
2469
+ tag = `W/"${meta2.size.toString(16)}-${stamp.toString(16)}"`;
2540
2470
  headers2.etag = tag;
2541
- if (meta.modified) headers2["last-modified"] = meta.modified.toUTCString();
2471
+ if (meta2.modified) headers2["last-modified"] = meta2.modified.toUTCString();
2542
2472
  }
2543
- const canRange = !!(meta && file2.slice);
2473
+ const canRange = !!(meta2 && file2.slice);
2544
2474
  if (canRange) headers2["accept-ranges"] = "bytes";
2545
2475
  if (tag && ctx.headers["if-none-match"] === tag) {
2546
2476
  return status(304).headers(headers2).send();
2547
2477
  }
2548
2478
  const rangeHeader = ctx.headers.range;
2549
2479
  const ifRange = ctx.headers["if-range"];
2550
- if (meta && file2.slice && rangeHeader && (!ifRange || ifRange === tag)) {
2551
- const range = parseRange(rangeHeader, meta.size);
2480
+ if (meta2 && file2.slice && rangeHeader && (!ifRange || ifRange === tag)) {
2481
+ const range = parseRange(rangeHeader, meta2.size);
2552
2482
  if (range === "unsatisfiable") {
2553
- return status(416).headers({ ...headers2, "content-range": `bytes */${meta.size}` }).send();
2483
+ return status(416).headers({ ...headers2, "content-range": `bytes */${meta2.size}` }).send();
2554
2484
  }
2555
2485
  if (range) {
2556
2486
  const { start, end } = range;
2557
2487
  return type(ctype).status(206).headers({
2558
2488
  ...headers2,
2559
- "content-range": `bytes ${start}-${end}/${meta.size}`,
2489
+ "content-range": `bytes ${start}-${end}/${meta2.size}`,
2560
2490
  "content-length": String(end - start + 1)
2561
2491
  }).send(file2.slice(start, end + 1).stream());
2562
2492
  }
@@ -2627,7 +2557,7 @@ var generateOpenApiPaths = async (handlers, specPath) => {
2627
2557
  for (const [method, routes] of Object.entries(handlers)) {
2628
2558
  for (const route of routes) {
2629
2559
  const path = route.path;
2630
- const meta = route.options ?? {};
2560
+ const meta2 = route.options ?? {};
2631
2561
  const config2 = getConfig(route.options?.schema);
2632
2562
  if (typeof path !== "string" || path === "*" || path === specPath) {
2633
2563
  continue;
@@ -2638,13 +2568,13 @@ var generateOpenApiPaths = async (handlers, specPath) => {
2638
2568
  paths[normalizedPath] = {};
2639
2569
  }
2640
2570
  let requestBody;
2641
- if (meta?.body) {
2642
- const schema = await toJsonSchema(meta.body);
2571
+ if (meta2?.body) {
2572
+ const schema = await toJsonSchema(meta2.body);
2643
2573
  requestBody = { content: { "application/json": { schema } } };
2644
2574
  }
2645
2575
  let responses;
2646
- if (meta?.response) {
2647
- const schema = await toJsonSchema(meta.response);
2576
+ if (meta2?.response) {
2577
+ const schema = await toJsonSchema(meta2.response);
2648
2578
  responses = {
2649
2579
  200: { description: "OK", content: { "application/json": { schema } } }
2650
2580
  };
@@ -2660,8 +2590,8 @@ var generateOpenApiPaths = async (handlers, specPath) => {
2660
2590
  schema: { type: type2 }
2661
2591
  });
2662
2592
  });
2663
- if (meta?.query) {
2664
- const schema = await toJsonSchema(meta.query);
2593
+ if (meta2?.query) {
2594
+ const schema = await toJsonSchema(meta2.query);
2665
2595
  for (const [name, prop] of Object.entries(schema.properties ?? {})) {
2666
2596
  parameters.push({
2667
2597
  name,
@@ -2734,7 +2664,10 @@ function timer(ctx) {
2734
2664
  async function socketUser(app, headers2, cookies2) {
2735
2665
  if (!app.settings.auth) return void 0;
2736
2666
  const ctx = { options: app.settings, headers: headers2, cookies: cookies2 };
2737
- return getUser(ctx);
2667
+ for (const entry3 of app.settings.auth) {
2668
+ const user = await entry3.user(ctx);
2669
+ if (user) return user;
2670
+ }
2738
2671
  }
2739
2672
 
2740
2673
  // src/helpers/wsNode.ts
@@ -3233,8 +3166,7 @@ function ServerTest(app) {
3233
3166
  }
3234
3167
 
3235
3168
  // src/index.ts
3236
- import { default as default2 } from "polystore";
3237
- import { default as default3 } from "bucket";
3169
+ import { default as default2 } from "bucket";
3238
3170
  var Server = class extends Router {
3239
3171
  settings;
3240
3172
  platform;
@@ -3299,7 +3231,7 @@ export {
3299
3231
  Server,
3300
3232
  ServerError_default as ServerError,
3301
3233
  ValidationError,
3302
- default3 as bucket,
3234
+ default2 as bucket,
3303
3235
  cache,
3304
3236
  cookies,
3305
3237
  server as default,
@@ -3307,7 +3239,6 @@ export {
3307
3239
  file,
3308
3240
  headers,
3309
3241
  json,
3310
- default2 as kv,
3311
3242
  redirect,
3312
3243
  router,
3313
3244
  send,