kavachos 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/a2a/index.d.ts +1 -1
- package/dist/agent/index.d.ts +2 -2
- package/dist/audit/index.d.ts +1 -1
- package/dist/auth/index.d.ts +2 -2
- package/dist/auth/index.js +716 -755
- package/dist/auth/index.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +713 -648
- package/dist/index.js.map +1 -1
- package/dist/permission/index.d.ts +2 -2
- package/dist/{types-5Ua5KlPc.d.ts → types-B02D3kZy.d.ts} +2 -0
- package/package.json +1 -1
package/dist/auth/index.js
CHANGED
|
@@ -954,7 +954,7 @@ function createAdminModule(config, db, sessionManager) {
|
|
|
954
954
|
const url = new URL(request.url);
|
|
955
955
|
const { pathname } = url;
|
|
956
956
|
const { method } = request;
|
|
957
|
-
const
|
|
957
|
+
const json2 = (data, status = 200) => new Response(JSON.stringify(data), {
|
|
958
958
|
status,
|
|
959
959
|
headers: { "Content-Type": "application/json" }
|
|
960
960
|
});
|
|
@@ -963,14 +963,14 @@ function createAdminModule(config, db, sessionManager) {
|
|
|
963
963
|
const offset = url.searchParams.get("offset") ? Number(url.searchParams.get("offset")) : void 0;
|
|
964
964
|
const search = url.searchParams.get("search") ?? void 0;
|
|
965
965
|
const result = await listUsers({ limit, offset, search });
|
|
966
|
-
return
|
|
966
|
+
return json2(result);
|
|
967
967
|
}
|
|
968
968
|
const userMatch = /^\/auth\/admin\/users\/([^/]+)$/.exec(pathname);
|
|
969
969
|
if (method === "GET" && userMatch) {
|
|
970
970
|
const userId = decodeURIComponent(userMatch[1] ?? "");
|
|
971
971
|
const user = await getUser(userId);
|
|
972
|
-
if (!user) return
|
|
973
|
-
return
|
|
972
|
+
if (!user) return json2({ error: "User not found" }, 404);
|
|
973
|
+
return json2(user);
|
|
974
974
|
}
|
|
975
975
|
const banMatch = /^\/auth\/admin\/users\/([^/]+)\/ban$/.exec(pathname);
|
|
976
976
|
if (method === "POST" && banMatch) {
|
|
@@ -983,19 +983,19 @@ function createAdminModule(config, db, sessionManager) {
|
|
|
983
983
|
const reason = typeof body.reason === "string" ? body.reason : void 0;
|
|
984
984
|
const expiresAt = body.expiresAt ? new Date(body.expiresAt) : void 0;
|
|
985
985
|
await banUser(userId, reason, expiresAt);
|
|
986
|
-
return
|
|
986
|
+
return json2({ success: true });
|
|
987
987
|
}
|
|
988
988
|
const unbanMatch = /^\/auth\/admin\/users\/([^/]+)\/unban$/.exec(pathname);
|
|
989
989
|
if (method === "POST" && unbanMatch) {
|
|
990
990
|
const userId = decodeURIComponent(unbanMatch[1] ?? "");
|
|
991
991
|
await unbanUser(userId);
|
|
992
|
-
return
|
|
992
|
+
return json2({ success: true });
|
|
993
993
|
}
|
|
994
994
|
const deleteMatch = /^\/auth\/admin\/users\/([^/]+)$/.exec(pathname);
|
|
995
995
|
if (method === "DELETE" && deleteMatch) {
|
|
996
996
|
const userId = decodeURIComponent(deleteMatch[1] ?? "");
|
|
997
997
|
await deleteUser(userId);
|
|
998
|
-
return
|
|
998
|
+
return json2({ success: true });
|
|
999
999
|
}
|
|
1000
1000
|
const impersonateMatch = /^\/auth\/admin\/impersonate\/([^/]+)$/.exec(pathname);
|
|
1001
1001
|
if (method === "POST" && impersonateMatch) {
|
|
@@ -1004,28 +1004,28 @@ function createAdminModule(config, db, sessionManager) {
|
|
|
1004
1004
|
try {
|
|
1005
1005
|
body = await request.json();
|
|
1006
1006
|
} catch {
|
|
1007
|
-
return
|
|
1007
|
+
return json2({ error: "Invalid JSON body" }, 400);
|
|
1008
1008
|
}
|
|
1009
1009
|
const adminUserId = body.adminUserId;
|
|
1010
1010
|
if (typeof adminUserId !== "string") {
|
|
1011
|
-
return
|
|
1011
|
+
return json2({ error: "Missing required field: adminUserId" }, 400);
|
|
1012
1012
|
}
|
|
1013
1013
|
try {
|
|
1014
1014
|
const result = await impersonate(adminUserId, targetUserId);
|
|
1015
|
-
return
|
|
1015
|
+
return json2(result);
|
|
1016
1016
|
} catch (err2) {
|
|
1017
|
-
return
|
|
1017
|
+
return json2({ error: err2 instanceof Error ? err2.message : "Unknown error" }, 403);
|
|
1018
1018
|
}
|
|
1019
1019
|
}
|
|
1020
1020
|
if (method === "POST" && pathname === "/auth/admin/stop-impersonation") {
|
|
1021
1021
|
const auth = request.headers.get("Authorization");
|
|
1022
1022
|
const token = auth?.startsWith("Bearer ") ? auth.slice(7) : null;
|
|
1023
|
-
if (!token) return
|
|
1023
|
+
if (!token) return json2({ error: "Missing Authorization header" }, 401);
|
|
1024
1024
|
try {
|
|
1025
1025
|
await stopImpersonation(token);
|
|
1026
|
-
return
|
|
1026
|
+
return json2({ success: true });
|
|
1027
1027
|
} catch (err2) {
|
|
1028
|
-
return
|
|
1028
|
+
return json2({ error: err2 instanceof Error ? err2.message : "Unknown error" }, 400);
|
|
1029
1029
|
}
|
|
1030
1030
|
}
|
|
1031
1031
|
return null;
|
|
@@ -1044,6 +1044,210 @@ function createAdminModule(config, db, sessionManager) {
|
|
|
1044
1044
|
};
|
|
1045
1045
|
}
|
|
1046
1046
|
|
|
1047
|
+
// src/plugin/helpers.ts
|
|
1048
|
+
function json(body, status = 200) {
|
|
1049
|
+
return new Response(JSON.stringify(body), {
|
|
1050
|
+
status,
|
|
1051
|
+
headers: { "Content-Type": "application/json" }
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
async function parseBody(request) {
|
|
1055
|
+
try {
|
|
1056
|
+
const data = await request.json();
|
|
1057
|
+
return { ok: true, data };
|
|
1058
|
+
} catch {
|
|
1059
|
+
return {
|
|
1060
|
+
ok: false,
|
|
1061
|
+
response: json({ error: "Invalid JSON body" }, 400)
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
function buildSetCookie(name, value, maxAge, path = "/") {
|
|
1066
|
+
return `${name}=${encodeURIComponent(value)}; HttpOnly; Secure; SameSite=Lax; Path=${path}; Max-Age=${maxAge}`;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
// src/auth/admin-plugin.ts
|
|
1070
|
+
function admin(config) {
|
|
1071
|
+
return {
|
|
1072
|
+
id: "kavach-admin",
|
|
1073
|
+
async init(ctx) {
|
|
1074
|
+
const module = createAdminModule(config ?? {}, ctx.db, ctx.sessionManager ?? null);
|
|
1075
|
+
ctx.addEndpoint({
|
|
1076
|
+
method: "GET",
|
|
1077
|
+
path: "/auth/admin/users",
|
|
1078
|
+
metadata: {
|
|
1079
|
+
requireAuth: true,
|
|
1080
|
+
description: "List all users (admin only)"
|
|
1081
|
+
},
|
|
1082
|
+
async handler(request, endpointCtx) {
|
|
1083
|
+
const user = await endpointCtx.getUser(request);
|
|
1084
|
+
if (!user) {
|
|
1085
|
+
return json({ error: "Authentication required" }, 401);
|
|
1086
|
+
}
|
|
1087
|
+
const isAdminUser = await module.isAdmin(user.id);
|
|
1088
|
+
if (!isAdminUser) {
|
|
1089
|
+
return json({ error: "Admin access required" }, 403);
|
|
1090
|
+
}
|
|
1091
|
+
const url = new URL(request.url);
|
|
1092
|
+
const limitParam = url.searchParams.get("limit");
|
|
1093
|
+
const offsetParam = url.searchParams.get("offset");
|
|
1094
|
+
const search = url.searchParams.get("search") ?? void 0;
|
|
1095
|
+
const result = await module.listUsers({
|
|
1096
|
+
limit: limitParam ? Number(limitParam) : void 0,
|
|
1097
|
+
offset: offsetParam ? Number(offsetParam) : void 0,
|
|
1098
|
+
search
|
|
1099
|
+
});
|
|
1100
|
+
return json(result);
|
|
1101
|
+
}
|
|
1102
|
+
});
|
|
1103
|
+
ctx.addEndpoint({
|
|
1104
|
+
method: "GET",
|
|
1105
|
+
path: "/auth/admin/users/:id",
|
|
1106
|
+
metadata: {
|
|
1107
|
+
requireAuth: true,
|
|
1108
|
+
description: "Get a user by ID (admin only)"
|
|
1109
|
+
},
|
|
1110
|
+
async handler(request, endpointCtx) {
|
|
1111
|
+
const user = await endpointCtx.getUser(request);
|
|
1112
|
+
if (!user) {
|
|
1113
|
+
return json({ error: "Authentication required" }, 401);
|
|
1114
|
+
}
|
|
1115
|
+
const isAdminUser = await module.isAdmin(user.id);
|
|
1116
|
+
if (!isAdminUser) {
|
|
1117
|
+
return json({ error: "Admin access required" }, 403);
|
|
1118
|
+
}
|
|
1119
|
+
const url = new URL(request.url);
|
|
1120
|
+
const segments = url.pathname.split("/").filter(Boolean);
|
|
1121
|
+
const targetId = segments[3];
|
|
1122
|
+
if (!targetId) {
|
|
1123
|
+
return json({ error: "Missing user ID in path" }, 400);
|
|
1124
|
+
}
|
|
1125
|
+
const found = await module.getUser(decodeURIComponent(targetId));
|
|
1126
|
+
if (!found) {
|
|
1127
|
+
return json({ error: "User not found" }, 404);
|
|
1128
|
+
}
|
|
1129
|
+
return json(found);
|
|
1130
|
+
}
|
|
1131
|
+
});
|
|
1132
|
+
ctx.addEndpoint({
|
|
1133
|
+
method: "POST",
|
|
1134
|
+
path: "/auth/admin/users/:id/ban",
|
|
1135
|
+
metadata: {
|
|
1136
|
+
requireAuth: true,
|
|
1137
|
+
description: "Ban a user (admin only)"
|
|
1138
|
+
},
|
|
1139
|
+
async handler(request, endpointCtx) {
|
|
1140
|
+
const user = await endpointCtx.getUser(request);
|
|
1141
|
+
if (!user) {
|
|
1142
|
+
return json({ error: "Authentication required" }, 401);
|
|
1143
|
+
}
|
|
1144
|
+
const isAdminUser = await module.isAdmin(user.id);
|
|
1145
|
+
if (!isAdminUser) {
|
|
1146
|
+
return json({ error: "Admin access required" }, 403);
|
|
1147
|
+
}
|
|
1148
|
+
const url = new URL(request.url);
|
|
1149
|
+
const segments = url.pathname.split("/").filter(Boolean);
|
|
1150
|
+
const targetId = segments[3];
|
|
1151
|
+
if (!targetId) {
|
|
1152
|
+
return json({ error: "Missing user ID in path" }, 400);
|
|
1153
|
+
}
|
|
1154
|
+
const bodyResult = await parseBody(request);
|
|
1155
|
+
if (!bodyResult.ok) return bodyResult.response;
|
|
1156
|
+
const reason = typeof bodyResult.data.reason === "string" ? bodyResult.data.reason : void 0;
|
|
1157
|
+
const expiresAt = bodyResult.data.expiresAt ? new Date(bodyResult.data.expiresAt) : void 0;
|
|
1158
|
+
await module.banUser(decodeURIComponent(targetId), reason, expiresAt);
|
|
1159
|
+
return json({ success: true });
|
|
1160
|
+
}
|
|
1161
|
+
});
|
|
1162
|
+
ctx.addEndpoint({
|
|
1163
|
+
method: "POST",
|
|
1164
|
+
path: "/auth/admin/users/:id/unban",
|
|
1165
|
+
metadata: {
|
|
1166
|
+
requireAuth: true,
|
|
1167
|
+
description: "Unban a user (admin only)"
|
|
1168
|
+
},
|
|
1169
|
+
async handler(request, endpointCtx) {
|
|
1170
|
+
const user = await endpointCtx.getUser(request);
|
|
1171
|
+
if (!user) {
|
|
1172
|
+
return json({ error: "Authentication required" }, 401);
|
|
1173
|
+
}
|
|
1174
|
+
const isAdminUser = await module.isAdmin(user.id);
|
|
1175
|
+
if (!isAdminUser) {
|
|
1176
|
+
return json({ error: "Admin access required" }, 403);
|
|
1177
|
+
}
|
|
1178
|
+
const url = new URL(request.url);
|
|
1179
|
+
const segments = url.pathname.split("/").filter(Boolean);
|
|
1180
|
+
const targetId = segments[3];
|
|
1181
|
+
if (!targetId) {
|
|
1182
|
+
return json({ error: "Missing user ID in path" }, 400);
|
|
1183
|
+
}
|
|
1184
|
+
await module.unbanUser(decodeURIComponent(targetId));
|
|
1185
|
+
return json({ success: true });
|
|
1186
|
+
}
|
|
1187
|
+
});
|
|
1188
|
+
ctx.addEndpoint({
|
|
1189
|
+
method: "DELETE",
|
|
1190
|
+
path: "/auth/admin/users/:id",
|
|
1191
|
+
metadata: {
|
|
1192
|
+
requireAuth: true,
|
|
1193
|
+
description: "Delete a user (admin only)"
|
|
1194
|
+
},
|
|
1195
|
+
async handler(request, endpointCtx) {
|
|
1196
|
+
const user = await endpointCtx.getUser(request);
|
|
1197
|
+
if (!user) {
|
|
1198
|
+
return json({ error: "Authentication required" }, 401);
|
|
1199
|
+
}
|
|
1200
|
+
const isAdminUser = await module.isAdmin(user.id);
|
|
1201
|
+
if (!isAdminUser) {
|
|
1202
|
+
return json({ error: "Admin access required" }, 403);
|
|
1203
|
+
}
|
|
1204
|
+
const url = new URL(request.url);
|
|
1205
|
+
const segments = url.pathname.split("/").filter(Boolean);
|
|
1206
|
+
const targetId = segments[3];
|
|
1207
|
+
if (!targetId) {
|
|
1208
|
+
return json({ error: "Missing user ID in path" }, 400);
|
|
1209
|
+
}
|
|
1210
|
+
await module.deleteUser(decodeURIComponent(targetId));
|
|
1211
|
+
return json({ success: true });
|
|
1212
|
+
}
|
|
1213
|
+
});
|
|
1214
|
+
ctx.addEndpoint({
|
|
1215
|
+
method: "POST",
|
|
1216
|
+
path: "/auth/admin/users/:id/impersonate",
|
|
1217
|
+
metadata: {
|
|
1218
|
+
requireAuth: true,
|
|
1219
|
+
description: "Start an impersonation session as a target user (admin only)"
|
|
1220
|
+
},
|
|
1221
|
+
async handler(request, endpointCtx) {
|
|
1222
|
+
const user = await endpointCtx.getUser(request);
|
|
1223
|
+
if (!user) {
|
|
1224
|
+
return json({ error: "Authentication required" }, 401);
|
|
1225
|
+
}
|
|
1226
|
+
const isAdminUser = await module.isAdmin(user.id);
|
|
1227
|
+
if (!isAdminUser) {
|
|
1228
|
+
return json({ error: "Admin access required" }, 403);
|
|
1229
|
+
}
|
|
1230
|
+
const url = new URL(request.url);
|
|
1231
|
+
const segments = url.pathname.split("/").filter(Boolean);
|
|
1232
|
+
const targetId = segments[3];
|
|
1233
|
+
if (!targetId) {
|
|
1234
|
+
return json({ error: "Missing user ID in path" }, 400);
|
|
1235
|
+
}
|
|
1236
|
+
try {
|
|
1237
|
+
const result = await module.impersonate(user.id, decodeURIComponent(targetId));
|
|
1238
|
+
return json(result);
|
|
1239
|
+
} catch (err2) {
|
|
1240
|
+
return json(
|
|
1241
|
+
{ error: err2 instanceof Error ? err2.message : "Impersonation failed" },
|
|
1242
|
+
403
|
|
1243
|
+
);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1047
1251
|
// src/crypto/web-crypto.ts
|
|
1048
1252
|
var HEX_CHARS = "0123456789abcdef";
|
|
1049
1253
|
function toHex(bytes) {
|
|
@@ -1222,272 +1426,7 @@ function constantTimeEqual(a, b) {
|
|
|
1222
1426
|
return diff === 0;
|
|
1223
1427
|
}
|
|
1224
1428
|
|
|
1225
|
-
// src/
|
|
1226
|
-
var DEFAULT_MAX_AGE_SECONDS = 60 * 60 * 24 * 7;
|
|
1227
|
-
function createSessionManager(config, db) {
|
|
1228
|
-
if (!config.secret || config.secret.length < 32) {
|
|
1229
|
-
throw new Error("SessionManager: secret must be at least 32 characters.");
|
|
1230
|
-
}
|
|
1231
|
-
const maxAge = config.maxAge ?? DEFAULT_MAX_AGE_SECONDS;
|
|
1232
|
-
const keyObject = new TextEncoder().encode(config.secret);
|
|
1233
|
-
function rowToSession2(row) {
|
|
1234
|
-
return {
|
|
1235
|
-
id: row.id,
|
|
1236
|
-
userId: row.userId,
|
|
1237
|
-
expiresAt: row.expiresAt,
|
|
1238
|
-
createdAt: row.createdAt,
|
|
1239
|
-
...row.metadata !== null && { metadata: row.metadata }
|
|
1240
|
-
};
|
|
1241
|
-
}
|
|
1242
|
-
async function create(userId, metadata) {
|
|
1243
|
-
const id = generateId();
|
|
1244
|
-
const now = /* @__PURE__ */ new Date();
|
|
1245
|
-
const expiresAt = new Date(now.getTime() + maxAge * 1e3);
|
|
1246
|
-
await db.insert(sessions).values({
|
|
1247
|
-
id,
|
|
1248
|
-
userId,
|
|
1249
|
-
expiresAt,
|
|
1250
|
-
metadata: metadata ?? null,
|
|
1251
|
-
createdAt: now
|
|
1252
|
-
});
|
|
1253
|
-
const token = await new SignJWT({ sub: id }).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime(Math.floor(expiresAt.getTime() / 1e3)).sign(keyObject);
|
|
1254
|
-
const session = {
|
|
1255
|
-
id,
|
|
1256
|
-
userId,
|
|
1257
|
-
expiresAt,
|
|
1258
|
-
createdAt: now,
|
|
1259
|
-
...metadata !== void 0 && { metadata }
|
|
1260
|
-
};
|
|
1261
|
-
return { session, token };
|
|
1262
|
-
}
|
|
1263
|
-
async function validate(token) {
|
|
1264
|
-
let sessionId;
|
|
1265
|
-
try {
|
|
1266
|
-
const { payload } = await jwtVerify(token, keyObject);
|
|
1267
|
-
if (typeof payload.sub !== "string" || !payload.sub) return null;
|
|
1268
|
-
sessionId = payload.sub;
|
|
1269
|
-
} catch {
|
|
1270
|
-
return null;
|
|
1271
|
-
}
|
|
1272
|
-
const now = /* @__PURE__ */ new Date();
|
|
1273
|
-
const rows = await db.select().from(sessions).where(and(eq(sessions.id, sessionId)));
|
|
1274
|
-
const row = rows[0];
|
|
1275
|
-
if (!row) return null;
|
|
1276
|
-
if (row.expiresAt <= now) {
|
|
1277
|
-
await db.delete(sessions).where(eq(sessions.id, sessionId));
|
|
1278
|
-
return null;
|
|
1279
|
-
}
|
|
1280
|
-
return rowToSession2(row);
|
|
1281
|
-
}
|
|
1282
|
-
async function revoke(sessionId) {
|
|
1283
|
-
await db.delete(sessions).where(eq(sessions.id, sessionId));
|
|
1284
|
-
}
|
|
1285
|
-
async function revokeAll(userId) {
|
|
1286
|
-
await db.delete(sessions).where(eq(sessions.userId, userId));
|
|
1287
|
-
}
|
|
1288
|
-
async function list(userId) {
|
|
1289
|
-
const now = /* @__PURE__ */ new Date();
|
|
1290
|
-
const rows = await db.select().from(sessions).where(and(eq(sessions.userId, userId)));
|
|
1291
|
-
return rows.filter((row) => row.expiresAt > now).sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()).map(rowToSession2);
|
|
1292
|
-
}
|
|
1293
|
-
return { create, validate, revoke, revokeAll, list };
|
|
1294
|
-
}
|
|
1295
|
-
|
|
1296
|
-
// src/auth/admin-plugin.ts
|
|
1297
|
-
function jsonResponse2(body, status = 200) {
|
|
1298
|
-
return new Response(JSON.stringify(body), {
|
|
1299
|
-
status,
|
|
1300
|
-
headers: { "Content-Type": "application/json" }
|
|
1301
|
-
});
|
|
1302
|
-
}
|
|
1303
|
-
async function parseBody(request) {
|
|
1304
|
-
try {
|
|
1305
|
-
return await request.json();
|
|
1306
|
-
} catch {
|
|
1307
|
-
return {};
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
function admin(config) {
|
|
1311
|
-
return {
|
|
1312
|
-
id: "kavach-admin",
|
|
1313
|
-
async init(ctx) {
|
|
1314
|
-
const sessionConfig = ctx.config.auth?.session;
|
|
1315
|
-
const sessionManager = sessionConfig ? createSessionManager(sessionConfig, ctx.db) : null;
|
|
1316
|
-
const module = createAdminModule(config ?? {}, ctx.db, sessionManager);
|
|
1317
|
-
ctx.addEndpoint({
|
|
1318
|
-
method: "GET",
|
|
1319
|
-
path: "/auth/admin/users",
|
|
1320
|
-
metadata: {
|
|
1321
|
-
requireAuth: true,
|
|
1322
|
-
description: "List all users (admin only)"
|
|
1323
|
-
},
|
|
1324
|
-
async handler(request, endpointCtx) {
|
|
1325
|
-
const user = await endpointCtx.getUser(request);
|
|
1326
|
-
if (!user) {
|
|
1327
|
-
return jsonResponse2({ error: "Authentication required" }, 401);
|
|
1328
|
-
}
|
|
1329
|
-
const isAdminUser = await module.isAdmin(user.id);
|
|
1330
|
-
if (!isAdminUser) {
|
|
1331
|
-
return jsonResponse2({ error: "Admin access required" }, 403);
|
|
1332
|
-
}
|
|
1333
|
-
const url = new URL(request.url);
|
|
1334
|
-
const limitParam = url.searchParams.get("limit");
|
|
1335
|
-
const offsetParam = url.searchParams.get("offset");
|
|
1336
|
-
const search = url.searchParams.get("search") ?? void 0;
|
|
1337
|
-
const result = await module.listUsers({
|
|
1338
|
-
limit: limitParam ? Number(limitParam) : void 0,
|
|
1339
|
-
offset: offsetParam ? Number(offsetParam) : void 0,
|
|
1340
|
-
search
|
|
1341
|
-
});
|
|
1342
|
-
return jsonResponse2(result);
|
|
1343
|
-
}
|
|
1344
|
-
});
|
|
1345
|
-
ctx.addEndpoint({
|
|
1346
|
-
method: "GET",
|
|
1347
|
-
path: "/auth/admin/users/:id",
|
|
1348
|
-
metadata: {
|
|
1349
|
-
requireAuth: true,
|
|
1350
|
-
description: "Get a user by ID (admin only)"
|
|
1351
|
-
},
|
|
1352
|
-
async handler(request, endpointCtx) {
|
|
1353
|
-
const user = await endpointCtx.getUser(request);
|
|
1354
|
-
if (!user) {
|
|
1355
|
-
return jsonResponse2({ error: "Authentication required" }, 401);
|
|
1356
|
-
}
|
|
1357
|
-
const isAdminUser = await module.isAdmin(user.id);
|
|
1358
|
-
if (!isAdminUser) {
|
|
1359
|
-
return jsonResponse2({ error: "Admin access required" }, 403);
|
|
1360
|
-
}
|
|
1361
|
-
const url = new URL(request.url);
|
|
1362
|
-
const segments = url.pathname.split("/").filter(Boolean);
|
|
1363
|
-
const targetId = segments[3];
|
|
1364
|
-
if (!targetId) {
|
|
1365
|
-
return jsonResponse2({ error: "Missing user ID in path" }, 400);
|
|
1366
|
-
}
|
|
1367
|
-
const found = await module.getUser(decodeURIComponent(targetId));
|
|
1368
|
-
if (!found) {
|
|
1369
|
-
return jsonResponse2({ error: "User not found" }, 404);
|
|
1370
|
-
}
|
|
1371
|
-
return jsonResponse2(found);
|
|
1372
|
-
}
|
|
1373
|
-
});
|
|
1374
|
-
ctx.addEndpoint({
|
|
1375
|
-
method: "POST",
|
|
1376
|
-
path: "/auth/admin/users/:id/ban",
|
|
1377
|
-
metadata: {
|
|
1378
|
-
requireAuth: true,
|
|
1379
|
-
description: "Ban a user (admin only)"
|
|
1380
|
-
},
|
|
1381
|
-
async handler(request, endpointCtx) {
|
|
1382
|
-
const user = await endpointCtx.getUser(request);
|
|
1383
|
-
if (!user) {
|
|
1384
|
-
return jsonResponse2({ error: "Authentication required" }, 401);
|
|
1385
|
-
}
|
|
1386
|
-
const isAdminUser = await module.isAdmin(user.id);
|
|
1387
|
-
if (!isAdminUser) {
|
|
1388
|
-
return jsonResponse2({ error: "Admin access required" }, 403);
|
|
1389
|
-
}
|
|
1390
|
-
const url = new URL(request.url);
|
|
1391
|
-
const segments = url.pathname.split("/").filter(Boolean);
|
|
1392
|
-
const targetId = segments[3];
|
|
1393
|
-
if (!targetId) {
|
|
1394
|
-
return jsonResponse2({ error: "Missing user ID in path" }, 400);
|
|
1395
|
-
}
|
|
1396
|
-
const body = await parseBody(request);
|
|
1397
|
-
const reason = typeof body.reason === "string" ? body.reason : void 0;
|
|
1398
|
-
const expiresAt = body.expiresAt ? new Date(body.expiresAt) : void 0;
|
|
1399
|
-
await module.banUser(decodeURIComponent(targetId), reason, expiresAt);
|
|
1400
|
-
return jsonResponse2({ success: true });
|
|
1401
|
-
}
|
|
1402
|
-
});
|
|
1403
|
-
ctx.addEndpoint({
|
|
1404
|
-
method: "POST",
|
|
1405
|
-
path: "/auth/admin/users/:id/unban",
|
|
1406
|
-
metadata: {
|
|
1407
|
-
requireAuth: true,
|
|
1408
|
-
description: "Unban a user (admin only)"
|
|
1409
|
-
},
|
|
1410
|
-
async handler(request, endpointCtx) {
|
|
1411
|
-
const user = await endpointCtx.getUser(request);
|
|
1412
|
-
if (!user) {
|
|
1413
|
-
return jsonResponse2({ error: "Authentication required" }, 401);
|
|
1414
|
-
}
|
|
1415
|
-
const isAdminUser = await module.isAdmin(user.id);
|
|
1416
|
-
if (!isAdminUser) {
|
|
1417
|
-
return jsonResponse2({ error: "Admin access required" }, 403);
|
|
1418
|
-
}
|
|
1419
|
-
const url = new URL(request.url);
|
|
1420
|
-
const segments = url.pathname.split("/").filter(Boolean);
|
|
1421
|
-
const targetId = segments[3];
|
|
1422
|
-
if (!targetId) {
|
|
1423
|
-
return jsonResponse2({ error: "Missing user ID in path" }, 400);
|
|
1424
|
-
}
|
|
1425
|
-
await module.unbanUser(decodeURIComponent(targetId));
|
|
1426
|
-
return jsonResponse2({ success: true });
|
|
1427
|
-
}
|
|
1428
|
-
});
|
|
1429
|
-
ctx.addEndpoint({
|
|
1430
|
-
method: "DELETE",
|
|
1431
|
-
path: "/auth/admin/users/:id",
|
|
1432
|
-
metadata: {
|
|
1433
|
-
requireAuth: true,
|
|
1434
|
-
description: "Delete a user (admin only)"
|
|
1435
|
-
},
|
|
1436
|
-
async handler(request, endpointCtx) {
|
|
1437
|
-
const user = await endpointCtx.getUser(request);
|
|
1438
|
-
if (!user) {
|
|
1439
|
-
return jsonResponse2({ error: "Authentication required" }, 401);
|
|
1440
|
-
}
|
|
1441
|
-
const isAdminUser = await module.isAdmin(user.id);
|
|
1442
|
-
if (!isAdminUser) {
|
|
1443
|
-
return jsonResponse2({ error: "Admin access required" }, 403);
|
|
1444
|
-
}
|
|
1445
|
-
const url = new URL(request.url);
|
|
1446
|
-
const segments = url.pathname.split("/").filter(Boolean);
|
|
1447
|
-
const targetId = segments[3];
|
|
1448
|
-
if (!targetId) {
|
|
1449
|
-
return jsonResponse2({ error: "Missing user ID in path" }, 400);
|
|
1450
|
-
}
|
|
1451
|
-
await module.deleteUser(decodeURIComponent(targetId));
|
|
1452
|
-
return jsonResponse2({ success: true });
|
|
1453
|
-
}
|
|
1454
|
-
});
|
|
1455
|
-
ctx.addEndpoint({
|
|
1456
|
-
method: "POST",
|
|
1457
|
-
path: "/auth/admin/users/:id/impersonate",
|
|
1458
|
-
metadata: {
|
|
1459
|
-
requireAuth: true,
|
|
1460
|
-
description: "Start an impersonation session as a target user (admin only)"
|
|
1461
|
-
},
|
|
1462
|
-
async handler(request, endpointCtx) {
|
|
1463
|
-
const user = await endpointCtx.getUser(request);
|
|
1464
|
-
if (!user) {
|
|
1465
|
-
return jsonResponse2({ error: "Authentication required" }, 401);
|
|
1466
|
-
}
|
|
1467
|
-
const isAdminUser = await module.isAdmin(user.id);
|
|
1468
|
-
if (!isAdminUser) {
|
|
1469
|
-
return jsonResponse2({ error: "Admin access required" }, 403);
|
|
1470
|
-
}
|
|
1471
|
-
const url = new URL(request.url);
|
|
1472
|
-
const segments = url.pathname.split("/").filter(Boolean);
|
|
1473
|
-
const targetId = segments[3];
|
|
1474
|
-
if (!targetId) {
|
|
1475
|
-
return jsonResponse2({ error: "Missing user ID in path" }, 400);
|
|
1476
|
-
}
|
|
1477
|
-
try {
|
|
1478
|
-
const result = await module.impersonate(user.id, decodeURIComponent(targetId));
|
|
1479
|
-
return jsonResponse2(result);
|
|
1480
|
-
} catch (err2) {
|
|
1481
|
-
return jsonResponse2(
|
|
1482
|
-
{ error: err2 instanceof Error ? err2.message : "Impersonation failed" },
|
|
1483
|
-
403
|
|
1484
|
-
);
|
|
1485
|
-
}
|
|
1486
|
-
}
|
|
1487
|
-
});
|
|
1488
|
-
}
|
|
1489
|
-
};
|
|
1490
|
-
}
|
|
1429
|
+
// src/auth/anonymous.ts
|
|
1491
1430
|
var DEFAULT_SESSION_TTL_SECONDS = 60 * 60 * 24;
|
|
1492
1431
|
var DEFAULT_MAX_AGE_MS = 1e3 * 60 * 60 * 24;
|
|
1493
1432
|
var ANONYMOUS_EMAIL_DOMAIN = "kavachos.anonymous";
|
|
@@ -1557,29 +1496,14 @@ function createAnonymousAuthModule(config, db, sessionManager) {
|
|
|
1557
1496
|
}
|
|
1558
1497
|
|
|
1559
1498
|
// src/auth/anonymous-plugin.ts
|
|
1560
|
-
function jsonResponse3(body, status = 200) {
|
|
1561
|
-
return new Response(JSON.stringify(body), {
|
|
1562
|
-
status,
|
|
1563
|
-
headers: { "Content-Type": "application/json" }
|
|
1564
|
-
});
|
|
1565
|
-
}
|
|
1566
|
-
async function parseBody2(request) {
|
|
1567
|
-
try {
|
|
1568
|
-
return await request.json();
|
|
1569
|
-
} catch {
|
|
1570
|
-
return {};
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
1499
|
function anonymousAuth(config) {
|
|
1574
1500
|
return {
|
|
1575
1501
|
id: "kavach-anonymous",
|
|
1576
1502
|
async init(ctx) {
|
|
1577
|
-
|
|
1578
|
-
if (!sessionSecret) {
|
|
1503
|
+
if (!ctx.sessionManager) {
|
|
1579
1504
|
throw new Error("anonymousAuth plugin requires auth.session.secret to be configured");
|
|
1580
1505
|
}
|
|
1581
|
-
const
|
|
1582
|
-
const mod = createAnonymousAuthModule(config ?? {}, ctx.db, sessionManager);
|
|
1506
|
+
const mod = createAnonymousAuthModule(config ?? {}, ctx.db, ctx.sessionManager);
|
|
1583
1507
|
ctx.addEndpoint({
|
|
1584
1508
|
method: "POST",
|
|
1585
1509
|
path: "/auth/anonymous",
|
|
@@ -1590,9 +1514,9 @@ function anonymousAuth(config) {
|
|
|
1590
1514
|
async handler(_request, _endpointCtx) {
|
|
1591
1515
|
try {
|
|
1592
1516
|
const result = await mod.createAnonymousUser();
|
|
1593
|
-
return
|
|
1517
|
+
return json({ userId: result.userId, sessionToken: result.sessionToken });
|
|
1594
1518
|
} catch (err2) {
|
|
1595
|
-
return
|
|
1519
|
+
return json(
|
|
1596
1520
|
{ error: err2 instanceof Error ? err2.message : "Failed to create anonymous user" },
|
|
1597
1521
|
500
|
|
1598
1522
|
);
|
|
@@ -1609,21 +1533,22 @@ function anonymousAuth(config) {
|
|
|
1609
1533
|
async handler(request, endpointCtx) {
|
|
1610
1534
|
const user = await endpointCtx.getUser(request);
|
|
1611
1535
|
if (!user) {
|
|
1612
|
-
return
|
|
1536
|
+
return json({ error: "Authentication required" }, 401);
|
|
1613
1537
|
}
|
|
1614
|
-
const
|
|
1615
|
-
|
|
1616
|
-
const
|
|
1538
|
+
const bodyResult = await parseBody(request);
|
|
1539
|
+
if (!bodyResult.ok) return bodyResult.response;
|
|
1540
|
+
const email = typeof bodyResult.data.email === "string" ? bodyResult.data.email.trim() : null;
|
|
1541
|
+
const name = typeof bodyResult.data.name === "string" ? bodyResult.data.name.trim() : void 0;
|
|
1617
1542
|
if (!email) {
|
|
1618
|
-
return
|
|
1543
|
+
return json({ error: "Missing required field: email" }, 400);
|
|
1619
1544
|
}
|
|
1620
1545
|
try {
|
|
1621
1546
|
await mod.upgradeUser(user.id, { email, name });
|
|
1622
|
-
return
|
|
1547
|
+
return json({ upgraded: true });
|
|
1623
1548
|
} catch (err2) {
|
|
1624
1549
|
const message = err2 instanceof Error ? err2.message : "Upgrade failed";
|
|
1625
1550
|
const status = message.includes("not an anonymous user") ? 400 : 500;
|
|
1626
|
-
return
|
|
1551
|
+
return json({ error: message }, status);
|
|
1627
1552
|
}
|
|
1628
1553
|
}
|
|
1629
1554
|
});
|
|
@@ -1637,10 +1562,10 @@ function anonymousAuth(config) {
|
|
|
1637
1562
|
async handler(request, endpointCtx) {
|
|
1638
1563
|
const user = await endpointCtx.getUser(request);
|
|
1639
1564
|
if (!user) {
|
|
1640
|
-
return
|
|
1565
|
+
return json({ error: "Authentication required" }, 401);
|
|
1641
1566
|
}
|
|
1642
1567
|
const anonymous = await mod.isAnonymous(user.id);
|
|
1643
|
-
return
|
|
1568
|
+
return json({ anonymous });
|
|
1644
1569
|
}
|
|
1645
1570
|
});
|
|
1646
1571
|
}
|
|
@@ -1746,7 +1671,7 @@ function createApiKeyManagerModule(config, db) {
|
|
|
1746
1671
|
const url = new URL(request.url);
|
|
1747
1672
|
const { pathname } = url;
|
|
1748
1673
|
const { method } = request;
|
|
1749
|
-
const
|
|
1674
|
+
const json2 = (data, status = 200) => new Response(JSON.stringify(data), {
|
|
1750
1675
|
status,
|
|
1751
1676
|
headers: { "Content-Type": "application/json" }
|
|
1752
1677
|
});
|
|
@@ -1755,11 +1680,11 @@ function createApiKeyManagerModule(config, db) {
|
|
|
1755
1680
|
try {
|
|
1756
1681
|
body = await request.json();
|
|
1757
1682
|
} catch {
|
|
1758
|
-
return
|
|
1683
|
+
return json2({ error: "Invalid JSON body" }, 400);
|
|
1759
1684
|
}
|
|
1760
1685
|
const b = body;
|
|
1761
1686
|
if (typeof b.userId !== "string" || typeof b.name !== "string" || !Array.isArray(b.permissions)) {
|
|
1762
|
-
return
|
|
1687
|
+
return json2({ error: "Missing required fields: userId, name, permissions" }, 400);
|
|
1763
1688
|
}
|
|
1764
1689
|
const expiresAt = b.expiresAt ? new Date(b.expiresAt) : void 0;
|
|
1765
1690
|
const result = await create({
|
|
@@ -1768,28 +1693,28 @@ function createApiKeyManagerModule(config, db) {
|
|
|
1768
1693
|
permissions: b.permissions,
|
|
1769
1694
|
expiresAt
|
|
1770
1695
|
});
|
|
1771
|
-
return
|
|
1696
|
+
return json2(result, 201);
|
|
1772
1697
|
}
|
|
1773
1698
|
const listMatch = /^\/auth\/api-keys\/([^/]+)$/.exec(pathname);
|
|
1774
1699
|
if (method === "GET" && listMatch) {
|
|
1775
1700
|
const userId = decodeURIComponent(listMatch[1] ?? "");
|
|
1776
1701
|
const keys = await list(userId);
|
|
1777
|
-
return
|
|
1702
|
+
return json2(keys);
|
|
1778
1703
|
}
|
|
1779
1704
|
const deleteMatch = /^\/auth\/api-keys\/([^/]+)$/.exec(pathname);
|
|
1780
1705
|
if (method === "DELETE" && deleteMatch) {
|
|
1781
1706
|
const keyId = decodeURIComponent(deleteMatch[1] ?? "");
|
|
1782
1707
|
await revoke(keyId);
|
|
1783
|
-
return
|
|
1708
|
+
return json2({ success: true });
|
|
1784
1709
|
}
|
|
1785
1710
|
const rotateMatch = /^\/auth\/api-keys\/([^/]+)\/rotate$/.exec(pathname);
|
|
1786
1711
|
if (method === "POST" && rotateMatch) {
|
|
1787
1712
|
const keyId = decodeURIComponent(rotateMatch[1] ?? "");
|
|
1788
1713
|
try {
|
|
1789
1714
|
const result = await rotate(keyId);
|
|
1790
|
-
return
|
|
1715
|
+
return json2(result);
|
|
1791
1716
|
} catch (err2) {
|
|
1792
|
-
return
|
|
1717
|
+
return json2({ error: err2 instanceof Error ? err2.message : "Unknown error" }, 404);
|
|
1793
1718
|
}
|
|
1794
1719
|
}
|
|
1795
1720
|
return null;
|
|
@@ -1806,19 +1731,6 @@ function createApiKeyManagerModule(config, db) {
|
|
|
1806
1731
|
}
|
|
1807
1732
|
|
|
1808
1733
|
// src/auth/api-key-plugin.ts
|
|
1809
|
-
function jsonResponse4(body, status = 200) {
|
|
1810
|
-
return new Response(JSON.stringify(body), {
|
|
1811
|
-
status,
|
|
1812
|
-
headers: { "Content-Type": "application/json" }
|
|
1813
|
-
});
|
|
1814
|
-
}
|
|
1815
|
-
async function parseBody3(request) {
|
|
1816
|
-
try {
|
|
1817
|
-
return await request.json();
|
|
1818
|
-
} catch {
|
|
1819
|
-
return {};
|
|
1820
|
-
}
|
|
1821
|
-
}
|
|
1822
1734
|
function apiKeys2(config) {
|
|
1823
1735
|
return {
|
|
1824
1736
|
id: "kavach-api-key",
|
|
@@ -1834,15 +1746,16 @@ function apiKeys2(config) {
|
|
|
1834
1746
|
async handler(request, endpointCtx) {
|
|
1835
1747
|
const user = await endpointCtx.getUser(request);
|
|
1836
1748
|
if (!user) {
|
|
1837
|
-
return
|
|
1749
|
+
return json({ error: "Authentication required" }, 401);
|
|
1838
1750
|
}
|
|
1839
|
-
const
|
|
1840
|
-
|
|
1841
|
-
const
|
|
1751
|
+
const bodyResult = await parseBody(request);
|
|
1752
|
+
if (!bodyResult.ok) return bodyResult.response;
|
|
1753
|
+
const name = typeof bodyResult.data.name === "string" ? bodyResult.data.name.trim() : null;
|
|
1754
|
+
const permissions2 = Array.isArray(bodyResult.data.permissions) ? bodyResult.data.permissions : null;
|
|
1842
1755
|
if (!name || !permissions2) {
|
|
1843
|
-
return
|
|
1756
|
+
return json({ error: "Missing required fields: name, permissions" }, 400);
|
|
1844
1757
|
}
|
|
1845
|
-
const expiresAt =
|
|
1758
|
+
const expiresAt = bodyResult.data.expiresAt ? new Date(bodyResult.data.expiresAt) : void 0;
|
|
1846
1759
|
try {
|
|
1847
1760
|
const result = await module.create({
|
|
1848
1761
|
userId: user.id,
|
|
@@ -1850,9 +1763,9 @@ function apiKeys2(config) {
|
|
|
1850
1763
|
permissions: permissions2,
|
|
1851
1764
|
expiresAt
|
|
1852
1765
|
});
|
|
1853
|
-
return
|
|
1766
|
+
return json(result, 201);
|
|
1854
1767
|
} catch (err2) {
|
|
1855
|
-
return
|
|
1768
|
+
return json(
|
|
1856
1769
|
{ error: err2 instanceof Error ? err2.message : "Failed to create API key" },
|
|
1857
1770
|
500
|
|
1858
1771
|
);
|
|
@@ -1869,10 +1782,10 @@ function apiKeys2(config) {
|
|
|
1869
1782
|
async handler(request, endpointCtx) {
|
|
1870
1783
|
const user = await endpointCtx.getUser(request);
|
|
1871
1784
|
if (!user) {
|
|
1872
|
-
return
|
|
1785
|
+
return json({ error: "Authentication required" }, 401);
|
|
1873
1786
|
}
|
|
1874
1787
|
const keys = await module.list(user.id);
|
|
1875
|
-
return
|
|
1788
|
+
return json({ apiKeys: keys });
|
|
1876
1789
|
}
|
|
1877
1790
|
});
|
|
1878
1791
|
ctx.addEndpoint({
|
|
@@ -1885,21 +1798,21 @@ function apiKeys2(config) {
|
|
|
1885
1798
|
async handler(request, endpointCtx) {
|
|
1886
1799
|
const user = await endpointCtx.getUser(request);
|
|
1887
1800
|
if (!user) {
|
|
1888
|
-
return
|
|
1801
|
+
return json({ error: "Authentication required" }, 401);
|
|
1889
1802
|
}
|
|
1890
1803
|
const url = new URL(request.url);
|
|
1891
1804
|
const segments = url.pathname.split("/").filter(Boolean);
|
|
1892
1805
|
const keyId = segments[2];
|
|
1893
1806
|
if (!keyId) {
|
|
1894
|
-
return
|
|
1807
|
+
return json({ error: "Missing API key ID in path" }, 400);
|
|
1895
1808
|
}
|
|
1896
1809
|
const keys = await module.list(user.id);
|
|
1897
1810
|
const owned = keys.some((k) => k.id === decodeURIComponent(keyId));
|
|
1898
1811
|
if (!owned) {
|
|
1899
|
-
return
|
|
1812
|
+
return json({ error: "API key not found" }, 404);
|
|
1900
1813
|
}
|
|
1901
1814
|
await module.revoke(decodeURIComponent(keyId));
|
|
1902
|
-
return
|
|
1815
|
+
return json({ revoked: true });
|
|
1903
1816
|
}
|
|
1904
1817
|
});
|
|
1905
1818
|
ctx.addEndpoint({
|
|
@@ -1912,24 +1825,24 @@ function apiKeys2(config) {
|
|
|
1912
1825
|
async handler(request, endpointCtx) {
|
|
1913
1826
|
const user = await endpointCtx.getUser(request);
|
|
1914
1827
|
if (!user) {
|
|
1915
|
-
return
|
|
1828
|
+
return json({ error: "Authentication required" }, 401);
|
|
1916
1829
|
}
|
|
1917
1830
|
const url = new URL(request.url);
|
|
1918
1831
|
const segments = url.pathname.split("/").filter(Boolean);
|
|
1919
1832
|
const keyId = segments[2];
|
|
1920
1833
|
if (!keyId) {
|
|
1921
|
-
return
|
|
1834
|
+
return json({ error: "Missing API key ID in path" }, 400);
|
|
1922
1835
|
}
|
|
1923
1836
|
const keys = await module.list(user.id);
|
|
1924
1837
|
const owned = keys.some((k) => k.id === decodeURIComponent(keyId));
|
|
1925
1838
|
if (!owned) {
|
|
1926
|
-
return
|
|
1839
|
+
return json({ error: "API key not found" }, 404);
|
|
1927
1840
|
}
|
|
1928
1841
|
try {
|
|
1929
1842
|
const result = await module.rotate(decodeURIComponent(keyId));
|
|
1930
|
-
return
|
|
1843
|
+
return json(result);
|
|
1931
1844
|
} catch (err2) {
|
|
1932
|
-
return
|
|
1845
|
+
return json(
|
|
1933
1846
|
{ error: err2 instanceof Error ? err2.message : "Failed to rotate API key" },
|
|
1934
1847
|
400
|
|
1935
1848
|
);
|
|
@@ -2319,13 +2232,13 @@ function customSession(config = {}) {
|
|
|
2319
2232
|
const url = new URL(request.url);
|
|
2320
2233
|
const sessionId = url.searchParams.get("sessionId");
|
|
2321
2234
|
if (!sessionId) {
|
|
2322
|
-
return
|
|
2235
|
+
return jsonResponse2({ error: "Missing required query parameter: sessionId" }, 400);
|
|
2323
2236
|
}
|
|
2324
2237
|
try {
|
|
2325
2238
|
const fields = await mod.getSessionFields(sessionId);
|
|
2326
|
-
return
|
|
2239
|
+
return jsonResponse2({ fields: fields ?? {} });
|
|
2327
2240
|
} catch (err2) {
|
|
2328
|
-
return
|
|
2241
|
+
return jsonResponse2(
|
|
2329
2242
|
{ error: err2 instanceof Error ? err2.message : "Failed to get session fields" },
|
|
2330
2243
|
500
|
|
2331
2244
|
);
|
|
@@ -2344,23 +2257,23 @@ function customSession(config = {}) {
|
|
|
2344
2257
|
try {
|
|
2345
2258
|
body = await request.json();
|
|
2346
2259
|
} catch {
|
|
2347
|
-
return
|
|
2260
|
+
return jsonResponse2({ error: "Invalid JSON body" }, 400);
|
|
2348
2261
|
}
|
|
2349
2262
|
const sessionId = typeof body.sessionId === "string" ? body.sessionId : null;
|
|
2350
2263
|
if (!sessionId) {
|
|
2351
|
-
return
|
|
2264
|
+
return jsonResponse2({ error: "Missing required field: sessionId" }, 400);
|
|
2352
2265
|
}
|
|
2353
2266
|
const fields = body.fields !== null && body.fields !== void 0 && typeof body.fields === "object" && !Array.isArray(body.fields) ? body.fields : null;
|
|
2354
2267
|
if (!fields) {
|
|
2355
|
-
return
|
|
2268
|
+
return jsonResponse2({ error: "Missing or invalid field: fields" }, 400);
|
|
2356
2269
|
}
|
|
2357
2270
|
try {
|
|
2358
2271
|
await mod.updateSessionFields(sessionId, fields);
|
|
2359
|
-
return
|
|
2272
|
+
return jsonResponse2({ updated: true });
|
|
2360
2273
|
} catch (err2) {
|
|
2361
2274
|
const message = err2 instanceof Error ? err2.message : "Update failed";
|
|
2362
2275
|
const status = message.includes("not found") ? 404 : 500;
|
|
2363
|
-
return
|
|
2276
|
+
return jsonResponse2({ error: message }, status);
|
|
2364
2277
|
}
|
|
2365
2278
|
}
|
|
2366
2279
|
});
|
|
@@ -2370,7 +2283,7 @@ function customSession(config = {}) {
|
|
|
2370
2283
|
}
|
|
2371
2284
|
};
|
|
2372
2285
|
}
|
|
2373
|
-
function
|
|
2286
|
+
function jsonResponse2(body, status = 200) {
|
|
2374
2287
|
return new Response(JSON.stringify(body), {
|
|
2375
2288
|
status,
|
|
2376
2289
|
headers: { "Content-Type": "application/json" }
|
|
@@ -2383,13 +2296,13 @@ var DEFAULT_CODE_EXPIRY_SECONDS = 900;
|
|
|
2383
2296
|
var DEFAULT_POLL_INTERVAL_SECONDS = 5;
|
|
2384
2297
|
var USER_CODE_ALPHABET = "BCDFGHJKLMNPQRSTVWXZ";
|
|
2385
2298
|
var SLOW_DOWN_THRESHOLD_MS = 4e3;
|
|
2386
|
-
function
|
|
2299
|
+
function jsonResponse3(body, status = 200) {
|
|
2387
2300
|
return new Response(JSON.stringify(body), {
|
|
2388
2301
|
status,
|
|
2389
2302
|
headers: { "Content-Type": "application/json" }
|
|
2390
2303
|
});
|
|
2391
2304
|
}
|
|
2392
|
-
async function
|
|
2305
|
+
async function parseBody2(request) {
|
|
2393
2306
|
try {
|
|
2394
2307
|
return await request.json();
|
|
2395
2308
|
} catch {
|
|
@@ -2513,7 +2426,7 @@ function createDeviceAuthModule(config) {
|
|
|
2513
2426
|
const { method, pathname } = { method: request.method, pathname: url.pathname };
|
|
2514
2427
|
if (method === "POST" && pathname.endsWith("/auth/device/code")) {
|
|
2515
2428
|
const response = await requestCode();
|
|
2516
|
-
return
|
|
2429
|
+
return jsonResponse3({
|
|
2517
2430
|
device_code: response.deviceCode,
|
|
2518
2431
|
user_code: response.userCode,
|
|
2519
2432
|
verification_uri: response.verificationUri,
|
|
@@ -2523,17 +2436,17 @@ function createDeviceAuthModule(config) {
|
|
|
2523
2436
|
});
|
|
2524
2437
|
}
|
|
2525
2438
|
if (method === "POST" && pathname.endsWith("/auth/device/token")) {
|
|
2526
|
-
const body = await
|
|
2439
|
+
const body = await parseBody2(request);
|
|
2527
2440
|
const deviceCode = typeof body.device_code === "string" ? body.device_code : null;
|
|
2528
2441
|
if (!deviceCode) {
|
|
2529
|
-
return
|
|
2442
|
+
return jsonResponse3(
|
|
2530
2443
|
{ error: "invalid_request", error_description: "Missing device_code" },
|
|
2531
2444
|
400
|
|
2532
2445
|
);
|
|
2533
2446
|
}
|
|
2534
2447
|
const grant = grantsByDevice.get(deviceCode);
|
|
2535
2448
|
if (grant?.lastPolledAt && Date.now() - grant.lastPolledAt < SLOW_DOWN_THRESHOLD_MS) {
|
|
2536
|
-
return
|
|
2449
|
+
return jsonResponse3(
|
|
2537
2450
|
{
|
|
2538
2451
|
error: "slow_down",
|
|
2539
2452
|
error_description: "Polling too frequently",
|
|
@@ -2544,10 +2457,10 @@ function createDeviceAuthModule(config) {
|
|
|
2544
2457
|
}
|
|
2545
2458
|
const status = await checkAuthorization(deviceCode);
|
|
2546
2459
|
if (status.status === "authorized") {
|
|
2547
|
-
return
|
|
2460
|
+
return jsonResponse3({ authorized: true, user_id: status.userId });
|
|
2548
2461
|
}
|
|
2549
2462
|
if (status.status === "pending") {
|
|
2550
|
-
return
|
|
2463
|
+
return jsonResponse3(
|
|
2551
2464
|
{
|
|
2552
2465
|
error: "authorization_pending",
|
|
2553
2466
|
error_description: "The user has not yet authorized the device"
|
|
@@ -2556,7 +2469,7 @@ function createDeviceAuthModule(config) {
|
|
|
2556
2469
|
);
|
|
2557
2470
|
}
|
|
2558
2471
|
if (status.status === "denied") {
|
|
2559
|
-
return
|
|
2472
|
+
return jsonResponse3(
|
|
2560
2473
|
{
|
|
2561
2474
|
error: "access_denied",
|
|
2562
2475
|
error_description: "The user denied the authorization request"
|
|
@@ -2564,7 +2477,7 @@ function createDeviceAuthModule(config) {
|
|
|
2564
2477
|
400
|
|
2565
2478
|
);
|
|
2566
2479
|
}
|
|
2567
|
-
return
|
|
2480
|
+
return jsonResponse3(
|
|
2568
2481
|
{
|
|
2569
2482
|
error: "expired_token",
|
|
2570
2483
|
error_description: "The device code has expired"
|
|
@@ -2573,12 +2486,12 @@ function createDeviceAuthModule(config) {
|
|
|
2573
2486
|
);
|
|
2574
2487
|
}
|
|
2575
2488
|
if (method === "POST" && pathname.endsWith("/auth/device/authorize")) {
|
|
2576
|
-
const body = await
|
|
2489
|
+
const body = await parseBody2(request);
|
|
2577
2490
|
const userCode = typeof body.user_code === "string" ? body.user_code : null;
|
|
2578
2491
|
const userId = typeof body.user_id === "string" ? body.user_id : null;
|
|
2579
2492
|
const action = typeof body.action === "string" ? body.action : "approve";
|
|
2580
2493
|
if (!userCode || !userId) {
|
|
2581
|
-
return
|
|
2494
|
+
return jsonResponse3(
|
|
2582
2495
|
{ error: "invalid_request", error_description: "Missing user_code or user_id" },
|
|
2583
2496
|
400
|
|
2584
2497
|
);
|
|
@@ -2586,12 +2499,12 @@ function createDeviceAuthModule(config) {
|
|
|
2586
2499
|
try {
|
|
2587
2500
|
if (action === "deny") {
|
|
2588
2501
|
await deny(userCode);
|
|
2589
|
-
return
|
|
2502
|
+
return jsonResponse3({ denied: true });
|
|
2590
2503
|
}
|
|
2591
2504
|
await authorize(userCode, userId);
|
|
2592
|
-
return
|
|
2505
|
+
return jsonResponse3({ authorized: true });
|
|
2593
2506
|
} catch (err2) {
|
|
2594
|
-
return
|
|
2507
|
+
return jsonResponse3(
|
|
2595
2508
|
{
|
|
2596
2509
|
error: "invalid_request",
|
|
2597
2510
|
error_description: err2 instanceof Error ? err2.message : "Authorization failed"
|
|
@@ -2872,31 +2785,16 @@ function createEmailOtpModule(config, db, sessionManager) {
|
|
|
2872
2785
|
}
|
|
2873
2786
|
|
|
2874
2787
|
// src/auth/email-otp-plugin.ts
|
|
2875
|
-
function jsonResponse7(body, status = 200) {
|
|
2876
|
-
return new Response(JSON.stringify(body), {
|
|
2877
|
-
status,
|
|
2878
|
-
headers: { "Content-Type": "application/json" }
|
|
2879
|
-
});
|
|
2880
|
-
}
|
|
2881
|
-
async function parseBody5(request) {
|
|
2882
|
-
try {
|
|
2883
|
-
return await request.json();
|
|
2884
|
-
} catch {
|
|
2885
|
-
return {};
|
|
2886
|
-
}
|
|
2887
|
-
}
|
|
2888
2788
|
function emailOtp(config) {
|
|
2889
2789
|
return {
|
|
2890
2790
|
id: "kavach-email-otp",
|
|
2891
2791
|
async init(ctx) {
|
|
2892
|
-
|
|
2893
|
-
if (!sessionConfig) {
|
|
2792
|
+
if (!ctx.sessionManager) {
|
|
2894
2793
|
throw new Error(
|
|
2895
2794
|
"kavach-email-otp plugin requires auth.session to be configured so that sessions can be issued on successful verification."
|
|
2896
2795
|
);
|
|
2897
2796
|
}
|
|
2898
|
-
const
|
|
2899
|
-
const module = createEmailOtpModule(config, ctx.db, sessionManager);
|
|
2797
|
+
const module = createEmailOtpModule(config, ctx.db, ctx.sessionManager);
|
|
2900
2798
|
ctx.addEndpoint({
|
|
2901
2799
|
method: "POST",
|
|
2902
2800
|
path: "/auth/email-otp/send",
|
|
@@ -2905,19 +2803,17 @@ function emailOtp(config) {
|
|
|
2905
2803
|
description: "Send a one-time passcode to the provided email address"
|
|
2906
2804
|
},
|
|
2907
2805
|
async handler(request) {
|
|
2908
|
-
const
|
|
2909
|
-
|
|
2806
|
+
const bodyResult = await parseBody(request);
|
|
2807
|
+
if (!bodyResult.ok) return bodyResult.response;
|
|
2808
|
+
const rawEmail = typeof bodyResult.data.email === "string" ? bodyResult.data.email.trim().toLowerCase() : null;
|
|
2910
2809
|
if (!rawEmail) {
|
|
2911
|
-
return
|
|
2810
|
+
return json({ error: "Missing required field: email" }, 400);
|
|
2912
2811
|
}
|
|
2913
2812
|
try {
|
|
2914
2813
|
const result = await module.sendCode(rawEmail);
|
|
2915
|
-
return
|
|
2814
|
+
return json(result);
|
|
2916
2815
|
} catch (err2) {
|
|
2917
|
-
return
|
|
2918
|
-
{ error: err2 instanceof Error ? err2.message : "Failed to send OTP" },
|
|
2919
|
-
500
|
|
2920
|
-
);
|
|
2816
|
+
return json({ error: err2 instanceof Error ? err2.message : "Failed to send OTP" }, 500);
|
|
2921
2817
|
}
|
|
2922
2818
|
}
|
|
2923
2819
|
});
|
|
@@ -2929,17 +2825,18 @@ function emailOtp(config) {
|
|
|
2929
2825
|
description: "Verify an OTP code and return a session on success"
|
|
2930
2826
|
},
|
|
2931
2827
|
async handler(request) {
|
|
2932
|
-
const
|
|
2933
|
-
|
|
2934
|
-
const
|
|
2828
|
+
const bodyResult = await parseBody(request);
|
|
2829
|
+
if (!bodyResult.ok) return bodyResult.response;
|
|
2830
|
+
const rawEmail = typeof bodyResult.data.email === "string" ? bodyResult.data.email.trim().toLowerCase() : null;
|
|
2831
|
+
const code = typeof bodyResult.data.code === "string" ? bodyResult.data.code.trim() : null;
|
|
2935
2832
|
if (!rawEmail || !code) {
|
|
2936
|
-
return
|
|
2833
|
+
return json({ error: "Missing required fields: email, code" }, 400);
|
|
2937
2834
|
}
|
|
2938
2835
|
const result = await module.verifyCode(rawEmail, code);
|
|
2939
2836
|
if (!result) {
|
|
2940
|
-
return
|
|
2837
|
+
return json({ error: "Invalid or expired OTP code" }, 401);
|
|
2941
2838
|
}
|
|
2942
|
-
return
|
|
2839
|
+
return json(result);
|
|
2943
2840
|
}
|
|
2944
2841
|
});
|
|
2945
2842
|
}
|
|
@@ -2950,7 +2847,7 @@ var TOKEN_PURPOSE = "email-verify";
|
|
|
2950
2847
|
function makeError(code, message, details) {
|
|
2951
2848
|
return { code, message, ...details !== void 0 ? { details } : {} };
|
|
2952
2849
|
}
|
|
2953
|
-
function
|
|
2850
|
+
function jsonResponse4(body, status = 200) {
|
|
2954
2851
|
return new Response(JSON.stringify(body), {
|
|
2955
2852
|
status,
|
|
2956
2853
|
headers: { "Content-Type": "application/json" }
|
|
@@ -3042,29 +2939,29 @@ function createEmailVerificationModule(config, db, tokenModule) {
|
|
|
3042
2939
|
try {
|
|
3043
2940
|
body = await request.json();
|
|
3044
2941
|
} catch {
|
|
3045
|
-
return
|
|
2942
|
+
return jsonResponse4({ error: "Invalid JSON body" }, 400);
|
|
3046
2943
|
}
|
|
3047
2944
|
const b = body;
|
|
3048
2945
|
if (pathname === "/auth/verify-email/send") {
|
|
3049
2946
|
if (typeof b.userId !== "string" || typeof b.email !== "string") {
|
|
3050
|
-
return
|
|
2947
|
+
return jsonResponse4({ error: "Missing required fields: userId, email" }, 400);
|
|
3051
2948
|
}
|
|
3052
2949
|
const result = await sendVerification(b.userId, b.email);
|
|
3053
2950
|
if (!result.success) {
|
|
3054
2951
|
const status = result.error.code === "USER_NOT_FOUND" ? 404 : 500;
|
|
3055
|
-
return
|
|
2952
|
+
return jsonResponse4({ error: result.error.message }, status);
|
|
3056
2953
|
}
|
|
3057
2954
|
return new Response(null, { status: 204 });
|
|
3058
2955
|
}
|
|
3059
2956
|
if (pathname === "/auth/verify-email/confirm") {
|
|
3060
2957
|
if (typeof b.token !== "string") {
|
|
3061
|
-
return
|
|
2958
|
+
return jsonResponse4({ error: "Missing required field: token" }, 400);
|
|
3062
2959
|
}
|
|
3063
2960
|
const result = await verify(b.token);
|
|
3064
2961
|
if (!result.success) {
|
|
3065
|
-
return
|
|
2962
|
+
return jsonResponse4({ error: result.error.message }, 400);
|
|
3066
2963
|
}
|
|
3067
|
-
return
|
|
2964
|
+
return jsonResponse4({ userId: result.data.userId, email: result.data.email });
|
|
3068
2965
|
}
|
|
3069
2966
|
return null;
|
|
3070
2967
|
}
|
|
@@ -4098,19 +3995,6 @@ function createGdprModule(db) {
|
|
|
4098
3995
|
}
|
|
4099
3996
|
|
|
4100
3997
|
// src/auth/gdpr-plugin.ts
|
|
4101
|
-
function jsonResponse9(body, status = 200) {
|
|
4102
|
-
return new Response(JSON.stringify(body), {
|
|
4103
|
-
status,
|
|
4104
|
-
headers: { "Content-Type": "application/json" }
|
|
4105
|
-
});
|
|
4106
|
-
}
|
|
4107
|
-
async function parseBody6(request) {
|
|
4108
|
-
try {
|
|
4109
|
-
return await request.json();
|
|
4110
|
-
} catch {
|
|
4111
|
-
return {};
|
|
4112
|
-
}
|
|
4113
|
-
}
|
|
4114
3998
|
function gdpr() {
|
|
4115
3999
|
return {
|
|
4116
4000
|
id: "kavach-gdpr",
|
|
@@ -4126,16 +4010,13 @@ function gdpr() {
|
|
|
4126
4010
|
async handler(request, endpointCtx) {
|
|
4127
4011
|
const user = await endpointCtx.getUser(request);
|
|
4128
4012
|
if (!user) {
|
|
4129
|
-
return
|
|
4013
|
+
return json({ error: "Authentication required" }, 401);
|
|
4130
4014
|
}
|
|
4131
4015
|
try {
|
|
4132
4016
|
const data = await module.exportUserData(user.id);
|
|
4133
|
-
return
|
|
4017
|
+
return json(data);
|
|
4134
4018
|
} catch (err2) {
|
|
4135
|
-
return
|
|
4136
|
-
{ error: err2 instanceof Error ? err2.message : "Export failed" },
|
|
4137
|
-
500
|
|
4138
|
-
);
|
|
4019
|
+
return json({ error: err2 instanceof Error ? err2.message : "Export failed" }, 500);
|
|
4139
4020
|
}
|
|
4140
4021
|
}
|
|
4141
4022
|
});
|
|
@@ -4149,30 +4030,28 @@ function gdpr() {
|
|
|
4149
4030
|
async handler(request, endpointCtx) {
|
|
4150
4031
|
const user = await endpointCtx.getUser(request);
|
|
4151
4032
|
if (!user) {
|
|
4152
|
-
return
|
|
4033
|
+
return json({ error: "Authentication required" }, 401);
|
|
4153
4034
|
}
|
|
4154
|
-
const
|
|
4155
|
-
if (
|
|
4156
|
-
|
|
4035
|
+
const bodyResult = await parseBody(request);
|
|
4036
|
+
if (!bodyResult.ok) return bodyResult.response;
|
|
4037
|
+
if (bodyResult.data.confirm !== "delete my account") {
|
|
4038
|
+
return json(
|
|
4157
4039
|
{
|
|
4158
4040
|
error: 'Confirmation required. Send { "confirm": "delete my account" } in the request body.'
|
|
4159
4041
|
},
|
|
4160
4042
|
400
|
|
4161
4043
|
);
|
|
4162
4044
|
}
|
|
4163
|
-
const keepAuditLogs = typeof
|
|
4164
|
-
const deleteOrganizations = typeof
|
|
4045
|
+
const keepAuditLogs = typeof bodyResult.data.keepAuditLogs === "boolean" ? bodyResult.data.keepAuditLogs : true;
|
|
4046
|
+
const deleteOrganizations = typeof bodyResult.data.deleteOrganizations === "boolean" ? bodyResult.data.deleteOrganizations : false;
|
|
4165
4047
|
try {
|
|
4166
4048
|
const result = await module.deleteUser(user.id, {
|
|
4167
4049
|
keepAuditLogs,
|
|
4168
4050
|
deleteOrganizations
|
|
4169
4051
|
});
|
|
4170
|
-
return
|
|
4052
|
+
return json({ success: true, ...result });
|
|
4171
4053
|
} catch (err2) {
|
|
4172
|
-
return
|
|
4173
|
-
{ error: err2 instanceof Error ? err2.message : "Deletion failed" },
|
|
4174
|
-
500
|
|
4175
|
-
);
|
|
4054
|
+
return json({ error: err2 instanceof Error ? err2.message : "Deletion failed" }, 500);
|
|
4176
4055
|
}
|
|
4177
4056
|
}
|
|
4178
4057
|
});
|
|
@@ -4186,13 +4065,13 @@ function gdpr() {
|
|
|
4186
4065
|
async handler(request, endpointCtx) {
|
|
4187
4066
|
const user = await endpointCtx.getUser(request);
|
|
4188
4067
|
if (!user) {
|
|
4189
|
-
return
|
|
4068
|
+
return json({ error: "Authentication required" }, 401);
|
|
4190
4069
|
}
|
|
4191
4070
|
try {
|
|
4192
4071
|
await module.anonymizeUser(user.id);
|
|
4193
|
-
return
|
|
4072
|
+
return json({ success: true });
|
|
4194
4073
|
} catch (err2) {
|
|
4195
|
-
return
|
|
4074
|
+
return json(
|
|
4196
4075
|
{ error: err2 instanceof Error ? err2.message : "Anonymization failed" },
|
|
4197
4076
|
500
|
|
4198
4077
|
);
|
|
@@ -4828,14 +4707,12 @@ function magicLink(config) {
|
|
|
4828
4707
|
return {
|
|
4829
4708
|
id: "kavach-magic-link",
|
|
4830
4709
|
async init(ctx) {
|
|
4831
|
-
|
|
4832
|
-
if (!sessionConfig) {
|
|
4710
|
+
if (!ctx.sessionManager) {
|
|
4833
4711
|
throw new Error(
|
|
4834
4712
|
"kavach-magic-link plugin requires auth.session to be configured so that sessions can be issued on successful verification."
|
|
4835
4713
|
);
|
|
4836
4714
|
}
|
|
4837
|
-
const
|
|
4838
|
-
const module = createMagicLinkModule(config, ctx.db, sessionManager);
|
|
4715
|
+
const module = createMagicLinkModule(config, ctx.db, ctx.sessionManager);
|
|
4839
4716
|
const sendLimiter = createRateLimiter({ max: 5, window: 60 });
|
|
4840
4717
|
ctx.addEndpoint({
|
|
4841
4718
|
method: "POST",
|
|
@@ -4845,35 +4722,19 @@ function magicLink(config) {
|
|
|
4845
4722
|
description: "Send a magic link to the provided email address"
|
|
4846
4723
|
},
|
|
4847
4724
|
handler: withRateLimit(async (request) => {
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
} catch {
|
|
4852
|
-
return new Response(JSON.stringify({ error: "Invalid JSON body" }), {
|
|
4853
|
-
status: 400,
|
|
4854
|
-
headers: { "Content-Type": "application/json" }
|
|
4855
|
-
});
|
|
4856
|
-
}
|
|
4857
|
-
const b = body;
|
|
4858
|
-
const rawEmail = typeof b.email === "string" ? b.email.trim().toLowerCase() : null;
|
|
4725
|
+
const bodyResult = await parseBody(request);
|
|
4726
|
+
if (!bodyResult.ok) return bodyResult.response;
|
|
4727
|
+
const rawEmail = typeof bodyResult.data.email === "string" ? bodyResult.data.email.trim().toLowerCase() : null;
|
|
4859
4728
|
if (!rawEmail) {
|
|
4860
|
-
return
|
|
4861
|
-
status: 400,
|
|
4862
|
-
headers: { "Content-Type": "application/json" }
|
|
4863
|
-
});
|
|
4729
|
+
return json({ error: "Missing required field: email" }, 400);
|
|
4864
4730
|
}
|
|
4865
4731
|
try {
|
|
4866
4732
|
const result = await module.sendLink(rawEmail);
|
|
4867
|
-
return
|
|
4868
|
-
status: 200,
|
|
4869
|
-
headers: { "Content-Type": "application/json" }
|
|
4870
|
-
});
|
|
4733
|
+
return json(result);
|
|
4871
4734
|
} catch (err2) {
|
|
4872
|
-
return
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
}),
|
|
4876
|
-
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
4735
|
+
return json(
|
|
4736
|
+
{ error: err2 instanceof Error ? err2.message : "Failed to send magic link" },
|
|
4737
|
+
500
|
|
4877
4738
|
);
|
|
4878
4739
|
}
|
|
4879
4740
|
}, sendLimiter)
|
|
@@ -4888,22 +4749,13 @@ function magicLink(config) {
|
|
|
4888
4749
|
const url = new URL(request.url);
|
|
4889
4750
|
const token = url.searchParams.get("token");
|
|
4890
4751
|
if (!token) {
|
|
4891
|
-
return
|
|
4892
|
-
status: 400,
|
|
4893
|
-
headers: { "Content-Type": "application/json" }
|
|
4894
|
-
});
|
|
4752
|
+
return json({ error: "Missing token query parameter" }, 400);
|
|
4895
4753
|
}
|
|
4896
4754
|
const result = await module.verify(token);
|
|
4897
4755
|
if (!result) {
|
|
4898
|
-
return
|
|
4899
|
-
status: 401,
|
|
4900
|
-
headers: { "Content-Type": "application/json" }
|
|
4901
|
-
});
|
|
4756
|
+
return json({ error: "Invalid or expired magic link" }, 401);
|
|
4902
4757
|
}
|
|
4903
|
-
return
|
|
4904
|
-
status: 200,
|
|
4905
|
-
headers: { "Content-Type": "application/json" }
|
|
4906
|
-
});
|
|
4758
|
+
return json(result);
|
|
4907
4759
|
}
|
|
4908
4760
|
});
|
|
4909
4761
|
}
|
|
@@ -5128,9 +4980,7 @@ function createOAuthModule(db, config) {
|
|
|
5128
4980
|
pruneExpiredStates
|
|
5129
4981
|
};
|
|
5130
4982
|
}
|
|
5131
|
-
|
|
5132
|
-
// src/auth/oauth/plugin.ts
|
|
5133
|
-
function jsonResponse10(body, status = 200) {
|
|
4983
|
+
function jsonResponse5(body, status = 200) {
|
|
5134
4984
|
return new Response(JSON.stringify(body), {
|
|
5135
4985
|
status,
|
|
5136
4986
|
headers: { "Content-Type": "application/json" }
|
|
@@ -5148,6 +4998,12 @@ function oauth(config) {
|
|
|
5148
4998
|
async init(ctx) {
|
|
5149
4999
|
const module = createOAuthModule(ctx.db, config);
|
|
5150
5000
|
const baseUrl = ctx.config.baseUrl ?? "";
|
|
5001
|
+
const sessionManager = ctx.sessionManager;
|
|
5002
|
+
if (!sessionManager) {
|
|
5003
|
+
throw new Error(
|
|
5004
|
+
"kavach-oauth plugin requires auth.session to be configured so that sessions can be issued on successful OAuth callback."
|
|
5005
|
+
);
|
|
5006
|
+
}
|
|
5151
5007
|
const authorizeLimiter = createRateLimiter({ max: 20, window: 60 });
|
|
5152
5008
|
function getRedirectUri(provider) {
|
|
5153
5009
|
if (config.buildRedirectUri) {
|
|
@@ -5166,14 +5022,14 @@ function oauth(config) {
|
|
|
5166
5022
|
const url = new URL(request.url);
|
|
5167
5023
|
const provider = url.searchParams.get("_param_provider");
|
|
5168
5024
|
if (!provider) {
|
|
5169
|
-
return
|
|
5025
|
+
return jsonResponse5({ error: "Missing provider parameter" }, 400);
|
|
5170
5026
|
}
|
|
5171
5027
|
const redirectUri = getRedirectUri(provider);
|
|
5172
5028
|
try {
|
|
5173
5029
|
const { url: authUrl } = await module.getAuthorizationUrl(provider, redirectUri);
|
|
5174
5030
|
return redirectResponse(authUrl);
|
|
5175
5031
|
} catch (err2) {
|
|
5176
|
-
return
|
|
5032
|
+
return jsonResponse5(
|
|
5177
5033
|
{ error: err2 instanceof Error ? err2.message : "Failed to build authorization URL" },
|
|
5178
5034
|
400
|
|
5179
5035
|
);
|
|
@@ -5190,21 +5046,62 @@ function oauth(config) {
|
|
|
5190
5046
|
const code = url.searchParams.get("code");
|
|
5191
5047
|
const state = url.searchParams.get("state");
|
|
5192
5048
|
if (!provider) {
|
|
5193
|
-
return
|
|
5049
|
+
return jsonResponse5({ error: "Missing provider parameter" }, 400);
|
|
5194
5050
|
}
|
|
5195
5051
|
if (!code || !state) {
|
|
5196
|
-
return
|
|
5052
|
+
return jsonResponse5({ error: "Missing code or state query parameter" }, 400);
|
|
5197
5053
|
}
|
|
5198
5054
|
const redirectUri = getRedirectUri(provider);
|
|
5199
5055
|
try {
|
|
5200
5056
|
const result = await module.handleCallback(provider, code, state, redirectUri);
|
|
5201
|
-
|
|
5057
|
+
const email = result.userInfo.email;
|
|
5058
|
+
let userId = result.account.userId;
|
|
5059
|
+
if (userId === "__pending__" && email && ctx.db) {
|
|
5060
|
+
const existing = await ctx.db.select().from(users).where(eq(users.email, email));
|
|
5061
|
+
if (existing[0]) {
|
|
5062
|
+
userId = existing[0].id;
|
|
5063
|
+
} else {
|
|
5064
|
+
const newId = crypto.randomUUID();
|
|
5065
|
+
await ctx.db.insert(users).values({
|
|
5066
|
+
id: newId,
|
|
5067
|
+
email,
|
|
5068
|
+
name: result.userInfo.name ?? null,
|
|
5069
|
+
externalProvider: `oauth:${provider}`,
|
|
5070
|
+
externalId: result.userInfo.id,
|
|
5071
|
+
emailVerified: 1,
|
|
5072
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
5073
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
5074
|
+
});
|
|
5075
|
+
userId = newId;
|
|
5076
|
+
}
|
|
5077
|
+
await module.linkAccount(userId, provider, result.userInfo, {
|
|
5078
|
+
accessToken: result.account.accessToken,
|
|
5079
|
+
refreshToken: result.account.refreshToken ?? void 0,
|
|
5080
|
+
tokenType: "Bearer",
|
|
5081
|
+
raw: {}
|
|
5082
|
+
});
|
|
5083
|
+
}
|
|
5084
|
+
if (userId !== "__pending__") {
|
|
5085
|
+
const { session, token } = await sessionManager.create(userId);
|
|
5086
|
+
const maxAge = Math.floor((session.expiresAt.getTime() - Date.now()) / 1e3);
|
|
5087
|
+
const cookie = buildSetCookie("kavach_session", token, maxAge);
|
|
5088
|
+
const userInfo = encodeURIComponent(JSON.stringify({ id: userId, email }));
|
|
5089
|
+
const callbackUrl = `${baseUrl}/?auth_user=${userInfo}`;
|
|
5090
|
+
return new Response(null, {
|
|
5091
|
+
status: 302,
|
|
5092
|
+
headers: {
|
|
5093
|
+
Location: callbackUrl,
|
|
5094
|
+
"Set-Cookie": cookie
|
|
5095
|
+
}
|
|
5096
|
+
});
|
|
5097
|
+
}
|
|
5098
|
+
return jsonResponse5({
|
|
5202
5099
|
isNewAccount: result.isNewAccount,
|
|
5203
5100
|
account: result.account,
|
|
5204
5101
|
userInfo: result.userInfo
|
|
5205
5102
|
});
|
|
5206
5103
|
} catch (err2) {
|
|
5207
|
-
return
|
|
5104
|
+
return jsonResponse5(
|
|
5208
5105
|
{ error: err2 instanceof Error ? err2.message : "OAuth callback failed" },
|
|
5209
5106
|
400
|
|
5210
5107
|
);
|
|
@@ -5221,29 +5118,29 @@ function oauth(config) {
|
|
|
5221
5118
|
async handler(request, endpointCtx) {
|
|
5222
5119
|
const user = await endpointCtx.getUser(request);
|
|
5223
5120
|
if (!user) {
|
|
5224
|
-
return
|
|
5121
|
+
return jsonResponse5({ error: "Authentication required" }, 401);
|
|
5225
5122
|
}
|
|
5226
5123
|
let body;
|
|
5227
5124
|
try {
|
|
5228
5125
|
body = await request.json();
|
|
5229
5126
|
} catch {
|
|
5230
|
-
return
|
|
5127
|
+
return jsonResponse5({ error: "Invalid JSON body" }, 400);
|
|
5231
5128
|
}
|
|
5232
5129
|
const b = body;
|
|
5233
5130
|
const provider = typeof b.provider === "string" ? b.provider : null;
|
|
5234
5131
|
const userInfo = typeof b.userInfo === "object" && b.userInfo !== null ? b.userInfo : null;
|
|
5235
5132
|
const tokens = typeof b.tokens === "object" && b.tokens !== null ? b.tokens : null;
|
|
5236
5133
|
if (!provider || !userInfo || !tokens) {
|
|
5237
|
-
return
|
|
5134
|
+
return jsonResponse5(
|
|
5238
5135
|
{ error: "Missing required fields: provider, userInfo, tokens" },
|
|
5239
5136
|
400
|
|
5240
5137
|
);
|
|
5241
5138
|
}
|
|
5242
5139
|
try {
|
|
5243
5140
|
const account = await module.linkAccount(user.id, provider, userInfo, tokens);
|
|
5244
|
-
return
|
|
5141
|
+
return jsonResponse5({ account });
|
|
5245
5142
|
} catch (err2) {
|
|
5246
|
-
return
|
|
5143
|
+
return jsonResponse5(
|
|
5247
5144
|
{ error: err2 instanceof Error ? err2.message : "Failed to link account" },
|
|
5248
5145
|
400
|
|
5249
5146
|
);
|
|
@@ -5259,7 +5156,7 @@ function oauth(config) {
|
|
|
5259
5156
|
id: p.id,
|
|
5260
5157
|
name: p.name
|
|
5261
5158
|
}));
|
|
5262
|
-
return
|
|
5159
|
+
return jsonResponse5({ providers });
|
|
5263
5160
|
}
|
|
5264
5161
|
});
|
|
5265
5162
|
}
|
|
@@ -7056,25 +6953,25 @@ function createOneTapModule(config, db, sessionManager) {
|
|
|
7056
6953
|
const text3 = await request.text();
|
|
7057
6954
|
formData = new URLSearchParams(text3);
|
|
7058
6955
|
} catch {
|
|
7059
|
-
return
|
|
6956
|
+
return jsonResponse6({ error: "Failed to parse request body" }, 400);
|
|
7060
6957
|
}
|
|
7061
6958
|
const credential = formData.get("credential");
|
|
7062
6959
|
const bodyToken = formData.get(csrfCookieName);
|
|
7063
6960
|
if (!credential) {
|
|
7064
|
-
return
|
|
6961
|
+
return jsonResponse6({ error: "Missing credential field" }, 400);
|
|
7065
6962
|
}
|
|
7066
6963
|
const cookieToken = getCsrfCookie(request);
|
|
7067
6964
|
if (!cookieToken || !bodyToken || cookieToken !== bodyToken) {
|
|
7068
|
-
return
|
|
6965
|
+
return jsonResponse6({ error: "CSRF token mismatch" }, 403);
|
|
7069
6966
|
}
|
|
7070
6967
|
let googleUser;
|
|
7071
6968
|
try {
|
|
7072
6969
|
googleUser = await verify(credential);
|
|
7073
6970
|
} catch (err2) {
|
|
7074
6971
|
if (err2 instanceof OneTapVerifyError && err2.code === "USER_NOT_FOUND") {
|
|
7075
|
-
return
|
|
6972
|
+
return jsonResponse6({ error: err2.message }, 403);
|
|
7076
6973
|
}
|
|
7077
|
-
return
|
|
6974
|
+
return jsonResponse6(
|
|
7078
6975
|
{ error: err2 instanceof Error ? err2.message : "Token verification failed" },
|
|
7079
6976
|
401
|
|
7080
6977
|
);
|
|
@@ -7084,27 +6981,96 @@ function createOneTapModule(config, db, sessionManager) {
|
|
|
7084
6981
|
user = await findOrCreateUser(googleUser);
|
|
7085
6982
|
} catch (err2) {
|
|
7086
6983
|
if (err2 instanceof OneTapVerifyError && err2.code === "USER_NOT_FOUND") {
|
|
7087
|
-
return
|
|
6984
|
+
return jsonResponse6({ error: err2.message }, 403);
|
|
7088
6985
|
}
|
|
7089
|
-
return
|
|
6986
|
+
return jsonResponse6(
|
|
7090
6987
|
{ error: err2 instanceof Error ? err2.message : "Failed to resolve user" },
|
|
7091
6988
|
500
|
|
7092
6989
|
);
|
|
7093
6990
|
}
|
|
7094
6991
|
const { token: sessionToken, session } = await sessionManager.create(user.id);
|
|
7095
|
-
return
|
|
6992
|
+
return jsonResponse6({
|
|
7096
6993
|
user: { id: user.id, email: user.email },
|
|
7097
6994
|
session: { token: sessionToken, expiresAt: session.expiresAt }
|
|
7098
6995
|
});
|
|
7099
6996
|
}
|
|
7100
6997
|
return { verify, handleRequest };
|
|
7101
6998
|
}
|
|
7102
|
-
function
|
|
6999
|
+
function jsonResponse6(body, status = 200) {
|
|
7103
7000
|
return new Response(JSON.stringify(body), {
|
|
7104
7001
|
status,
|
|
7105
7002
|
headers: { "Content-Type": "application/json" }
|
|
7106
7003
|
});
|
|
7107
7004
|
}
|
|
7005
|
+
var DEFAULT_MAX_AGE_SECONDS = 60 * 60 * 24 * 7;
|
|
7006
|
+
function createSessionManager(config, db) {
|
|
7007
|
+
if (!config.secret || config.secret.length < 32) {
|
|
7008
|
+
throw new Error("SessionManager: secret must be at least 32 characters.");
|
|
7009
|
+
}
|
|
7010
|
+
const maxAge = config.maxAge ?? DEFAULT_MAX_AGE_SECONDS;
|
|
7011
|
+
const keyObject = new TextEncoder().encode(config.secret);
|
|
7012
|
+
function rowToSession2(row) {
|
|
7013
|
+
return {
|
|
7014
|
+
id: row.id,
|
|
7015
|
+
userId: row.userId,
|
|
7016
|
+
expiresAt: row.expiresAt,
|
|
7017
|
+
createdAt: row.createdAt,
|
|
7018
|
+
...row.metadata !== null && { metadata: row.metadata }
|
|
7019
|
+
};
|
|
7020
|
+
}
|
|
7021
|
+
async function create(userId, metadata) {
|
|
7022
|
+
const id = generateId();
|
|
7023
|
+
const now = /* @__PURE__ */ new Date();
|
|
7024
|
+
const expiresAt = new Date(now.getTime() + maxAge * 1e3);
|
|
7025
|
+
await db.insert(sessions).values({
|
|
7026
|
+
id,
|
|
7027
|
+
userId,
|
|
7028
|
+
expiresAt,
|
|
7029
|
+
metadata: metadata ?? null,
|
|
7030
|
+
createdAt: now
|
|
7031
|
+
});
|
|
7032
|
+
const token = await new SignJWT({ sub: id }).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime(Math.floor(expiresAt.getTime() / 1e3)).sign(keyObject);
|
|
7033
|
+
const session = {
|
|
7034
|
+
id,
|
|
7035
|
+
userId,
|
|
7036
|
+
expiresAt,
|
|
7037
|
+
createdAt: now,
|
|
7038
|
+
...metadata !== void 0 && { metadata }
|
|
7039
|
+
};
|
|
7040
|
+
return { session, token };
|
|
7041
|
+
}
|
|
7042
|
+
async function validate(token) {
|
|
7043
|
+
let sessionId;
|
|
7044
|
+
try {
|
|
7045
|
+
const { payload } = await jwtVerify(token, keyObject);
|
|
7046
|
+
if (typeof payload.sub !== "string" || !payload.sub) return null;
|
|
7047
|
+
sessionId = payload.sub;
|
|
7048
|
+
} catch {
|
|
7049
|
+
return null;
|
|
7050
|
+
}
|
|
7051
|
+
const now = /* @__PURE__ */ new Date();
|
|
7052
|
+
const rows = await db.select().from(sessions).where(and(eq(sessions.id, sessionId)));
|
|
7053
|
+
const row = rows[0];
|
|
7054
|
+
if (!row) return null;
|
|
7055
|
+
if (row.expiresAt <= now) {
|
|
7056
|
+
await db.delete(sessions).where(eq(sessions.id, sessionId));
|
|
7057
|
+
return null;
|
|
7058
|
+
}
|
|
7059
|
+
return rowToSession2(row);
|
|
7060
|
+
}
|
|
7061
|
+
async function revoke(sessionId) {
|
|
7062
|
+
await db.delete(sessions).where(eq(sessions.id, sessionId));
|
|
7063
|
+
}
|
|
7064
|
+
async function revokeAll(userId) {
|
|
7065
|
+
await db.delete(sessions).where(eq(sessions.userId, userId));
|
|
7066
|
+
}
|
|
7067
|
+
async function list(userId) {
|
|
7068
|
+
const now = /* @__PURE__ */ new Date();
|
|
7069
|
+
const rows = await db.select().from(sessions).where(and(eq(sessions.userId, userId)));
|
|
7070
|
+
return rows.filter((row) => row.expiresAt > now).sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()).map(rowToSession2);
|
|
7071
|
+
}
|
|
7072
|
+
return { create, validate, revoke, revokeAll, list };
|
|
7073
|
+
}
|
|
7108
7074
|
|
|
7109
7075
|
// src/auth/one-tap-plugin.ts
|
|
7110
7076
|
function oneTap(config) {
|
|
@@ -8396,14 +8362,14 @@ function rowToInvitation(row) {
|
|
|
8396
8362
|
createdAt: row.createdAt
|
|
8397
8363
|
};
|
|
8398
8364
|
}
|
|
8399
|
-
function
|
|
8365
|
+
function jsonResponse7(body, status = 200) {
|
|
8400
8366
|
return new Response(JSON.stringify(body), {
|
|
8401
8367
|
status,
|
|
8402
8368
|
headers: { "Content-Type": "application/json" }
|
|
8403
8369
|
});
|
|
8404
8370
|
}
|
|
8405
8371
|
function errorResponse(message, status) {
|
|
8406
|
-
return
|
|
8372
|
+
return jsonResponse7({ error: message }, status);
|
|
8407
8373
|
}
|
|
8408
8374
|
function createOrgModule(config, db) {
|
|
8409
8375
|
const maxMembers = config.maxMembers ?? DEFAULT_MAX_MEMBERS;
|
|
@@ -8709,7 +8675,7 @@ function createOrgModule(config, db) {
|
|
|
8709
8675
|
try {
|
|
8710
8676
|
const body = await request.json();
|
|
8711
8677
|
const org = await create(body);
|
|
8712
|
-
return
|
|
8678
|
+
return jsonResponse7(org, 201);
|
|
8713
8679
|
} catch (err2) {
|
|
8714
8680
|
return errorResponse(err2 instanceof Error ? err2.message : "Unknown error", 400);
|
|
8715
8681
|
}
|
|
@@ -8719,7 +8685,7 @@ function createOrgModule(config, db) {
|
|
|
8719
8685
|
const userId = userOrgMatch[1];
|
|
8720
8686
|
if (!userId) return errorResponse("Missing userId", 400);
|
|
8721
8687
|
const orgs = await list(userId);
|
|
8722
|
-
return
|
|
8688
|
+
return jsonResponse7(orgs);
|
|
8723
8689
|
}
|
|
8724
8690
|
const orgBaseMatch = pathname.match(/^\/auth\/org\/([^/]+)(\/.*)?$/);
|
|
8725
8691
|
if (!orgBaseMatch) return null;
|
|
@@ -8729,13 +8695,13 @@ function createOrgModule(config, db) {
|
|
|
8729
8695
|
if (method === "GET" && subPath === "") {
|
|
8730
8696
|
const org = await get(orgId);
|
|
8731
8697
|
if (!org) return errorResponse("Organization not found", 404);
|
|
8732
|
-
return
|
|
8698
|
+
return jsonResponse7(org);
|
|
8733
8699
|
}
|
|
8734
8700
|
if (method === "PATCH" && subPath === "") {
|
|
8735
8701
|
try {
|
|
8736
8702
|
const body = await request.json();
|
|
8737
8703
|
const org = await update(orgId, body);
|
|
8738
|
-
return
|
|
8704
|
+
return jsonResponse7(org);
|
|
8739
8705
|
} catch (err2) {
|
|
8740
8706
|
return errorResponse(err2 instanceof Error ? err2.message : "Unknown error", 400);
|
|
8741
8707
|
}
|
|
@@ -8743,7 +8709,7 @@ function createOrgModule(config, db) {
|
|
|
8743
8709
|
if (method === "DELETE" && subPath === "") {
|
|
8744
8710
|
try {
|
|
8745
8711
|
await remove(orgId);
|
|
8746
|
-
return
|
|
8712
|
+
return jsonResponse7({ success: true });
|
|
8747
8713
|
} catch (err2) {
|
|
8748
8714
|
return errorResponse(err2 instanceof Error ? err2.message : "Unknown error", 400);
|
|
8749
8715
|
}
|
|
@@ -8752,14 +8718,14 @@ function createOrgModule(config, db) {
|
|
|
8752
8718
|
try {
|
|
8753
8719
|
const body = await request.json();
|
|
8754
8720
|
const member = await addMember(orgId, body.userId, body.role);
|
|
8755
|
-
return
|
|
8721
|
+
return jsonResponse7(member, 201);
|
|
8756
8722
|
} catch (err2) {
|
|
8757
8723
|
return errorResponse(err2 instanceof Error ? err2.message : "Unknown error", 400);
|
|
8758
8724
|
}
|
|
8759
8725
|
}
|
|
8760
8726
|
if (method === "GET" && subPath === "/members") {
|
|
8761
8727
|
const members = await getMembers(orgId);
|
|
8762
|
-
return
|
|
8728
|
+
return jsonResponse7(members);
|
|
8763
8729
|
}
|
|
8764
8730
|
const memberMatch = subPath.match(/^\/members\/([^/]+)$/);
|
|
8765
8731
|
if (method === "PATCH" && memberMatch) {
|
|
@@ -8768,7 +8734,7 @@ function createOrgModule(config, db) {
|
|
|
8768
8734
|
try {
|
|
8769
8735
|
const body = await request.json();
|
|
8770
8736
|
const member = await updateMemberRole(orgId, userId, body.role);
|
|
8771
|
-
return
|
|
8737
|
+
return jsonResponse7(member);
|
|
8772
8738
|
} catch (err2) {
|
|
8773
8739
|
return errorResponse(err2 instanceof Error ? err2.message : "Unknown error", 400);
|
|
8774
8740
|
}
|
|
@@ -8777,20 +8743,20 @@ function createOrgModule(config, db) {
|
|
|
8777
8743
|
const userId = memberMatch[1];
|
|
8778
8744
|
if (!userId) return errorResponse("Missing userId", 400);
|
|
8779
8745
|
await removeMember(orgId, userId);
|
|
8780
|
-
return
|
|
8746
|
+
return jsonResponse7({ success: true });
|
|
8781
8747
|
}
|
|
8782
8748
|
if (method === "POST" && subPath === "/invite") {
|
|
8783
8749
|
try {
|
|
8784
8750
|
const body = await request.json();
|
|
8785
8751
|
const invitation = await invite({ orgId, ...body });
|
|
8786
|
-
return
|
|
8752
|
+
return jsonResponse7(invitation, 201);
|
|
8787
8753
|
} catch (err2) {
|
|
8788
8754
|
return errorResponse(err2 instanceof Error ? err2.message : "Unknown error", 400);
|
|
8789
8755
|
}
|
|
8790
8756
|
}
|
|
8791
8757
|
if (method === "GET" && subPath === "/invitations") {
|
|
8792
8758
|
const invitations = await listInvitations(orgId);
|
|
8793
|
-
return
|
|
8759
|
+
return jsonResponse7(invitations);
|
|
8794
8760
|
}
|
|
8795
8761
|
const permMatch = subPath.match(/^\/permissions\/([^/]+)\/([^/]+)$/);
|
|
8796
8762
|
if (method === "GET" && permMatch) {
|
|
@@ -8798,20 +8764,20 @@ function createOrgModule(config, db) {
|
|
|
8798
8764
|
const permission = permMatch[2];
|
|
8799
8765
|
if (!userId || !permission) return errorResponse("Missing userId or permission", 400);
|
|
8800
8766
|
const allowed = await hasPermission(orgId, userId, permission);
|
|
8801
|
-
return
|
|
8767
|
+
return jsonResponse7({ allowed });
|
|
8802
8768
|
}
|
|
8803
8769
|
if (method === "POST" && subPath === "/roles") {
|
|
8804
8770
|
try {
|
|
8805
8771
|
const body = await request.json();
|
|
8806
8772
|
const role = await createRole(orgId, body);
|
|
8807
|
-
return
|
|
8773
|
+
return jsonResponse7(role, 201);
|
|
8808
8774
|
} catch (err2) {
|
|
8809
8775
|
return errorResponse(err2 instanceof Error ? err2.message : "Unknown error", 400);
|
|
8810
8776
|
}
|
|
8811
8777
|
}
|
|
8812
8778
|
if (method === "GET" && subPath === "/roles") {
|
|
8813
8779
|
const roles = await getRoles(orgId);
|
|
8814
|
-
return
|
|
8780
|
+
return jsonResponse7(roles);
|
|
8815
8781
|
}
|
|
8816
8782
|
return null;
|
|
8817
8783
|
}
|
|
@@ -8826,7 +8792,7 @@ function createOrgModule(config, db) {
|
|
|
8826
8792
|
try {
|
|
8827
8793
|
const body = await request.json();
|
|
8828
8794
|
const member = await acceptInvitation(invitationId, body.userId);
|
|
8829
|
-
return
|
|
8795
|
+
return jsonResponse7(member, 201);
|
|
8830
8796
|
} catch (err2) {
|
|
8831
8797
|
return errorResponse(err2 instanceof Error ? err2.message : "Unknown error", 400);
|
|
8832
8798
|
}
|
|
@@ -8836,7 +8802,7 @@ function createOrgModule(config, db) {
|
|
|
8836
8802
|
const invitationId = revokeMatch[1];
|
|
8837
8803
|
if (!invitationId) return errorResponse("Missing invitationId", 400);
|
|
8838
8804
|
await revokeInvitation(invitationId);
|
|
8839
|
-
return
|
|
8805
|
+
return jsonResponse7({ success: true });
|
|
8840
8806
|
}
|
|
8841
8807
|
return handleRequest(request);
|
|
8842
8808
|
}
|
|
@@ -8867,13 +8833,13 @@ function createOrgModule(config, db) {
|
|
|
8867
8833
|
}
|
|
8868
8834
|
|
|
8869
8835
|
// src/auth/organization-plugin.ts
|
|
8870
|
-
function
|
|
8836
|
+
function jsonResponse8(body, status = 200) {
|
|
8871
8837
|
return new Response(JSON.stringify(body), {
|
|
8872
8838
|
status,
|
|
8873
8839
|
headers: { "Content-Type": "application/json" }
|
|
8874
8840
|
});
|
|
8875
8841
|
}
|
|
8876
|
-
async function
|
|
8842
|
+
async function parseBody3(request) {
|
|
8877
8843
|
try {
|
|
8878
8844
|
return await request.json();
|
|
8879
8845
|
} catch {
|
|
@@ -8896,20 +8862,20 @@ function organization(config) {
|
|
|
8896
8862
|
async handler(request, endpointCtx) {
|
|
8897
8863
|
const user = await endpointCtx.getUser(request);
|
|
8898
8864
|
if (!user) {
|
|
8899
|
-
return
|
|
8865
|
+
return jsonResponse8({ error: "Authentication required" }, 401);
|
|
8900
8866
|
}
|
|
8901
|
-
const body = await
|
|
8867
|
+
const body = await parseBody3(request);
|
|
8902
8868
|
const name = typeof body.name === "string" ? body.name.trim() : null;
|
|
8903
8869
|
const slug = typeof body.slug === "string" ? body.slug.trim() : null;
|
|
8904
8870
|
if (!name || !slug) {
|
|
8905
|
-
return
|
|
8871
|
+
return jsonResponse8({ error: "Missing required fields: name, slug" }, 400);
|
|
8906
8872
|
}
|
|
8907
8873
|
const metadata = body.metadata !== void 0 && typeof body.metadata === "object" && body.metadata !== null ? body.metadata : void 0;
|
|
8908
8874
|
try {
|
|
8909
8875
|
const org = await module.create({ name, slug, ownerId: user.id, metadata });
|
|
8910
|
-
return
|
|
8876
|
+
return jsonResponse8(org, 201);
|
|
8911
8877
|
} catch (err2) {
|
|
8912
|
-
return
|
|
8878
|
+
return jsonResponse8(
|
|
8913
8879
|
{ error: err2 instanceof Error ? err2.message : "Failed to create organization" },
|
|
8914
8880
|
400
|
|
8915
8881
|
);
|
|
@@ -8926,10 +8892,10 @@ function organization(config) {
|
|
|
8926
8892
|
async handler(request, endpointCtx) {
|
|
8927
8893
|
const user = await endpointCtx.getUser(request);
|
|
8928
8894
|
if (!user) {
|
|
8929
|
-
return
|
|
8895
|
+
return jsonResponse8({ error: "Authentication required" }, 401);
|
|
8930
8896
|
}
|
|
8931
8897
|
const orgs = await module.list(user.id);
|
|
8932
|
-
return
|
|
8898
|
+
return jsonResponse8({ organizations: orgs });
|
|
8933
8899
|
}
|
|
8934
8900
|
});
|
|
8935
8901
|
ctx.addEndpoint({
|
|
@@ -8942,23 +8908,23 @@ function organization(config) {
|
|
|
8942
8908
|
async handler(request, endpointCtx) {
|
|
8943
8909
|
const user = await endpointCtx.getUser(request);
|
|
8944
8910
|
if (!user) {
|
|
8945
|
-
return
|
|
8911
|
+
return jsonResponse8({ error: "Authentication required" }, 401);
|
|
8946
8912
|
}
|
|
8947
8913
|
const url = new URL(request.url);
|
|
8948
8914
|
const segments = url.pathname.split("/").filter(Boolean);
|
|
8949
8915
|
const orgId = segments[2];
|
|
8950
8916
|
if (!orgId) {
|
|
8951
|
-
return
|
|
8917
|
+
return jsonResponse8({ error: "Missing organization ID in path" }, 400);
|
|
8952
8918
|
}
|
|
8953
8919
|
const member = await module.getMember(orgId, user.id);
|
|
8954
8920
|
if (!member || !ADMIN_ROLES.has(member.role)) {
|
|
8955
|
-
return
|
|
8921
|
+
return jsonResponse8({ error: "Admin or owner role required" }, 403);
|
|
8956
8922
|
}
|
|
8957
|
-
const body = await
|
|
8923
|
+
const body = await parseBody3(request);
|
|
8958
8924
|
const email = typeof body.email === "string" ? body.email.trim().toLowerCase() : null;
|
|
8959
8925
|
const role = typeof body.role === "string" ? body.role : "member";
|
|
8960
8926
|
if (!email) {
|
|
8961
|
-
return
|
|
8927
|
+
return jsonResponse8({ error: "Missing required field: email" }, 400);
|
|
8962
8928
|
}
|
|
8963
8929
|
try {
|
|
8964
8930
|
const invitation = await module.invite({
|
|
@@ -8967,9 +8933,9 @@ function organization(config) {
|
|
|
8967
8933
|
role,
|
|
8968
8934
|
invitedBy: user.id
|
|
8969
8935
|
});
|
|
8970
|
-
return
|
|
8936
|
+
return jsonResponse8(invitation, 201);
|
|
8971
8937
|
} catch (err2) {
|
|
8972
|
-
return
|
|
8938
|
+
return jsonResponse8(
|
|
8973
8939
|
{ error: err2 instanceof Error ? err2.message : "Failed to send invitation" },
|
|
8974
8940
|
400
|
|
8975
8941
|
);
|
|
@@ -8986,20 +8952,20 @@ function organization(config) {
|
|
|
8986
8952
|
async handler(request, endpointCtx) {
|
|
8987
8953
|
const user = await endpointCtx.getUser(request);
|
|
8988
8954
|
if (!user) {
|
|
8989
|
-
return
|
|
8955
|
+
return jsonResponse8({ error: "Authentication required" }, 401);
|
|
8990
8956
|
}
|
|
8991
8957
|
const url = new URL(request.url);
|
|
8992
8958
|
const segments = url.pathname.split("/").filter(Boolean);
|
|
8993
8959
|
const orgId = segments[2];
|
|
8994
8960
|
if (!orgId) {
|
|
8995
|
-
return
|
|
8961
|
+
return jsonResponse8({ error: "Missing organization ID in path" }, 400);
|
|
8996
8962
|
}
|
|
8997
8963
|
const callerMember = await module.getMember(orgId, user.id);
|
|
8998
8964
|
if (!callerMember) {
|
|
8999
|
-
return
|
|
8965
|
+
return jsonResponse8({ error: "You are not a member of this organization" }, 403);
|
|
9000
8966
|
}
|
|
9001
8967
|
const members = await module.getMembers(orgId);
|
|
9002
|
-
return
|
|
8968
|
+
return jsonResponse8({ members });
|
|
9003
8969
|
}
|
|
9004
8970
|
});
|
|
9005
8971
|
ctx.addEndpoint({
|
|
@@ -9012,29 +8978,29 @@ function organization(config) {
|
|
|
9012
8978
|
async handler(request, endpointCtx) {
|
|
9013
8979
|
const user = await endpointCtx.getUser(request);
|
|
9014
8980
|
if (!user) {
|
|
9015
|
-
return
|
|
8981
|
+
return jsonResponse8({ error: "Authentication required" }, 401);
|
|
9016
8982
|
}
|
|
9017
8983
|
const url = new URL(request.url);
|
|
9018
8984
|
const segments = url.pathname.split("/").filter(Boolean);
|
|
9019
8985
|
const orgId = segments[2];
|
|
9020
8986
|
const targetUserId = segments[4];
|
|
9021
8987
|
if (!orgId || !targetUserId) {
|
|
9022
|
-
return
|
|
8988
|
+
return jsonResponse8({ error: "Missing organization ID or user ID in path" }, 400);
|
|
9023
8989
|
}
|
|
9024
8990
|
const callerMember = await module.getMember(orgId, user.id);
|
|
9025
8991
|
if (!callerMember || !ADMIN_ROLES.has(callerMember.role)) {
|
|
9026
|
-
return
|
|
8992
|
+
return jsonResponse8({ error: "Admin or owner role required" }, 403);
|
|
9027
8993
|
}
|
|
9028
|
-
const body = await
|
|
8994
|
+
const body = await parseBody3(request);
|
|
9029
8995
|
const role = typeof body.role === "string" ? body.role : null;
|
|
9030
8996
|
if (!role) {
|
|
9031
|
-
return
|
|
8997
|
+
return jsonResponse8({ error: "Missing required field: role" }, 400);
|
|
9032
8998
|
}
|
|
9033
8999
|
try {
|
|
9034
9000
|
const member = await module.updateMemberRole(orgId, targetUserId, role);
|
|
9035
|
-
return
|
|
9001
|
+
return jsonResponse8(member);
|
|
9036
9002
|
} catch (err2) {
|
|
9037
|
-
return
|
|
9003
|
+
return jsonResponse8(
|
|
9038
9004
|
{ error: err2 instanceof Error ? err2.message : "Failed to update member role" },
|
|
9039
9005
|
400
|
|
9040
9006
|
);
|
|
@@ -9051,24 +9017,24 @@ function organization(config) {
|
|
|
9051
9017
|
async handler(request, endpointCtx) {
|
|
9052
9018
|
const user = await endpointCtx.getUser(request);
|
|
9053
9019
|
if (!user) {
|
|
9054
|
-
return
|
|
9020
|
+
return jsonResponse8({ error: "Authentication required" }, 401);
|
|
9055
9021
|
}
|
|
9056
9022
|
const url = new URL(request.url);
|
|
9057
9023
|
const segments = url.pathname.split("/").filter(Boolean);
|
|
9058
9024
|
const orgId = segments[2];
|
|
9059
9025
|
const targetUserId = segments[4];
|
|
9060
9026
|
if (!orgId || !targetUserId) {
|
|
9061
|
-
return
|
|
9027
|
+
return jsonResponse8({ error: "Missing organization ID or user ID in path" }, 400);
|
|
9062
9028
|
}
|
|
9063
9029
|
const callerMember = await module.getMember(orgId, user.id);
|
|
9064
9030
|
if (!callerMember || !ADMIN_ROLES.has(callerMember.role)) {
|
|
9065
|
-
return
|
|
9031
|
+
return jsonResponse8({ error: "Admin or owner role required" }, 403);
|
|
9066
9032
|
}
|
|
9067
9033
|
try {
|
|
9068
9034
|
await module.removeMember(orgId, targetUserId);
|
|
9069
|
-
return
|
|
9035
|
+
return jsonResponse8({ removed: true });
|
|
9070
9036
|
} catch (err2) {
|
|
9071
|
-
return
|
|
9037
|
+
return jsonResponse8(
|
|
9072
9038
|
{ error: err2 instanceof Error ? err2.message : "Failed to remove member" },
|
|
9073
9039
|
400
|
|
9074
9040
|
);
|
|
@@ -9473,13 +9439,13 @@ function parseAuthData(authData) {
|
|
|
9473
9439
|
}
|
|
9474
9440
|
return { rpIdHash, flags, signCount, attestedCredentialData };
|
|
9475
9441
|
}
|
|
9476
|
-
function
|
|
9442
|
+
function jsonResponse9(body, status = 200) {
|
|
9477
9443
|
return new Response(JSON.stringify(body), {
|
|
9478
9444
|
status,
|
|
9479
9445
|
headers: { "Content-Type": "application/json" }
|
|
9480
9446
|
});
|
|
9481
9447
|
}
|
|
9482
|
-
async function
|
|
9448
|
+
async function parseBody4(request) {
|
|
9483
9449
|
try {
|
|
9484
9450
|
return await request.json();
|
|
9485
9451
|
} catch {
|
|
@@ -9817,84 +9783,84 @@ function createPasskeyModule(config, db) {
|
|
|
9817
9783
|
const method = request.method.toUpperCase();
|
|
9818
9784
|
const segments = getPathSegments(url);
|
|
9819
9785
|
if (method === "POST" && segments.length === 4 && segments[1] === "passkey" && segments[2] === "register" && segments[3] === "options") {
|
|
9820
|
-
const body = await
|
|
9786
|
+
const body = await parseBody4(request);
|
|
9821
9787
|
const userId = typeof body.userId === "string" ? body.userId : null;
|
|
9822
9788
|
const userName = typeof body.userName === "string" ? body.userName : null;
|
|
9823
9789
|
if (!userId || !userName) {
|
|
9824
|
-
return
|
|
9790
|
+
return jsonResponse9({ error: "userId and userName required" }, 400);
|
|
9825
9791
|
}
|
|
9826
9792
|
try {
|
|
9827
9793
|
const options = await getRegistrationOptions(userId, userName);
|
|
9828
|
-
return
|
|
9794
|
+
return jsonResponse9(options);
|
|
9829
9795
|
} catch (err2) {
|
|
9830
9796
|
const message = err2 instanceof Error ? err2.message : "Failed to generate options";
|
|
9831
9797
|
const code = err2 instanceof PasskeyError ? err2.code : "INTERNAL_ERROR";
|
|
9832
|
-
return
|
|
9798
|
+
return jsonResponse9({ error: message, code }, 500);
|
|
9833
9799
|
}
|
|
9834
9800
|
}
|
|
9835
9801
|
if (method === "POST" && segments.length === 4 && segments[1] === "passkey" && segments[2] === "register" && segments[3] === "verify") {
|
|
9836
|
-
const body = await
|
|
9802
|
+
const body = await parseBody4(request);
|
|
9837
9803
|
const userId = typeof body.userId === "string" ? body.userId : null;
|
|
9838
|
-
if (!userId) return
|
|
9804
|
+
if (!userId) return jsonResponse9({ error: "userId required" }, 400);
|
|
9839
9805
|
const resp = body.response;
|
|
9840
|
-
if (!resp) return
|
|
9806
|
+
if (!resp) return jsonResponse9({ error: "response required" }, 400);
|
|
9841
9807
|
try {
|
|
9842
9808
|
const result = await verifyRegistration(userId, resp);
|
|
9843
|
-
return
|
|
9809
|
+
return jsonResponse9(result);
|
|
9844
9810
|
} catch (err2) {
|
|
9845
9811
|
const message = err2 instanceof Error ? err2.message : "Registration failed";
|
|
9846
9812
|
const code = err2 instanceof PasskeyError ? err2.code : "INTERNAL_ERROR";
|
|
9847
|
-
return
|
|
9813
|
+
return jsonResponse9({ error: message, code }, 400);
|
|
9848
9814
|
}
|
|
9849
9815
|
}
|
|
9850
9816
|
if (method === "POST" && segments.length === 4 && segments[1] === "passkey" && segments[2] === "login" && segments[3] === "options") {
|
|
9851
|
-
const body = await
|
|
9817
|
+
const body = await parseBody4(request);
|
|
9852
9818
|
const userId = typeof body.userId === "string" ? body.userId : void 0;
|
|
9853
9819
|
try {
|
|
9854
9820
|
const options = await getAuthenticationOptions(userId);
|
|
9855
|
-
return
|
|
9821
|
+
return jsonResponse9(options);
|
|
9856
9822
|
} catch (err2) {
|
|
9857
9823
|
const message = err2 instanceof Error ? err2.message : "Failed to generate options";
|
|
9858
9824
|
const code = err2 instanceof PasskeyError ? err2.code : "INTERNAL_ERROR";
|
|
9859
|
-
return
|
|
9825
|
+
return jsonResponse9({ error: message, code }, 500);
|
|
9860
9826
|
}
|
|
9861
9827
|
}
|
|
9862
9828
|
if (method === "POST" && segments.length === 4 && segments[1] === "passkey" && segments[2] === "login" && segments[3] === "verify") {
|
|
9863
|
-
const body = await
|
|
9829
|
+
const body = await parseBody4(request);
|
|
9864
9830
|
const resp = body.response;
|
|
9865
|
-
if (!resp) return
|
|
9831
|
+
if (!resp) return jsonResponse9({ error: "response required" }, 400);
|
|
9866
9832
|
try {
|
|
9867
9833
|
const result = await verifyAuthentication(resp);
|
|
9868
|
-
return
|
|
9834
|
+
return jsonResponse9(result);
|
|
9869
9835
|
} catch (err2) {
|
|
9870
9836
|
const message = err2 instanceof Error ? err2.message : "Authentication failed";
|
|
9871
9837
|
const code = err2 instanceof PasskeyError ? err2.code : "INTERNAL_ERROR";
|
|
9872
|
-
return
|
|
9838
|
+
return jsonResponse9({ error: message, code }, 401);
|
|
9873
9839
|
}
|
|
9874
9840
|
}
|
|
9875
9841
|
if (method === "GET" && segments.length === 3 && segments[1] === "passkey" && segments[2] === "credentials") {
|
|
9876
9842
|
const userId = url.searchParams.get("userId");
|
|
9877
|
-
if (!userId) return
|
|
9843
|
+
if (!userId) return jsonResponse9({ error: "userId query param required" }, 400);
|
|
9878
9844
|
try {
|
|
9879
9845
|
const creds = await listCredentials(userId);
|
|
9880
|
-
return
|
|
9846
|
+
return jsonResponse9({ credentials: creds });
|
|
9881
9847
|
} catch (err2) {
|
|
9882
9848
|
const message = err2 instanceof Error ? err2.message : "Failed to list credentials";
|
|
9883
|
-
return
|
|
9849
|
+
return jsonResponse9({ error: message }, 500);
|
|
9884
9850
|
}
|
|
9885
9851
|
}
|
|
9886
9852
|
if (method === "DELETE" && segments.length === 4 && segments[1] === "passkey" && segments[2] === "credentials") {
|
|
9887
9853
|
const credentialId = segments[3];
|
|
9888
|
-
if (!credentialId) return
|
|
9889
|
-
const body = await
|
|
9854
|
+
if (!credentialId) return jsonResponse9({ error: "Credential ID required" }, 400);
|
|
9855
|
+
const body = await parseBody4(request);
|
|
9890
9856
|
const userId = typeof body.userId === "string" ? body.userId : null;
|
|
9891
|
-
if (!userId) return
|
|
9857
|
+
if (!userId) return jsonResponse9({ error: "userId required" }, 400);
|
|
9892
9858
|
try {
|
|
9893
9859
|
await removeCredential(credentialId, userId);
|
|
9894
|
-
return
|
|
9860
|
+
return jsonResponse9({ removed: true });
|
|
9895
9861
|
} catch (err2) {
|
|
9896
9862
|
const message = err2 instanceof Error ? err2.message : "Failed to remove credential";
|
|
9897
|
-
return
|
|
9863
|
+
return jsonResponse9({ error: message }, 500);
|
|
9898
9864
|
}
|
|
9899
9865
|
}
|
|
9900
9866
|
return null;
|
|
@@ -9911,13 +9877,13 @@ function createPasskeyModule(config, db) {
|
|
|
9911
9877
|
}
|
|
9912
9878
|
|
|
9913
9879
|
// src/auth/passkey-plugin.ts
|
|
9914
|
-
function
|
|
9880
|
+
function jsonResponse10(body, status = 200) {
|
|
9915
9881
|
return new Response(JSON.stringify(body), {
|
|
9916
9882
|
status,
|
|
9917
9883
|
headers: { "Content-Type": "application/json" }
|
|
9918
9884
|
});
|
|
9919
9885
|
}
|
|
9920
|
-
async function
|
|
9886
|
+
async function parseBody5(request) {
|
|
9921
9887
|
try {
|
|
9922
9888
|
return await request.json();
|
|
9923
9889
|
} catch {
|
|
@@ -9939,16 +9905,16 @@ function passkey(config) {
|
|
|
9939
9905
|
async handler(request, endpointCtx) {
|
|
9940
9906
|
const user = await endpointCtx.getUser(request);
|
|
9941
9907
|
if (!user) {
|
|
9942
|
-
return
|
|
9908
|
+
return jsonResponse10({ error: "Authentication required" }, 401);
|
|
9943
9909
|
}
|
|
9944
|
-
const body = await
|
|
9910
|
+
const body = await parseBody5(request);
|
|
9945
9911
|
const userId = typeof body.userId === "string" ? body.userId : user.id;
|
|
9946
9912
|
const userName = typeof body.userName === "string" ? body.userName : user.email ?? user.id;
|
|
9947
9913
|
try {
|
|
9948
9914
|
const options = await module.getRegistrationOptions(userId, userName);
|
|
9949
|
-
return
|
|
9915
|
+
return jsonResponse10(options);
|
|
9950
9916
|
} catch (err2) {
|
|
9951
|
-
return
|
|
9917
|
+
return jsonResponse10(
|
|
9952
9918
|
{ error: err2 instanceof Error ? err2.message : "Failed to generate options" },
|
|
9953
9919
|
500
|
|
9954
9920
|
);
|
|
@@ -9965,19 +9931,19 @@ function passkey(config) {
|
|
|
9965
9931
|
async handler(request, endpointCtx) {
|
|
9966
9932
|
const user = await endpointCtx.getUser(request);
|
|
9967
9933
|
if (!user) {
|
|
9968
|
-
return
|
|
9934
|
+
return jsonResponse10({ error: "Authentication required" }, 401);
|
|
9969
9935
|
}
|
|
9970
|
-
const body = await
|
|
9936
|
+
const body = await parseBody5(request);
|
|
9971
9937
|
const userId = typeof body.userId === "string" ? body.userId : user.id;
|
|
9972
9938
|
const response = body.response;
|
|
9973
9939
|
if (!response) {
|
|
9974
|
-
return
|
|
9940
|
+
return jsonResponse10({ error: "Missing required field: response" }, 400);
|
|
9975
9941
|
}
|
|
9976
9942
|
try {
|
|
9977
9943
|
const result = await module.verifyRegistration(userId, response);
|
|
9978
|
-
return
|
|
9944
|
+
return jsonResponse10(result);
|
|
9979
9945
|
} catch (err2) {
|
|
9980
|
-
return
|
|
9946
|
+
return jsonResponse10(
|
|
9981
9947
|
{ error: err2 instanceof Error ? err2.message : "Registration failed" },
|
|
9982
9948
|
400
|
|
9983
9949
|
);
|
|
@@ -9991,13 +9957,13 @@ function passkey(config) {
|
|
|
9991
9957
|
description: "Get WebAuthn authentication options"
|
|
9992
9958
|
},
|
|
9993
9959
|
async handler(request) {
|
|
9994
|
-
const body = await
|
|
9960
|
+
const body = await parseBody5(request);
|
|
9995
9961
|
const userId = typeof body.userId === "string" ? body.userId : void 0;
|
|
9996
9962
|
try {
|
|
9997
9963
|
const options = await module.getAuthenticationOptions(userId);
|
|
9998
|
-
return
|
|
9964
|
+
return jsonResponse10(options);
|
|
9999
9965
|
} catch (err2) {
|
|
10000
|
-
return
|
|
9966
|
+
return jsonResponse10(
|
|
10001
9967
|
{ error: err2 instanceof Error ? err2.message : "Failed to generate options" },
|
|
10002
9968
|
500
|
|
10003
9969
|
);
|
|
@@ -10011,19 +9977,37 @@ function passkey(config) {
|
|
|
10011
9977
|
description: "Verify a WebAuthn assertion and return the authenticated user"
|
|
10012
9978
|
},
|
|
10013
9979
|
async handler(request) {
|
|
10014
|
-
const body = await
|
|
9980
|
+
const body = await parseBody5(request);
|
|
10015
9981
|
const response = body.response;
|
|
10016
9982
|
if (!response) {
|
|
10017
|
-
return
|
|
9983
|
+
return jsonResponse10({ error: "Missing required field: response" }, 400);
|
|
10018
9984
|
}
|
|
10019
9985
|
try {
|
|
10020
9986
|
const result = await module.verifyAuthentication(response);
|
|
10021
9987
|
if (!result) {
|
|
10022
|
-
return
|
|
9988
|
+
return jsonResponse10({ error: "Authentication failed" }, 401);
|
|
10023
9989
|
}
|
|
10024
|
-
|
|
9990
|
+
if (ctx.sessionManager) {
|
|
9991
|
+
const { session, token } = await ctx.sessionManager.create(result.userId);
|
|
9992
|
+
const maxAge = Math.floor((session.expiresAt.getTime() - Date.now()) / 1e3);
|
|
9993
|
+
return new Response(
|
|
9994
|
+
JSON.stringify({
|
|
9995
|
+
user: { id: result.userId },
|
|
9996
|
+
session: { token, expiresAt: session.expiresAt },
|
|
9997
|
+
credential: result.credential
|
|
9998
|
+
}),
|
|
9999
|
+
{
|
|
10000
|
+
status: 200,
|
|
10001
|
+
headers: {
|
|
10002
|
+
"Content-Type": "application/json",
|
|
10003
|
+
"Set-Cookie": buildSetCookie("kavach_session", token, maxAge)
|
|
10004
|
+
}
|
|
10005
|
+
}
|
|
10006
|
+
);
|
|
10007
|
+
}
|
|
10008
|
+
return jsonResponse10(result);
|
|
10025
10009
|
} catch (err2) {
|
|
10026
|
-
return
|
|
10010
|
+
return jsonResponse10(
|
|
10027
10011
|
{ error: err2 instanceof Error ? err2.message : "Authentication failed" },
|
|
10028
10012
|
401
|
|
10029
10013
|
);
|
|
@@ -10040,13 +10024,13 @@ function passkey(config) {
|
|
|
10040
10024
|
async handler(request, endpointCtx) {
|
|
10041
10025
|
const user = await endpointCtx.getUser(request);
|
|
10042
10026
|
if (!user) {
|
|
10043
|
-
return
|
|
10027
|
+
return jsonResponse10({ error: "Authentication required" }, 401);
|
|
10044
10028
|
}
|
|
10045
10029
|
try {
|
|
10046
10030
|
const credentials = await module.listCredentials(user.id);
|
|
10047
|
-
return
|
|
10031
|
+
return jsonResponse10({ credentials });
|
|
10048
10032
|
} catch (err2) {
|
|
10049
|
-
return
|
|
10033
|
+
return jsonResponse10(
|
|
10050
10034
|
{ error: err2 instanceof Error ? err2.message : "Failed to list credentials" },
|
|
10051
10035
|
500
|
|
10052
10036
|
);
|
|
@@ -10063,19 +10047,19 @@ function passkey(config) {
|
|
|
10063
10047
|
async handler(request, endpointCtx) {
|
|
10064
10048
|
const user = await endpointCtx.getUser(request);
|
|
10065
10049
|
if (!user) {
|
|
10066
|
-
return
|
|
10050
|
+
return jsonResponse10({ error: "Authentication required" }, 401);
|
|
10067
10051
|
}
|
|
10068
10052
|
const url = new URL(request.url);
|
|
10069
10053
|
const segments = url.pathname.split("/").filter(Boolean);
|
|
10070
10054
|
const credentialId = segments[3];
|
|
10071
10055
|
if (!credentialId) {
|
|
10072
|
-
return
|
|
10056
|
+
return jsonResponse10({ error: "Missing credential ID in path" }, 400);
|
|
10073
10057
|
}
|
|
10074
10058
|
try {
|
|
10075
10059
|
await module.removeCredential(credentialId, user.id);
|
|
10076
|
-
return
|
|
10060
|
+
return jsonResponse10({ removed: true });
|
|
10077
10061
|
} catch (err2) {
|
|
10078
|
-
return
|
|
10062
|
+
return jsonResponse10(
|
|
10079
10063
|
{ error: err2 instanceof Error ? err2.message : "Failed to remove credential" },
|
|
10080
10064
|
500
|
|
10081
10065
|
);
|
|
@@ -10092,7 +10076,7 @@ var TOKEN_PURPOSE2 = "password-reset";
|
|
|
10092
10076
|
function makeError7(code, message, details) {
|
|
10093
10077
|
return { code, message, ...details !== void 0 ? { details } : {} };
|
|
10094
10078
|
}
|
|
10095
|
-
function
|
|
10079
|
+
function jsonResponse11(body, status = 200) {
|
|
10096
10080
|
return new Response(JSON.stringify(body), {
|
|
10097
10081
|
status,
|
|
10098
10082
|
headers: { "Content-Type": "application/json" }
|
|
@@ -10208,27 +10192,27 @@ function createPasswordResetModule(config, db, sessionManager, tokenModule) {
|
|
|
10208
10192
|
try {
|
|
10209
10193
|
body = await request.json();
|
|
10210
10194
|
} catch {
|
|
10211
|
-
return
|
|
10195
|
+
return jsonResponse11({ error: "Invalid JSON body" }, 400);
|
|
10212
10196
|
}
|
|
10213
10197
|
const b = body;
|
|
10214
10198
|
if (pathname === "/auth/forgot-password") {
|
|
10215
10199
|
if (typeof b.email !== "string") {
|
|
10216
|
-
return
|
|
10200
|
+
return jsonResponse11({ error: "Missing required field: email" }, 400);
|
|
10217
10201
|
}
|
|
10218
10202
|
const result = await requestReset(b.email);
|
|
10219
10203
|
if (!result.success) {
|
|
10220
|
-
return
|
|
10204
|
+
return jsonResponse11({ error: result.error.message }, 500);
|
|
10221
10205
|
}
|
|
10222
10206
|
return new Response(null, { status: 204 });
|
|
10223
10207
|
}
|
|
10224
10208
|
if (pathname === "/auth/reset-password") {
|
|
10225
10209
|
if (typeof b.token !== "string" || typeof b.password !== "string") {
|
|
10226
|
-
return
|
|
10210
|
+
return jsonResponse11({ error: "Missing required fields: token, password" }, 400);
|
|
10227
10211
|
}
|
|
10228
10212
|
const result = await resetPassword(b.token, b.password);
|
|
10229
10213
|
if (!result.success) {
|
|
10230
10214
|
const status = result.error.code === "INVALID_PASSWORD" ? 400 : 400;
|
|
10231
|
-
return
|
|
10215
|
+
return jsonResponse11({ error: result.error.message }, status);
|
|
10232
10216
|
}
|
|
10233
10217
|
return new Response(null, { status: 204 });
|
|
10234
10218
|
}
|
|
@@ -10263,7 +10247,7 @@ function generateNumericCode2(length) {
|
|
|
10263
10247
|
function normalisePhone(phone) {
|
|
10264
10248
|
return phone.replace(/\s+/g, "");
|
|
10265
10249
|
}
|
|
10266
|
-
function
|
|
10250
|
+
function jsonResponse12(body, status = 200) {
|
|
10267
10251
|
return new Response(JSON.stringify(body), {
|
|
10268
10252
|
status,
|
|
10269
10253
|
headers: { "Content-Type": "application/json" }
|
|
@@ -10335,25 +10319,25 @@ function createPhoneAuthModule(config, db, sessionManager) {
|
|
|
10335
10319
|
try {
|
|
10336
10320
|
body = await request.json();
|
|
10337
10321
|
} catch {
|
|
10338
|
-
return
|
|
10322
|
+
return jsonResponse12({ error: "Invalid JSON body" }, 400);
|
|
10339
10323
|
}
|
|
10340
10324
|
const b = body;
|
|
10341
10325
|
if (pathname === "/auth/phone/send-code") {
|
|
10342
10326
|
if (typeof b.phoneNumber !== "string") {
|
|
10343
|
-
return
|
|
10327
|
+
return jsonResponse12({ error: "Missing required field: phoneNumber" }, 400);
|
|
10344
10328
|
}
|
|
10345
10329
|
const result = await sendCode(b.phoneNumber);
|
|
10346
|
-
return
|
|
10330
|
+
return jsonResponse12(result);
|
|
10347
10331
|
}
|
|
10348
10332
|
if (pathname === "/auth/phone/verify") {
|
|
10349
10333
|
if (typeof b.phoneNumber !== "string" || typeof b.code !== "string") {
|
|
10350
|
-
return
|
|
10334
|
+
return jsonResponse12({ error: "Missing required fields: phoneNumber, code" }, 400);
|
|
10351
10335
|
}
|
|
10352
10336
|
const result = await verifyCode(b.phoneNumber, b.code);
|
|
10353
10337
|
if (!result) {
|
|
10354
|
-
return
|
|
10338
|
+
return jsonResponse12({ error: "Invalid or expired code" }, 401);
|
|
10355
10339
|
}
|
|
10356
|
-
return
|
|
10340
|
+
return jsonResponse12(result);
|
|
10357
10341
|
}
|
|
10358
10342
|
return null;
|
|
10359
10343
|
}
|
|
@@ -10373,12 +10357,12 @@ async function polarRequest(accessToken, baseUrl, method, path, body) {
|
|
|
10373
10357
|
headers,
|
|
10374
10358
|
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
10375
10359
|
});
|
|
10376
|
-
const
|
|
10360
|
+
const json2 = await response.json();
|
|
10377
10361
|
if (!response.ok) {
|
|
10378
|
-
const message =
|
|
10362
|
+
const message = json2.detail ?? json2.message ?? `Polar API error: ${response.status}`;
|
|
10379
10363
|
throw new Error(message);
|
|
10380
10364
|
}
|
|
10381
|
-
return
|
|
10365
|
+
return json2;
|
|
10382
10366
|
}
|
|
10383
10367
|
async function verifyWebhookSignature(payload, signatureHeader, webhookSecret) {
|
|
10384
10368
|
const prefix = "sha256=";
|
|
@@ -10601,19 +10585,6 @@ function createPolarModule(config, db) {
|
|
|
10601
10585
|
}
|
|
10602
10586
|
|
|
10603
10587
|
// src/auth/polar-plugin.ts
|
|
10604
|
-
function json(body, status = 200) {
|
|
10605
|
-
return new Response(JSON.stringify(body), {
|
|
10606
|
-
status,
|
|
10607
|
-
headers: { "Content-Type": "application/json" }
|
|
10608
|
-
});
|
|
10609
|
-
}
|
|
10610
|
-
async function parseBody10(request) {
|
|
10611
|
-
try {
|
|
10612
|
-
return await request.json();
|
|
10613
|
-
} catch {
|
|
10614
|
-
return {};
|
|
10615
|
-
}
|
|
10616
|
-
}
|
|
10617
10588
|
function polar(config) {
|
|
10618
10589
|
return {
|
|
10619
10590
|
id: "kavach-polar",
|
|
@@ -10631,13 +10602,14 @@ function polar(config) {
|
|
|
10631
10602
|
if (!user) {
|
|
10632
10603
|
return json({ error: "Authentication required" }, 401);
|
|
10633
10604
|
}
|
|
10634
|
-
const
|
|
10635
|
-
|
|
10605
|
+
const bodyResult = await parseBody(request);
|
|
10606
|
+
if (!bodyResult.ok) return bodyResult.response;
|
|
10607
|
+
const productId = typeof bodyResult.data.productId === "string" ? bodyResult.data.productId.trim() : null;
|
|
10636
10608
|
if (!productId) {
|
|
10637
10609
|
return json({ error: "Missing required field: productId" }, 400);
|
|
10638
10610
|
}
|
|
10639
|
-
const successUrl = typeof
|
|
10640
|
-
const customerEmail = typeof
|
|
10611
|
+
const successUrl = typeof bodyResult.data.successUrl === "string" ? bodyResult.data.successUrl : void 0;
|
|
10612
|
+
const customerEmail = typeof bodyResult.data.customerEmail === "string" ? bodyResult.data.customerEmail : void 0;
|
|
10641
10613
|
try {
|
|
10642
10614
|
const result = await module.createCheckout(user.id, productId, {
|
|
10643
10615
|
successUrl,
|
|
@@ -12082,13 +12054,13 @@ function scim(config) {
|
|
|
12082
12054
|
// src/auth/siwe.ts
|
|
12083
12055
|
var DEFAULT_NONCE_TTL_SECONDS = 300;
|
|
12084
12056
|
var SIWE_VERSION = "1";
|
|
12085
|
-
function
|
|
12057
|
+
function jsonResponse13(body, status = 200) {
|
|
12086
12058
|
return new Response(JSON.stringify(body), {
|
|
12087
12059
|
status,
|
|
12088
12060
|
headers: { "Content-Type": "application/json" }
|
|
12089
12061
|
});
|
|
12090
12062
|
}
|
|
12091
|
-
async function
|
|
12063
|
+
async function parseBody6(request) {
|
|
12092
12064
|
try {
|
|
12093
12065
|
return await request.json();
|
|
12094
12066
|
} catch {
|
|
@@ -12223,20 +12195,20 @@ function createSiweModule(config) {
|
|
|
12223
12195
|
const { method, pathname } = { method: request.method, pathname: url.pathname };
|
|
12224
12196
|
if (method === "GET" && pathname.endsWith("/auth/siwe/nonce")) {
|
|
12225
12197
|
const nonce = await generateNonce();
|
|
12226
|
-
return
|
|
12198
|
+
return jsonResponse13({ nonce });
|
|
12227
12199
|
}
|
|
12228
12200
|
if (method === "POST" && pathname.endsWith("/auth/siwe/verify")) {
|
|
12229
|
-
const body = await
|
|
12201
|
+
const body = await parseBody6(request);
|
|
12230
12202
|
const message = typeof body.message === "string" ? body.message : null;
|
|
12231
12203
|
const signature = typeof body.signature === "string" ? body.signature : null;
|
|
12232
12204
|
if (!message || !signature) {
|
|
12233
|
-
return
|
|
12205
|
+
return jsonResponse13({ error: "Missing required fields: message, signature" }, 400);
|
|
12234
12206
|
}
|
|
12235
12207
|
try {
|
|
12236
12208
|
const result = await verify(message, signature);
|
|
12237
|
-
return
|
|
12209
|
+
return jsonResponse13({ address: result.address, chainId: result.chainId });
|
|
12238
12210
|
} catch (err2) {
|
|
12239
|
-
return
|
|
12211
|
+
return jsonResponse13(
|
|
12240
12212
|
{ error: err2 instanceof Error ? err2.message : "Verification failed" },
|
|
12241
12213
|
400
|
|
12242
12214
|
);
|
|
@@ -13273,7 +13245,7 @@ function createSsoModule(config, db) {
|
|
|
13273
13245
|
const url = new URL(request.url);
|
|
13274
13246
|
const { pathname } = url;
|
|
13275
13247
|
const { method } = request;
|
|
13276
|
-
const
|
|
13248
|
+
const json2 = (data, status = 200) => new Response(JSON.stringify(data), {
|
|
13277
13249
|
status,
|
|
13278
13250
|
headers: { "Content-Type": "application/json" }
|
|
13279
13251
|
});
|
|
@@ -13282,14 +13254,14 @@ function createSsoModule(config, db) {
|
|
|
13282
13254
|
try {
|
|
13283
13255
|
body = await request.json();
|
|
13284
13256
|
} catch {
|
|
13285
|
-
return
|
|
13257
|
+
return json2({ error: "Invalid JSON body" }, 400);
|
|
13286
13258
|
}
|
|
13287
13259
|
const b = body;
|
|
13288
13260
|
if (typeof b.orgId !== "string" || typeof b.providerId !== "string" || typeof b.type !== "string" || typeof b.domain !== "string") {
|
|
13289
|
-
return
|
|
13261
|
+
return json2({ error: "Missing required fields: orgId, providerId, type, domain" }, 400);
|
|
13290
13262
|
}
|
|
13291
13263
|
if (b.type !== "saml" && b.type !== "oidc") {
|
|
13292
|
-
return
|
|
13264
|
+
return json2({ error: "type must be 'saml' or 'oidc'" }, 400);
|
|
13293
13265
|
}
|
|
13294
13266
|
const conn = await createConnection({
|
|
13295
13267
|
orgId: b.orgId,
|
|
@@ -13297,19 +13269,19 @@ function createSsoModule(config, db) {
|
|
|
13297
13269
|
type: b.type,
|
|
13298
13270
|
domain: b.domain
|
|
13299
13271
|
});
|
|
13300
|
-
return
|
|
13272
|
+
return json2(conn, 201);
|
|
13301
13273
|
}
|
|
13302
13274
|
const listMatch = /^\/auth\/sso\/connections\/([^/]+)$/.exec(pathname);
|
|
13303
13275
|
if (method === "GET" && listMatch) {
|
|
13304
13276
|
const orgId = decodeURIComponent(listMatch[1] ?? "");
|
|
13305
13277
|
const conns = await listConnections(orgId);
|
|
13306
|
-
return
|
|
13278
|
+
return json2(conns);
|
|
13307
13279
|
}
|
|
13308
13280
|
const deleteMatch = /^\/auth\/sso\/connections\/([^/]+)$/.exec(pathname);
|
|
13309
13281
|
if (method === "DELETE" && deleteMatch) {
|
|
13310
13282
|
const connId = decodeURIComponent(deleteMatch[1] ?? "");
|
|
13311
13283
|
await removeConnection(connId);
|
|
13312
|
-
return
|
|
13284
|
+
return json2({ success: true });
|
|
13313
13285
|
}
|
|
13314
13286
|
const samlInitMatch = /^\/auth\/sso\/saml\/([^/]+)$/.exec(pathname);
|
|
13315
13287
|
if (method === "GET" && samlInitMatch) {
|
|
@@ -13319,7 +13291,7 @@ function createSsoModule(config, db) {
|
|
|
13319
13291
|
const authUrl = await getSamlAuthUrl(connId, relayState);
|
|
13320
13292
|
return new Response(null, { status: 302, headers: { Location: authUrl } });
|
|
13321
13293
|
} catch (err2) {
|
|
13322
|
-
return
|
|
13294
|
+
return json2({ error: err2 instanceof Error ? err2.message : "Unknown error" }, 400);
|
|
13323
13295
|
}
|
|
13324
13296
|
}
|
|
13325
13297
|
const samlAcsMatch = /^\/auth\/sso\/saml\/([^/]+)\/acs$/.exec(pathname);
|
|
@@ -13332,14 +13304,14 @@ function createSsoModule(config, db) {
|
|
|
13332
13304
|
if (typeof val !== "string") throw new Error("Missing SAMLResponse");
|
|
13333
13305
|
samlResponse = val;
|
|
13334
13306
|
} catch {
|
|
13335
|
-
return
|
|
13307
|
+
return json2({ error: "Missing or invalid SAMLResponse" }, 400);
|
|
13336
13308
|
}
|
|
13337
13309
|
try {
|
|
13338
13310
|
const result = await handleSamlResponse(connId, samlResponse);
|
|
13339
|
-
return
|
|
13311
|
+
return json2(result);
|
|
13340
13312
|
} catch (err2) {
|
|
13341
13313
|
const status = err2 instanceof SsoError && err2.code === SSO_ERROR.RATE_LIMITED ? 429 : 401;
|
|
13342
|
-
return
|
|
13314
|
+
return json2(
|
|
13343
13315
|
{
|
|
13344
13316
|
error: err2 instanceof Error ? err2.message : "SAML error",
|
|
13345
13317
|
code: err2 instanceof SsoError ? err2.code : "SAML_ERROR"
|
|
@@ -13357,20 +13329,20 @@ function createSsoModule(config, db) {
|
|
|
13357
13329
|
const authUrl = await getOidcAuthUrl(connId, state, nonce);
|
|
13358
13330
|
return new Response(null, { status: 302, headers: { Location: authUrl } });
|
|
13359
13331
|
} catch (err2) {
|
|
13360
|
-
return
|
|
13332
|
+
return json2({ error: err2 instanceof Error ? err2.message : "Unknown error" }, 400);
|
|
13361
13333
|
}
|
|
13362
13334
|
}
|
|
13363
13335
|
const oidcCbMatch = /^\/auth\/sso\/oidc\/([^/]+)\/callback$/.exec(pathname);
|
|
13364
13336
|
if (method === "GET" && oidcCbMatch) {
|
|
13365
13337
|
const connId = decodeURIComponent(oidcCbMatch[1] ?? "");
|
|
13366
13338
|
const code = url.searchParams.get("code");
|
|
13367
|
-
if (!code) return
|
|
13339
|
+
if (!code) return json2({ error: "Missing code parameter" }, 400);
|
|
13368
13340
|
try {
|
|
13369
13341
|
const result = await handleOidcCallback(connId, code);
|
|
13370
|
-
return
|
|
13342
|
+
return json2(result);
|
|
13371
13343
|
} catch (err2) {
|
|
13372
13344
|
const status = err2 instanceof SsoError && err2.code === SSO_ERROR.RATE_LIMITED ? 429 : 401;
|
|
13373
|
-
return
|
|
13345
|
+
return json2(
|
|
13374
13346
|
{
|
|
13375
13347
|
error: err2 instanceof Error ? err2.message : "OIDC error",
|
|
13376
13348
|
code: err2 instanceof SsoError ? err2.code : "OIDC_ERROR"
|
|
@@ -13471,12 +13443,12 @@ async function stripeRequest(secretKey, apiVersion, method, path, body) {
|
|
|
13471
13443
|
headers,
|
|
13472
13444
|
body: bodyStr
|
|
13473
13445
|
});
|
|
13474
|
-
const
|
|
13446
|
+
const json2 = await response.json();
|
|
13475
13447
|
if (!response.ok) {
|
|
13476
|
-
const message =
|
|
13448
|
+
const message = json2.error?.message ?? `Stripe API error: ${response.status}`;
|
|
13477
13449
|
throw new Error(message);
|
|
13478
13450
|
}
|
|
13479
|
-
return
|
|
13451
|
+
return json2;
|
|
13480
13452
|
}
|
|
13481
13453
|
async function verifyWebhookSignature2(payload, signatureHeader, webhookSecret) {
|
|
13482
13454
|
const parts = {};
|
|
@@ -13786,19 +13758,6 @@ function createStripeModule(config, db) {
|
|
|
13786
13758
|
}
|
|
13787
13759
|
|
|
13788
13760
|
// src/auth/stripe-plugin.ts
|
|
13789
|
-
function json2(body, status = 200) {
|
|
13790
|
-
return new Response(JSON.stringify(body), {
|
|
13791
|
-
status,
|
|
13792
|
-
headers: { "Content-Type": "application/json" }
|
|
13793
|
-
});
|
|
13794
|
-
}
|
|
13795
|
-
async function parseBody12(request) {
|
|
13796
|
-
try {
|
|
13797
|
-
return await request.json();
|
|
13798
|
-
} catch {
|
|
13799
|
-
return {};
|
|
13800
|
-
}
|
|
13801
|
-
}
|
|
13802
13761
|
function stripe(config) {
|
|
13803
13762
|
return {
|
|
13804
13763
|
id: "kavach-stripe",
|
|
@@ -13814,17 +13773,18 @@ function stripe(config) {
|
|
|
13814
13773
|
async handler(request, endpointCtx) {
|
|
13815
13774
|
const user = await endpointCtx.getUser(request);
|
|
13816
13775
|
if (!user) {
|
|
13817
|
-
return
|
|
13776
|
+
return json({ error: "Authentication required" }, 401);
|
|
13818
13777
|
}
|
|
13819
|
-
const
|
|
13820
|
-
|
|
13778
|
+
const bodyResult = await parseBody(request);
|
|
13779
|
+
if (!bodyResult.ok) return bodyResult.response;
|
|
13780
|
+
const priceId = typeof bodyResult.data.priceId === "string" ? bodyResult.data.priceId.trim() : null;
|
|
13821
13781
|
if (!priceId) {
|
|
13822
|
-
return
|
|
13782
|
+
return json({ error: "Missing required field: priceId" }, 400);
|
|
13823
13783
|
}
|
|
13824
|
-
const successUrl = typeof
|
|
13825
|
-
const cancelUrl = typeof
|
|
13826
|
-
const trialDays = typeof
|
|
13827
|
-
const metadata =
|
|
13784
|
+
const successUrl = typeof bodyResult.data.successUrl === "string" ? bodyResult.data.successUrl : void 0;
|
|
13785
|
+
const cancelUrl = typeof bodyResult.data.cancelUrl === "string" ? bodyResult.data.cancelUrl : void 0;
|
|
13786
|
+
const trialDays = typeof bodyResult.data.trialDays === "number" ? bodyResult.data.trialDays : void 0;
|
|
13787
|
+
const metadata = bodyResult.data.metadata != null && typeof bodyResult.data.metadata === "object" && !Array.isArray(bodyResult.data.metadata) ? bodyResult.data.metadata : void 0;
|
|
13828
13788
|
try {
|
|
13829
13789
|
const result = await module.createCheckoutSession(user.id, priceId, {
|
|
13830
13790
|
successUrl,
|
|
@@ -13832,9 +13792,9 @@ function stripe(config) {
|
|
|
13832
13792
|
trialDays,
|
|
13833
13793
|
metadata
|
|
13834
13794
|
});
|
|
13835
|
-
return
|
|
13795
|
+
return json(result);
|
|
13836
13796
|
} catch (err2) {
|
|
13837
|
-
return
|
|
13797
|
+
return json(
|
|
13838
13798
|
{
|
|
13839
13799
|
error: err2 instanceof Error ? err2.message : "Failed to create checkout session"
|
|
13840
13800
|
},
|
|
@@ -13853,18 +13813,19 @@ function stripe(config) {
|
|
|
13853
13813
|
async handler(request, endpointCtx) {
|
|
13854
13814
|
const user = await endpointCtx.getUser(request);
|
|
13855
13815
|
if (!user) {
|
|
13856
|
-
return
|
|
13816
|
+
return json({ error: "Authentication required" }, 401);
|
|
13857
13817
|
}
|
|
13858
|
-
const
|
|
13859
|
-
|
|
13818
|
+
const bodyResult = await parseBody(request);
|
|
13819
|
+
if (!bodyResult.ok) return bodyResult.response;
|
|
13820
|
+
const returnUrl = typeof bodyResult.data.returnUrl === "string" ? bodyResult.data.returnUrl.trim() : null;
|
|
13860
13821
|
if (!returnUrl) {
|
|
13861
|
-
return
|
|
13822
|
+
return json({ error: "Missing required field: returnUrl" }, 400);
|
|
13862
13823
|
}
|
|
13863
13824
|
try {
|
|
13864
13825
|
const result = await module.createPortalSession(user.id, returnUrl);
|
|
13865
|
-
return
|
|
13826
|
+
return json(result);
|
|
13866
13827
|
} catch (err2) {
|
|
13867
|
-
return
|
|
13828
|
+
return json(
|
|
13868
13829
|
{
|
|
13869
13830
|
error: err2 instanceof Error ? err2.message : "Failed to create portal session"
|
|
13870
13831
|
},
|
|
@@ -13883,10 +13844,10 @@ function stripe(config) {
|
|
|
13883
13844
|
async handler(request, endpointCtx) {
|
|
13884
13845
|
const user = await endpointCtx.getUser(request);
|
|
13885
13846
|
if (!user) {
|
|
13886
|
-
return
|
|
13847
|
+
return json({ error: "Authentication required" }, 401);
|
|
13887
13848
|
}
|
|
13888
13849
|
const subscription = await module.getSubscription(user.id);
|
|
13889
|
-
return
|
|
13850
|
+
return json({ subscription });
|
|
13890
13851
|
}
|
|
13891
13852
|
});
|
|
13892
13853
|
ctx.addEndpoint({
|
|
@@ -13988,13 +13949,13 @@ async function generateBackupCodes(count) {
|
|
|
13988
13949
|
}
|
|
13989
13950
|
return { plain, hashed };
|
|
13990
13951
|
}
|
|
13991
|
-
function
|
|
13952
|
+
function jsonResponse14(body, status = 200) {
|
|
13992
13953
|
return new Response(JSON.stringify(body), {
|
|
13993
13954
|
status,
|
|
13994
13955
|
headers: { "Content-Type": "application/json" }
|
|
13995
13956
|
});
|
|
13996
13957
|
}
|
|
13997
|
-
async function
|
|
13958
|
+
async function parseBody7(request) {
|
|
13998
13959
|
try {
|
|
13999
13960
|
return await request.json();
|
|
14000
13961
|
} catch {
|
|
@@ -14092,50 +14053,50 @@ function createTotpModule(config, db) {
|
|
|
14092
14053
|
const method = request.method.toUpperCase();
|
|
14093
14054
|
if (method !== "POST") return null;
|
|
14094
14055
|
if (path === "/auth/2fa/setup") {
|
|
14095
|
-
const body = await
|
|
14056
|
+
const body = await parseBody7(request);
|
|
14096
14057
|
const userId = typeof body.userId === "string" ? body.userId : null;
|
|
14097
|
-
if (!userId) return
|
|
14058
|
+
if (!userId) return jsonResponse14({ error: "userId required" }, 400);
|
|
14098
14059
|
try {
|
|
14099
14060
|
const result = await setup(userId);
|
|
14100
|
-
return
|
|
14061
|
+
return jsonResponse14(result);
|
|
14101
14062
|
} catch (err2) {
|
|
14102
|
-
return
|
|
14063
|
+
return jsonResponse14({ error: err2 instanceof Error ? err2.message : "Setup failed" }, 500);
|
|
14103
14064
|
}
|
|
14104
14065
|
}
|
|
14105
14066
|
if (path === "/auth/2fa/enable") {
|
|
14106
|
-
const body = await
|
|
14067
|
+
const body = await parseBody7(request);
|
|
14107
14068
|
const userId = typeof body.userId === "string" ? body.userId : null;
|
|
14108
14069
|
const code = typeof body.code === "string" ? body.code : null;
|
|
14109
|
-
if (!userId || !code) return
|
|
14070
|
+
if (!userId || !code) return jsonResponse14({ error: "userId and code required" }, 400);
|
|
14110
14071
|
const result = await enable(userId, code);
|
|
14111
|
-
return
|
|
14072
|
+
return jsonResponse14(result);
|
|
14112
14073
|
}
|
|
14113
14074
|
if (path === "/auth/2fa/verify") {
|
|
14114
|
-
const body = await
|
|
14075
|
+
const body = await parseBody7(request);
|
|
14115
14076
|
const userId = typeof body.userId === "string" ? body.userId : null;
|
|
14116
14077
|
const code = typeof body.code === "string" ? body.code : null;
|
|
14117
|
-
if (!userId || !code) return
|
|
14078
|
+
if (!userId || !code) return jsonResponse14({ error: "userId and code required" }, 400);
|
|
14118
14079
|
const result = await verify(userId, code);
|
|
14119
|
-
return
|
|
14080
|
+
return jsonResponse14(result);
|
|
14120
14081
|
}
|
|
14121
14082
|
if (path === "/auth/2fa/disable") {
|
|
14122
|
-
const body = await
|
|
14083
|
+
const body = await parseBody7(request);
|
|
14123
14084
|
const userId = typeof body.userId === "string" ? body.userId : null;
|
|
14124
14085
|
const code = typeof body.code === "string" ? body.code : null;
|
|
14125
|
-
if (!userId || !code) return
|
|
14086
|
+
if (!userId || !code) return jsonResponse14({ error: "userId and code required" }, 400);
|
|
14126
14087
|
const result = await disable(userId, code);
|
|
14127
|
-
return
|
|
14088
|
+
return jsonResponse14(result);
|
|
14128
14089
|
}
|
|
14129
14090
|
if (path === "/auth/2fa/backup-codes") {
|
|
14130
|
-
const body = await
|
|
14091
|
+
const body = await parseBody7(request);
|
|
14131
14092
|
const userId = typeof body.userId === "string" ? body.userId : null;
|
|
14132
14093
|
const code = typeof body.code === "string" ? body.code : null;
|
|
14133
|
-
if (!userId || !code) return
|
|
14094
|
+
if (!userId || !code) return jsonResponse14({ error: "userId and code required" }, 400);
|
|
14134
14095
|
try {
|
|
14135
14096
|
const result = await regenerateBackupCodes(userId, code);
|
|
14136
|
-
return
|
|
14097
|
+
return jsonResponse14(result);
|
|
14137
14098
|
} catch (err2) {
|
|
14138
|
-
return
|
|
14099
|
+
return jsonResponse14(
|
|
14139
14100
|
{ error: err2 instanceof Error ? err2.message : "Failed to regenerate codes" },
|
|
14140
14101
|
400
|
|
14141
14102
|
);
|
|
@@ -14155,13 +14116,13 @@ function createTotpModule(config, db) {
|
|
|
14155
14116
|
}
|
|
14156
14117
|
|
|
14157
14118
|
// src/auth/totp-plugin.ts
|
|
14158
|
-
function
|
|
14119
|
+
function jsonResponse15(body, status = 200) {
|
|
14159
14120
|
return new Response(JSON.stringify(body), {
|
|
14160
14121
|
status,
|
|
14161
14122
|
headers: { "Content-Type": "application/json" }
|
|
14162
14123
|
});
|
|
14163
14124
|
}
|
|
14164
|
-
async function
|
|
14125
|
+
async function parseBody8(request) {
|
|
14165
14126
|
try {
|
|
14166
14127
|
return await request.json();
|
|
14167
14128
|
} catch {
|
|
@@ -14183,13 +14144,13 @@ function twoFactor(config) {
|
|
|
14183
14144
|
async handler(request, endpointCtx) {
|
|
14184
14145
|
const user = await endpointCtx.getUser(request);
|
|
14185
14146
|
if (!user) {
|
|
14186
|
-
return
|
|
14147
|
+
return jsonResponse15({ error: "Authentication required" }, 401);
|
|
14187
14148
|
}
|
|
14188
14149
|
try {
|
|
14189
14150
|
const setup = await module.setup(user.id);
|
|
14190
|
-
return
|
|
14151
|
+
return jsonResponse15(setup);
|
|
14191
14152
|
} catch (err2) {
|
|
14192
|
-
return
|
|
14153
|
+
return jsonResponse15(
|
|
14193
14154
|
{ error: err2 instanceof Error ? err2.message : "Enrollment failed" },
|
|
14194
14155
|
500
|
|
14195
14156
|
);
|
|
@@ -14206,23 +14167,23 @@ function twoFactor(config) {
|
|
|
14206
14167
|
async handler(request, endpointCtx) {
|
|
14207
14168
|
const user = await endpointCtx.getUser(request);
|
|
14208
14169
|
if (!user) {
|
|
14209
|
-
return
|
|
14170
|
+
return jsonResponse15({ error: "Authentication required" }, 401);
|
|
14210
14171
|
}
|
|
14211
|
-
const body = await
|
|
14172
|
+
const body = await parseBody8(request);
|
|
14212
14173
|
const code = typeof body.code === "string" ? body.code : null;
|
|
14213
14174
|
if (!code) {
|
|
14214
|
-
return
|
|
14175
|
+
return jsonResponse15({ error: "Missing required field: code" }, 400);
|
|
14215
14176
|
}
|
|
14216
14177
|
const enabled = await module.isEnabled(user.id);
|
|
14217
14178
|
if (!enabled) {
|
|
14218
14179
|
const result2 = await module.enable(user.id, code);
|
|
14219
14180
|
if (!result2.enabled) {
|
|
14220
|
-
return
|
|
14181
|
+
return jsonResponse15({ error: "Invalid TOTP code" }, 400);
|
|
14221
14182
|
}
|
|
14222
|
-
return
|
|
14183
|
+
return jsonResponse15({ valid: true, activated: true });
|
|
14223
14184
|
}
|
|
14224
14185
|
const result = await module.verify(user.id, code);
|
|
14225
|
-
return
|
|
14186
|
+
return jsonResponse15(result);
|
|
14226
14187
|
}
|
|
14227
14188
|
});
|
|
14228
14189
|
ctx.addEndpoint({
|
|
@@ -14235,18 +14196,18 @@ function twoFactor(config) {
|
|
|
14235
14196
|
async handler(request, endpointCtx) {
|
|
14236
14197
|
const user = await endpointCtx.getUser(request);
|
|
14237
14198
|
if (!user) {
|
|
14238
|
-
return
|
|
14199
|
+
return jsonResponse15({ error: "Authentication required" }, 401);
|
|
14239
14200
|
}
|
|
14240
|
-
const body = await
|
|
14201
|
+
const body = await parseBody8(request);
|
|
14241
14202
|
const code = typeof body.code === "string" ? body.code : null;
|
|
14242
14203
|
if (!code) {
|
|
14243
|
-
return
|
|
14204
|
+
return jsonResponse15({ error: "Missing required field: code" }, 400);
|
|
14244
14205
|
}
|
|
14245
14206
|
const result = await module.disable(user.id, code);
|
|
14246
14207
|
if (!result.disabled) {
|
|
14247
|
-
return
|
|
14208
|
+
return jsonResponse15({ error: "Invalid TOTP code" }, 400);
|
|
14248
14209
|
}
|
|
14249
|
-
return
|
|
14210
|
+
return jsonResponse15(result);
|
|
14250
14211
|
}
|
|
14251
14212
|
});
|
|
14252
14213
|
ctx.addEndpoint({
|
|
@@ -14259,10 +14220,10 @@ function twoFactor(config) {
|
|
|
14259
14220
|
async handler(request, endpointCtx) {
|
|
14260
14221
|
const user = await endpointCtx.getUser(request);
|
|
14261
14222
|
if (!user) {
|
|
14262
|
-
return
|
|
14223
|
+
return jsonResponse15({ error: "Authentication required" }, 401);
|
|
14263
14224
|
}
|
|
14264
14225
|
const enabled = await module.isEnabled(user.id);
|
|
14265
|
-
return
|
|
14226
|
+
return jsonResponse15({ enabled });
|
|
14266
14227
|
}
|
|
14267
14228
|
});
|
|
14268
14229
|
ctx.addEndpoint({
|
|
@@ -14275,18 +14236,18 @@ function twoFactor(config) {
|
|
|
14275
14236
|
async handler(request, endpointCtx) {
|
|
14276
14237
|
const user = await endpointCtx.getUser(request);
|
|
14277
14238
|
if (!user) {
|
|
14278
|
-
return
|
|
14239
|
+
return jsonResponse15({ error: "Authentication required" }, 401);
|
|
14279
14240
|
}
|
|
14280
|
-
const body = await
|
|
14241
|
+
const body = await parseBody8(request);
|
|
14281
14242
|
const code = typeof body.code === "string" ? body.code : null;
|
|
14282
14243
|
if (!code) {
|
|
14283
|
-
return
|
|
14244
|
+
return jsonResponse15({ error: "Missing required field: code" }, 400);
|
|
14284
14245
|
}
|
|
14285
14246
|
try {
|
|
14286
14247
|
const result = await module.regenerateBackupCodes(user.id, code);
|
|
14287
|
-
return
|
|
14248
|
+
return jsonResponse15(result);
|
|
14288
14249
|
} catch (err2) {
|
|
14289
|
-
return
|
|
14250
|
+
return jsonResponse15(
|
|
14290
14251
|
{ error: err2 instanceof Error ? err2.message : "Failed to regenerate backup codes" },
|
|
14291
14252
|
400
|
|
14292
14253
|
);
|
|
@@ -14405,7 +14366,7 @@ async function hashPassword(password) {
|
|
|
14405
14366
|
async function verifyPassword(stored, candidate) {
|
|
14406
14367
|
return pbkdf2Verify(candidate, stored);
|
|
14407
14368
|
}
|
|
14408
|
-
function
|
|
14369
|
+
function jsonResponse16(body, status = 200) {
|
|
14409
14370
|
return new Response(JSON.stringify(body), {
|
|
14410
14371
|
status,
|
|
14411
14372
|
headers: { "Content-Type": "application/json" }
|
|
@@ -14529,12 +14490,12 @@ function createUsernameAuthModule(config, db, sessionManager) {
|
|
|
14529
14490
|
try {
|
|
14530
14491
|
body = await request.json();
|
|
14531
14492
|
} catch {
|
|
14532
|
-
return
|
|
14493
|
+
return jsonResponse16({ error: "Invalid JSON body" }, 400);
|
|
14533
14494
|
}
|
|
14534
14495
|
const b = body;
|
|
14535
14496
|
if (pathname === "/auth/username/sign-up") {
|
|
14536
14497
|
if (typeof b.username !== "string" || typeof b.password !== "string") {
|
|
14537
|
-
return
|
|
14498
|
+
return jsonResponse16({ error: "Missing required fields: username, password" }, 400);
|
|
14538
14499
|
}
|
|
14539
14500
|
try {
|
|
14540
14501
|
const result = await signUp({
|
|
@@ -14542,21 +14503,21 @@ function createUsernameAuthModule(config, db, sessionManager) {
|
|
|
14542
14503
|
password: b.password,
|
|
14543
14504
|
name: typeof b.name === "string" ? b.name : void 0
|
|
14544
14505
|
});
|
|
14545
|
-
return
|
|
14506
|
+
return jsonResponse16(result, 201);
|
|
14546
14507
|
} catch (err2) {
|
|
14547
|
-
return
|
|
14508
|
+
return jsonResponse16({ error: err2 instanceof Error ? err2.message : "Sign-up failed" }, 400);
|
|
14548
14509
|
}
|
|
14549
14510
|
}
|
|
14550
14511
|
if (pathname === "/auth/username/sign-in") {
|
|
14551
14512
|
if (typeof b.username !== "string" || typeof b.password !== "string") {
|
|
14552
|
-
return
|
|
14513
|
+
return jsonResponse16({ error: "Missing required fields: username, password" }, 400);
|
|
14553
14514
|
}
|
|
14554
14515
|
try {
|
|
14555
14516
|
const result = await signIn({ username: b.username, password: b.password });
|
|
14556
|
-
return
|
|
14517
|
+
return jsonResponse16(result);
|
|
14557
14518
|
} catch (err2) {
|
|
14558
14519
|
if (err2 instanceof Error && err2.message === "Password reset required") {
|
|
14559
|
-
return
|
|
14520
|
+
return jsonResponse16(
|
|
14560
14521
|
{
|
|
14561
14522
|
error: {
|
|
14562
14523
|
code: "PASSWORD_RESET_REQUIRED",
|
|
@@ -14566,21 +14527,21 @@ function createUsernameAuthModule(config, db, sessionManager) {
|
|
|
14566
14527
|
403
|
|
14567
14528
|
);
|
|
14568
14529
|
}
|
|
14569
|
-
return
|
|
14530
|
+
return jsonResponse16({ error: "Invalid username or password" }, 401);
|
|
14570
14531
|
}
|
|
14571
14532
|
}
|
|
14572
14533
|
if (pathname === "/auth/username/change-password") {
|
|
14573
14534
|
if (typeof b.userId !== "string" || typeof b.current !== "string" || typeof b.newPassword !== "string") {
|
|
14574
|
-
return
|
|
14535
|
+
return jsonResponse16(
|
|
14575
14536
|
{ error: "Missing required fields: userId, current, newPassword" },
|
|
14576
14537
|
400
|
|
14577
14538
|
);
|
|
14578
14539
|
}
|
|
14579
14540
|
try {
|
|
14580
14541
|
const result = await changePassword(b.userId, b.current, b.newPassword);
|
|
14581
|
-
return
|
|
14542
|
+
return jsonResponse16(result);
|
|
14582
14543
|
} catch (err2) {
|
|
14583
|
-
return
|
|
14544
|
+
return jsonResponse16(
|
|
14584
14545
|
{ error: err2 instanceof Error ? err2.message : "Change password failed" },
|
|
14585
14546
|
400
|
|
14586
14547
|
);
|
|
@@ -14588,13 +14549,13 @@ function createUsernameAuthModule(config, db, sessionManager) {
|
|
|
14588
14549
|
}
|
|
14589
14550
|
if (pathname === "/auth/username/change-username") {
|
|
14590
14551
|
if (typeof b.userId !== "string" || typeof b.newUsername !== "string") {
|
|
14591
|
-
return
|
|
14552
|
+
return jsonResponse16({ error: "Missing required fields: userId, newUsername" }, 400);
|
|
14592
14553
|
}
|
|
14593
14554
|
try {
|
|
14594
14555
|
const result = await changeUsername(b.userId, b.newUsername);
|
|
14595
|
-
return
|
|
14556
|
+
return jsonResponse16(result);
|
|
14596
14557
|
} catch (err2) {
|
|
14597
|
-
return
|
|
14558
|
+
return jsonResponse16(
|
|
14598
14559
|
{ error: err2 instanceof Error ? err2.message : "Change username failed" },
|
|
14599
14560
|
400
|
|
14600
14561
|
);
|