@server/next 0.45.2 → 0.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +96 -55
- package/index.js +757 -820
- 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
|
|
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(
|
|
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));
|
|
@@ -1040,13 +990,17 @@ async function verifyJwt(token, secret) {
|
|
|
1040
990
|
return null;
|
|
1041
991
|
}
|
|
1042
992
|
if (header?.alg !== "HS256") return null;
|
|
1043
|
-
|
|
1044
|
-
const
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
993
|
+
let ok = false;
|
|
994
|
+
for (const candidate of Array.isArray(secret) ? secret : [secret]) {
|
|
995
|
+
const key = await hmacKey(candidate);
|
|
996
|
+
ok = await crypto.subtle.verify(
|
|
997
|
+
"HMAC",
|
|
998
|
+
key,
|
|
999
|
+
unb64url(sig),
|
|
1000
|
+
enc.encode(`${head}.${body}`)
|
|
1001
|
+
);
|
|
1002
|
+
if (ok) break;
|
|
1003
|
+
}
|
|
1050
1004
|
if (!ok) return null;
|
|
1051
1005
|
let payload;
|
|
1052
1006
|
try {
|
|
@@ -1058,591 +1012,727 @@ async function verifyJwt(token, secret) {
|
|
|
1058
1012
|
return payload;
|
|
1059
1013
|
}
|
|
1060
1014
|
|
|
1061
|
-
// src/auth/
|
|
1062
|
-
var
|
|
1063
|
-
|
|
1064
|
-
|
|
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) {
|
|
1065
1030
|
throw ServerError_default.AUTH_INVALID_HEADER({ type: type2 });
|
|
1066
1031
|
}
|
|
1067
|
-
|
|
1068
|
-
|
|
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));
|
|
1069
1135
|
}
|
|
1070
|
-
return
|
|
1136
|
+
return `${base}?${query}`;
|
|
1071
1137
|
};
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
const
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1138
|
+
|
|
1139
|
+
// src/auth/providers/antarctic.ts
|
|
1140
|
+
var nowhere = {
|
|
1141
|
+
get: async () => null,
|
|
1142
|
+
set: async () => {
|
|
1143
|
+
},
|
|
1144
|
+
del: async () => {
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
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
|
+
});
|
|
1089
1162
|
};
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
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
|
+
}
|
|
1094
1187
|
};
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
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
|
+
42: FortyTwo
|
|
1255
|
+
};
|
|
1256
|
+
var ALIASES = {
|
|
1257
|
+
microsoft: "microsoftentraid",
|
|
1258
|
+
cognito: "amazoncognito",
|
|
1259
|
+
entra: "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] = providers[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();
|
|
1126
1284
|
});
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
assertUser(exposed, "onUser");
|
|
1130
|
-
return reply.status(201).json(exposed);
|
|
1131
|
-
}
|
|
1132
|
-
return reply.redirect(settings.redirect);
|
|
1285
|
+
doc.catch(() => discovered.delete(issuer));
|
|
1286
|
+
discovered.set(issuer, doc);
|
|
1133
1287
|
}
|
|
1134
|
-
|
|
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
|
+
};
|
|
1135
1345
|
}
|
|
1136
1346
|
|
|
1137
1347
|
// src/auth/state.ts
|
|
1138
|
-
var
|
|
1139
|
-
|
|
1140
|
-
|
|
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);
|
|
1141
1352
|
return {
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
secure: crossSite || ctx.platform.production,
|
|
1149
|
-
sameSite: crossSite ? "None" : "Lax"
|
|
1150
|
-
}
|
|
1353
|
+
value,
|
|
1354
|
+
path: "/",
|
|
1355
|
+
expires: EXPIRES,
|
|
1356
|
+
httpOnly: true,
|
|
1357
|
+
secure: ctx.platform.production,
|
|
1358
|
+
sameSite: "Lax"
|
|
1151
1359
|
};
|
|
1152
1360
|
}
|
|
1153
|
-
function
|
|
1154
|
-
const
|
|
1155
|
-
if (!
|
|
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) {
|
|
1156
1366
|
throw ServerError_default.AUTH_INVALID_STATE();
|
|
1157
1367
|
}
|
|
1158
|
-
|
|
1159
|
-
function clearState() {
|
|
1160
|
-
return createCookies(NAME, { value: null });
|
|
1368
|
+
return pending;
|
|
1161
1369
|
}
|
|
1162
1370
|
|
|
1163
|
-
// src/auth/
|
|
1164
|
-
var
|
|
1165
|
-
var TOKEN = "https://appleid.apple.com/auth/token";
|
|
1166
|
-
var b64url2 = (data) => {
|
|
1167
|
-
const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
1168
|
-
let bin = "";
|
|
1169
|
-
for (const byte of bytes) bin += String.fromCharCode(byte);
|
|
1170
|
-
return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1171
|
-
};
|
|
1172
|
-
var b64urlJson = (segment) => {
|
|
1173
|
-
let b64 = segment.replace(/-/g, "+").replace(/_/g, "/");
|
|
1174
|
-
b64 += "=".repeat((4 - b64.length % 4) % 4);
|
|
1175
|
-
const bytes = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
|
|
1176
|
-
return JSON.parse(new TextDecoder().decode(bytes));
|
|
1177
|
-
};
|
|
1178
|
-
var clientSecret = async () => {
|
|
1179
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
1180
|
-
const header = { alg: "ES256", kid: env.APPLE_KEY_ID, typ: "JWT" };
|
|
1181
|
-
const payload = {
|
|
1182
|
-
iss: env.APPLE_TEAM_ID,
|
|
1183
|
-
iat: now,
|
|
1184
|
-
exp: now + 3600,
|
|
1185
|
-
aud: "https://appleid.apple.com",
|
|
1186
|
-
sub: env.APPLE_ID
|
|
1187
|
-
};
|
|
1188
|
-
const data = `${b64url2(JSON.stringify(header))}.${b64url2(JSON.stringify(payload))}`;
|
|
1189
|
-
const pem = String(env.APPLE_PRIVATE_KEY).replace(/-----[^-]+-----/g, "").replace(/\s+/g, "");
|
|
1190
|
-
const der = Uint8Array.from(atob(pem), (c) => c.charCodeAt(0));
|
|
1191
|
-
const key = await crypto.subtle.importKey(
|
|
1192
|
-
"pkcs8",
|
|
1193
|
-
der,
|
|
1194
|
-
{ name: "ECDSA", namedCurve: "P-256" },
|
|
1195
|
-
false,
|
|
1196
|
-
["sign"]
|
|
1197
|
-
);
|
|
1198
|
-
const sig = await crypto.subtle.sign(
|
|
1199
|
-
{ name: "ECDSA", hash: "SHA-256" },
|
|
1200
|
-
key,
|
|
1201
|
-
new TextEncoder().encode(data)
|
|
1202
|
-
);
|
|
1203
|
-
return `${data}.${b64url2(new Uint8Array(sig))}`;
|
|
1204
|
-
};
|
|
1205
|
-
var login = (ctx) => {
|
|
1206
|
-
const { state, cookie } = startState(ctx, true);
|
|
1207
|
-
const params = new URLSearchParams({
|
|
1208
|
-
client_id: env.APPLE_ID,
|
|
1209
|
-
redirect_uri: `${ctx.url.origin}/auth/callback/apple`,
|
|
1210
|
-
response_type: "code",
|
|
1211
|
-
scope: "name email",
|
|
1212
|
-
// Requesting scopes forces Apple to POST the result back (form_post)
|
|
1213
|
-
response_mode: "form_post",
|
|
1214
|
-
state
|
|
1215
|
-
});
|
|
1216
|
-
return cookies("oauth_state", cookie).redirect(`${AUTHORIZE}?${params}`);
|
|
1217
|
-
};
|
|
1218
|
-
var exchange = async (code, redirectUri, user) => {
|
|
1219
|
-
const params = new URLSearchParams({
|
|
1220
|
-
client_id: env.APPLE_ID,
|
|
1221
|
-
client_secret: await clientSecret(),
|
|
1222
|
-
code: code ?? "",
|
|
1223
|
-
grant_type: "authorization_code"
|
|
1224
|
-
});
|
|
1225
|
-
if (redirectUri) params.set("redirect_uri", redirectUri);
|
|
1226
|
-
const tokenRes = await fetch(TOKEN, {
|
|
1227
|
-
method: "POST",
|
|
1228
|
-
headers: {
|
|
1229
|
-
accept: "application/json",
|
|
1230
|
-
"content-type": "application/x-www-form-urlencoded"
|
|
1231
|
-
},
|
|
1232
|
-
body: params
|
|
1233
|
-
});
|
|
1234
|
-
if (!tokenRes.ok) throw new Error("apple: token exchange failed");
|
|
1235
|
-
const token = await tokenRes.json();
|
|
1236
|
-
const claims = b64urlJson(token.id_token.split(".")[1]);
|
|
1237
|
-
let name;
|
|
1238
|
-
if (user) {
|
|
1239
|
-
const parsed = JSON.parse(user).name;
|
|
1240
|
-
if (parsed) name = `${parsed.firstName} ${parsed.lastName}`.trim();
|
|
1241
|
-
}
|
|
1242
|
-
return { ...claims, name };
|
|
1243
|
-
};
|
|
1244
|
-
var finish = async (ctx, raw, opts) => {
|
|
1245
|
-
const { onProfile } = ctx.options.auth;
|
|
1246
|
-
const profile = onProfile ? await onProfile(raw, "apple") : { id: raw.sub, name: raw.name, email: raw.email };
|
|
1247
|
-
assertUser(profile, "onProfile");
|
|
1248
|
-
return finishLogin(
|
|
1249
|
-
ctx,
|
|
1250
|
-
{
|
|
1251
|
-
provider: "apple",
|
|
1252
|
-
key: profile.id,
|
|
1253
|
-
email: profile.email,
|
|
1254
|
-
user: profile
|
|
1255
|
-
},
|
|
1256
|
-
opts
|
|
1257
|
-
);
|
|
1258
|
-
};
|
|
1259
|
-
var callback = async (ctx) => {
|
|
1260
|
-
const body = ctx.body || {};
|
|
1261
|
-
checkState(ctx, body.state);
|
|
1262
|
-
const url = `${ctx.url.origin}/auth/callback/apple`;
|
|
1263
|
-
const raw = await exchange(body.code, url, body.user);
|
|
1264
|
-
const res = await finish(ctx, raw);
|
|
1265
|
-
res.headers.append("set-cookie", clearState());
|
|
1266
|
-
return res;
|
|
1267
|
-
};
|
|
1268
|
-
var verify = async (ctx) => {
|
|
1269
|
-
const { code, redirect_uri, user } = ctx.body ?? {};
|
|
1270
|
-
if (!code) throw ServerError_default.AUTH_NO_CODE();
|
|
1271
|
-
const raw = await exchange(code, redirect_uri, user);
|
|
1272
|
-
return finish(ctx, raw, { json: true });
|
|
1273
|
-
};
|
|
1274
|
-
var apple_default = { login, callback, verify };
|
|
1275
|
-
|
|
1276
|
-
// src/auth/providers/oauth.ts
|
|
1371
|
+
// src/auth/flow.ts
|
|
1372
|
+
var SPEC = { schema: { tags: "auth" } };
|
|
1277
1373
|
var wantsJson = (ctx) => String(ctx.headers.accept || "").includes("application/json");
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
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
|
+
);
|
|
1295
1390
|
}
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
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
|
+
);
|
|
1301
1404
|
}
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
if (value) body.set(key, value);
|
|
1315
|
-
}
|
|
1316
|
-
const tokenRes = await fetch(config2.tokenUrl, {
|
|
1317
|
-
method: "POST",
|
|
1318
|
-
headers: {
|
|
1319
|
-
accept: "application/json",
|
|
1320
|
-
"content-type": "application/x-www-form-urlencoded"
|
|
1321
|
-
},
|
|
1322
|
-
body
|
|
1323
|
-
});
|
|
1324
|
-
if (!tokenRes.ok) throw new Error(`${config2.name}: token exchange failed`);
|
|
1325
|
-
const token = await tokenRes.json();
|
|
1326
|
-
const profileRes = await fetch(config2.profileUrl, {
|
|
1327
|
-
headers: {
|
|
1328
|
-
accept: "application/json",
|
|
1329
|
-
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
|
+
);
|
|
1330
1417
|
}
|
|
1331
|
-
})
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
);
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
const
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
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 redirects = typeof config2.redirect === "object" ? config2.redirect : {};
|
|
1425
|
+
const loginTo = typeof config2.redirect === "object" ? redirects.login : config2.redirect;
|
|
1426
|
+
const finish = async (ctx, profile) => {
|
|
1427
|
+
const payload = getUser ? await (async () => {
|
|
1428
|
+
const id = await onLogin(profile, ctx);
|
|
1429
|
+
if (id === void 0 || id === null) {
|
|
1430
|
+
throw new Error("`onLogin` must return the id the credential points at");
|
|
1431
|
+
}
|
|
1432
|
+
if (!isSigned(strategies[0])) return { sub: String(id) };
|
|
1433
|
+
const user2 = await getUser(String(id), ctx);
|
|
1434
|
+
return { user: await toPublicUser(user2) };
|
|
1435
|
+
})() : { user: profile };
|
|
1436
|
+
const signed = { ...payload, provider: profile.provider };
|
|
1437
|
+
const token = await issue(ctx, signed, expires);
|
|
1438
|
+
const user = signed.user ?? await getUser(signed.sub, ctx);
|
|
1439
|
+
const to = await target(loginTo, "/", user, ctx);
|
|
1440
|
+
if (inCookie(strategies[0])) {
|
|
1441
|
+
return cookies("session", {
|
|
1442
|
+
value: token,
|
|
1443
|
+
path: "/",
|
|
1444
|
+
expires,
|
|
1445
|
+
httpOnly: true,
|
|
1446
|
+
secure: ctx.platform.production,
|
|
1447
|
+
sameSite: "Lax"
|
|
1448
|
+
}).redirect(to);
|
|
1449
|
+
}
|
|
1450
|
+
return redirect(`${to}#token=${token}`);
|
|
1358
1451
|
};
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1452
|
+
return {
|
|
1453
|
+
name: "flow",
|
|
1454
|
+
async user(ctx) {
|
|
1455
|
+
const found = await read(ctx, strategies);
|
|
1456
|
+
if (!found) return;
|
|
1457
|
+
const { payload, strategy } = found;
|
|
1458
|
+
ctx.auth = meta(payload, strategy);
|
|
1459
|
+
if (payload.user) return payload.user;
|
|
1460
|
+
if (!payload.sub) return;
|
|
1461
|
+
return getUser(payload.sub, ctx);
|
|
1462
|
+
},
|
|
1463
|
+
routes(app) {
|
|
1464
|
+
for (const { name, options, provider } of list) {
|
|
1465
|
+
app.get(`/auth/login/${name}`, SPEC, async (ctx) => {
|
|
1466
|
+
const { url, state, payload } = await provider.authorize(ctx, options);
|
|
1467
|
+
const cookie = await startState(ctx, { state, payload });
|
|
1468
|
+
if (wantsJson(ctx)) {
|
|
1469
|
+
return cookies(NAME2, cookie).json({ url });
|
|
1470
|
+
}
|
|
1471
|
+
return cookies(NAME2, cookie).redirect(url);
|
|
1472
|
+
});
|
|
1473
|
+
const callback = async (ctx) => {
|
|
1474
|
+
const query = ctx.url.query;
|
|
1475
|
+
if (query.error) {
|
|
1476
|
+
const to = await target(redirects.error, "/", null, ctx);
|
|
1477
|
+
return redirect(`${to}?error=${encodeURIComponent(query.error)}`);
|
|
1478
|
+
}
|
|
1479
|
+
const pending = await readState(ctx, query.state);
|
|
1480
|
+
if (!query.code) throw ServerError_default.AUTH_NO_CODE();
|
|
1481
|
+
let res;
|
|
1482
|
+
try {
|
|
1483
|
+
const profile = await provider.exchange(
|
|
1484
|
+
ctx,
|
|
1485
|
+
options,
|
|
1486
|
+
query.code,
|
|
1487
|
+
pending
|
|
1488
|
+
);
|
|
1489
|
+
res = await finish(ctx, profile);
|
|
1490
|
+
} catch (error) {
|
|
1491
|
+
const to = await target(redirects.error, "/", null, ctx);
|
|
1492
|
+
const message = error.message;
|
|
1493
|
+
res = redirect(`${to}?error=${encodeURIComponent(message)}`);
|
|
1494
|
+
}
|
|
1495
|
+
res.headers.append(
|
|
1496
|
+
"set-cookie",
|
|
1497
|
+
`${NAME2}=; Path=/; Max-Age=0; HttpOnly`
|
|
1498
|
+
);
|
|
1499
|
+
return res;
|
|
1500
|
+
};
|
|
1501
|
+
app.get(`/auth/callback/${name}`, SPEC, callback);
|
|
1502
|
+
}
|
|
1503
|
+
app.post("/auth/logout", SPEC, async (ctx) => {
|
|
1504
|
+
const found = await read(ctx, strategies).catch(() => void 0);
|
|
1505
|
+
if (onLogout && found?.payload.sub) {
|
|
1506
|
+
await onLogout(found.payload.sub, ctx);
|
|
1507
|
+
}
|
|
1508
|
+
const to = await target(redirects.logout, "/", null, ctx);
|
|
1509
|
+
if (!inCookie(strategies[0])) return status(204);
|
|
1510
|
+
return cookies("session", { value: null }).redirect(to);
|
|
1511
|
+
});
|
|
1512
|
+
}
|
|
1364
1513
|
};
|
|
1365
|
-
return { login: login3, callback: callback3, verify: verify4 };
|
|
1366
|
-
}
|
|
1367
|
-
|
|
1368
|
-
// src/auth/providers/discord.ts
|
|
1369
|
-
var discord_default = oauthProvider({
|
|
1370
|
-
name: "discord",
|
|
1371
|
-
authorizeUrl: "https://discord.com/oauth2/authorize",
|
|
1372
|
-
tokenUrl: "https://discord.com/api/oauth2/token",
|
|
1373
|
-
profileUrl: "https://discord.com/api/users/@me",
|
|
1374
|
-
scope: "identify email",
|
|
1375
|
-
profile: (p) => ({
|
|
1376
|
-
id: p.id,
|
|
1377
|
-
email: p.email,
|
|
1378
|
-
name: p.global_name || p.username,
|
|
1379
|
-
picture: p.avatar ? `https://cdn.discordapp.com/avatars/${p.id}/${p.avatar}.png` : void 0
|
|
1380
|
-
})
|
|
1381
|
-
});
|
|
1382
|
-
|
|
1383
|
-
// src/auth/updateUser.ts
|
|
1384
|
-
async function updateUser(user, auth2, store) {
|
|
1385
|
-
if (auth2.provider === "email") {
|
|
1386
|
-
return await store.set(auth2.email, user);
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
|
|
1390
|
-
// src/auth/providers/email.ts
|
|
1391
|
-
async function emailLogin(ctx) {
|
|
1392
|
-
const { email, password } = ctx.body;
|
|
1393
|
-
if (!email) throw ServerError_default.LOGIN_NO_EMAIL();
|
|
1394
|
-
if (!/@/.test(email)) throw ServerError_default.LOGIN_INVALID_EMAIL();
|
|
1395
|
-
if (!password) throw ServerError_default.LOGIN_NO_PASSWORD();
|
|
1396
|
-
if (password.length < 8) throw ServerError_default.LOGIN_INVALID_PASSWORD();
|
|
1397
|
-
const users = ctx.options.auth.users;
|
|
1398
|
-
if (!await users.has(email)) throw ServerError_default.LOGIN_WRONG_EMAIL();
|
|
1399
|
-
const user = await users.get(email);
|
|
1400
|
-
const isValid = await verify2(password, user.password);
|
|
1401
|
-
if (!isValid) throw ServerError_default.LOGIN_WRONG_PASSWORD();
|
|
1402
|
-
return finishLogin(ctx, {
|
|
1403
|
-
provider: "email",
|
|
1404
|
-
key: user.email,
|
|
1405
|
-
email: user.email,
|
|
1406
|
-
user
|
|
1407
|
-
});
|
|
1408
1514
|
}
|
|
1409
|
-
async function emailRegister(ctx) {
|
|
1410
|
-
const { email, password, ...data } = ctx.body;
|
|
1411
|
-
if (!email) throw ServerError_default.REGISTER_NO_EMAIL();
|
|
1412
|
-
if (!/@/.test(email)) throw ServerError_default.REGISTER_INVALID_EMAIL();
|
|
1413
|
-
if (!password) throw ServerError_default.REGISTER_NO_PASSWORD();
|
|
1414
|
-
if (password.length < 8) throw ServerError_default.REGISTER_INVALID_PASSWORD();
|
|
1415
|
-
const users = ctx.options.auth.users;
|
|
1416
|
-
if (await users.has(email)) throw ServerError_default.REGISTER_EMAIL_EXISTS();
|
|
1417
|
-
const time = (/* @__PURE__ */ new Date()).toISOString().replace(/\.[0-9]*/, "");
|
|
1418
|
-
const user = {
|
|
1419
|
-
id: createId(email),
|
|
1420
|
-
strategy: ctx.options.auth.strategy,
|
|
1421
|
-
provider: "email",
|
|
1422
|
-
email,
|
|
1423
|
-
password: await hash2(password),
|
|
1424
|
-
time,
|
|
1425
|
-
...data
|
|
1426
|
-
};
|
|
1427
|
-
return finishLogin(ctx, {
|
|
1428
|
-
provider: "email",
|
|
1429
|
-
key: email,
|
|
1430
|
-
email,
|
|
1431
|
-
user
|
|
1432
|
-
});
|
|
1433
|
-
}
|
|
1434
|
-
async function emailResetPassword() {
|
|
1435
|
-
}
|
|
1436
|
-
async function emailUpdatePassword(ctx) {
|
|
1437
|
-
const passwords = ctx.body;
|
|
1438
|
-
const fullUser = await ctx.options.auth.users.get(ctx.user.email);
|
|
1439
|
-
if (!fullUser) throw ServerError_default.AUTH_NO_USER();
|
|
1440
|
-
const isValid = await verify2(passwords.previous, fullUser.password);
|
|
1441
|
-
if (!isValid) throw ServerError_default.LOGIN_WRONG_PASSWORD();
|
|
1442
|
-
fullUser.password = await hash2(passwords.updated);
|
|
1443
|
-
await updateUser(fullUser, ctx.user, ctx.options.auth.users);
|
|
1444
|
-
return 200;
|
|
1445
|
-
}
|
|
1446
|
-
var email_default = {
|
|
1447
|
-
login: emailLogin,
|
|
1448
|
-
register: emailRegister,
|
|
1449
|
-
reset: emailResetPassword,
|
|
1450
|
-
password: emailUpdatePassword
|
|
1451
|
-
};
|
|
1452
1515
|
|
|
1453
|
-
// src/auth/
|
|
1454
|
-
var
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
name: p.name,
|
|
1464
|
-
picture: p.picture?.data?.url
|
|
1465
|
-
})
|
|
1466
|
-
});
|
|
1467
|
-
|
|
1468
|
-
// src/auth/providers/github.ts
|
|
1469
|
-
var AUTHORIZE2 = "https://github.com/login/oauth/authorize";
|
|
1470
|
-
var oauth = async (code, extra) => {
|
|
1471
|
-
const fch = async (url, { body, headers: headers2 = {}, ...rest } = {}) => {
|
|
1472
|
-
headers2.accept = "application/json";
|
|
1473
|
-
headers2["content-type"] = "application/json";
|
|
1474
|
-
const res2 = await fetch(url, { ...rest, body, headers: headers2 });
|
|
1475
|
-
if (!res2.ok) throw new Error("Invalid request");
|
|
1476
|
-
return res2.json();
|
|
1477
|
-
};
|
|
1478
|
-
const params = {
|
|
1479
|
-
client_id: env.GITHUB_ID,
|
|
1480
|
-
client_secret: env.GITHUB_SECRET,
|
|
1481
|
-
code
|
|
1482
|
-
};
|
|
1483
|
-
for (const [key, value] of Object.entries(extra)) {
|
|
1484
|
-
if (value) params[key] = value;
|
|
1485
|
-
}
|
|
1486
|
-
const res = await fch("https://github.com/login/oauth/access_token", {
|
|
1487
|
-
method: "post",
|
|
1488
|
-
body: JSON.stringify(params)
|
|
1489
|
-
});
|
|
1490
|
-
return (path) => {
|
|
1491
|
-
return fch(`https://api.github.com${path}`, {
|
|
1492
|
-
headers: { Authorization: `Bearer ${res.access_token}` }
|
|
1493
|
-
});
|
|
1494
|
-
};
|
|
1516
|
+
// src/auth/verify.ts
|
|
1517
|
+
var enc2 = new TextEncoder();
|
|
1518
|
+
var dec2 = new TextDecoder();
|
|
1519
|
+
var unb64url2 = (seg) => {
|
|
1520
|
+
let b64 = seg.replace(/-/g, "+").replace(/_/g, "/");
|
|
1521
|
+
b64 += "=".repeat((4 - b64.length % 4) % 4);
|
|
1522
|
+
const bin = atob(b64);
|
|
1523
|
+
const bytes = new Uint8Array(bin.length);
|
|
1524
|
+
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
|
1525
|
+
return bytes;
|
|
1495
1526
|
};
|
|
1496
|
-
var
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
|
-
if (value) search.set(key, value);
|
|
1503
|
-
}
|
|
1504
|
-
return `${AUTHORIZE2}?${search}`;
|
|
1527
|
+
var ALGS = {
|
|
1528
|
+
RS256: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
|
|
1529
|
+
RS384: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-384" },
|
|
1530
|
+
RS512: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-512" },
|
|
1531
|
+
ES256: { name: "ECDSA", namedCurve: "P-256", hash: "SHA-256" },
|
|
1532
|
+
ES384: { name: "ECDSA", namedCurve: "P-384", hash: "SHA-384" }
|
|
1505
1533
|
};
|
|
1506
|
-
var
|
|
1507
|
-
|
|
1508
|
-
|
|
1534
|
+
var cache2 = /* @__PURE__ */ new Map();
|
|
1535
|
+
function keysOf(issuer, refresh = false) {
|
|
1536
|
+
let entry3 = cache2.get(issuer);
|
|
1537
|
+
if (!entry3 || refresh && Date.now() - entry3.at > 6e4) {
|
|
1538
|
+
const keys = (async () => {
|
|
1539
|
+
const url = `${issuer.replace(/\/$/, "")}/.well-known/openid-configuration`;
|
|
1540
|
+
const discovery = await fetch(url).then((r2) => r2.json());
|
|
1541
|
+
const set = await fetch(discovery.jwks_uri).then((r2) => r2.json());
|
|
1542
|
+
const out = /* @__PURE__ */ new Map();
|
|
1543
|
+
for (const jwk of set.keys ?? []) {
|
|
1544
|
+
const algorithm = ALGS[jwk.alg];
|
|
1545
|
+
if (!algorithm) continue;
|
|
1546
|
+
out.set(
|
|
1547
|
+
jwk.kid,
|
|
1548
|
+
await crypto.subtle.importKey("jwk", jwk, algorithm, false, ["verify"])
|
|
1549
|
+
);
|
|
1550
|
+
}
|
|
1551
|
+
return out;
|
|
1552
|
+
})();
|
|
1553
|
+
keys.catch(() => cache2.delete(issuer));
|
|
1554
|
+
entry3 = { at: Date.now(), keys };
|
|
1555
|
+
cache2.set(issuer, entry3);
|
|
1509
1556
|
}
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
const [
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
return { ...profile, email };
|
|
1557
|
+
return entry3.keys;
|
|
1558
|
+
}
|
|
1559
|
+
var bearer2 = (ctx) => {
|
|
1560
|
+
const header = ctx.headers.authorization;
|
|
1561
|
+
if (!header) return;
|
|
1562
|
+
const [type2, token] = header.trim().split(" ");
|
|
1563
|
+
if (type2?.toLowerCase() !== "bearer" || !token) {
|
|
1564
|
+
throw ServerError_default.AUTH_INVALID_HEADER({ type: type2 });
|
|
1565
|
+
}
|
|
1566
|
+
return token;
|
|
1521
1567
|
};
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
}
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1568
|
+
function entry2(options) {
|
|
1569
|
+
const { verify: issuer, audience } = options;
|
|
1570
|
+
const claimNames = options.audienceClaim ? Array.isArray(options.audienceClaim) ? options.audienceClaim : [options.audienceClaim] : ["aud"];
|
|
1571
|
+
if (!audience) {
|
|
1572
|
+
throw new Error(
|
|
1573
|
+
"`verify` needs an `audience`: one issuer serves many applications, and without it a token minted for another one is accepted here."
|
|
1574
|
+
);
|
|
1575
|
+
}
|
|
1576
|
+
const allowed = Array.isArray(audience) ? audience : [audience];
|
|
1577
|
+
return {
|
|
1578
|
+
name: `verify:${issuer}`,
|
|
1579
|
+
async user(ctx) {
|
|
1580
|
+
const token = options.cookie ? ctx.cookies[options.cookie] : bearer2(ctx);
|
|
1581
|
+
if (!token) return;
|
|
1582
|
+
const claims2 = await check(token, issuer, allowed, claimNames);
|
|
1583
|
+
ctx.auth = {
|
|
1584
|
+
issuedAt: new Date((claims2.iat ?? 0) * 1e3),
|
|
1585
|
+
expiresAt: claims2.exp ? new Date(claims2.exp * 1e3) : void 0,
|
|
1586
|
+
strategy: options.cookie ? "cookie" : "jwt",
|
|
1587
|
+
provider: issuer
|
|
1588
|
+
};
|
|
1589
|
+
if (!options.getUser) return claims2;
|
|
1590
|
+
return options.getUser(claims2.sub, ctx);
|
|
1591
|
+
}
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1594
|
+
async function check(token, issuer, allowed, claimNames) {
|
|
1595
|
+
const parts = token.split(".");
|
|
1596
|
+
if (parts.length !== 3) throw ServerError_default.AUTH_INVALID_TOKEN();
|
|
1597
|
+
const [head, body, sig] = parts;
|
|
1598
|
+
let header;
|
|
1599
|
+
let claims2;
|
|
1600
|
+
try {
|
|
1601
|
+
header = JSON.parse(dec2.decode(unb64url2(head)));
|
|
1602
|
+
claims2 = JSON.parse(dec2.decode(unb64url2(body)));
|
|
1603
|
+
} catch {
|
|
1604
|
+
throw ServerError_default.AUTH_INVALID_TOKEN();
|
|
1605
|
+
}
|
|
1606
|
+
const algorithm = ALGS[header?.alg];
|
|
1607
|
+
if (!algorithm) throw ServerError_default.AUTH_INVALID_TOKEN();
|
|
1608
|
+
let key = (await keysOf(issuer)).get(header.kid);
|
|
1609
|
+
if (!key) key = (await keysOf(issuer, true)).get(header.kid);
|
|
1610
|
+
if (!key) throw ServerError_default.AUTH_INVALID_TOKEN();
|
|
1611
|
+
const ok = await crypto.subtle.verify(
|
|
1612
|
+
algorithm.name === "ECDSA" ? { name: "ECDSA", hash: algorithm.hash } : algorithm,
|
|
1613
|
+
key,
|
|
1614
|
+
unb64url2(sig),
|
|
1615
|
+
enc2.encode(`${head}.${body}`)
|
|
1543
1616
|
);
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
return
|
|
1557
|
-
}
|
|
1558
|
-
var github_default = { login: login2, callback: callback2, verify: verify3 };
|
|
1559
|
-
|
|
1560
|
-
// src/auth/providers/google.ts
|
|
1561
|
-
var google_default = oauthProvider({
|
|
1562
|
-
name: "google",
|
|
1563
|
-
authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
1564
|
-
tokenUrl: "https://oauth2.googleapis.com/token",
|
|
1565
|
-
profileUrl: "https://openidconnect.googleapis.com/v1/userinfo",
|
|
1566
|
-
scope: "openid email profile",
|
|
1567
|
-
profile: (p) => ({
|
|
1568
|
-
id: p.sub,
|
|
1569
|
-
email: p.email,
|
|
1570
|
-
name: p.name,
|
|
1571
|
-
picture: p.picture
|
|
1572
|
-
})
|
|
1573
|
-
});
|
|
1574
|
-
|
|
1575
|
-
// src/auth/providers/microsoft.ts
|
|
1576
|
-
var microsoft_default = oauthProvider({
|
|
1577
|
-
name: "microsoft",
|
|
1578
|
-
authorizeUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
|
|
1579
|
-
tokenUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/token",
|
|
1580
|
-
profileUrl: "https://graph.microsoft.com/v1.0/me",
|
|
1581
|
-
scope: "openid email profile User.Read",
|
|
1582
|
-
profile: (p) => ({
|
|
1583
|
-
// Personal accounts expose `userPrincipalName` rather than `mail`
|
|
1584
|
-
id: p.id,
|
|
1585
|
-
email: p.mail || p.userPrincipalName,
|
|
1586
|
-
name: p.displayName
|
|
1587
|
-
})
|
|
1588
|
-
});
|
|
1617
|
+
if (!ok) throw ServerError_default.AUTH_INVALID_TOKEN();
|
|
1618
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1619
|
+
if (claims2.exp && now >= claims2.exp) throw ServerError_default.AUTH_INVALID_TOKEN();
|
|
1620
|
+
if (claims2.nbf && now < claims2.nbf) throw ServerError_default.AUTH_INVALID_TOKEN();
|
|
1621
|
+
if (claims2.iss !== issuer) throw ServerError_default.AUTH_INVALID_TOKEN();
|
|
1622
|
+
const name = claimNames.find((one) => claims2[one] !== void 0);
|
|
1623
|
+
if (!name) throw ServerError_default.AUTH_INVALID_TOKEN();
|
|
1624
|
+
const value = claims2[name];
|
|
1625
|
+
const aud = Array.isArray(value) ? value : [value];
|
|
1626
|
+
if (!aud.some((one) => allowed.includes(one))) {
|
|
1627
|
+
throw ServerError_default.AUTH_INVALID_TOKEN();
|
|
1628
|
+
}
|
|
1629
|
+
return claims2;
|
|
1630
|
+
}
|
|
1589
1631
|
|
|
1590
|
-
// src/auth/
|
|
1591
|
-
var
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1632
|
+
// src/auth/vendors.ts
|
|
1633
|
+
var VENDORS = {
|
|
1634
|
+
clerk: {
|
|
1635
|
+
cookie: "__session",
|
|
1636
|
+
// Clerk session tokens carry no `aud`: the authorized party (your
|
|
1637
|
+
// frontend origin) is in `azp`, which is what their own SDK checks
|
|
1638
|
+
audience: "your frontend origin, like https://app.example.com",
|
|
1639
|
+
claim: "azp",
|
|
1640
|
+
docs: "https://clerk.com/docs/backend-requests/resources/session-tokens"
|
|
1641
|
+
},
|
|
1642
|
+
supabase: {
|
|
1643
|
+
audience: '"authenticated"',
|
|
1644
|
+
docs: "https://supabase.com/docs/guides/auth/jwts"
|
|
1645
|
+
}
|
|
1599
1646
|
};
|
|
1647
|
+
var vendors_default = VENDORS;
|
|
1600
1648
|
|
|
1601
|
-
// src/auth/
|
|
1602
|
-
|
|
1603
|
-
function defaultOnUser(fullUser) {
|
|
1604
|
-
const { password: _password, ...user } = fullUser;
|
|
1605
|
-
return user;
|
|
1606
|
-
}
|
|
1607
|
-
var available = Object.keys(providers_default);
|
|
1608
|
-
function parseAuthOptions(auth2) {
|
|
1649
|
+
// src/auth/parse.ts
|
|
1650
|
+
function parseAuth(auth2) {
|
|
1609
1651
|
if (!auth2) return null;
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1652
|
+
const list = Array.isArray(auth2) ? auth2 : [auth2];
|
|
1653
|
+
return list.flatMap((one) => toEntry(one));
|
|
1654
|
+
}
|
|
1655
|
+
function vendorEntry(strategy, name) {
|
|
1656
|
+
const vendor = vendors_default[name];
|
|
1657
|
+
const KEY = name.toUpperCase();
|
|
1658
|
+
if (strategy !== "jwt" && strategy !== "cookie") {
|
|
1659
|
+
throw new Error(
|
|
1660
|
+
`"${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.`
|
|
1661
|
+
);
|
|
1613
1662
|
}
|
|
1614
|
-
if (!
|
|
1615
|
-
throw new Error(
|
|
1663
|
+
if (strategy === "cookie" && !vendor.cookie) {
|
|
1664
|
+
throw new Error(
|
|
1665
|
+
`"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 }.`
|
|
1666
|
+
);
|
|
1616
1667
|
}
|
|
1617
|
-
const
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1668
|
+
const issuer = globalThis.env[`${KEY}_ISSUER`];
|
|
1669
|
+
if (!issuer) {
|
|
1670
|
+
throw new Error(
|
|
1671
|
+
`${KEY}_ISSUER is not set, and it differs per account, so it cannot be guessed. See ${vendor.docs}`
|
|
1672
|
+
);
|
|
1621
1673
|
}
|
|
1622
|
-
const
|
|
1623
|
-
if (
|
|
1674
|
+
const audience = globalThis.env[`${KEY}_AUDIENCE`];
|
|
1675
|
+
if (!audience) {
|
|
1624
1676
|
throw new Error(
|
|
1625
|
-
|
|
1677
|
+
`${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.`
|
|
1626
1678
|
);
|
|
1627
1679
|
}
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
}
|
|
1680
|
+
return entry2({
|
|
1681
|
+
verify: issuer,
|
|
1682
|
+
audience,
|
|
1683
|
+
...vendor.claim ? { audienceClaim: vendor.claim } : {},
|
|
1684
|
+
...strategy === "cookie" ? { cookie: vendor.cookie } : {}
|
|
1685
|
+
});
|
|
1686
|
+
}
|
|
1687
|
+
function toEntry(auth2) {
|
|
1688
|
+
if (typeof auth2 === "string") {
|
|
1689
|
+
const [strategy, name] = auth2.split(":");
|
|
1690
|
+
if (!name) {
|
|
1691
|
+
throw new Error(
|
|
1692
|
+
`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.`
|
|
1693
|
+
);
|
|
1694
|
+
}
|
|
1695
|
+
if (vendors_default[name]) return [vendorEntry(strategy, name)];
|
|
1696
|
+
return [entry({ strategy, providers: name })];
|
|
1697
|
+
}
|
|
1698
|
+
if (typeof auth2 === "function") {
|
|
1699
|
+
return [{ name: "function", user: async (ctx) => auth2(ctx) }];
|
|
1700
|
+
}
|
|
1701
|
+
if (auth2 && typeof auth2 === "object") {
|
|
1702
|
+
if ("verify" in auth2) return [entry2(auth2)];
|
|
1703
|
+
if ("providers" in auth2) return [entry(auth2)];
|
|
1704
|
+
if ("handler" in auth2) {
|
|
1705
|
+
const instance = auth2;
|
|
1706
|
+
const path = (instance.path ?? "/api/auth").replace(/\/$/, "");
|
|
1707
|
+
const raw = { parser: "stream" };
|
|
1708
|
+
const forward = (ctx) => instance.handler(
|
|
1709
|
+
new Request(ctx.url.href, {
|
|
1710
|
+
method: ctx.method,
|
|
1711
|
+
headers: ctx.headers,
|
|
1712
|
+
body: ctx.body,
|
|
1713
|
+
// Required by fetch whenever a body is a stream
|
|
1714
|
+
...ctx.body ? { duplex: "half" } : {}
|
|
1715
|
+
})
|
|
1716
|
+
);
|
|
1717
|
+
return [
|
|
1718
|
+
{
|
|
1719
|
+
name: `instance:${path}`,
|
|
1720
|
+
user: async (ctx) => instance.user?.(ctx),
|
|
1721
|
+
routes: (app) => {
|
|
1722
|
+
const wildcard = `${path}/*`;
|
|
1723
|
+
app.get(wildcard, raw, forward);
|
|
1724
|
+
app.post(wildcard, raw, forward);
|
|
1725
|
+
app.put(wildcard, raw, forward);
|
|
1726
|
+
app.patch(wildcard, raw, forward);
|
|
1727
|
+
app.delete(wildcard, raw, forward);
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
];
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
throw new Error(
|
|
1734
|
+
"Invalid `auth`: it takes a string, a function, `{ providers }`, `{ verify, audience }`, a library instance, or an array of those."
|
|
1735
|
+
);
|
|
1646
1736
|
}
|
|
1647
1737
|
|
|
1648
1738
|
// src/helpers/color.ts
|
|
@@ -1707,16 +1797,16 @@ var STATUS_TEXT = {
|
|
|
1707
1797
|
502: "Bad Gateway",
|
|
1708
1798
|
503: "Service Unavailable"
|
|
1709
1799
|
};
|
|
1710
|
-
var
|
|
1800
|
+
var UNITS3 = ["b", "kb", "mb", "gb", "tb"];
|
|
1711
1801
|
function formatBytes(bytes) {
|
|
1712
1802
|
if (!bytes || bytes < 0) return "0b";
|
|
1713
1803
|
const i = Math.min(
|
|
1714
1804
|
Math.floor(Math.log(bytes) / Math.log(1024)),
|
|
1715
|
-
|
|
1805
|
+
UNITS3.length - 1
|
|
1716
1806
|
);
|
|
1717
1807
|
const value = bytes / 1024 ** i;
|
|
1718
1808
|
const rounded = i === 0 ? Math.round(value) : Math.round(value * 10) / 10;
|
|
1719
|
-
return `${rounded}${
|
|
1809
|
+
return `${rounded}${UNITS3[i]}`;
|
|
1720
1810
|
}
|
|
1721
1811
|
var SCOPE_COLORS = {
|
|
1722
1812
|
start: "green",
|
|
@@ -1754,6 +1844,13 @@ function createLogger(level) {
|
|
|
1754
1844
|
};
|
|
1755
1845
|
}
|
|
1756
1846
|
|
|
1847
|
+
// src/helpers/secrets.ts
|
|
1848
|
+
function resolveSecrets(option) {
|
|
1849
|
+
const given = option ?? globalThis.env.SECRETS?.split(",");
|
|
1850
|
+
const list = (Array.isArray(given) ? given : [given]).map((one) => one?.trim()).filter(Boolean);
|
|
1851
|
+
return list.length ? list : [`unsafe-${createId()}`];
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1757
1854
|
// src/helpers/security.ts
|
|
1758
1855
|
function resolveSecurity(security) {
|
|
1759
1856
|
const off = security === false;
|
|
@@ -1827,13 +1924,23 @@ function config(options = {}) {
|
|
|
1827
1924
|
);
|
|
1828
1925
|
}
|
|
1829
1926
|
}
|
|
1927
|
+
if (opts.secret !== void 0) {
|
|
1928
|
+
throw new Error(
|
|
1929
|
+
"The `secret` option is now `secrets`, and takes one key or several: `secrets: [current, previous]` signs with the first and verifies with any, so rotating a key no longer signs everyone out."
|
|
1930
|
+
);
|
|
1931
|
+
}
|
|
1932
|
+
if (env2.SECRET && !env2.SECRETS) {
|
|
1933
|
+
throw new Error(
|
|
1934
|
+
"The SECRET environment variable is now SECRETS, a comma-separated list. Rename it, or every token signed with the old key breaks."
|
|
1935
|
+
);
|
|
1936
|
+
}
|
|
1830
1937
|
const raw = options.log ?? env2.LOG_LEVEL;
|
|
1831
1938
|
const level = raw === true ? "info" : raw === false ? void 0 : raw;
|
|
1832
1939
|
const log = createLogger(level);
|
|
1833
1940
|
const settings = {
|
|
1834
1941
|
// `env.PORT` is a string, so coerce it: `settings.port` is a number
|
|
1835
1942
|
port: options.port || Number(env2.PORT) || 3e3,
|
|
1836
|
-
|
|
1943
|
+
secrets: resolveSecrets(options.secrets),
|
|
1837
1944
|
log,
|
|
1838
1945
|
// How request bodies are read: parsed into ctx.body by default; `raw` keeps
|
|
1839
1946
|
// the Buffer, `stream` hands the handler the unread web ReadableStream.
|
|
@@ -1884,31 +1991,13 @@ function config(options = {}) {
|
|
|
1884
1991
|
settings.uploads = resolveUploads(options.uploads);
|
|
1885
1992
|
const production = env2.NODE_ENV === "production";
|
|
1886
1993
|
if (options.auth || env2.AUTH) {
|
|
1887
|
-
settings.auth =
|
|
1994
|
+
settings.auth = parseAuth(
|
|
1888
1995
|
options.auth || env2.AUTH || null
|
|
1889
1996
|
);
|
|
1890
1997
|
}
|
|
1891
|
-
if (settings.auth) {
|
|
1892
|
-
if (!settings.auth.users) {
|
|
1893
|
-
if (production) {
|
|
1894
|
-
throw new Error(
|
|
1895
|
-
"Auth in production needs a persistent `users` store, like auth: { ..., users: kv(redis).prefix('user:') }."
|
|
1896
|
-
);
|
|
1897
|
-
}
|
|
1898
|
-
settings.auth.users = toStore(/* @__PURE__ */ new Map());
|
|
1899
|
-
}
|
|
1900
|
-
if (!settings.auth.sessions && !settings.auth.strategy.includes("jwt")) {
|
|
1901
|
-
if (production) {
|
|
1902
|
-
throw new Error(
|
|
1903
|
-
"Auth in production needs a persistent `sessions` store, like auth: { ..., sessions: kv(redis).prefix('session:') }."
|
|
1904
|
-
);
|
|
1905
|
-
}
|
|
1906
|
-
settings.auth.sessions = toStoreExpiring(/* @__PURE__ */ new Map(), "1w");
|
|
1907
|
-
}
|
|
1908
|
-
}
|
|
1909
|
-
if (settings.auth?.strategy.includes("jwt") && settings.secret.startsWith("unsafe-")) {
|
|
1998
|
+
if (settings.auth?.some((one) => one.name === "flow") && settings.secrets[0].startsWith("unsafe-")) {
|
|
1910
1999
|
console.warn(
|
|
1911
|
-
"[server:auth]
|
|
2000
|
+
"[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)."
|
|
1912
2001
|
);
|
|
1913
2002
|
}
|
|
1914
2003
|
if (options.openapi) {
|
|
@@ -1925,7 +2014,8 @@ function config(options = {}) {
|
|
|
1925
2014
|
settings.onResponse = options.onResponse;
|
|
1926
2015
|
const loc = (v) => typeof v === "string" ? v : "enabled";
|
|
1927
2016
|
if (settings.auth) {
|
|
1928
|
-
|
|
2017
|
+
const names = settings.auth.map((one) => one.name).join(", ");
|
|
2018
|
+
log.message("auth", ` enabled`);
|
|
1929
2019
|
}
|
|
1930
2020
|
if (settings.public) log.message("public", loc(options.public));
|
|
1931
2021
|
if (settings.uploads) log.message("uploads", loc(options.uploads));
|
|
@@ -2141,9 +2231,9 @@ async function validateResponse(out, options) {
|
|
|
2141
2231
|
if (out?.constructor !== Object && !Array.isArray(out)) return out;
|
|
2142
2232
|
return await run(options.response, out, "response");
|
|
2143
2233
|
}
|
|
2144
|
-
function replace2(
|
|
2145
|
-
for (const key of Object.keys(
|
|
2146
|
-
Object.assign(
|
|
2234
|
+
function replace2(target2, values) {
|
|
2235
|
+
for (const key of Object.keys(target2)) delete target2[key];
|
|
2236
|
+
Object.assign(target2, values);
|
|
2147
2237
|
}
|
|
2148
2238
|
|
|
2149
2239
|
// src/helpers/handleRequest.ts
|
|
@@ -2211,37 +2301,6 @@ async function getResponse(app, ctx) {
|
|
|
2211
2301
|
}
|
|
2212
2302
|
}
|
|
2213
2303
|
|
|
2214
|
-
// src/helpers/hash.ts
|
|
2215
|
-
import * as crypto2 from "crypto";
|
|
2216
|
-
import { getRandomValues } from "crypto";
|
|
2217
|
-
import { promisify } from "util";
|
|
2218
|
-
async function hash2(password) {
|
|
2219
|
-
if ("Bun" in globalThis) {
|
|
2220
|
-
return await Bun.password.hash(password, {
|
|
2221
|
-
algorithm: "argon2id",
|
|
2222
|
-
memoryCost: 65536,
|
|
2223
|
-
timeCost: 3
|
|
2224
|
-
});
|
|
2225
|
-
}
|
|
2226
|
-
if (!("argon2" in crypto2)) {
|
|
2227
|
-
throw new Error(
|
|
2228
|
-
"Password hashing needs argon2: run on Bun, or on Node 24+ where node:crypto provides it."
|
|
2229
|
-
);
|
|
2230
|
-
}
|
|
2231
|
-
const nonce = getRandomValues(new Uint8Array(32));
|
|
2232
|
-
const argon23 = promisify(crypto2.argon2);
|
|
2233
|
-
const buf = await argon23("argon2id", {
|
|
2234
|
-
message: Buffer.from(password),
|
|
2235
|
-
nonce,
|
|
2236
|
-
parallelism: 1,
|
|
2237
|
-
tagLength: 32,
|
|
2238
|
-
memory: 65536,
|
|
2239
|
-
passes: 3
|
|
2240
|
-
});
|
|
2241
|
-
const b64 = (bytes) => Buffer.from(bytes).toString("base64").replace(/=+$/, "");
|
|
2242
|
-
return `$argon2id$v=19$m=65536,t=3,p=1$${b64(nonce)}$${b64(buf)}`;
|
|
2243
|
-
}
|
|
2244
|
-
|
|
2245
2304
|
// src/helpers/iteratorAsyncToReadable.ts
|
|
2246
2305
|
function iteratorAsyncToReadable(asyncGenerator) {
|
|
2247
2306
|
let cancelled = false;
|
|
@@ -2332,142 +2391,19 @@ function toWeb(nodeStream) {
|
|
|
2332
2391
|
});
|
|
2333
2392
|
}
|
|
2334
2393
|
|
|
2335
|
-
// src/helpers/verify.ts
|
|
2336
|
-
import * as crypto3 from "crypto";
|
|
2337
|
-
async function verify2(password, hash3) {
|
|
2338
|
-
if ("Bun" in globalThis) {
|
|
2339
|
-
return Bun.password.verify(password, hash3, "argon2id");
|
|
2340
|
-
}
|
|
2341
|
-
const match = /^\$argon2(id|i|d)\$v=(\d+)\$m=(\d+),t=(\d+),p=(\d+)\$([^$]+)\$([^$]+)$/.exec(
|
|
2342
|
-
hash3
|
|
2343
|
-
);
|
|
2344
|
-
if (!match) throw new Error("Invalid Argon2 hash format");
|
|
2345
|
-
const [, variant, , memory, passes, parallelism, saltB64, hashB64] = match;
|
|
2346
|
-
const nonce = Buffer.from(saltB64, "base64");
|
|
2347
|
-
const expected = Buffer.from(hashB64, "base64");
|
|
2348
|
-
return new Promise((resolve, reject) => {
|
|
2349
|
-
crypto3.argon2(
|
|
2350
|
-
`argon2${variant}`,
|
|
2351
|
-
{
|
|
2352
|
-
message: password,
|
|
2353
|
-
nonce,
|
|
2354
|
-
memory: parseInt(memory, 10),
|
|
2355
|
-
passes: parseInt(passes, 10),
|
|
2356
|
-
parallelism: parseInt(parallelism, 10),
|
|
2357
|
-
tagLength: expected.length
|
|
2358
|
-
},
|
|
2359
|
-
(err, derivedKey) => {
|
|
2360
|
-
if (err) return reject(err);
|
|
2361
|
-
if (derivedKey.length !== expected.length) return resolve(false);
|
|
2362
|
-
resolve(crypto3.timingSafeEqual(derivedKey, expected));
|
|
2363
|
-
}
|
|
2364
|
-
);
|
|
2365
|
-
});
|
|
2366
|
-
}
|
|
2367
|
-
|
|
2368
|
-
// src/auth/getUser.ts
|
|
2369
|
-
async function getJwtUser(ctx) {
|
|
2370
|
-
const header = ctx.headers.authorization;
|
|
2371
|
-
if (!header) return;
|
|
2372
|
-
const [type2, token] = header.trim().split(" ");
|
|
2373
|
-
if (type2?.toLowerCase() !== "bearer" || !token) {
|
|
2374
|
-
throw ServerError_default.AUTH_INVALID_HEADER({ type: type2 });
|
|
2375
|
-
}
|
|
2376
|
-
const payload = await verifyJwt(token, ctx.options.secret);
|
|
2377
|
-
if (!payload) throw ServerError_default.AUTH_INVALID_TOKEN();
|
|
2378
|
-
const { iat, exp, ...claims } = payload;
|
|
2379
|
-
if (!claims.id || !claims.email) throw ServerError_default.AUTH_INVALID_TOKEN();
|
|
2380
|
-
if (!ctx.options.auth.providers.includes(claims.provider)) {
|
|
2381
|
-
throw ServerError_default.AUTH_INVALID_PROVIDER({
|
|
2382
|
-
provider: claims.provider,
|
|
2383
|
-
valid: ctx.options.auth.providers
|
|
2384
|
-
});
|
|
2385
|
-
}
|
|
2386
|
-
const exposed = await ctx.options.auth.onUser(claims, ctx);
|
|
2387
|
-
assertUser(exposed, "onUser");
|
|
2388
|
-
return exposed;
|
|
2389
|
-
}
|
|
2390
|
-
async function getAuthSession(ctx) {
|
|
2391
|
-
const id = findSessionId(ctx);
|
|
2392
|
-
if (!id) return;
|
|
2393
|
-
const session = await ctx.options.auth.sessions.get(id);
|
|
2394
|
-
return session?.user ? session : void 0;
|
|
2395
|
-
}
|
|
2396
|
-
async function getUser(ctx) {
|
|
2397
|
-
if (!ctx.options.auth) return;
|
|
2398
|
-
const options = ctx.options.auth;
|
|
2399
|
-
if (options.strategy.includes("jwt")) return getJwtUser(ctx);
|
|
2400
|
-
const auth2 = await getAuthSession(ctx);
|
|
2401
|
-
if (!auth2) return;
|
|
2402
|
-
if (!options.providers.includes(auth2.provider)) {
|
|
2403
|
-
throw ServerError_default.AUTH_INVALID_PROVIDER({
|
|
2404
|
-
provider: auth2.provider,
|
|
2405
|
-
valid: options.providers
|
|
2406
|
-
});
|
|
2407
|
-
}
|
|
2408
|
-
const user = await options.users.get(auth2.user);
|
|
2409
|
-
if (!user) throw ServerError_default.AUTH_NO_USER();
|
|
2410
|
-
const exposed = await options.onUser(user, ctx);
|
|
2411
|
-
assertUser(exposed, "onUser");
|
|
2412
|
-
return exposed;
|
|
2413
|
-
}
|
|
2414
|
-
|
|
2415
|
-
// src/auth/logout.ts
|
|
2416
|
-
async function logout(ctx) {
|
|
2417
|
-
const { strategy } = ctx.options.auth;
|
|
2418
|
-
if (!strategy.includes("jwt")) {
|
|
2419
|
-
const prev = findSessionId(ctx);
|
|
2420
|
-
if (prev) await ctx.options.auth.sessions.del(prev);
|
|
2421
|
-
}
|
|
2422
|
-
if (ctx.options.auth.onLogout) await ctx.options.auth.onLogout(ctx);
|
|
2423
|
-
if (strategy.includes("token") || strategy.includes("jwt")) {
|
|
2424
|
-
return { token: null };
|
|
2425
|
-
}
|
|
2426
|
-
if (strategy.includes("cookie")) {
|
|
2427
|
-
return cookies({ session: null }).redirect("/");
|
|
2428
|
-
}
|
|
2429
|
-
throw new Error("Unknown auth type");
|
|
2430
|
-
}
|
|
2431
|
-
|
|
2432
2394
|
// src/auth/index.ts
|
|
2433
|
-
var oauth2 = [
|
|
2434
|
-
"github",
|
|
2435
|
-
"google",
|
|
2436
|
-
"microsoft",
|
|
2437
|
-
"discord",
|
|
2438
|
-
"facebook"
|
|
2439
|
-
];
|
|
2440
2395
|
function auth(app) {
|
|
2396
|
+
const entries = app.settings.auth;
|
|
2441
2397
|
app.use(async function middle(ctx) {
|
|
2442
|
-
|
|
2398
|
+
for (const entry3 of entries) {
|
|
2399
|
+
const user = await entry3.user(ctx);
|
|
2400
|
+
if (user) {
|
|
2401
|
+
ctx.user = user;
|
|
2402
|
+
return;
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2443
2405
|
});
|
|
2444
|
-
const
|
|
2445
|
-
app.post("/auth/logout", spec, logout);
|
|
2446
|
-
const enabled = app.settings.auth.providers;
|
|
2447
|
-
for (const name of oauth2) {
|
|
2448
|
-
if (!enabled.includes(name)) continue;
|
|
2449
|
-
const key = name.toUpperCase();
|
|
2450
|
-
if (!env[`${key}_ID`]) throw new Error(`${key}_ID not defined`);
|
|
2451
|
-
if (!env[`${key}_SECRET`]) throw new Error(`${key}_SECRET not defined`);
|
|
2452
|
-
app.get(`/auth/login/${name}`, spec, providers_default[name].login);
|
|
2453
|
-
app.get(`/auth/callback/${name}`, spec, providers_default[name].callback);
|
|
2454
|
-
app.post(`/auth/verify/${name}`, spec, providers_default[name].verify);
|
|
2455
|
-
}
|
|
2456
|
-
if (enabled.includes("apple")) {
|
|
2457
|
-
const keys = ["APPLE_ID", "APPLE_TEAM_ID", "APPLE_KEY_ID", "APPLE_PRIVATE_KEY"];
|
|
2458
|
-
for (const key of keys) {
|
|
2459
|
-
if (!env[key]) throw new Error(`${key} not defined`);
|
|
2460
|
-
}
|
|
2461
|
-
app.get("/auth/login/apple", spec, providers_default.apple.login);
|
|
2462
|
-
app.post("/auth/callback/apple", spec, providers_default.apple.callback);
|
|
2463
|
-
app.post("/auth/verify/apple", spec, providers_default.apple.verify);
|
|
2464
|
-
}
|
|
2465
|
-
if (enabled.includes("email")) {
|
|
2466
|
-
app.post("/auth/register/email", spec, providers_default.email.register);
|
|
2467
|
-
app.post("/auth/login/email", spec, providers_default.email.login);
|
|
2468
|
-
app.put("/auth/password/email", spec, providers_default.email.password);
|
|
2469
|
-
app.put("/auth/reset/email", spec, providers_default.email.reset);
|
|
2470
|
-
}
|
|
2406
|
+
for (const entry3 of entries) entry3.routes?.(app);
|
|
2471
2407
|
}
|
|
2472
2408
|
|
|
2473
2409
|
// src/helpers/parseRange.ts
|
|
@@ -2503,35 +2439,35 @@ async function assets(ctx) {
|
|
|
2503
2439
|
const key = ctx.url.pathname.replace(/^\/+/, "");
|
|
2504
2440
|
const file2 = ctx.options.public.file(key);
|
|
2505
2441
|
const info = file2.info?.bind(file2);
|
|
2506
|
-
const
|
|
2507
|
-
if (info ? !
|
|
2442
|
+
const meta2 = info ? await info() : null;
|
|
2443
|
+
if (info ? !meta2 : !await file2.exists()) return;
|
|
2508
2444
|
const ext = ctx.url.pathname.split(".").pop()?.toLowerCase();
|
|
2509
|
-
const ctype = ext && mimes_default[ext] ||
|
|
2445
|
+
const ctype = ext && mimes_default[ext] || meta2?.type || ext;
|
|
2510
2446
|
const headers2 = { "cache-control": CACHE_CONTROL };
|
|
2511
2447
|
let tag;
|
|
2512
|
-
if (
|
|
2513
|
-
const stamp =
|
|
2514
|
-
tag = `W/"${
|
|
2448
|
+
if (meta2) {
|
|
2449
|
+
const stamp = meta2.modified ? meta2.modified.getTime() : 0;
|
|
2450
|
+
tag = `W/"${meta2.size.toString(16)}-${stamp.toString(16)}"`;
|
|
2515
2451
|
headers2.etag = tag;
|
|
2516
|
-
if (
|
|
2452
|
+
if (meta2.modified) headers2["last-modified"] = meta2.modified.toUTCString();
|
|
2517
2453
|
}
|
|
2518
|
-
const canRange = !!(
|
|
2454
|
+
const canRange = !!(meta2 && file2.slice);
|
|
2519
2455
|
if (canRange) headers2["accept-ranges"] = "bytes";
|
|
2520
2456
|
if (tag && ctx.headers["if-none-match"] === tag) {
|
|
2521
2457
|
return status(304).headers(headers2).send();
|
|
2522
2458
|
}
|
|
2523
2459
|
const rangeHeader = ctx.headers.range;
|
|
2524
2460
|
const ifRange = ctx.headers["if-range"];
|
|
2525
|
-
if (
|
|
2526
|
-
const range = parseRange(rangeHeader,
|
|
2461
|
+
if (meta2 && file2.slice && rangeHeader && (!ifRange || ifRange === tag)) {
|
|
2462
|
+
const range = parseRange(rangeHeader, meta2.size);
|
|
2527
2463
|
if (range === "unsatisfiable") {
|
|
2528
|
-
return status(416).headers({ ...headers2, "content-range": `bytes */${
|
|
2464
|
+
return status(416).headers({ ...headers2, "content-range": `bytes */${meta2.size}` }).send();
|
|
2529
2465
|
}
|
|
2530
2466
|
if (range) {
|
|
2531
2467
|
const { start, end } = range;
|
|
2532
2468
|
return type(ctype).status(206).headers({
|
|
2533
2469
|
...headers2,
|
|
2534
|
-
"content-range": `bytes ${start}-${end}/${
|
|
2470
|
+
"content-range": `bytes ${start}-${end}/${meta2.size}`,
|
|
2535
2471
|
"content-length": String(end - start + 1)
|
|
2536
2472
|
}).send(file2.slice(start, end + 1).stream());
|
|
2537
2473
|
}
|
|
@@ -2602,7 +2538,7 @@ var generateOpenApiPaths = async (handlers, specPath) => {
|
|
|
2602
2538
|
for (const [method, routes] of Object.entries(handlers)) {
|
|
2603
2539
|
for (const route of routes) {
|
|
2604
2540
|
const path = route.path;
|
|
2605
|
-
const
|
|
2541
|
+
const meta2 = route.options ?? {};
|
|
2606
2542
|
const config2 = getConfig(route.options?.schema);
|
|
2607
2543
|
if (typeof path !== "string" || path === "*" || path === specPath) {
|
|
2608
2544
|
continue;
|
|
@@ -2613,13 +2549,13 @@ var generateOpenApiPaths = async (handlers, specPath) => {
|
|
|
2613
2549
|
paths[normalizedPath] = {};
|
|
2614
2550
|
}
|
|
2615
2551
|
let requestBody;
|
|
2616
|
-
if (
|
|
2617
|
-
const schema = await toJsonSchema(
|
|
2552
|
+
if (meta2?.body) {
|
|
2553
|
+
const schema = await toJsonSchema(meta2.body);
|
|
2618
2554
|
requestBody = { content: { "application/json": { schema } } };
|
|
2619
2555
|
}
|
|
2620
2556
|
let responses;
|
|
2621
|
-
if (
|
|
2622
|
-
const schema = await toJsonSchema(
|
|
2557
|
+
if (meta2?.response) {
|
|
2558
|
+
const schema = await toJsonSchema(meta2.response);
|
|
2623
2559
|
responses = {
|
|
2624
2560
|
200: { description: "OK", content: { "application/json": { schema } } }
|
|
2625
2561
|
};
|
|
@@ -2635,8 +2571,8 @@ var generateOpenApiPaths = async (handlers, specPath) => {
|
|
|
2635
2571
|
schema: { type: type2 }
|
|
2636
2572
|
});
|
|
2637
2573
|
});
|
|
2638
|
-
if (
|
|
2639
|
-
const schema = await toJsonSchema(
|
|
2574
|
+
if (meta2?.query) {
|
|
2575
|
+
const schema = await toJsonSchema(meta2.query);
|
|
2640
2576
|
for (const [name, prop] of Object.entries(schema.properties ?? {})) {
|
|
2641
2577
|
parameters.push({
|
|
2642
2578
|
name,
|
|
@@ -2709,7 +2645,10 @@ function timer(ctx) {
|
|
|
2709
2645
|
async function socketUser(app, headers2, cookies2) {
|
|
2710
2646
|
if (!app.settings.auth) return void 0;
|
|
2711
2647
|
const ctx = { options: app.settings, headers: headers2, cookies: cookies2 };
|
|
2712
|
-
|
|
2648
|
+
for (const entry3 of app.settings.auth) {
|
|
2649
|
+
const user = await entry3.user(ctx);
|
|
2650
|
+
if (user) return user;
|
|
2651
|
+
}
|
|
2713
2652
|
}
|
|
2714
2653
|
|
|
2715
2654
|
// src/helpers/wsNode.ts
|
|
@@ -3208,8 +3147,7 @@ function ServerTest(app) {
|
|
|
3208
3147
|
}
|
|
3209
3148
|
|
|
3210
3149
|
// src/index.ts
|
|
3211
|
-
import { default as default2 } from "
|
|
3212
|
-
import { default as default3 } from "bucket";
|
|
3150
|
+
import { default as default2 } from "bucket";
|
|
3213
3151
|
var Server = class extends Router {
|
|
3214
3152
|
settings;
|
|
3215
3153
|
platform;
|
|
@@ -3274,7 +3212,7 @@ export {
|
|
|
3274
3212
|
Server,
|
|
3275
3213
|
ServerError_default as ServerError,
|
|
3276
3214
|
ValidationError,
|
|
3277
|
-
|
|
3215
|
+
default2 as bucket,
|
|
3278
3216
|
cache,
|
|
3279
3217
|
cookies,
|
|
3280
3218
|
server as default,
|
|
@@ -3282,7 +3220,6 @@ export {
|
|
|
3282
3220
|
file,
|
|
3283
3221
|
headers,
|
|
3284
3222
|
json,
|
|
3285
|
-
default2 as kv,
|
|
3286
3223
|
redirect,
|
|
3287
3224
|
router,
|
|
3288
3225
|
send,
|