kavachos 0.1.5 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +720 -773
- package/dist/auth/index.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +708 -657
- 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,218 @@ 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 = "/", secure = true) {
|
|
1066
|
+
const parts = [
|
|
1067
|
+
`${name}=${encodeURIComponent(value)}`,
|
|
1068
|
+
"HttpOnly",
|
|
1069
|
+
"SameSite=Lax",
|
|
1070
|
+
`Path=${path}`,
|
|
1071
|
+
`Max-Age=${maxAge}`
|
|
1072
|
+
];
|
|
1073
|
+
if (secure) parts.splice(1, 0, "Secure");
|
|
1074
|
+
return parts.join("; ");
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
// src/auth/admin-plugin.ts
|
|
1078
|
+
function admin(config) {
|
|
1079
|
+
return {
|
|
1080
|
+
id: "kavach-admin",
|
|
1081
|
+
async init(ctx) {
|
|
1082
|
+
const module = createAdminModule(config ?? {}, ctx.db, ctx.sessionManager ?? null);
|
|
1083
|
+
ctx.addEndpoint({
|
|
1084
|
+
method: "GET",
|
|
1085
|
+
path: "/auth/admin/users",
|
|
1086
|
+
metadata: {
|
|
1087
|
+
requireAuth: true,
|
|
1088
|
+
description: "List all users (admin only)"
|
|
1089
|
+
},
|
|
1090
|
+
async handler(request, endpointCtx) {
|
|
1091
|
+
const user = await endpointCtx.getUser(request);
|
|
1092
|
+
if (!user) {
|
|
1093
|
+
return json({ error: "Authentication required" }, 401);
|
|
1094
|
+
}
|
|
1095
|
+
const isAdminUser = await module.isAdmin(user.id);
|
|
1096
|
+
if (!isAdminUser) {
|
|
1097
|
+
return json({ error: "Admin access required" }, 403);
|
|
1098
|
+
}
|
|
1099
|
+
const url = new URL(request.url);
|
|
1100
|
+
const limitParam = url.searchParams.get("limit");
|
|
1101
|
+
const offsetParam = url.searchParams.get("offset");
|
|
1102
|
+
const search = url.searchParams.get("search") ?? void 0;
|
|
1103
|
+
const result = await module.listUsers({
|
|
1104
|
+
limit: limitParam ? Number(limitParam) : void 0,
|
|
1105
|
+
offset: offsetParam ? Number(offsetParam) : void 0,
|
|
1106
|
+
search
|
|
1107
|
+
});
|
|
1108
|
+
return json(result);
|
|
1109
|
+
}
|
|
1110
|
+
});
|
|
1111
|
+
ctx.addEndpoint({
|
|
1112
|
+
method: "GET",
|
|
1113
|
+
path: "/auth/admin/users/:id",
|
|
1114
|
+
metadata: {
|
|
1115
|
+
requireAuth: true,
|
|
1116
|
+
description: "Get a user by ID (admin only)"
|
|
1117
|
+
},
|
|
1118
|
+
async handler(request, endpointCtx) {
|
|
1119
|
+
const user = await endpointCtx.getUser(request);
|
|
1120
|
+
if (!user) {
|
|
1121
|
+
return json({ error: "Authentication required" }, 401);
|
|
1122
|
+
}
|
|
1123
|
+
const isAdminUser = await module.isAdmin(user.id);
|
|
1124
|
+
if (!isAdminUser) {
|
|
1125
|
+
return json({ error: "Admin access required" }, 403);
|
|
1126
|
+
}
|
|
1127
|
+
const url = new URL(request.url);
|
|
1128
|
+
const segments = url.pathname.split("/").filter(Boolean);
|
|
1129
|
+
const targetId = segments[3];
|
|
1130
|
+
if (!targetId) {
|
|
1131
|
+
return json({ error: "Missing user ID in path" }, 400);
|
|
1132
|
+
}
|
|
1133
|
+
const found = await module.getUser(decodeURIComponent(targetId));
|
|
1134
|
+
if (!found) {
|
|
1135
|
+
return json({ error: "User not found" }, 404);
|
|
1136
|
+
}
|
|
1137
|
+
return json(found);
|
|
1138
|
+
}
|
|
1139
|
+
});
|
|
1140
|
+
ctx.addEndpoint({
|
|
1141
|
+
method: "POST",
|
|
1142
|
+
path: "/auth/admin/users/:id/ban",
|
|
1143
|
+
metadata: {
|
|
1144
|
+
requireAuth: true,
|
|
1145
|
+
description: "Ban a user (admin only)"
|
|
1146
|
+
},
|
|
1147
|
+
async handler(request, endpointCtx) {
|
|
1148
|
+
const user = await endpointCtx.getUser(request);
|
|
1149
|
+
if (!user) {
|
|
1150
|
+
return json({ error: "Authentication required" }, 401);
|
|
1151
|
+
}
|
|
1152
|
+
const isAdminUser = await module.isAdmin(user.id);
|
|
1153
|
+
if (!isAdminUser) {
|
|
1154
|
+
return json({ error: "Admin access required" }, 403);
|
|
1155
|
+
}
|
|
1156
|
+
const url = new URL(request.url);
|
|
1157
|
+
const segments = url.pathname.split("/").filter(Boolean);
|
|
1158
|
+
const targetId = segments[3];
|
|
1159
|
+
if (!targetId) {
|
|
1160
|
+
return json({ error: "Missing user ID in path" }, 400);
|
|
1161
|
+
}
|
|
1162
|
+
const bodyResult = await parseBody(request);
|
|
1163
|
+
if (!bodyResult.ok) return bodyResult.response;
|
|
1164
|
+
const reason = typeof bodyResult.data.reason === "string" ? bodyResult.data.reason : void 0;
|
|
1165
|
+
const expiresAt = bodyResult.data.expiresAt ? new Date(bodyResult.data.expiresAt) : void 0;
|
|
1166
|
+
await module.banUser(decodeURIComponent(targetId), reason, expiresAt);
|
|
1167
|
+
return json({ success: true });
|
|
1168
|
+
}
|
|
1169
|
+
});
|
|
1170
|
+
ctx.addEndpoint({
|
|
1171
|
+
method: "POST",
|
|
1172
|
+
path: "/auth/admin/users/:id/unban",
|
|
1173
|
+
metadata: {
|
|
1174
|
+
requireAuth: true,
|
|
1175
|
+
description: "Unban a user (admin only)"
|
|
1176
|
+
},
|
|
1177
|
+
async handler(request, endpointCtx) {
|
|
1178
|
+
const user = await endpointCtx.getUser(request);
|
|
1179
|
+
if (!user) {
|
|
1180
|
+
return json({ error: "Authentication required" }, 401);
|
|
1181
|
+
}
|
|
1182
|
+
const isAdminUser = await module.isAdmin(user.id);
|
|
1183
|
+
if (!isAdminUser) {
|
|
1184
|
+
return json({ error: "Admin access required" }, 403);
|
|
1185
|
+
}
|
|
1186
|
+
const url = new URL(request.url);
|
|
1187
|
+
const segments = url.pathname.split("/").filter(Boolean);
|
|
1188
|
+
const targetId = segments[3];
|
|
1189
|
+
if (!targetId) {
|
|
1190
|
+
return json({ error: "Missing user ID in path" }, 400);
|
|
1191
|
+
}
|
|
1192
|
+
await module.unbanUser(decodeURIComponent(targetId));
|
|
1193
|
+
return json({ success: true });
|
|
1194
|
+
}
|
|
1195
|
+
});
|
|
1196
|
+
ctx.addEndpoint({
|
|
1197
|
+
method: "DELETE",
|
|
1198
|
+
path: "/auth/admin/users/:id",
|
|
1199
|
+
metadata: {
|
|
1200
|
+
requireAuth: true,
|
|
1201
|
+
description: "Delete a user (admin only)"
|
|
1202
|
+
},
|
|
1203
|
+
async handler(request, endpointCtx) {
|
|
1204
|
+
const user = await endpointCtx.getUser(request);
|
|
1205
|
+
if (!user) {
|
|
1206
|
+
return json({ error: "Authentication required" }, 401);
|
|
1207
|
+
}
|
|
1208
|
+
const isAdminUser = await module.isAdmin(user.id);
|
|
1209
|
+
if (!isAdminUser) {
|
|
1210
|
+
return json({ error: "Admin access required" }, 403);
|
|
1211
|
+
}
|
|
1212
|
+
const url = new URL(request.url);
|
|
1213
|
+
const segments = url.pathname.split("/").filter(Boolean);
|
|
1214
|
+
const targetId = segments[3];
|
|
1215
|
+
if (!targetId) {
|
|
1216
|
+
return json({ error: "Missing user ID in path" }, 400);
|
|
1217
|
+
}
|
|
1218
|
+
await module.deleteUser(decodeURIComponent(targetId));
|
|
1219
|
+
return json({ success: true });
|
|
1220
|
+
}
|
|
1221
|
+
});
|
|
1222
|
+
ctx.addEndpoint({
|
|
1223
|
+
method: "POST",
|
|
1224
|
+
path: "/auth/admin/users/:id/impersonate",
|
|
1225
|
+
metadata: {
|
|
1226
|
+
requireAuth: true,
|
|
1227
|
+
description: "Start an impersonation session as a target user (admin only)"
|
|
1228
|
+
},
|
|
1229
|
+
async handler(request, endpointCtx) {
|
|
1230
|
+
const user = await endpointCtx.getUser(request);
|
|
1231
|
+
if (!user) {
|
|
1232
|
+
return json({ error: "Authentication required" }, 401);
|
|
1233
|
+
}
|
|
1234
|
+
const isAdminUser = await module.isAdmin(user.id);
|
|
1235
|
+
if (!isAdminUser) {
|
|
1236
|
+
return json({ error: "Admin access required" }, 403);
|
|
1237
|
+
}
|
|
1238
|
+
const url = new URL(request.url);
|
|
1239
|
+
const segments = url.pathname.split("/").filter(Boolean);
|
|
1240
|
+
const targetId = segments[3];
|
|
1241
|
+
if (!targetId) {
|
|
1242
|
+
return json({ error: "Missing user ID in path" }, 400);
|
|
1243
|
+
}
|
|
1244
|
+
try {
|
|
1245
|
+
const result = await module.impersonate(user.id, decodeURIComponent(targetId));
|
|
1246
|
+
return json(result);
|
|
1247
|
+
} catch (err2) {
|
|
1248
|
+
return json(
|
|
1249
|
+
{ error: err2 instanceof Error ? err2.message : "Impersonation failed" },
|
|
1250
|
+
403
|
|
1251
|
+
);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1047
1259
|
// src/crypto/web-crypto.ts
|
|
1048
1260
|
var HEX_CHARS = "0123456789abcdef";
|
|
1049
1261
|
function toHex(bytes) {
|
|
@@ -1222,272 +1434,7 @@ function constantTimeEqual(a, b) {
|
|
|
1222
1434
|
return diff === 0;
|
|
1223
1435
|
}
|
|
1224
1436
|
|
|
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
|
-
}
|
|
1437
|
+
// src/auth/anonymous.ts
|
|
1491
1438
|
var DEFAULT_SESSION_TTL_SECONDS = 60 * 60 * 24;
|
|
1492
1439
|
var DEFAULT_MAX_AGE_MS = 1e3 * 60 * 60 * 24;
|
|
1493
1440
|
var ANONYMOUS_EMAIL_DOMAIN = "kavachos.anonymous";
|
|
@@ -1557,29 +1504,14 @@ function createAnonymousAuthModule(config, db, sessionManager) {
|
|
|
1557
1504
|
}
|
|
1558
1505
|
|
|
1559
1506
|
// 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
1507
|
function anonymousAuth(config) {
|
|
1574
1508
|
return {
|
|
1575
1509
|
id: "kavach-anonymous",
|
|
1576
1510
|
async init(ctx) {
|
|
1577
|
-
|
|
1578
|
-
if (!sessionSecret) {
|
|
1511
|
+
if (!ctx.sessionManager) {
|
|
1579
1512
|
throw new Error("anonymousAuth plugin requires auth.session.secret to be configured");
|
|
1580
1513
|
}
|
|
1581
|
-
const
|
|
1582
|
-
const mod = createAnonymousAuthModule(config ?? {}, ctx.db, sessionManager);
|
|
1514
|
+
const mod = createAnonymousAuthModule(config ?? {}, ctx.db, ctx.sessionManager);
|
|
1583
1515
|
ctx.addEndpoint({
|
|
1584
1516
|
method: "POST",
|
|
1585
1517
|
path: "/auth/anonymous",
|
|
@@ -1590,9 +1522,9 @@ function anonymousAuth(config) {
|
|
|
1590
1522
|
async handler(_request, _endpointCtx) {
|
|
1591
1523
|
try {
|
|
1592
1524
|
const result = await mod.createAnonymousUser();
|
|
1593
|
-
return
|
|
1525
|
+
return json({ userId: result.userId, sessionToken: result.sessionToken });
|
|
1594
1526
|
} catch (err2) {
|
|
1595
|
-
return
|
|
1527
|
+
return json(
|
|
1596
1528
|
{ error: err2 instanceof Error ? err2.message : "Failed to create anonymous user" },
|
|
1597
1529
|
500
|
|
1598
1530
|
);
|
|
@@ -1609,21 +1541,22 @@ function anonymousAuth(config) {
|
|
|
1609
1541
|
async handler(request, endpointCtx) {
|
|
1610
1542
|
const user = await endpointCtx.getUser(request);
|
|
1611
1543
|
if (!user) {
|
|
1612
|
-
return
|
|
1544
|
+
return json({ error: "Authentication required" }, 401);
|
|
1613
1545
|
}
|
|
1614
|
-
const
|
|
1615
|
-
|
|
1616
|
-
const
|
|
1546
|
+
const bodyResult = await parseBody(request);
|
|
1547
|
+
if (!bodyResult.ok) return bodyResult.response;
|
|
1548
|
+
const email = typeof bodyResult.data.email === "string" ? bodyResult.data.email.trim() : null;
|
|
1549
|
+
const name = typeof bodyResult.data.name === "string" ? bodyResult.data.name.trim() : void 0;
|
|
1617
1550
|
if (!email) {
|
|
1618
|
-
return
|
|
1551
|
+
return json({ error: "Missing required field: email" }, 400);
|
|
1619
1552
|
}
|
|
1620
1553
|
try {
|
|
1621
1554
|
await mod.upgradeUser(user.id, { email, name });
|
|
1622
|
-
return
|
|
1555
|
+
return json({ upgraded: true });
|
|
1623
1556
|
} catch (err2) {
|
|
1624
1557
|
const message = err2 instanceof Error ? err2.message : "Upgrade failed";
|
|
1625
1558
|
const status = message.includes("not an anonymous user") ? 400 : 500;
|
|
1626
|
-
return
|
|
1559
|
+
return json({ error: message }, status);
|
|
1627
1560
|
}
|
|
1628
1561
|
}
|
|
1629
1562
|
});
|
|
@@ -1637,10 +1570,10 @@ function anonymousAuth(config) {
|
|
|
1637
1570
|
async handler(request, endpointCtx) {
|
|
1638
1571
|
const user = await endpointCtx.getUser(request);
|
|
1639
1572
|
if (!user) {
|
|
1640
|
-
return
|
|
1573
|
+
return json({ error: "Authentication required" }, 401);
|
|
1641
1574
|
}
|
|
1642
1575
|
const anonymous = await mod.isAnonymous(user.id);
|
|
1643
|
-
return
|
|
1576
|
+
return json({ anonymous });
|
|
1644
1577
|
}
|
|
1645
1578
|
});
|
|
1646
1579
|
}
|
|
@@ -1746,7 +1679,7 @@ function createApiKeyManagerModule(config, db) {
|
|
|
1746
1679
|
const url = new URL(request.url);
|
|
1747
1680
|
const { pathname } = url;
|
|
1748
1681
|
const { method } = request;
|
|
1749
|
-
const
|
|
1682
|
+
const json2 = (data, status = 200) => new Response(JSON.stringify(data), {
|
|
1750
1683
|
status,
|
|
1751
1684
|
headers: { "Content-Type": "application/json" }
|
|
1752
1685
|
});
|
|
@@ -1755,11 +1688,11 @@ function createApiKeyManagerModule(config, db) {
|
|
|
1755
1688
|
try {
|
|
1756
1689
|
body = await request.json();
|
|
1757
1690
|
} catch {
|
|
1758
|
-
return
|
|
1691
|
+
return json2({ error: "Invalid JSON body" }, 400);
|
|
1759
1692
|
}
|
|
1760
1693
|
const b = body;
|
|
1761
1694
|
if (typeof b.userId !== "string" || typeof b.name !== "string" || !Array.isArray(b.permissions)) {
|
|
1762
|
-
return
|
|
1695
|
+
return json2({ error: "Missing required fields: userId, name, permissions" }, 400);
|
|
1763
1696
|
}
|
|
1764
1697
|
const expiresAt = b.expiresAt ? new Date(b.expiresAt) : void 0;
|
|
1765
1698
|
const result = await create({
|
|
@@ -1768,28 +1701,28 @@ function createApiKeyManagerModule(config, db) {
|
|
|
1768
1701
|
permissions: b.permissions,
|
|
1769
1702
|
expiresAt
|
|
1770
1703
|
});
|
|
1771
|
-
return
|
|
1704
|
+
return json2(result, 201);
|
|
1772
1705
|
}
|
|
1773
1706
|
const listMatch = /^\/auth\/api-keys\/([^/]+)$/.exec(pathname);
|
|
1774
1707
|
if (method === "GET" && listMatch) {
|
|
1775
1708
|
const userId = decodeURIComponent(listMatch[1] ?? "");
|
|
1776
1709
|
const keys = await list(userId);
|
|
1777
|
-
return
|
|
1710
|
+
return json2(keys);
|
|
1778
1711
|
}
|
|
1779
1712
|
const deleteMatch = /^\/auth\/api-keys\/([^/]+)$/.exec(pathname);
|
|
1780
1713
|
if (method === "DELETE" && deleteMatch) {
|
|
1781
1714
|
const keyId = decodeURIComponent(deleteMatch[1] ?? "");
|
|
1782
1715
|
await revoke(keyId);
|
|
1783
|
-
return
|
|
1716
|
+
return json2({ success: true });
|
|
1784
1717
|
}
|
|
1785
1718
|
const rotateMatch = /^\/auth\/api-keys\/([^/]+)\/rotate$/.exec(pathname);
|
|
1786
1719
|
if (method === "POST" && rotateMatch) {
|
|
1787
1720
|
const keyId = decodeURIComponent(rotateMatch[1] ?? "");
|
|
1788
1721
|
try {
|
|
1789
1722
|
const result = await rotate(keyId);
|
|
1790
|
-
return
|
|
1723
|
+
return json2(result);
|
|
1791
1724
|
} catch (err2) {
|
|
1792
|
-
return
|
|
1725
|
+
return json2({ error: err2 instanceof Error ? err2.message : "Unknown error" }, 404);
|
|
1793
1726
|
}
|
|
1794
1727
|
}
|
|
1795
1728
|
return null;
|
|
@@ -1806,19 +1739,6 @@ function createApiKeyManagerModule(config, db) {
|
|
|
1806
1739
|
}
|
|
1807
1740
|
|
|
1808
1741
|
// 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
1742
|
function apiKeys2(config) {
|
|
1823
1743
|
return {
|
|
1824
1744
|
id: "kavach-api-key",
|
|
@@ -1834,15 +1754,16 @@ function apiKeys2(config) {
|
|
|
1834
1754
|
async handler(request, endpointCtx) {
|
|
1835
1755
|
const user = await endpointCtx.getUser(request);
|
|
1836
1756
|
if (!user) {
|
|
1837
|
-
return
|
|
1757
|
+
return json({ error: "Authentication required" }, 401);
|
|
1838
1758
|
}
|
|
1839
|
-
const
|
|
1840
|
-
|
|
1841
|
-
const
|
|
1759
|
+
const bodyResult = await parseBody(request);
|
|
1760
|
+
if (!bodyResult.ok) return bodyResult.response;
|
|
1761
|
+
const name = typeof bodyResult.data.name === "string" ? bodyResult.data.name.trim() : null;
|
|
1762
|
+
const permissions2 = Array.isArray(bodyResult.data.permissions) ? bodyResult.data.permissions : null;
|
|
1842
1763
|
if (!name || !permissions2) {
|
|
1843
|
-
return
|
|
1764
|
+
return json({ error: "Missing required fields: name, permissions" }, 400);
|
|
1844
1765
|
}
|
|
1845
|
-
const expiresAt =
|
|
1766
|
+
const expiresAt = bodyResult.data.expiresAt ? new Date(bodyResult.data.expiresAt) : void 0;
|
|
1846
1767
|
try {
|
|
1847
1768
|
const result = await module.create({
|
|
1848
1769
|
userId: user.id,
|
|
@@ -1850,9 +1771,9 @@ function apiKeys2(config) {
|
|
|
1850
1771
|
permissions: permissions2,
|
|
1851
1772
|
expiresAt
|
|
1852
1773
|
});
|
|
1853
|
-
return
|
|
1774
|
+
return json(result, 201);
|
|
1854
1775
|
} catch (err2) {
|
|
1855
|
-
return
|
|
1776
|
+
return json(
|
|
1856
1777
|
{ error: err2 instanceof Error ? err2.message : "Failed to create API key" },
|
|
1857
1778
|
500
|
|
1858
1779
|
);
|
|
@@ -1869,10 +1790,10 @@ function apiKeys2(config) {
|
|
|
1869
1790
|
async handler(request, endpointCtx) {
|
|
1870
1791
|
const user = await endpointCtx.getUser(request);
|
|
1871
1792
|
if (!user) {
|
|
1872
|
-
return
|
|
1793
|
+
return json({ error: "Authentication required" }, 401);
|
|
1873
1794
|
}
|
|
1874
1795
|
const keys = await module.list(user.id);
|
|
1875
|
-
return
|
|
1796
|
+
return json({ apiKeys: keys });
|
|
1876
1797
|
}
|
|
1877
1798
|
});
|
|
1878
1799
|
ctx.addEndpoint({
|
|
@@ -1885,21 +1806,21 @@ function apiKeys2(config) {
|
|
|
1885
1806
|
async handler(request, endpointCtx) {
|
|
1886
1807
|
const user = await endpointCtx.getUser(request);
|
|
1887
1808
|
if (!user) {
|
|
1888
|
-
return
|
|
1809
|
+
return json({ error: "Authentication required" }, 401);
|
|
1889
1810
|
}
|
|
1890
1811
|
const url = new URL(request.url);
|
|
1891
1812
|
const segments = url.pathname.split("/").filter(Boolean);
|
|
1892
1813
|
const keyId = segments[2];
|
|
1893
1814
|
if (!keyId) {
|
|
1894
|
-
return
|
|
1815
|
+
return json({ error: "Missing API key ID in path" }, 400);
|
|
1895
1816
|
}
|
|
1896
1817
|
const keys = await module.list(user.id);
|
|
1897
1818
|
const owned = keys.some((k) => k.id === decodeURIComponent(keyId));
|
|
1898
1819
|
if (!owned) {
|
|
1899
|
-
return
|
|
1820
|
+
return json({ error: "API key not found" }, 404);
|
|
1900
1821
|
}
|
|
1901
1822
|
await module.revoke(decodeURIComponent(keyId));
|
|
1902
|
-
return
|
|
1823
|
+
return json({ revoked: true });
|
|
1903
1824
|
}
|
|
1904
1825
|
});
|
|
1905
1826
|
ctx.addEndpoint({
|
|
@@ -1912,24 +1833,24 @@ function apiKeys2(config) {
|
|
|
1912
1833
|
async handler(request, endpointCtx) {
|
|
1913
1834
|
const user = await endpointCtx.getUser(request);
|
|
1914
1835
|
if (!user) {
|
|
1915
|
-
return
|
|
1836
|
+
return json({ error: "Authentication required" }, 401);
|
|
1916
1837
|
}
|
|
1917
1838
|
const url = new URL(request.url);
|
|
1918
1839
|
const segments = url.pathname.split("/").filter(Boolean);
|
|
1919
1840
|
const keyId = segments[2];
|
|
1920
1841
|
if (!keyId) {
|
|
1921
|
-
return
|
|
1842
|
+
return json({ error: "Missing API key ID in path" }, 400);
|
|
1922
1843
|
}
|
|
1923
1844
|
const keys = await module.list(user.id);
|
|
1924
1845
|
const owned = keys.some((k) => k.id === decodeURIComponent(keyId));
|
|
1925
1846
|
if (!owned) {
|
|
1926
|
-
return
|
|
1847
|
+
return json({ error: "API key not found" }, 404);
|
|
1927
1848
|
}
|
|
1928
1849
|
try {
|
|
1929
1850
|
const result = await module.rotate(decodeURIComponent(keyId));
|
|
1930
|
-
return
|
|
1851
|
+
return json(result);
|
|
1931
1852
|
} catch (err2) {
|
|
1932
|
-
return
|
|
1853
|
+
return json(
|
|
1933
1854
|
{ error: err2 instanceof Error ? err2.message : "Failed to rotate API key" },
|
|
1934
1855
|
400
|
|
1935
1856
|
);
|
|
@@ -2319,13 +2240,13 @@ function customSession(config = {}) {
|
|
|
2319
2240
|
const url = new URL(request.url);
|
|
2320
2241
|
const sessionId = url.searchParams.get("sessionId");
|
|
2321
2242
|
if (!sessionId) {
|
|
2322
|
-
return
|
|
2243
|
+
return jsonResponse2({ error: "Missing required query parameter: sessionId" }, 400);
|
|
2323
2244
|
}
|
|
2324
2245
|
try {
|
|
2325
2246
|
const fields = await mod.getSessionFields(sessionId);
|
|
2326
|
-
return
|
|
2247
|
+
return jsonResponse2({ fields: fields ?? {} });
|
|
2327
2248
|
} catch (err2) {
|
|
2328
|
-
return
|
|
2249
|
+
return jsonResponse2(
|
|
2329
2250
|
{ error: err2 instanceof Error ? err2.message : "Failed to get session fields" },
|
|
2330
2251
|
500
|
|
2331
2252
|
);
|
|
@@ -2344,23 +2265,23 @@ function customSession(config = {}) {
|
|
|
2344
2265
|
try {
|
|
2345
2266
|
body = await request.json();
|
|
2346
2267
|
} catch {
|
|
2347
|
-
return
|
|
2268
|
+
return jsonResponse2({ error: "Invalid JSON body" }, 400);
|
|
2348
2269
|
}
|
|
2349
2270
|
const sessionId = typeof body.sessionId === "string" ? body.sessionId : null;
|
|
2350
2271
|
if (!sessionId) {
|
|
2351
|
-
return
|
|
2272
|
+
return jsonResponse2({ error: "Missing required field: sessionId" }, 400);
|
|
2352
2273
|
}
|
|
2353
2274
|
const fields = body.fields !== null && body.fields !== void 0 && typeof body.fields === "object" && !Array.isArray(body.fields) ? body.fields : null;
|
|
2354
2275
|
if (!fields) {
|
|
2355
|
-
return
|
|
2276
|
+
return jsonResponse2({ error: "Missing or invalid field: fields" }, 400);
|
|
2356
2277
|
}
|
|
2357
2278
|
try {
|
|
2358
2279
|
await mod.updateSessionFields(sessionId, fields);
|
|
2359
|
-
return
|
|
2280
|
+
return jsonResponse2({ updated: true });
|
|
2360
2281
|
} catch (err2) {
|
|
2361
2282
|
const message = err2 instanceof Error ? err2.message : "Update failed";
|
|
2362
2283
|
const status = message.includes("not found") ? 404 : 500;
|
|
2363
|
-
return
|
|
2284
|
+
return jsonResponse2({ error: message }, status);
|
|
2364
2285
|
}
|
|
2365
2286
|
}
|
|
2366
2287
|
});
|
|
@@ -2370,7 +2291,7 @@ function customSession(config = {}) {
|
|
|
2370
2291
|
}
|
|
2371
2292
|
};
|
|
2372
2293
|
}
|
|
2373
|
-
function
|
|
2294
|
+
function jsonResponse2(body, status = 200) {
|
|
2374
2295
|
return new Response(JSON.stringify(body), {
|
|
2375
2296
|
status,
|
|
2376
2297
|
headers: { "Content-Type": "application/json" }
|
|
@@ -2383,13 +2304,13 @@ var DEFAULT_CODE_EXPIRY_SECONDS = 900;
|
|
|
2383
2304
|
var DEFAULT_POLL_INTERVAL_SECONDS = 5;
|
|
2384
2305
|
var USER_CODE_ALPHABET = "BCDFGHJKLMNPQRSTVWXZ";
|
|
2385
2306
|
var SLOW_DOWN_THRESHOLD_MS = 4e3;
|
|
2386
|
-
function
|
|
2307
|
+
function jsonResponse3(body, status = 200) {
|
|
2387
2308
|
return new Response(JSON.stringify(body), {
|
|
2388
2309
|
status,
|
|
2389
2310
|
headers: { "Content-Type": "application/json" }
|
|
2390
2311
|
});
|
|
2391
2312
|
}
|
|
2392
|
-
async function
|
|
2313
|
+
async function parseBody2(request) {
|
|
2393
2314
|
try {
|
|
2394
2315
|
return await request.json();
|
|
2395
2316
|
} catch {
|
|
@@ -2513,7 +2434,7 @@ function createDeviceAuthModule(config) {
|
|
|
2513
2434
|
const { method, pathname } = { method: request.method, pathname: url.pathname };
|
|
2514
2435
|
if (method === "POST" && pathname.endsWith("/auth/device/code")) {
|
|
2515
2436
|
const response = await requestCode();
|
|
2516
|
-
return
|
|
2437
|
+
return jsonResponse3({
|
|
2517
2438
|
device_code: response.deviceCode,
|
|
2518
2439
|
user_code: response.userCode,
|
|
2519
2440
|
verification_uri: response.verificationUri,
|
|
@@ -2523,17 +2444,17 @@ function createDeviceAuthModule(config) {
|
|
|
2523
2444
|
});
|
|
2524
2445
|
}
|
|
2525
2446
|
if (method === "POST" && pathname.endsWith("/auth/device/token")) {
|
|
2526
|
-
const body = await
|
|
2447
|
+
const body = await parseBody2(request);
|
|
2527
2448
|
const deviceCode = typeof body.device_code === "string" ? body.device_code : null;
|
|
2528
2449
|
if (!deviceCode) {
|
|
2529
|
-
return
|
|
2450
|
+
return jsonResponse3(
|
|
2530
2451
|
{ error: "invalid_request", error_description: "Missing device_code" },
|
|
2531
2452
|
400
|
|
2532
2453
|
);
|
|
2533
2454
|
}
|
|
2534
2455
|
const grant = grantsByDevice.get(deviceCode);
|
|
2535
2456
|
if (grant?.lastPolledAt && Date.now() - grant.lastPolledAt < SLOW_DOWN_THRESHOLD_MS) {
|
|
2536
|
-
return
|
|
2457
|
+
return jsonResponse3(
|
|
2537
2458
|
{
|
|
2538
2459
|
error: "slow_down",
|
|
2539
2460
|
error_description: "Polling too frequently",
|
|
@@ -2544,10 +2465,10 @@ function createDeviceAuthModule(config) {
|
|
|
2544
2465
|
}
|
|
2545
2466
|
const status = await checkAuthorization(deviceCode);
|
|
2546
2467
|
if (status.status === "authorized") {
|
|
2547
|
-
return
|
|
2468
|
+
return jsonResponse3({ authorized: true, user_id: status.userId });
|
|
2548
2469
|
}
|
|
2549
2470
|
if (status.status === "pending") {
|
|
2550
|
-
return
|
|
2471
|
+
return jsonResponse3(
|
|
2551
2472
|
{
|
|
2552
2473
|
error: "authorization_pending",
|
|
2553
2474
|
error_description: "The user has not yet authorized the device"
|
|
@@ -2556,7 +2477,7 @@ function createDeviceAuthModule(config) {
|
|
|
2556
2477
|
);
|
|
2557
2478
|
}
|
|
2558
2479
|
if (status.status === "denied") {
|
|
2559
|
-
return
|
|
2480
|
+
return jsonResponse3(
|
|
2560
2481
|
{
|
|
2561
2482
|
error: "access_denied",
|
|
2562
2483
|
error_description: "The user denied the authorization request"
|
|
@@ -2564,7 +2485,7 @@ function createDeviceAuthModule(config) {
|
|
|
2564
2485
|
400
|
|
2565
2486
|
);
|
|
2566
2487
|
}
|
|
2567
|
-
return
|
|
2488
|
+
return jsonResponse3(
|
|
2568
2489
|
{
|
|
2569
2490
|
error: "expired_token",
|
|
2570
2491
|
error_description: "The device code has expired"
|
|
@@ -2573,12 +2494,12 @@ function createDeviceAuthModule(config) {
|
|
|
2573
2494
|
);
|
|
2574
2495
|
}
|
|
2575
2496
|
if (method === "POST" && pathname.endsWith("/auth/device/authorize")) {
|
|
2576
|
-
const body = await
|
|
2497
|
+
const body = await parseBody2(request);
|
|
2577
2498
|
const userCode = typeof body.user_code === "string" ? body.user_code : null;
|
|
2578
2499
|
const userId = typeof body.user_id === "string" ? body.user_id : null;
|
|
2579
2500
|
const action = typeof body.action === "string" ? body.action : "approve";
|
|
2580
2501
|
if (!userCode || !userId) {
|
|
2581
|
-
return
|
|
2502
|
+
return jsonResponse3(
|
|
2582
2503
|
{ error: "invalid_request", error_description: "Missing user_code or user_id" },
|
|
2583
2504
|
400
|
|
2584
2505
|
);
|
|
@@ -2586,12 +2507,12 @@ function createDeviceAuthModule(config) {
|
|
|
2586
2507
|
try {
|
|
2587
2508
|
if (action === "deny") {
|
|
2588
2509
|
await deny(userCode);
|
|
2589
|
-
return
|
|
2510
|
+
return jsonResponse3({ denied: true });
|
|
2590
2511
|
}
|
|
2591
2512
|
await authorize(userCode, userId);
|
|
2592
|
-
return
|
|
2513
|
+
return jsonResponse3({ authorized: true });
|
|
2593
2514
|
} catch (err2) {
|
|
2594
|
-
return
|
|
2515
|
+
return jsonResponse3(
|
|
2595
2516
|
{
|
|
2596
2517
|
error: "invalid_request",
|
|
2597
2518
|
error_description: err2 instanceof Error ? err2.message : "Authorization failed"
|
|
@@ -2872,31 +2793,16 @@ function createEmailOtpModule(config, db, sessionManager) {
|
|
|
2872
2793
|
}
|
|
2873
2794
|
|
|
2874
2795
|
// 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
2796
|
function emailOtp(config) {
|
|
2889
2797
|
return {
|
|
2890
2798
|
id: "kavach-email-otp",
|
|
2891
2799
|
async init(ctx) {
|
|
2892
|
-
|
|
2893
|
-
if (!sessionConfig) {
|
|
2800
|
+
if (!ctx.sessionManager) {
|
|
2894
2801
|
throw new Error(
|
|
2895
2802
|
"kavach-email-otp plugin requires auth.session to be configured so that sessions can be issued on successful verification."
|
|
2896
2803
|
);
|
|
2897
2804
|
}
|
|
2898
|
-
const
|
|
2899
|
-
const module = createEmailOtpModule(config, ctx.db, sessionManager);
|
|
2805
|
+
const module = createEmailOtpModule(config, ctx.db, ctx.sessionManager);
|
|
2900
2806
|
ctx.addEndpoint({
|
|
2901
2807
|
method: "POST",
|
|
2902
2808
|
path: "/auth/email-otp/send",
|
|
@@ -2905,19 +2811,17 @@ function emailOtp(config) {
|
|
|
2905
2811
|
description: "Send a one-time passcode to the provided email address"
|
|
2906
2812
|
},
|
|
2907
2813
|
async handler(request) {
|
|
2908
|
-
const
|
|
2909
|
-
|
|
2814
|
+
const bodyResult = await parseBody(request);
|
|
2815
|
+
if (!bodyResult.ok) return bodyResult.response;
|
|
2816
|
+
const rawEmail = typeof bodyResult.data.email === "string" ? bodyResult.data.email.trim().toLowerCase() : null;
|
|
2910
2817
|
if (!rawEmail) {
|
|
2911
|
-
return
|
|
2818
|
+
return json({ error: "Missing required field: email" }, 400);
|
|
2912
2819
|
}
|
|
2913
2820
|
try {
|
|
2914
2821
|
const result = await module.sendCode(rawEmail);
|
|
2915
|
-
return
|
|
2822
|
+
return json(result);
|
|
2916
2823
|
} catch (err2) {
|
|
2917
|
-
return
|
|
2918
|
-
{ error: err2 instanceof Error ? err2.message : "Failed to send OTP" },
|
|
2919
|
-
500
|
|
2920
|
-
);
|
|
2824
|
+
return json({ error: err2 instanceof Error ? err2.message : "Failed to send OTP" }, 500);
|
|
2921
2825
|
}
|
|
2922
2826
|
}
|
|
2923
2827
|
});
|
|
@@ -2929,17 +2833,18 @@ function emailOtp(config) {
|
|
|
2929
2833
|
description: "Verify an OTP code and return a session on success"
|
|
2930
2834
|
},
|
|
2931
2835
|
async handler(request) {
|
|
2932
|
-
const
|
|
2933
|
-
|
|
2934
|
-
const
|
|
2836
|
+
const bodyResult = await parseBody(request);
|
|
2837
|
+
if (!bodyResult.ok) return bodyResult.response;
|
|
2838
|
+
const rawEmail = typeof bodyResult.data.email === "string" ? bodyResult.data.email.trim().toLowerCase() : null;
|
|
2839
|
+
const code = typeof bodyResult.data.code === "string" ? bodyResult.data.code.trim() : null;
|
|
2935
2840
|
if (!rawEmail || !code) {
|
|
2936
|
-
return
|
|
2841
|
+
return json({ error: "Missing required fields: email, code" }, 400);
|
|
2937
2842
|
}
|
|
2938
2843
|
const result = await module.verifyCode(rawEmail, code);
|
|
2939
2844
|
if (!result) {
|
|
2940
|
-
return
|
|
2845
|
+
return json({ error: "Invalid or expired OTP code" }, 401);
|
|
2941
2846
|
}
|
|
2942
|
-
return
|
|
2847
|
+
return json(result);
|
|
2943
2848
|
}
|
|
2944
2849
|
});
|
|
2945
2850
|
}
|
|
@@ -2950,7 +2855,7 @@ var TOKEN_PURPOSE = "email-verify";
|
|
|
2950
2855
|
function makeError(code, message, details) {
|
|
2951
2856
|
return { code, message, ...details !== void 0 ? { details } : {} };
|
|
2952
2857
|
}
|
|
2953
|
-
function
|
|
2858
|
+
function jsonResponse4(body, status = 200) {
|
|
2954
2859
|
return new Response(JSON.stringify(body), {
|
|
2955
2860
|
status,
|
|
2956
2861
|
headers: { "Content-Type": "application/json" }
|
|
@@ -3042,29 +2947,29 @@ function createEmailVerificationModule(config, db, tokenModule) {
|
|
|
3042
2947
|
try {
|
|
3043
2948
|
body = await request.json();
|
|
3044
2949
|
} catch {
|
|
3045
|
-
return
|
|
2950
|
+
return jsonResponse4({ error: "Invalid JSON body" }, 400);
|
|
3046
2951
|
}
|
|
3047
2952
|
const b = body;
|
|
3048
2953
|
if (pathname === "/auth/verify-email/send") {
|
|
3049
2954
|
if (typeof b.userId !== "string" || typeof b.email !== "string") {
|
|
3050
|
-
return
|
|
2955
|
+
return jsonResponse4({ error: "Missing required fields: userId, email" }, 400);
|
|
3051
2956
|
}
|
|
3052
2957
|
const result = await sendVerification(b.userId, b.email);
|
|
3053
2958
|
if (!result.success) {
|
|
3054
2959
|
const status = result.error.code === "USER_NOT_FOUND" ? 404 : 500;
|
|
3055
|
-
return
|
|
2960
|
+
return jsonResponse4({ error: result.error.message }, status);
|
|
3056
2961
|
}
|
|
3057
2962
|
return new Response(null, { status: 204 });
|
|
3058
2963
|
}
|
|
3059
2964
|
if (pathname === "/auth/verify-email/confirm") {
|
|
3060
2965
|
if (typeof b.token !== "string") {
|
|
3061
|
-
return
|
|
2966
|
+
return jsonResponse4({ error: "Missing required field: token" }, 400);
|
|
3062
2967
|
}
|
|
3063
2968
|
const result = await verify(b.token);
|
|
3064
2969
|
if (!result.success) {
|
|
3065
|
-
return
|
|
2970
|
+
return jsonResponse4({ error: result.error.message }, 400);
|
|
3066
2971
|
}
|
|
3067
|
-
return
|
|
2972
|
+
return jsonResponse4({ userId: result.data.userId, email: result.data.email });
|
|
3068
2973
|
}
|
|
3069
2974
|
return null;
|
|
3070
2975
|
}
|
|
@@ -4087,30 +3992,17 @@ function createGdprModule(db) {
|
|
|
4087
3992
|
await db.update(users).set({
|
|
4088
3993
|
email: anonEmail,
|
|
4089
3994
|
name: null,
|
|
4090
|
-
externalId: null,
|
|
4091
|
-
metadata: {},
|
|
4092
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
4093
|
-
}).where(eq(users.id, userId));
|
|
4094
|
-
await db.delete(totpRecords).where(eq(totpRecords.userId, userId));
|
|
4095
|
-
await db.delete(passkeyCredentials).where(eq(passkeyCredentials.userId, userId));
|
|
4096
|
-
}
|
|
4097
|
-
return { exportUserData, deleteUser, anonymizeUser };
|
|
4098
|
-
}
|
|
4099
|
-
|
|
4100
|
-
// 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 {};
|
|
3995
|
+
externalId: null,
|
|
3996
|
+
metadata: {},
|
|
3997
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
3998
|
+
}).where(eq(users.id, userId));
|
|
3999
|
+
await db.delete(totpRecords).where(eq(totpRecords.userId, userId));
|
|
4000
|
+
await db.delete(passkeyCredentials).where(eq(passkeyCredentials.userId, userId));
|
|
4112
4001
|
}
|
|
4002
|
+
return { exportUserData, deleteUser, anonymizeUser };
|
|
4113
4003
|
}
|
|
4004
|
+
|
|
4005
|
+
// src/auth/gdpr-plugin.ts
|
|
4114
4006
|
function gdpr() {
|
|
4115
4007
|
return {
|
|
4116
4008
|
id: "kavach-gdpr",
|
|
@@ -4126,16 +4018,13 @@ function gdpr() {
|
|
|
4126
4018
|
async handler(request, endpointCtx) {
|
|
4127
4019
|
const user = await endpointCtx.getUser(request);
|
|
4128
4020
|
if (!user) {
|
|
4129
|
-
return
|
|
4021
|
+
return json({ error: "Authentication required" }, 401);
|
|
4130
4022
|
}
|
|
4131
4023
|
try {
|
|
4132
4024
|
const data = await module.exportUserData(user.id);
|
|
4133
|
-
return
|
|
4025
|
+
return json(data);
|
|
4134
4026
|
} catch (err2) {
|
|
4135
|
-
return
|
|
4136
|
-
{ error: err2 instanceof Error ? err2.message : "Export failed" },
|
|
4137
|
-
500
|
|
4138
|
-
);
|
|
4027
|
+
return json({ error: err2 instanceof Error ? err2.message : "Export failed" }, 500);
|
|
4139
4028
|
}
|
|
4140
4029
|
}
|
|
4141
4030
|
});
|
|
@@ -4149,30 +4038,28 @@ function gdpr() {
|
|
|
4149
4038
|
async handler(request, endpointCtx) {
|
|
4150
4039
|
const user = await endpointCtx.getUser(request);
|
|
4151
4040
|
if (!user) {
|
|
4152
|
-
return
|
|
4041
|
+
return json({ error: "Authentication required" }, 401);
|
|
4153
4042
|
}
|
|
4154
|
-
const
|
|
4155
|
-
if (
|
|
4156
|
-
|
|
4043
|
+
const bodyResult = await parseBody(request);
|
|
4044
|
+
if (!bodyResult.ok) return bodyResult.response;
|
|
4045
|
+
if (bodyResult.data.confirm !== "delete my account") {
|
|
4046
|
+
return json(
|
|
4157
4047
|
{
|
|
4158
4048
|
error: 'Confirmation required. Send { "confirm": "delete my account" } in the request body.'
|
|
4159
4049
|
},
|
|
4160
4050
|
400
|
|
4161
4051
|
);
|
|
4162
4052
|
}
|
|
4163
|
-
const keepAuditLogs = typeof
|
|
4164
|
-
const deleteOrganizations = typeof
|
|
4053
|
+
const keepAuditLogs = typeof bodyResult.data.keepAuditLogs === "boolean" ? bodyResult.data.keepAuditLogs : true;
|
|
4054
|
+
const deleteOrganizations = typeof bodyResult.data.deleteOrganizations === "boolean" ? bodyResult.data.deleteOrganizations : false;
|
|
4165
4055
|
try {
|
|
4166
4056
|
const result = await module.deleteUser(user.id, {
|
|
4167
4057
|
keepAuditLogs,
|
|
4168
4058
|
deleteOrganizations
|
|
4169
4059
|
});
|
|
4170
|
-
return
|
|
4060
|
+
return json({ success: true, ...result });
|
|
4171
4061
|
} catch (err2) {
|
|
4172
|
-
return
|
|
4173
|
-
{ error: err2 instanceof Error ? err2.message : "Deletion failed" },
|
|
4174
|
-
500
|
|
4175
|
-
);
|
|
4062
|
+
return json({ error: err2 instanceof Error ? err2.message : "Deletion failed" }, 500);
|
|
4176
4063
|
}
|
|
4177
4064
|
}
|
|
4178
4065
|
});
|
|
@@ -4186,13 +4073,13 @@ function gdpr() {
|
|
|
4186
4073
|
async handler(request, endpointCtx) {
|
|
4187
4074
|
const user = await endpointCtx.getUser(request);
|
|
4188
4075
|
if (!user) {
|
|
4189
|
-
return
|
|
4076
|
+
return json({ error: "Authentication required" }, 401);
|
|
4190
4077
|
}
|
|
4191
4078
|
try {
|
|
4192
4079
|
await module.anonymizeUser(user.id);
|
|
4193
|
-
return
|
|
4080
|
+
return json({ success: true });
|
|
4194
4081
|
} catch (err2) {
|
|
4195
|
-
return
|
|
4082
|
+
return json(
|
|
4196
4083
|
{ error: err2 instanceof Error ? err2.message : "Anonymization failed" },
|
|
4197
4084
|
500
|
|
4198
4085
|
);
|
|
@@ -4828,14 +4715,12 @@ function magicLink(config) {
|
|
|
4828
4715
|
return {
|
|
4829
4716
|
id: "kavach-magic-link",
|
|
4830
4717
|
async init(ctx) {
|
|
4831
|
-
|
|
4832
|
-
if (!sessionConfig) {
|
|
4718
|
+
if (!ctx.sessionManager) {
|
|
4833
4719
|
throw new Error(
|
|
4834
4720
|
"kavach-magic-link plugin requires auth.session to be configured so that sessions can be issued on successful verification."
|
|
4835
4721
|
);
|
|
4836
4722
|
}
|
|
4837
|
-
const
|
|
4838
|
-
const module = createMagicLinkModule(config, ctx.db, sessionManager);
|
|
4723
|
+
const module = createMagicLinkModule(config, ctx.db, ctx.sessionManager);
|
|
4839
4724
|
const sendLimiter = createRateLimiter({ max: 5, window: 60 });
|
|
4840
4725
|
ctx.addEndpoint({
|
|
4841
4726
|
method: "POST",
|
|
@@ -4845,35 +4730,19 @@ function magicLink(config) {
|
|
|
4845
4730
|
description: "Send a magic link to the provided email address"
|
|
4846
4731
|
},
|
|
4847
4732
|
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;
|
|
4733
|
+
const bodyResult = await parseBody(request);
|
|
4734
|
+
if (!bodyResult.ok) return bodyResult.response;
|
|
4735
|
+
const rawEmail = typeof bodyResult.data.email === "string" ? bodyResult.data.email.trim().toLowerCase() : null;
|
|
4859
4736
|
if (!rawEmail) {
|
|
4860
|
-
return
|
|
4861
|
-
status: 400,
|
|
4862
|
-
headers: { "Content-Type": "application/json" }
|
|
4863
|
-
});
|
|
4737
|
+
return json({ error: "Missing required field: email" }, 400);
|
|
4864
4738
|
}
|
|
4865
4739
|
try {
|
|
4866
4740
|
const result = await module.sendLink(rawEmail);
|
|
4867
|
-
return
|
|
4868
|
-
status: 200,
|
|
4869
|
-
headers: { "Content-Type": "application/json" }
|
|
4870
|
-
});
|
|
4741
|
+
return json(result);
|
|
4871
4742
|
} catch (err2) {
|
|
4872
|
-
return
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
}),
|
|
4876
|
-
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
4743
|
+
return json(
|
|
4744
|
+
{ error: err2 instanceof Error ? err2.message : "Failed to send magic link" },
|
|
4745
|
+
500
|
|
4877
4746
|
);
|
|
4878
4747
|
}
|
|
4879
4748
|
}, sendLimiter)
|
|
@@ -4888,22 +4757,13 @@ function magicLink(config) {
|
|
|
4888
4757
|
const url = new URL(request.url);
|
|
4889
4758
|
const token = url.searchParams.get("token");
|
|
4890
4759
|
if (!token) {
|
|
4891
|
-
return
|
|
4892
|
-
status: 400,
|
|
4893
|
-
headers: { "Content-Type": "application/json" }
|
|
4894
|
-
});
|
|
4760
|
+
return json({ error: "Missing token query parameter" }, 400);
|
|
4895
4761
|
}
|
|
4896
4762
|
const result = await module.verify(token);
|
|
4897
4763
|
if (!result) {
|
|
4898
|
-
return
|
|
4899
|
-
status: 401,
|
|
4900
|
-
headers: { "Content-Type": "application/json" }
|
|
4901
|
-
});
|
|
4764
|
+
return json({ error: "Invalid or expired magic link" }, 401);
|
|
4902
4765
|
}
|
|
4903
|
-
return
|
|
4904
|
-
status: 200,
|
|
4905
|
-
headers: { "Content-Type": "application/json" }
|
|
4906
|
-
});
|
|
4766
|
+
return json(result);
|
|
4907
4767
|
}
|
|
4908
4768
|
});
|
|
4909
4769
|
}
|
|
@@ -5128,7 +4988,7 @@ function createOAuthModule(db, config) {
|
|
|
5128
4988
|
pruneExpiredStates
|
|
5129
4989
|
};
|
|
5130
4990
|
}
|
|
5131
|
-
function
|
|
4991
|
+
function jsonResponse5(body, status = 200) {
|
|
5132
4992
|
return new Response(JSON.stringify(body), {
|
|
5133
4993
|
status,
|
|
5134
4994
|
headers: { "Content-Type": "application/json" }
|
|
@@ -5146,8 +5006,12 @@ function oauth(config) {
|
|
|
5146
5006
|
async init(ctx) {
|
|
5147
5007
|
const module = createOAuthModule(ctx.db, config);
|
|
5148
5008
|
const baseUrl = ctx.config.baseUrl ?? "";
|
|
5149
|
-
const
|
|
5150
|
-
|
|
5009
|
+
const sessionManager = ctx.sessionManager;
|
|
5010
|
+
if (!sessionManager) {
|
|
5011
|
+
throw new Error(
|
|
5012
|
+
"kavach-oauth plugin requires auth.session to be configured so that sessions can be issued on successful OAuth callback."
|
|
5013
|
+
);
|
|
5014
|
+
}
|
|
5151
5015
|
const authorizeLimiter = createRateLimiter({ max: 20, window: 60 });
|
|
5152
5016
|
function getRedirectUri(provider) {
|
|
5153
5017
|
if (config.buildRedirectUri) {
|
|
@@ -5166,14 +5030,14 @@ function oauth(config) {
|
|
|
5166
5030
|
const url = new URL(request.url);
|
|
5167
5031
|
const provider = url.searchParams.get("_param_provider");
|
|
5168
5032
|
if (!provider) {
|
|
5169
|
-
return
|
|
5033
|
+
return jsonResponse5({ error: "Missing provider parameter" }, 400);
|
|
5170
5034
|
}
|
|
5171
5035
|
const redirectUri = getRedirectUri(provider);
|
|
5172
5036
|
try {
|
|
5173
5037
|
const { url: authUrl } = await module.getAuthorizationUrl(provider, redirectUri);
|
|
5174
5038
|
return redirectResponse(authUrl);
|
|
5175
5039
|
} catch (err2) {
|
|
5176
|
-
return
|
|
5040
|
+
return jsonResponse5(
|
|
5177
5041
|
{ error: err2 instanceof Error ? err2.message : "Failed to build authorization URL" },
|
|
5178
5042
|
400
|
|
5179
5043
|
);
|
|
@@ -5190,10 +5054,10 @@ function oauth(config) {
|
|
|
5190
5054
|
const code = url.searchParams.get("code");
|
|
5191
5055
|
const state = url.searchParams.get("state");
|
|
5192
5056
|
if (!provider) {
|
|
5193
|
-
return
|
|
5057
|
+
return jsonResponse5({ error: "Missing provider parameter" }, 400);
|
|
5194
5058
|
}
|
|
5195
5059
|
if (!code || !state) {
|
|
5196
|
-
return
|
|
5060
|
+
return jsonResponse5({ error: "Missing code or state query parameter" }, 400);
|
|
5197
5061
|
}
|
|
5198
5062
|
const redirectUri = getRedirectUri(provider);
|
|
5199
5063
|
try {
|
|
@@ -5225,18 +5089,28 @@ function oauth(config) {
|
|
|
5225
5089
|
raw: {}
|
|
5226
5090
|
});
|
|
5227
5091
|
}
|
|
5228
|
-
if (
|
|
5092
|
+
if (userId !== "__pending__") {
|
|
5229
5093
|
const { session, token } = await sessionManager.create(userId);
|
|
5230
|
-
const
|
|
5231
|
-
|
|
5094
|
+
const maxAge = Math.floor((session.expiresAt.getTime() - Date.now()) / 1e3);
|
|
5095
|
+
const isSecure = baseUrl.startsWith("https://");
|
|
5096
|
+
const cookie = buildSetCookie("kavach_session", token, maxAge, "/", isSecure);
|
|
5097
|
+
const userInfo = encodeURIComponent(JSON.stringify({ id: userId, email }));
|
|
5098
|
+
const callbackUrl = `${baseUrl}/?auth_user=${userInfo}`;
|
|
5099
|
+
return new Response(null, {
|
|
5100
|
+
status: 302,
|
|
5101
|
+
headers: {
|
|
5102
|
+
Location: callbackUrl,
|
|
5103
|
+
"Set-Cookie": cookie
|
|
5104
|
+
}
|
|
5105
|
+
});
|
|
5232
5106
|
}
|
|
5233
|
-
return
|
|
5107
|
+
return jsonResponse5({
|
|
5234
5108
|
isNewAccount: result.isNewAccount,
|
|
5235
5109
|
account: result.account,
|
|
5236
5110
|
userInfo: result.userInfo
|
|
5237
5111
|
});
|
|
5238
5112
|
} catch (err2) {
|
|
5239
|
-
return
|
|
5113
|
+
return jsonResponse5(
|
|
5240
5114
|
{ error: err2 instanceof Error ? err2.message : "OAuth callback failed" },
|
|
5241
5115
|
400
|
|
5242
5116
|
);
|
|
@@ -5253,29 +5127,29 @@ function oauth(config) {
|
|
|
5253
5127
|
async handler(request, endpointCtx) {
|
|
5254
5128
|
const user = await endpointCtx.getUser(request);
|
|
5255
5129
|
if (!user) {
|
|
5256
|
-
return
|
|
5130
|
+
return jsonResponse5({ error: "Authentication required" }, 401);
|
|
5257
5131
|
}
|
|
5258
5132
|
let body;
|
|
5259
5133
|
try {
|
|
5260
5134
|
body = await request.json();
|
|
5261
5135
|
} catch {
|
|
5262
|
-
return
|
|
5136
|
+
return jsonResponse5({ error: "Invalid JSON body" }, 400);
|
|
5263
5137
|
}
|
|
5264
5138
|
const b = body;
|
|
5265
5139
|
const provider = typeof b.provider === "string" ? b.provider : null;
|
|
5266
5140
|
const userInfo = typeof b.userInfo === "object" && b.userInfo !== null ? b.userInfo : null;
|
|
5267
5141
|
const tokens = typeof b.tokens === "object" && b.tokens !== null ? b.tokens : null;
|
|
5268
5142
|
if (!provider || !userInfo || !tokens) {
|
|
5269
|
-
return
|
|
5143
|
+
return jsonResponse5(
|
|
5270
5144
|
{ error: "Missing required fields: provider, userInfo, tokens" },
|
|
5271
5145
|
400
|
|
5272
5146
|
);
|
|
5273
5147
|
}
|
|
5274
5148
|
try {
|
|
5275
5149
|
const account = await module.linkAccount(user.id, provider, userInfo, tokens);
|
|
5276
|
-
return
|
|
5150
|
+
return jsonResponse5({ account });
|
|
5277
5151
|
} catch (err2) {
|
|
5278
|
-
return
|
|
5152
|
+
return jsonResponse5(
|
|
5279
5153
|
{ error: err2 instanceof Error ? err2.message : "Failed to link account" },
|
|
5280
5154
|
400
|
|
5281
5155
|
);
|
|
@@ -5291,7 +5165,7 @@ function oauth(config) {
|
|
|
5291
5165
|
id: p.id,
|
|
5292
5166
|
name: p.name
|
|
5293
5167
|
}));
|
|
5294
|
-
return
|
|
5168
|
+
return jsonResponse5({ providers });
|
|
5295
5169
|
}
|
|
5296
5170
|
});
|
|
5297
5171
|
}
|
|
@@ -5523,13 +5397,14 @@ function createGithubProvider(config) {
|
|
|
5523
5397
|
});
|
|
5524
5398
|
return `${AUTHORIZATION_URL3}?${params.toString()}`;
|
|
5525
5399
|
}
|
|
5526
|
-
async function exchangeCode(code,
|
|
5400
|
+
async function exchangeCode(code, codeVerifier, redirectUri) {
|
|
5527
5401
|
const effectiveRedirectUri = config.redirectUri ?? redirectUri;
|
|
5528
5402
|
const body = new URLSearchParams({
|
|
5529
5403
|
client_id: config.clientId,
|
|
5530
5404
|
client_secret: config.clientSecret,
|
|
5531
5405
|
code,
|
|
5532
|
-
redirect_uri: effectiveRedirectUri
|
|
5406
|
+
redirect_uri: effectiveRedirectUri,
|
|
5407
|
+
code_verifier: codeVerifier
|
|
5533
5408
|
});
|
|
5534
5409
|
const response = await fetch(TOKEN_URL3, {
|
|
5535
5410
|
method: "POST",
|
|
@@ -5825,14 +5700,15 @@ function createLinkedInProvider(config) {
|
|
|
5825
5700
|
});
|
|
5826
5701
|
return `${AUTHORIZATION_URL6}?${params.toString()}`;
|
|
5827
5702
|
}
|
|
5828
|
-
async function exchangeCode(code,
|
|
5703
|
+
async function exchangeCode(code, codeVerifier, redirectUri) {
|
|
5829
5704
|
const effectiveRedirectUri = config.redirectUri ?? redirectUri;
|
|
5830
5705
|
const body = new URLSearchParams({
|
|
5831
5706
|
grant_type: "authorization_code",
|
|
5832
5707
|
client_id: config.clientId,
|
|
5833
5708
|
client_secret: config.clientSecret,
|
|
5834
5709
|
code,
|
|
5835
|
-
redirect_uri: effectiveRedirectUri
|
|
5710
|
+
redirect_uri: effectiveRedirectUri,
|
|
5711
|
+
code_verifier: codeVerifier
|
|
5836
5712
|
});
|
|
5837
5713
|
const response = await fetch(TOKEN_URL6, {
|
|
5838
5714
|
method: "POST",
|
|
@@ -6016,14 +5892,15 @@ function createSlackProvider(config) {
|
|
|
6016
5892
|
});
|
|
6017
5893
|
return `${AUTHORIZATION_URL8}?${params.toString()}`;
|
|
6018
5894
|
}
|
|
6019
|
-
async function exchangeCode(code,
|
|
5895
|
+
async function exchangeCode(code, codeVerifier, redirectUri) {
|
|
6020
5896
|
const effectiveRedirectUri = config.redirectUri ?? redirectUri;
|
|
6021
5897
|
const body = new URLSearchParams({
|
|
6022
5898
|
grant_type: "authorization_code",
|
|
6023
5899
|
client_id: config.clientId,
|
|
6024
5900
|
client_secret: config.clientSecret,
|
|
6025
5901
|
code,
|
|
6026
|
-
redirect_uri: effectiveRedirectUri
|
|
5902
|
+
redirect_uri: effectiveRedirectUri,
|
|
5903
|
+
code_verifier: codeVerifier
|
|
6027
5904
|
});
|
|
6028
5905
|
const response = await fetch(TOKEN_URL8, {
|
|
6029
5906
|
method: "POST",
|
|
@@ -7088,25 +6965,25 @@ function createOneTapModule(config, db, sessionManager) {
|
|
|
7088
6965
|
const text3 = await request.text();
|
|
7089
6966
|
formData = new URLSearchParams(text3);
|
|
7090
6967
|
} catch {
|
|
7091
|
-
return
|
|
6968
|
+
return jsonResponse6({ error: "Failed to parse request body" }, 400);
|
|
7092
6969
|
}
|
|
7093
6970
|
const credential = formData.get("credential");
|
|
7094
6971
|
const bodyToken = formData.get(csrfCookieName);
|
|
7095
6972
|
if (!credential) {
|
|
7096
|
-
return
|
|
6973
|
+
return jsonResponse6({ error: "Missing credential field" }, 400);
|
|
7097
6974
|
}
|
|
7098
6975
|
const cookieToken = getCsrfCookie(request);
|
|
7099
6976
|
if (!cookieToken || !bodyToken || cookieToken !== bodyToken) {
|
|
7100
|
-
return
|
|
6977
|
+
return jsonResponse6({ error: "CSRF token mismatch" }, 403);
|
|
7101
6978
|
}
|
|
7102
6979
|
let googleUser;
|
|
7103
6980
|
try {
|
|
7104
6981
|
googleUser = await verify(credential);
|
|
7105
6982
|
} catch (err2) {
|
|
7106
6983
|
if (err2 instanceof OneTapVerifyError && err2.code === "USER_NOT_FOUND") {
|
|
7107
|
-
return
|
|
6984
|
+
return jsonResponse6({ error: err2.message }, 403);
|
|
7108
6985
|
}
|
|
7109
|
-
return
|
|
6986
|
+
return jsonResponse6(
|
|
7110
6987
|
{ error: err2 instanceof Error ? err2.message : "Token verification failed" },
|
|
7111
6988
|
401
|
|
7112
6989
|
);
|
|
@@ -7116,27 +6993,96 @@ function createOneTapModule(config, db, sessionManager) {
|
|
|
7116
6993
|
user = await findOrCreateUser(googleUser);
|
|
7117
6994
|
} catch (err2) {
|
|
7118
6995
|
if (err2 instanceof OneTapVerifyError && err2.code === "USER_NOT_FOUND") {
|
|
7119
|
-
return
|
|
6996
|
+
return jsonResponse6({ error: err2.message }, 403);
|
|
7120
6997
|
}
|
|
7121
|
-
return
|
|
6998
|
+
return jsonResponse6(
|
|
7122
6999
|
{ error: err2 instanceof Error ? err2.message : "Failed to resolve user" },
|
|
7123
7000
|
500
|
|
7124
7001
|
);
|
|
7125
7002
|
}
|
|
7126
7003
|
const { token: sessionToken, session } = await sessionManager.create(user.id);
|
|
7127
|
-
return
|
|
7004
|
+
return jsonResponse6({
|
|
7128
7005
|
user: { id: user.id, email: user.email },
|
|
7129
7006
|
session: { token: sessionToken, expiresAt: session.expiresAt }
|
|
7130
7007
|
});
|
|
7131
7008
|
}
|
|
7132
7009
|
return { verify, handleRequest };
|
|
7133
7010
|
}
|
|
7134
|
-
function
|
|
7011
|
+
function jsonResponse6(body, status = 200) {
|
|
7135
7012
|
return new Response(JSON.stringify(body), {
|
|
7136
7013
|
status,
|
|
7137
7014
|
headers: { "Content-Type": "application/json" }
|
|
7138
7015
|
});
|
|
7139
7016
|
}
|
|
7017
|
+
var DEFAULT_MAX_AGE_SECONDS = 60 * 60 * 24 * 7;
|
|
7018
|
+
function createSessionManager(config, db) {
|
|
7019
|
+
if (!config.secret || config.secret.length < 32) {
|
|
7020
|
+
throw new Error("SessionManager: secret must be at least 32 characters.");
|
|
7021
|
+
}
|
|
7022
|
+
const maxAge = config.maxAge ?? DEFAULT_MAX_AGE_SECONDS;
|
|
7023
|
+
const keyObject = new TextEncoder().encode(config.secret);
|
|
7024
|
+
function rowToSession2(row) {
|
|
7025
|
+
return {
|
|
7026
|
+
id: row.id,
|
|
7027
|
+
userId: row.userId,
|
|
7028
|
+
expiresAt: row.expiresAt,
|
|
7029
|
+
createdAt: row.createdAt,
|
|
7030
|
+
...row.metadata !== null && { metadata: row.metadata }
|
|
7031
|
+
};
|
|
7032
|
+
}
|
|
7033
|
+
async function create(userId, metadata) {
|
|
7034
|
+
const id = generateId();
|
|
7035
|
+
const now = /* @__PURE__ */ new Date();
|
|
7036
|
+
const expiresAt = new Date(now.getTime() + maxAge * 1e3);
|
|
7037
|
+
await db.insert(sessions).values({
|
|
7038
|
+
id,
|
|
7039
|
+
userId,
|
|
7040
|
+
expiresAt,
|
|
7041
|
+
metadata: metadata ?? null,
|
|
7042
|
+
createdAt: now
|
|
7043
|
+
});
|
|
7044
|
+
const token = await new SignJWT({ sub: id }).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime(Math.floor(expiresAt.getTime() / 1e3)).sign(keyObject);
|
|
7045
|
+
const session = {
|
|
7046
|
+
id,
|
|
7047
|
+
userId,
|
|
7048
|
+
expiresAt,
|
|
7049
|
+
createdAt: now,
|
|
7050
|
+
...metadata !== void 0 && { metadata }
|
|
7051
|
+
};
|
|
7052
|
+
return { session, token };
|
|
7053
|
+
}
|
|
7054
|
+
async function validate(token) {
|
|
7055
|
+
let sessionId;
|
|
7056
|
+
try {
|
|
7057
|
+
const { payload } = await jwtVerify(token, keyObject);
|
|
7058
|
+
if (typeof payload.sub !== "string" || !payload.sub) return null;
|
|
7059
|
+
sessionId = payload.sub;
|
|
7060
|
+
} catch {
|
|
7061
|
+
return null;
|
|
7062
|
+
}
|
|
7063
|
+
const now = /* @__PURE__ */ new Date();
|
|
7064
|
+
const rows = await db.select().from(sessions).where(and(eq(sessions.id, sessionId)));
|
|
7065
|
+
const row = rows[0];
|
|
7066
|
+
if (!row) return null;
|
|
7067
|
+
if (row.expiresAt <= now) {
|
|
7068
|
+
await db.delete(sessions).where(eq(sessions.id, sessionId));
|
|
7069
|
+
return null;
|
|
7070
|
+
}
|
|
7071
|
+
return rowToSession2(row);
|
|
7072
|
+
}
|
|
7073
|
+
async function revoke(sessionId) {
|
|
7074
|
+
await db.delete(sessions).where(eq(sessions.id, sessionId));
|
|
7075
|
+
}
|
|
7076
|
+
async function revokeAll(userId) {
|
|
7077
|
+
await db.delete(sessions).where(eq(sessions.userId, userId));
|
|
7078
|
+
}
|
|
7079
|
+
async function list(userId) {
|
|
7080
|
+
const now = /* @__PURE__ */ new Date();
|
|
7081
|
+
const rows = await db.select().from(sessions).where(and(eq(sessions.userId, userId)));
|
|
7082
|
+
return rows.filter((row) => row.expiresAt > now).sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()).map(rowToSession2);
|
|
7083
|
+
}
|
|
7084
|
+
return { create, validate, revoke, revokeAll, list };
|
|
7085
|
+
}
|
|
7140
7086
|
|
|
7141
7087
|
// src/auth/one-tap-plugin.ts
|
|
7142
7088
|
function oneTap(config) {
|
|
@@ -8428,14 +8374,14 @@ function rowToInvitation(row) {
|
|
|
8428
8374
|
createdAt: row.createdAt
|
|
8429
8375
|
};
|
|
8430
8376
|
}
|
|
8431
|
-
function
|
|
8377
|
+
function jsonResponse7(body, status = 200) {
|
|
8432
8378
|
return new Response(JSON.stringify(body), {
|
|
8433
8379
|
status,
|
|
8434
8380
|
headers: { "Content-Type": "application/json" }
|
|
8435
8381
|
});
|
|
8436
8382
|
}
|
|
8437
8383
|
function errorResponse(message, status) {
|
|
8438
|
-
return
|
|
8384
|
+
return jsonResponse7({ error: message }, status);
|
|
8439
8385
|
}
|
|
8440
8386
|
function createOrgModule(config, db) {
|
|
8441
8387
|
const maxMembers = config.maxMembers ?? DEFAULT_MAX_MEMBERS;
|
|
@@ -8741,7 +8687,7 @@ function createOrgModule(config, db) {
|
|
|
8741
8687
|
try {
|
|
8742
8688
|
const body = await request.json();
|
|
8743
8689
|
const org = await create(body);
|
|
8744
|
-
return
|
|
8690
|
+
return jsonResponse7(org, 201);
|
|
8745
8691
|
} catch (err2) {
|
|
8746
8692
|
return errorResponse(err2 instanceof Error ? err2.message : "Unknown error", 400);
|
|
8747
8693
|
}
|
|
@@ -8751,7 +8697,7 @@ function createOrgModule(config, db) {
|
|
|
8751
8697
|
const userId = userOrgMatch[1];
|
|
8752
8698
|
if (!userId) return errorResponse("Missing userId", 400);
|
|
8753
8699
|
const orgs = await list(userId);
|
|
8754
|
-
return
|
|
8700
|
+
return jsonResponse7(orgs);
|
|
8755
8701
|
}
|
|
8756
8702
|
const orgBaseMatch = pathname.match(/^\/auth\/org\/([^/]+)(\/.*)?$/);
|
|
8757
8703
|
if (!orgBaseMatch) return null;
|
|
@@ -8761,13 +8707,13 @@ function createOrgModule(config, db) {
|
|
|
8761
8707
|
if (method === "GET" && subPath === "") {
|
|
8762
8708
|
const org = await get(orgId);
|
|
8763
8709
|
if (!org) return errorResponse("Organization not found", 404);
|
|
8764
|
-
return
|
|
8710
|
+
return jsonResponse7(org);
|
|
8765
8711
|
}
|
|
8766
8712
|
if (method === "PATCH" && subPath === "") {
|
|
8767
8713
|
try {
|
|
8768
8714
|
const body = await request.json();
|
|
8769
8715
|
const org = await update(orgId, body);
|
|
8770
|
-
return
|
|
8716
|
+
return jsonResponse7(org);
|
|
8771
8717
|
} catch (err2) {
|
|
8772
8718
|
return errorResponse(err2 instanceof Error ? err2.message : "Unknown error", 400);
|
|
8773
8719
|
}
|
|
@@ -8775,7 +8721,7 @@ function createOrgModule(config, db) {
|
|
|
8775
8721
|
if (method === "DELETE" && subPath === "") {
|
|
8776
8722
|
try {
|
|
8777
8723
|
await remove(orgId);
|
|
8778
|
-
return
|
|
8724
|
+
return jsonResponse7({ success: true });
|
|
8779
8725
|
} catch (err2) {
|
|
8780
8726
|
return errorResponse(err2 instanceof Error ? err2.message : "Unknown error", 400);
|
|
8781
8727
|
}
|
|
@@ -8784,14 +8730,14 @@ function createOrgModule(config, db) {
|
|
|
8784
8730
|
try {
|
|
8785
8731
|
const body = await request.json();
|
|
8786
8732
|
const member = await addMember(orgId, body.userId, body.role);
|
|
8787
|
-
return
|
|
8733
|
+
return jsonResponse7(member, 201);
|
|
8788
8734
|
} catch (err2) {
|
|
8789
8735
|
return errorResponse(err2 instanceof Error ? err2.message : "Unknown error", 400);
|
|
8790
8736
|
}
|
|
8791
8737
|
}
|
|
8792
8738
|
if (method === "GET" && subPath === "/members") {
|
|
8793
8739
|
const members = await getMembers(orgId);
|
|
8794
|
-
return
|
|
8740
|
+
return jsonResponse7(members);
|
|
8795
8741
|
}
|
|
8796
8742
|
const memberMatch = subPath.match(/^\/members\/([^/]+)$/);
|
|
8797
8743
|
if (method === "PATCH" && memberMatch) {
|
|
@@ -8800,7 +8746,7 @@ function createOrgModule(config, db) {
|
|
|
8800
8746
|
try {
|
|
8801
8747
|
const body = await request.json();
|
|
8802
8748
|
const member = await updateMemberRole(orgId, userId, body.role);
|
|
8803
|
-
return
|
|
8749
|
+
return jsonResponse7(member);
|
|
8804
8750
|
} catch (err2) {
|
|
8805
8751
|
return errorResponse(err2 instanceof Error ? err2.message : "Unknown error", 400);
|
|
8806
8752
|
}
|
|
@@ -8809,20 +8755,20 @@ function createOrgModule(config, db) {
|
|
|
8809
8755
|
const userId = memberMatch[1];
|
|
8810
8756
|
if (!userId) return errorResponse("Missing userId", 400);
|
|
8811
8757
|
await removeMember(orgId, userId);
|
|
8812
|
-
return
|
|
8758
|
+
return jsonResponse7({ success: true });
|
|
8813
8759
|
}
|
|
8814
8760
|
if (method === "POST" && subPath === "/invite") {
|
|
8815
8761
|
try {
|
|
8816
8762
|
const body = await request.json();
|
|
8817
8763
|
const invitation = await invite({ orgId, ...body });
|
|
8818
|
-
return
|
|
8764
|
+
return jsonResponse7(invitation, 201);
|
|
8819
8765
|
} catch (err2) {
|
|
8820
8766
|
return errorResponse(err2 instanceof Error ? err2.message : "Unknown error", 400);
|
|
8821
8767
|
}
|
|
8822
8768
|
}
|
|
8823
8769
|
if (method === "GET" && subPath === "/invitations") {
|
|
8824
8770
|
const invitations = await listInvitations(orgId);
|
|
8825
|
-
return
|
|
8771
|
+
return jsonResponse7(invitations);
|
|
8826
8772
|
}
|
|
8827
8773
|
const permMatch = subPath.match(/^\/permissions\/([^/]+)\/([^/]+)$/);
|
|
8828
8774
|
if (method === "GET" && permMatch) {
|
|
@@ -8830,20 +8776,20 @@ function createOrgModule(config, db) {
|
|
|
8830
8776
|
const permission = permMatch[2];
|
|
8831
8777
|
if (!userId || !permission) return errorResponse("Missing userId or permission", 400);
|
|
8832
8778
|
const allowed = await hasPermission(orgId, userId, permission);
|
|
8833
|
-
return
|
|
8779
|
+
return jsonResponse7({ allowed });
|
|
8834
8780
|
}
|
|
8835
8781
|
if (method === "POST" && subPath === "/roles") {
|
|
8836
8782
|
try {
|
|
8837
8783
|
const body = await request.json();
|
|
8838
8784
|
const role = await createRole(orgId, body);
|
|
8839
|
-
return
|
|
8785
|
+
return jsonResponse7(role, 201);
|
|
8840
8786
|
} catch (err2) {
|
|
8841
8787
|
return errorResponse(err2 instanceof Error ? err2.message : "Unknown error", 400);
|
|
8842
8788
|
}
|
|
8843
8789
|
}
|
|
8844
8790
|
if (method === "GET" && subPath === "/roles") {
|
|
8845
8791
|
const roles = await getRoles(orgId);
|
|
8846
|
-
return
|
|
8792
|
+
return jsonResponse7(roles);
|
|
8847
8793
|
}
|
|
8848
8794
|
return null;
|
|
8849
8795
|
}
|
|
@@ -8858,7 +8804,7 @@ function createOrgModule(config, db) {
|
|
|
8858
8804
|
try {
|
|
8859
8805
|
const body = await request.json();
|
|
8860
8806
|
const member = await acceptInvitation(invitationId, body.userId);
|
|
8861
|
-
return
|
|
8807
|
+
return jsonResponse7(member, 201);
|
|
8862
8808
|
} catch (err2) {
|
|
8863
8809
|
return errorResponse(err2 instanceof Error ? err2.message : "Unknown error", 400);
|
|
8864
8810
|
}
|
|
@@ -8868,7 +8814,7 @@ function createOrgModule(config, db) {
|
|
|
8868
8814
|
const invitationId = revokeMatch[1];
|
|
8869
8815
|
if (!invitationId) return errorResponse("Missing invitationId", 400);
|
|
8870
8816
|
await revokeInvitation(invitationId);
|
|
8871
|
-
return
|
|
8817
|
+
return jsonResponse7({ success: true });
|
|
8872
8818
|
}
|
|
8873
8819
|
return handleRequest(request);
|
|
8874
8820
|
}
|
|
@@ -8899,13 +8845,13 @@ function createOrgModule(config, db) {
|
|
|
8899
8845
|
}
|
|
8900
8846
|
|
|
8901
8847
|
// src/auth/organization-plugin.ts
|
|
8902
|
-
function
|
|
8848
|
+
function jsonResponse8(body, status = 200) {
|
|
8903
8849
|
return new Response(JSON.stringify(body), {
|
|
8904
8850
|
status,
|
|
8905
8851
|
headers: { "Content-Type": "application/json" }
|
|
8906
8852
|
});
|
|
8907
8853
|
}
|
|
8908
|
-
async function
|
|
8854
|
+
async function parseBody3(request) {
|
|
8909
8855
|
try {
|
|
8910
8856
|
return await request.json();
|
|
8911
8857
|
} catch {
|
|
@@ -8928,20 +8874,20 @@ function organization(config) {
|
|
|
8928
8874
|
async handler(request, endpointCtx) {
|
|
8929
8875
|
const user = await endpointCtx.getUser(request);
|
|
8930
8876
|
if (!user) {
|
|
8931
|
-
return
|
|
8877
|
+
return jsonResponse8({ error: "Authentication required" }, 401);
|
|
8932
8878
|
}
|
|
8933
|
-
const body = await
|
|
8879
|
+
const body = await parseBody3(request);
|
|
8934
8880
|
const name = typeof body.name === "string" ? body.name.trim() : null;
|
|
8935
8881
|
const slug = typeof body.slug === "string" ? body.slug.trim() : null;
|
|
8936
8882
|
if (!name || !slug) {
|
|
8937
|
-
return
|
|
8883
|
+
return jsonResponse8({ error: "Missing required fields: name, slug" }, 400);
|
|
8938
8884
|
}
|
|
8939
8885
|
const metadata = body.metadata !== void 0 && typeof body.metadata === "object" && body.metadata !== null ? body.metadata : void 0;
|
|
8940
8886
|
try {
|
|
8941
8887
|
const org = await module.create({ name, slug, ownerId: user.id, metadata });
|
|
8942
|
-
return
|
|
8888
|
+
return jsonResponse8(org, 201);
|
|
8943
8889
|
} catch (err2) {
|
|
8944
|
-
return
|
|
8890
|
+
return jsonResponse8(
|
|
8945
8891
|
{ error: err2 instanceof Error ? err2.message : "Failed to create organization" },
|
|
8946
8892
|
400
|
|
8947
8893
|
);
|
|
@@ -8958,10 +8904,10 @@ function organization(config) {
|
|
|
8958
8904
|
async handler(request, endpointCtx) {
|
|
8959
8905
|
const user = await endpointCtx.getUser(request);
|
|
8960
8906
|
if (!user) {
|
|
8961
|
-
return
|
|
8907
|
+
return jsonResponse8({ error: "Authentication required" }, 401);
|
|
8962
8908
|
}
|
|
8963
8909
|
const orgs = await module.list(user.id);
|
|
8964
|
-
return
|
|
8910
|
+
return jsonResponse8({ organizations: orgs });
|
|
8965
8911
|
}
|
|
8966
8912
|
});
|
|
8967
8913
|
ctx.addEndpoint({
|
|
@@ -8974,23 +8920,23 @@ function organization(config) {
|
|
|
8974
8920
|
async handler(request, endpointCtx) {
|
|
8975
8921
|
const user = await endpointCtx.getUser(request);
|
|
8976
8922
|
if (!user) {
|
|
8977
|
-
return
|
|
8923
|
+
return jsonResponse8({ error: "Authentication required" }, 401);
|
|
8978
8924
|
}
|
|
8979
8925
|
const url = new URL(request.url);
|
|
8980
8926
|
const segments = url.pathname.split("/").filter(Boolean);
|
|
8981
8927
|
const orgId = segments[2];
|
|
8982
8928
|
if (!orgId) {
|
|
8983
|
-
return
|
|
8929
|
+
return jsonResponse8({ error: "Missing organization ID in path" }, 400);
|
|
8984
8930
|
}
|
|
8985
8931
|
const member = await module.getMember(orgId, user.id);
|
|
8986
8932
|
if (!member || !ADMIN_ROLES.has(member.role)) {
|
|
8987
|
-
return
|
|
8933
|
+
return jsonResponse8({ error: "Admin or owner role required" }, 403);
|
|
8988
8934
|
}
|
|
8989
|
-
const body = await
|
|
8935
|
+
const body = await parseBody3(request);
|
|
8990
8936
|
const email = typeof body.email === "string" ? body.email.trim().toLowerCase() : null;
|
|
8991
8937
|
const role = typeof body.role === "string" ? body.role : "member";
|
|
8992
8938
|
if (!email) {
|
|
8993
|
-
return
|
|
8939
|
+
return jsonResponse8({ error: "Missing required field: email" }, 400);
|
|
8994
8940
|
}
|
|
8995
8941
|
try {
|
|
8996
8942
|
const invitation = await module.invite({
|
|
@@ -8999,9 +8945,9 @@ function organization(config) {
|
|
|
8999
8945
|
role,
|
|
9000
8946
|
invitedBy: user.id
|
|
9001
8947
|
});
|
|
9002
|
-
return
|
|
8948
|
+
return jsonResponse8(invitation, 201);
|
|
9003
8949
|
} catch (err2) {
|
|
9004
|
-
return
|
|
8950
|
+
return jsonResponse8(
|
|
9005
8951
|
{ error: err2 instanceof Error ? err2.message : "Failed to send invitation" },
|
|
9006
8952
|
400
|
|
9007
8953
|
);
|
|
@@ -9018,20 +8964,20 @@ function organization(config) {
|
|
|
9018
8964
|
async handler(request, endpointCtx) {
|
|
9019
8965
|
const user = await endpointCtx.getUser(request);
|
|
9020
8966
|
if (!user) {
|
|
9021
|
-
return
|
|
8967
|
+
return jsonResponse8({ error: "Authentication required" }, 401);
|
|
9022
8968
|
}
|
|
9023
8969
|
const url = new URL(request.url);
|
|
9024
8970
|
const segments = url.pathname.split("/").filter(Boolean);
|
|
9025
8971
|
const orgId = segments[2];
|
|
9026
8972
|
if (!orgId) {
|
|
9027
|
-
return
|
|
8973
|
+
return jsonResponse8({ error: "Missing organization ID in path" }, 400);
|
|
9028
8974
|
}
|
|
9029
8975
|
const callerMember = await module.getMember(orgId, user.id);
|
|
9030
8976
|
if (!callerMember) {
|
|
9031
|
-
return
|
|
8977
|
+
return jsonResponse8({ error: "You are not a member of this organization" }, 403);
|
|
9032
8978
|
}
|
|
9033
8979
|
const members = await module.getMembers(orgId);
|
|
9034
|
-
return
|
|
8980
|
+
return jsonResponse8({ members });
|
|
9035
8981
|
}
|
|
9036
8982
|
});
|
|
9037
8983
|
ctx.addEndpoint({
|
|
@@ -9044,29 +8990,29 @@ function organization(config) {
|
|
|
9044
8990
|
async handler(request, endpointCtx) {
|
|
9045
8991
|
const user = await endpointCtx.getUser(request);
|
|
9046
8992
|
if (!user) {
|
|
9047
|
-
return
|
|
8993
|
+
return jsonResponse8({ error: "Authentication required" }, 401);
|
|
9048
8994
|
}
|
|
9049
8995
|
const url = new URL(request.url);
|
|
9050
8996
|
const segments = url.pathname.split("/").filter(Boolean);
|
|
9051
8997
|
const orgId = segments[2];
|
|
9052
8998
|
const targetUserId = segments[4];
|
|
9053
8999
|
if (!orgId || !targetUserId) {
|
|
9054
|
-
return
|
|
9000
|
+
return jsonResponse8({ error: "Missing organization ID or user ID in path" }, 400);
|
|
9055
9001
|
}
|
|
9056
9002
|
const callerMember = await module.getMember(orgId, user.id);
|
|
9057
9003
|
if (!callerMember || !ADMIN_ROLES.has(callerMember.role)) {
|
|
9058
|
-
return
|
|
9004
|
+
return jsonResponse8({ error: "Admin or owner role required" }, 403);
|
|
9059
9005
|
}
|
|
9060
|
-
const body = await
|
|
9006
|
+
const body = await parseBody3(request);
|
|
9061
9007
|
const role = typeof body.role === "string" ? body.role : null;
|
|
9062
9008
|
if (!role) {
|
|
9063
|
-
return
|
|
9009
|
+
return jsonResponse8({ error: "Missing required field: role" }, 400);
|
|
9064
9010
|
}
|
|
9065
9011
|
try {
|
|
9066
9012
|
const member = await module.updateMemberRole(orgId, targetUserId, role);
|
|
9067
|
-
return
|
|
9013
|
+
return jsonResponse8(member);
|
|
9068
9014
|
} catch (err2) {
|
|
9069
|
-
return
|
|
9015
|
+
return jsonResponse8(
|
|
9070
9016
|
{ error: err2 instanceof Error ? err2.message : "Failed to update member role" },
|
|
9071
9017
|
400
|
|
9072
9018
|
);
|
|
@@ -9083,24 +9029,24 @@ function organization(config) {
|
|
|
9083
9029
|
async handler(request, endpointCtx) {
|
|
9084
9030
|
const user = await endpointCtx.getUser(request);
|
|
9085
9031
|
if (!user) {
|
|
9086
|
-
return
|
|
9032
|
+
return jsonResponse8({ error: "Authentication required" }, 401);
|
|
9087
9033
|
}
|
|
9088
9034
|
const url = new URL(request.url);
|
|
9089
9035
|
const segments = url.pathname.split("/").filter(Boolean);
|
|
9090
9036
|
const orgId = segments[2];
|
|
9091
9037
|
const targetUserId = segments[4];
|
|
9092
9038
|
if (!orgId || !targetUserId) {
|
|
9093
|
-
return
|
|
9039
|
+
return jsonResponse8({ error: "Missing organization ID or user ID in path" }, 400);
|
|
9094
9040
|
}
|
|
9095
9041
|
const callerMember = await module.getMember(orgId, user.id);
|
|
9096
9042
|
if (!callerMember || !ADMIN_ROLES.has(callerMember.role)) {
|
|
9097
|
-
return
|
|
9043
|
+
return jsonResponse8({ error: "Admin or owner role required" }, 403);
|
|
9098
9044
|
}
|
|
9099
9045
|
try {
|
|
9100
9046
|
await module.removeMember(orgId, targetUserId);
|
|
9101
|
-
return
|
|
9047
|
+
return jsonResponse8({ removed: true });
|
|
9102
9048
|
} catch (err2) {
|
|
9103
|
-
return
|
|
9049
|
+
return jsonResponse8(
|
|
9104
9050
|
{ error: err2 instanceof Error ? err2.message : "Failed to remove member" },
|
|
9105
9051
|
400
|
|
9106
9052
|
);
|
|
@@ -9505,13 +9451,13 @@ function parseAuthData(authData) {
|
|
|
9505
9451
|
}
|
|
9506
9452
|
return { rpIdHash, flags, signCount, attestedCredentialData };
|
|
9507
9453
|
}
|
|
9508
|
-
function
|
|
9454
|
+
function jsonResponse9(body, status = 200) {
|
|
9509
9455
|
return new Response(JSON.stringify(body), {
|
|
9510
9456
|
status,
|
|
9511
9457
|
headers: { "Content-Type": "application/json" }
|
|
9512
9458
|
});
|
|
9513
9459
|
}
|
|
9514
|
-
async function
|
|
9460
|
+
async function parseBody4(request) {
|
|
9515
9461
|
try {
|
|
9516
9462
|
return await request.json();
|
|
9517
9463
|
} catch {
|
|
@@ -9849,84 +9795,84 @@ function createPasskeyModule(config, db) {
|
|
|
9849
9795
|
const method = request.method.toUpperCase();
|
|
9850
9796
|
const segments = getPathSegments(url);
|
|
9851
9797
|
if (method === "POST" && segments.length === 4 && segments[1] === "passkey" && segments[2] === "register" && segments[3] === "options") {
|
|
9852
|
-
const body = await
|
|
9798
|
+
const body = await parseBody4(request);
|
|
9853
9799
|
const userId = typeof body.userId === "string" ? body.userId : null;
|
|
9854
9800
|
const userName = typeof body.userName === "string" ? body.userName : null;
|
|
9855
9801
|
if (!userId || !userName) {
|
|
9856
|
-
return
|
|
9802
|
+
return jsonResponse9({ error: "userId and userName required" }, 400);
|
|
9857
9803
|
}
|
|
9858
9804
|
try {
|
|
9859
9805
|
const options = await getRegistrationOptions(userId, userName);
|
|
9860
|
-
return
|
|
9806
|
+
return jsonResponse9(options);
|
|
9861
9807
|
} catch (err2) {
|
|
9862
9808
|
const message = err2 instanceof Error ? err2.message : "Failed to generate options";
|
|
9863
9809
|
const code = err2 instanceof PasskeyError ? err2.code : "INTERNAL_ERROR";
|
|
9864
|
-
return
|
|
9810
|
+
return jsonResponse9({ error: message, code }, 500);
|
|
9865
9811
|
}
|
|
9866
9812
|
}
|
|
9867
9813
|
if (method === "POST" && segments.length === 4 && segments[1] === "passkey" && segments[2] === "register" && segments[3] === "verify") {
|
|
9868
|
-
const body = await
|
|
9814
|
+
const body = await parseBody4(request);
|
|
9869
9815
|
const userId = typeof body.userId === "string" ? body.userId : null;
|
|
9870
|
-
if (!userId) return
|
|
9816
|
+
if (!userId) return jsonResponse9({ error: "userId required" }, 400);
|
|
9871
9817
|
const resp = body.response;
|
|
9872
|
-
if (!resp) return
|
|
9818
|
+
if (!resp) return jsonResponse9({ error: "response required" }, 400);
|
|
9873
9819
|
try {
|
|
9874
9820
|
const result = await verifyRegistration(userId, resp);
|
|
9875
|
-
return
|
|
9821
|
+
return jsonResponse9(result);
|
|
9876
9822
|
} catch (err2) {
|
|
9877
9823
|
const message = err2 instanceof Error ? err2.message : "Registration failed";
|
|
9878
9824
|
const code = err2 instanceof PasskeyError ? err2.code : "INTERNAL_ERROR";
|
|
9879
|
-
return
|
|
9825
|
+
return jsonResponse9({ error: message, code }, 400);
|
|
9880
9826
|
}
|
|
9881
9827
|
}
|
|
9882
9828
|
if (method === "POST" && segments.length === 4 && segments[1] === "passkey" && segments[2] === "login" && segments[3] === "options") {
|
|
9883
|
-
const body = await
|
|
9829
|
+
const body = await parseBody4(request);
|
|
9884
9830
|
const userId = typeof body.userId === "string" ? body.userId : void 0;
|
|
9885
9831
|
try {
|
|
9886
9832
|
const options = await getAuthenticationOptions(userId);
|
|
9887
|
-
return
|
|
9833
|
+
return jsonResponse9(options);
|
|
9888
9834
|
} catch (err2) {
|
|
9889
9835
|
const message = err2 instanceof Error ? err2.message : "Failed to generate options";
|
|
9890
9836
|
const code = err2 instanceof PasskeyError ? err2.code : "INTERNAL_ERROR";
|
|
9891
|
-
return
|
|
9837
|
+
return jsonResponse9({ error: message, code }, 500);
|
|
9892
9838
|
}
|
|
9893
9839
|
}
|
|
9894
9840
|
if (method === "POST" && segments.length === 4 && segments[1] === "passkey" && segments[2] === "login" && segments[3] === "verify") {
|
|
9895
|
-
const body = await
|
|
9841
|
+
const body = await parseBody4(request);
|
|
9896
9842
|
const resp = body.response;
|
|
9897
|
-
if (!resp) return
|
|
9843
|
+
if (!resp) return jsonResponse9({ error: "response required" }, 400);
|
|
9898
9844
|
try {
|
|
9899
9845
|
const result = await verifyAuthentication(resp);
|
|
9900
|
-
return
|
|
9846
|
+
return jsonResponse9(result);
|
|
9901
9847
|
} catch (err2) {
|
|
9902
9848
|
const message = err2 instanceof Error ? err2.message : "Authentication failed";
|
|
9903
9849
|
const code = err2 instanceof PasskeyError ? err2.code : "INTERNAL_ERROR";
|
|
9904
|
-
return
|
|
9850
|
+
return jsonResponse9({ error: message, code }, 401);
|
|
9905
9851
|
}
|
|
9906
9852
|
}
|
|
9907
9853
|
if (method === "GET" && segments.length === 3 && segments[1] === "passkey" && segments[2] === "credentials") {
|
|
9908
9854
|
const userId = url.searchParams.get("userId");
|
|
9909
|
-
if (!userId) return
|
|
9855
|
+
if (!userId) return jsonResponse9({ error: "userId query param required" }, 400);
|
|
9910
9856
|
try {
|
|
9911
9857
|
const creds = await listCredentials(userId);
|
|
9912
|
-
return
|
|
9858
|
+
return jsonResponse9({ credentials: creds });
|
|
9913
9859
|
} catch (err2) {
|
|
9914
9860
|
const message = err2 instanceof Error ? err2.message : "Failed to list credentials";
|
|
9915
|
-
return
|
|
9861
|
+
return jsonResponse9({ error: message }, 500);
|
|
9916
9862
|
}
|
|
9917
9863
|
}
|
|
9918
9864
|
if (method === "DELETE" && segments.length === 4 && segments[1] === "passkey" && segments[2] === "credentials") {
|
|
9919
9865
|
const credentialId = segments[3];
|
|
9920
|
-
if (!credentialId) return
|
|
9921
|
-
const body = await
|
|
9866
|
+
if (!credentialId) return jsonResponse9({ error: "Credential ID required" }, 400);
|
|
9867
|
+
const body = await parseBody4(request);
|
|
9922
9868
|
const userId = typeof body.userId === "string" ? body.userId : null;
|
|
9923
|
-
if (!userId) return
|
|
9869
|
+
if (!userId) return jsonResponse9({ error: "userId required" }, 400);
|
|
9924
9870
|
try {
|
|
9925
9871
|
await removeCredential(credentialId, userId);
|
|
9926
|
-
return
|
|
9872
|
+
return jsonResponse9({ removed: true });
|
|
9927
9873
|
} catch (err2) {
|
|
9928
9874
|
const message = err2 instanceof Error ? err2.message : "Failed to remove credential";
|
|
9929
|
-
return
|
|
9875
|
+
return jsonResponse9({ error: message }, 500);
|
|
9930
9876
|
}
|
|
9931
9877
|
}
|
|
9932
9878
|
return null;
|
|
@@ -9943,13 +9889,13 @@ function createPasskeyModule(config, db) {
|
|
|
9943
9889
|
}
|
|
9944
9890
|
|
|
9945
9891
|
// src/auth/passkey-plugin.ts
|
|
9946
|
-
function
|
|
9892
|
+
function jsonResponse10(body, status = 200) {
|
|
9947
9893
|
return new Response(JSON.stringify(body), {
|
|
9948
9894
|
status,
|
|
9949
9895
|
headers: { "Content-Type": "application/json" }
|
|
9950
9896
|
});
|
|
9951
9897
|
}
|
|
9952
|
-
async function
|
|
9898
|
+
async function parseBody5(request) {
|
|
9953
9899
|
try {
|
|
9954
9900
|
return await request.json();
|
|
9955
9901
|
} catch {
|
|
@@ -9971,16 +9917,16 @@ function passkey(config) {
|
|
|
9971
9917
|
async handler(request, endpointCtx) {
|
|
9972
9918
|
const user = await endpointCtx.getUser(request);
|
|
9973
9919
|
if (!user) {
|
|
9974
|
-
return
|
|
9920
|
+
return jsonResponse10({ error: "Authentication required" }, 401);
|
|
9975
9921
|
}
|
|
9976
|
-
const body = await
|
|
9922
|
+
const body = await parseBody5(request);
|
|
9977
9923
|
const userId = typeof body.userId === "string" ? body.userId : user.id;
|
|
9978
9924
|
const userName = typeof body.userName === "string" ? body.userName : user.email ?? user.id;
|
|
9979
9925
|
try {
|
|
9980
9926
|
const options = await module.getRegistrationOptions(userId, userName);
|
|
9981
|
-
return
|
|
9927
|
+
return jsonResponse10(options);
|
|
9982
9928
|
} catch (err2) {
|
|
9983
|
-
return
|
|
9929
|
+
return jsonResponse10(
|
|
9984
9930
|
{ error: err2 instanceof Error ? err2.message : "Failed to generate options" },
|
|
9985
9931
|
500
|
|
9986
9932
|
);
|
|
@@ -9997,19 +9943,19 @@ function passkey(config) {
|
|
|
9997
9943
|
async handler(request, endpointCtx) {
|
|
9998
9944
|
const user = await endpointCtx.getUser(request);
|
|
9999
9945
|
if (!user) {
|
|
10000
|
-
return
|
|
9946
|
+
return jsonResponse10({ error: "Authentication required" }, 401);
|
|
10001
9947
|
}
|
|
10002
|
-
const body = await
|
|
9948
|
+
const body = await parseBody5(request);
|
|
10003
9949
|
const userId = typeof body.userId === "string" ? body.userId : user.id;
|
|
10004
9950
|
const response = body.response;
|
|
10005
9951
|
if (!response) {
|
|
10006
|
-
return
|
|
9952
|
+
return jsonResponse10({ error: "Missing required field: response" }, 400);
|
|
10007
9953
|
}
|
|
10008
9954
|
try {
|
|
10009
9955
|
const result = await module.verifyRegistration(userId, response);
|
|
10010
|
-
return
|
|
9956
|
+
return jsonResponse10(result);
|
|
10011
9957
|
} catch (err2) {
|
|
10012
|
-
return
|
|
9958
|
+
return jsonResponse10(
|
|
10013
9959
|
{ error: err2 instanceof Error ? err2.message : "Registration failed" },
|
|
10014
9960
|
400
|
|
10015
9961
|
);
|
|
@@ -10023,13 +9969,13 @@ function passkey(config) {
|
|
|
10023
9969
|
description: "Get WebAuthn authentication options"
|
|
10024
9970
|
},
|
|
10025
9971
|
async handler(request) {
|
|
10026
|
-
const body = await
|
|
9972
|
+
const body = await parseBody5(request);
|
|
10027
9973
|
const userId = typeof body.userId === "string" ? body.userId : void 0;
|
|
10028
9974
|
try {
|
|
10029
9975
|
const options = await module.getAuthenticationOptions(userId);
|
|
10030
|
-
return
|
|
9976
|
+
return jsonResponse10(options);
|
|
10031
9977
|
} catch (err2) {
|
|
10032
|
-
return
|
|
9978
|
+
return jsonResponse10(
|
|
10033
9979
|
{ error: err2 instanceof Error ? err2.message : "Failed to generate options" },
|
|
10034
9980
|
500
|
|
10035
9981
|
);
|
|
@@ -10043,19 +9989,43 @@ function passkey(config) {
|
|
|
10043
9989
|
description: "Verify a WebAuthn assertion and return the authenticated user"
|
|
10044
9990
|
},
|
|
10045
9991
|
async handler(request) {
|
|
10046
|
-
const body = await
|
|
9992
|
+
const body = await parseBody5(request);
|
|
10047
9993
|
const response = body.response;
|
|
10048
9994
|
if (!response) {
|
|
10049
|
-
return
|
|
9995
|
+
return jsonResponse10({ error: "Missing required field: response" }, 400);
|
|
10050
9996
|
}
|
|
10051
9997
|
try {
|
|
10052
9998
|
const result = await module.verifyAuthentication(response);
|
|
10053
9999
|
if (!result) {
|
|
10054
|
-
return
|
|
10000
|
+
return jsonResponse10({ error: "Authentication failed" }, 401);
|
|
10055
10001
|
}
|
|
10056
|
-
|
|
10002
|
+
if (ctx.sessionManager) {
|
|
10003
|
+
const { session, token } = await ctx.sessionManager.create(result.userId);
|
|
10004
|
+
const maxAge = Math.floor((session.expiresAt.getTime() - Date.now()) / 1e3);
|
|
10005
|
+
return new Response(
|
|
10006
|
+
JSON.stringify({
|
|
10007
|
+
user: { id: result.userId },
|
|
10008
|
+
session: { token, expiresAt: session.expiresAt },
|
|
10009
|
+
credential: result.credential
|
|
10010
|
+
}),
|
|
10011
|
+
{
|
|
10012
|
+
status: 200,
|
|
10013
|
+
headers: {
|
|
10014
|
+
"Content-Type": "application/json",
|
|
10015
|
+
"Set-Cookie": buildSetCookie(
|
|
10016
|
+
"kavach_session",
|
|
10017
|
+
token,
|
|
10018
|
+
maxAge,
|
|
10019
|
+
"/",
|
|
10020
|
+
(ctx.config.baseUrl ?? "").startsWith("https://")
|
|
10021
|
+
)
|
|
10022
|
+
}
|
|
10023
|
+
}
|
|
10024
|
+
);
|
|
10025
|
+
}
|
|
10026
|
+
return jsonResponse10(result);
|
|
10057
10027
|
} catch (err2) {
|
|
10058
|
-
return
|
|
10028
|
+
return jsonResponse10(
|
|
10059
10029
|
{ error: err2 instanceof Error ? err2.message : "Authentication failed" },
|
|
10060
10030
|
401
|
|
10061
10031
|
);
|
|
@@ -10072,13 +10042,13 @@ function passkey(config) {
|
|
|
10072
10042
|
async handler(request, endpointCtx) {
|
|
10073
10043
|
const user = await endpointCtx.getUser(request);
|
|
10074
10044
|
if (!user) {
|
|
10075
|
-
return
|
|
10045
|
+
return jsonResponse10({ error: "Authentication required" }, 401);
|
|
10076
10046
|
}
|
|
10077
10047
|
try {
|
|
10078
10048
|
const credentials = await module.listCredentials(user.id);
|
|
10079
|
-
return
|
|
10049
|
+
return jsonResponse10({ credentials });
|
|
10080
10050
|
} catch (err2) {
|
|
10081
|
-
return
|
|
10051
|
+
return jsonResponse10(
|
|
10082
10052
|
{ error: err2 instanceof Error ? err2.message : "Failed to list credentials" },
|
|
10083
10053
|
500
|
|
10084
10054
|
);
|
|
@@ -10095,19 +10065,19 @@ function passkey(config) {
|
|
|
10095
10065
|
async handler(request, endpointCtx) {
|
|
10096
10066
|
const user = await endpointCtx.getUser(request);
|
|
10097
10067
|
if (!user) {
|
|
10098
|
-
return
|
|
10068
|
+
return jsonResponse10({ error: "Authentication required" }, 401);
|
|
10099
10069
|
}
|
|
10100
10070
|
const url = new URL(request.url);
|
|
10101
10071
|
const segments = url.pathname.split("/").filter(Boolean);
|
|
10102
10072
|
const credentialId = segments[3];
|
|
10103
10073
|
if (!credentialId) {
|
|
10104
|
-
return
|
|
10074
|
+
return jsonResponse10({ error: "Missing credential ID in path" }, 400);
|
|
10105
10075
|
}
|
|
10106
10076
|
try {
|
|
10107
10077
|
await module.removeCredential(credentialId, user.id);
|
|
10108
|
-
return
|
|
10078
|
+
return jsonResponse10({ removed: true });
|
|
10109
10079
|
} catch (err2) {
|
|
10110
|
-
return
|
|
10080
|
+
return jsonResponse10(
|
|
10111
10081
|
{ error: err2 instanceof Error ? err2.message : "Failed to remove credential" },
|
|
10112
10082
|
500
|
|
10113
10083
|
);
|
|
@@ -10124,7 +10094,7 @@ var TOKEN_PURPOSE2 = "password-reset";
|
|
|
10124
10094
|
function makeError7(code, message, details) {
|
|
10125
10095
|
return { code, message, ...details !== void 0 ? { details } : {} };
|
|
10126
10096
|
}
|
|
10127
|
-
function
|
|
10097
|
+
function jsonResponse11(body, status = 200) {
|
|
10128
10098
|
return new Response(JSON.stringify(body), {
|
|
10129
10099
|
status,
|
|
10130
10100
|
headers: { "Content-Type": "application/json" }
|
|
@@ -10240,27 +10210,27 @@ function createPasswordResetModule(config, db, sessionManager, tokenModule) {
|
|
|
10240
10210
|
try {
|
|
10241
10211
|
body = await request.json();
|
|
10242
10212
|
} catch {
|
|
10243
|
-
return
|
|
10213
|
+
return jsonResponse11({ error: "Invalid JSON body" }, 400);
|
|
10244
10214
|
}
|
|
10245
10215
|
const b = body;
|
|
10246
10216
|
if (pathname === "/auth/forgot-password") {
|
|
10247
10217
|
if (typeof b.email !== "string") {
|
|
10248
|
-
return
|
|
10218
|
+
return jsonResponse11({ error: "Missing required field: email" }, 400);
|
|
10249
10219
|
}
|
|
10250
10220
|
const result = await requestReset(b.email);
|
|
10251
10221
|
if (!result.success) {
|
|
10252
|
-
return
|
|
10222
|
+
return jsonResponse11({ error: result.error.message }, 500);
|
|
10253
10223
|
}
|
|
10254
10224
|
return new Response(null, { status: 204 });
|
|
10255
10225
|
}
|
|
10256
10226
|
if (pathname === "/auth/reset-password") {
|
|
10257
10227
|
if (typeof b.token !== "string" || typeof b.password !== "string") {
|
|
10258
|
-
return
|
|
10228
|
+
return jsonResponse11({ error: "Missing required fields: token, password" }, 400);
|
|
10259
10229
|
}
|
|
10260
10230
|
const result = await resetPassword(b.token, b.password);
|
|
10261
10231
|
if (!result.success) {
|
|
10262
10232
|
const status = result.error.code === "INVALID_PASSWORD" ? 400 : 400;
|
|
10263
|
-
return
|
|
10233
|
+
return jsonResponse11({ error: result.error.message }, status);
|
|
10264
10234
|
}
|
|
10265
10235
|
return new Response(null, { status: 204 });
|
|
10266
10236
|
}
|
|
@@ -10295,7 +10265,7 @@ function generateNumericCode2(length) {
|
|
|
10295
10265
|
function normalisePhone(phone) {
|
|
10296
10266
|
return phone.replace(/\s+/g, "");
|
|
10297
10267
|
}
|
|
10298
|
-
function
|
|
10268
|
+
function jsonResponse12(body, status = 200) {
|
|
10299
10269
|
return new Response(JSON.stringify(body), {
|
|
10300
10270
|
status,
|
|
10301
10271
|
headers: { "Content-Type": "application/json" }
|
|
@@ -10367,25 +10337,25 @@ function createPhoneAuthModule(config, db, sessionManager) {
|
|
|
10367
10337
|
try {
|
|
10368
10338
|
body = await request.json();
|
|
10369
10339
|
} catch {
|
|
10370
|
-
return
|
|
10340
|
+
return jsonResponse12({ error: "Invalid JSON body" }, 400);
|
|
10371
10341
|
}
|
|
10372
10342
|
const b = body;
|
|
10373
10343
|
if (pathname === "/auth/phone/send-code") {
|
|
10374
10344
|
if (typeof b.phoneNumber !== "string") {
|
|
10375
|
-
return
|
|
10345
|
+
return jsonResponse12({ error: "Missing required field: phoneNumber" }, 400);
|
|
10376
10346
|
}
|
|
10377
10347
|
const result = await sendCode(b.phoneNumber);
|
|
10378
|
-
return
|
|
10348
|
+
return jsonResponse12(result);
|
|
10379
10349
|
}
|
|
10380
10350
|
if (pathname === "/auth/phone/verify") {
|
|
10381
10351
|
if (typeof b.phoneNumber !== "string" || typeof b.code !== "string") {
|
|
10382
|
-
return
|
|
10352
|
+
return jsonResponse12({ error: "Missing required fields: phoneNumber, code" }, 400);
|
|
10383
10353
|
}
|
|
10384
10354
|
const result = await verifyCode(b.phoneNumber, b.code);
|
|
10385
10355
|
if (!result) {
|
|
10386
|
-
return
|
|
10356
|
+
return jsonResponse12({ error: "Invalid or expired code" }, 401);
|
|
10387
10357
|
}
|
|
10388
|
-
return
|
|
10358
|
+
return jsonResponse12(result);
|
|
10389
10359
|
}
|
|
10390
10360
|
return null;
|
|
10391
10361
|
}
|
|
@@ -10405,12 +10375,12 @@ async function polarRequest(accessToken, baseUrl, method, path, body) {
|
|
|
10405
10375
|
headers,
|
|
10406
10376
|
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
10407
10377
|
});
|
|
10408
|
-
const
|
|
10378
|
+
const json2 = await response.json();
|
|
10409
10379
|
if (!response.ok) {
|
|
10410
|
-
const message =
|
|
10380
|
+
const message = json2.detail ?? json2.message ?? `Polar API error: ${response.status}`;
|
|
10411
10381
|
throw new Error(message);
|
|
10412
10382
|
}
|
|
10413
|
-
return
|
|
10383
|
+
return json2;
|
|
10414
10384
|
}
|
|
10415
10385
|
async function verifyWebhookSignature(payload, signatureHeader, webhookSecret) {
|
|
10416
10386
|
const prefix = "sha256=";
|
|
@@ -10633,19 +10603,6 @@ function createPolarModule(config, db) {
|
|
|
10633
10603
|
}
|
|
10634
10604
|
|
|
10635
10605
|
// src/auth/polar-plugin.ts
|
|
10636
|
-
function json(body, status = 200) {
|
|
10637
|
-
return new Response(JSON.stringify(body), {
|
|
10638
|
-
status,
|
|
10639
|
-
headers: { "Content-Type": "application/json" }
|
|
10640
|
-
});
|
|
10641
|
-
}
|
|
10642
|
-
async function parseBody10(request) {
|
|
10643
|
-
try {
|
|
10644
|
-
return await request.json();
|
|
10645
|
-
} catch {
|
|
10646
|
-
return {};
|
|
10647
|
-
}
|
|
10648
|
-
}
|
|
10649
10606
|
function polar(config) {
|
|
10650
10607
|
return {
|
|
10651
10608
|
id: "kavach-polar",
|
|
@@ -10663,13 +10620,14 @@ function polar(config) {
|
|
|
10663
10620
|
if (!user) {
|
|
10664
10621
|
return json({ error: "Authentication required" }, 401);
|
|
10665
10622
|
}
|
|
10666
|
-
const
|
|
10667
|
-
|
|
10623
|
+
const bodyResult = await parseBody(request);
|
|
10624
|
+
if (!bodyResult.ok) return bodyResult.response;
|
|
10625
|
+
const productId = typeof bodyResult.data.productId === "string" ? bodyResult.data.productId.trim() : null;
|
|
10668
10626
|
if (!productId) {
|
|
10669
10627
|
return json({ error: "Missing required field: productId" }, 400);
|
|
10670
10628
|
}
|
|
10671
|
-
const successUrl = typeof
|
|
10672
|
-
const customerEmail = typeof
|
|
10629
|
+
const successUrl = typeof bodyResult.data.successUrl === "string" ? bodyResult.data.successUrl : void 0;
|
|
10630
|
+
const customerEmail = typeof bodyResult.data.customerEmail === "string" ? bodyResult.data.customerEmail : void 0;
|
|
10673
10631
|
try {
|
|
10674
10632
|
const result = await module.createCheckout(user.id, productId, {
|
|
10675
10633
|
successUrl,
|
|
@@ -12114,13 +12072,13 @@ function scim(config) {
|
|
|
12114
12072
|
// src/auth/siwe.ts
|
|
12115
12073
|
var DEFAULT_NONCE_TTL_SECONDS = 300;
|
|
12116
12074
|
var SIWE_VERSION = "1";
|
|
12117
|
-
function
|
|
12075
|
+
function jsonResponse13(body, status = 200) {
|
|
12118
12076
|
return new Response(JSON.stringify(body), {
|
|
12119
12077
|
status,
|
|
12120
12078
|
headers: { "Content-Type": "application/json" }
|
|
12121
12079
|
});
|
|
12122
12080
|
}
|
|
12123
|
-
async function
|
|
12081
|
+
async function parseBody6(request) {
|
|
12124
12082
|
try {
|
|
12125
12083
|
return await request.json();
|
|
12126
12084
|
} catch {
|
|
@@ -12255,20 +12213,20 @@ function createSiweModule(config) {
|
|
|
12255
12213
|
const { method, pathname } = { method: request.method, pathname: url.pathname };
|
|
12256
12214
|
if (method === "GET" && pathname.endsWith("/auth/siwe/nonce")) {
|
|
12257
12215
|
const nonce = await generateNonce();
|
|
12258
|
-
return
|
|
12216
|
+
return jsonResponse13({ nonce });
|
|
12259
12217
|
}
|
|
12260
12218
|
if (method === "POST" && pathname.endsWith("/auth/siwe/verify")) {
|
|
12261
|
-
const body = await
|
|
12219
|
+
const body = await parseBody6(request);
|
|
12262
12220
|
const message = typeof body.message === "string" ? body.message : null;
|
|
12263
12221
|
const signature = typeof body.signature === "string" ? body.signature : null;
|
|
12264
12222
|
if (!message || !signature) {
|
|
12265
|
-
return
|
|
12223
|
+
return jsonResponse13({ error: "Missing required fields: message, signature" }, 400);
|
|
12266
12224
|
}
|
|
12267
12225
|
try {
|
|
12268
12226
|
const result = await verify(message, signature);
|
|
12269
|
-
return
|
|
12227
|
+
return jsonResponse13({ address: result.address, chainId: result.chainId });
|
|
12270
12228
|
} catch (err2) {
|
|
12271
|
-
return
|
|
12229
|
+
return jsonResponse13(
|
|
12272
12230
|
{ error: err2 instanceof Error ? err2.message : "Verification failed" },
|
|
12273
12231
|
400
|
|
12274
12232
|
);
|
|
@@ -13305,7 +13263,7 @@ function createSsoModule(config, db) {
|
|
|
13305
13263
|
const url = new URL(request.url);
|
|
13306
13264
|
const { pathname } = url;
|
|
13307
13265
|
const { method } = request;
|
|
13308
|
-
const
|
|
13266
|
+
const json2 = (data, status = 200) => new Response(JSON.stringify(data), {
|
|
13309
13267
|
status,
|
|
13310
13268
|
headers: { "Content-Type": "application/json" }
|
|
13311
13269
|
});
|
|
@@ -13314,14 +13272,14 @@ function createSsoModule(config, db) {
|
|
|
13314
13272
|
try {
|
|
13315
13273
|
body = await request.json();
|
|
13316
13274
|
} catch {
|
|
13317
|
-
return
|
|
13275
|
+
return json2({ error: "Invalid JSON body" }, 400);
|
|
13318
13276
|
}
|
|
13319
13277
|
const b = body;
|
|
13320
13278
|
if (typeof b.orgId !== "string" || typeof b.providerId !== "string" || typeof b.type !== "string" || typeof b.domain !== "string") {
|
|
13321
|
-
return
|
|
13279
|
+
return json2({ error: "Missing required fields: orgId, providerId, type, domain" }, 400);
|
|
13322
13280
|
}
|
|
13323
13281
|
if (b.type !== "saml" && b.type !== "oidc") {
|
|
13324
|
-
return
|
|
13282
|
+
return json2({ error: "type must be 'saml' or 'oidc'" }, 400);
|
|
13325
13283
|
}
|
|
13326
13284
|
const conn = await createConnection({
|
|
13327
13285
|
orgId: b.orgId,
|
|
@@ -13329,19 +13287,19 @@ function createSsoModule(config, db) {
|
|
|
13329
13287
|
type: b.type,
|
|
13330
13288
|
domain: b.domain
|
|
13331
13289
|
});
|
|
13332
|
-
return
|
|
13290
|
+
return json2(conn, 201);
|
|
13333
13291
|
}
|
|
13334
13292
|
const listMatch = /^\/auth\/sso\/connections\/([^/]+)$/.exec(pathname);
|
|
13335
13293
|
if (method === "GET" && listMatch) {
|
|
13336
13294
|
const orgId = decodeURIComponent(listMatch[1] ?? "");
|
|
13337
13295
|
const conns = await listConnections(orgId);
|
|
13338
|
-
return
|
|
13296
|
+
return json2(conns);
|
|
13339
13297
|
}
|
|
13340
13298
|
const deleteMatch = /^\/auth\/sso\/connections\/([^/]+)$/.exec(pathname);
|
|
13341
13299
|
if (method === "DELETE" && deleteMatch) {
|
|
13342
13300
|
const connId = decodeURIComponent(deleteMatch[1] ?? "");
|
|
13343
13301
|
await removeConnection(connId);
|
|
13344
|
-
return
|
|
13302
|
+
return json2({ success: true });
|
|
13345
13303
|
}
|
|
13346
13304
|
const samlInitMatch = /^\/auth\/sso\/saml\/([^/]+)$/.exec(pathname);
|
|
13347
13305
|
if (method === "GET" && samlInitMatch) {
|
|
@@ -13351,7 +13309,7 @@ function createSsoModule(config, db) {
|
|
|
13351
13309
|
const authUrl = await getSamlAuthUrl(connId, relayState);
|
|
13352
13310
|
return new Response(null, { status: 302, headers: { Location: authUrl } });
|
|
13353
13311
|
} catch (err2) {
|
|
13354
|
-
return
|
|
13312
|
+
return json2({ error: err2 instanceof Error ? err2.message : "Unknown error" }, 400);
|
|
13355
13313
|
}
|
|
13356
13314
|
}
|
|
13357
13315
|
const samlAcsMatch = /^\/auth\/sso\/saml\/([^/]+)\/acs$/.exec(pathname);
|
|
@@ -13364,14 +13322,14 @@ function createSsoModule(config, db) {
|
|
|
13364
13322
|
if (typeof val !== "string") throw new Error("Missing SAMLResponse");
|
|
13365
13323
|
samlResponse = val;
|
|
13366
13324
|
} catch {
|
|
13367
|
-
return
|
|
13325
|
+
return json2({ error: "Missing or invalid SAMLResponse" }, 400);
|
|
13368
13326
|
}
|
|
13369
13327
|
try {
|
|
13370
13328
|
const result = await handleSamlResponse(connId, samlResponse);
|
|
13371
|
-
return
|
|
13329
|
+
return json2(result);
|
|
13372
13330
|
} catch (err2) {
|
|
13373
13331
|
const status = err2 instanceof SsoError && err2.code === SSO_ERROR.RATE_LIMITED ? 429 : 401;
|
|
13374
|
-
return
|
|
13332
|
+
return json2(
|
|
13375
13333
|
{
|
|
13376
13334
|
error: err2 instanceof Error ? err2.message : "SAML error",
|
|
13377
13335
|
code: err2 instanceof SsoError ? err2.code : "SAML_ERROR"
|
|
@@ -13389,20 +13347,20 @@ function createSsoModule(config, db) {
|
|
|
13389
13347
|
const authUrl = await getOidcAuthUrl(connId, state, nonce);
|
|
13390
13348
|
return new Response(null, { status: 302, headers: { Location: authUrl } });
|
|
13391
13349
|
} catch (err2) {
|
|
13392
|
-
return
|
|
13350
|
+
return json2({ error: err2 instanceof Error ? err2.message : "Unknown error" }, 400);
|
|
13393
13351
|
}
|
|
13394
13352
|
}
|
|
13395
13353
|
const oidcCbMatch = /^\/auth\/sso\/oidc\/([^/]+)\/callback$/.exec(pathname);
|
|
13396
13354
|
if (method === "GET" && oidcCbMatch) {
|
|
13397
13355
|
const connId = decodeURIComponent(oidcCbMatch[1] ?? "");
|
|
13398
13356
|
const code = url.searchParams.get("code");
|
|
13399
|
-
if (!code) return
|
|
13357
|
+
if (!code) return json2({ error: "Missing code parameter" }, 400);
|
|
13400
13358
|
try {
|
|
13401
13359
|
const result = await handleOidcCallback(connId, code);
|
|
13402
|
-
return
|
|
13360
|
+
return json2(result);
|
|
13403
13361
|
} catch (err2) {
|
|
13404
13362
|
const status = err2 instanceof SsoError && err2.code === SSO_ERROR.RATE_LIMITED ? 429 : 401;
|
|
13405
|
-
return
|
|
13363
|
+
return json2(
|
|
13406
13364
|
{
|
|
13407
13365
|
error: err2 instanceof Error ? err2.message : "OIDC error",
|
|
13408
13366
|
code: err2 instanceof SsoError ? err2.code : "OIDC_ERROR"
|
|
@@ -13503,12 +13461,12 @@ async function stripeRequest(secretKey, apiVersion, method, path, body) {
|
|
|
13503
13461
|
headers,
|
|
13504
13462
|
body: bodyStr
|
|
13505
13463
|
});
|
|
13506
|
-
const
|
|
13464
|
+
const json2 = await response.json();
|
|
13507
13465
|
if (!response.ok) {
|
|
13508
|
-
const message =
|
|
13466
|
+
const message = json2.error?.message ?? `Stripe API error: ${response.status}`;
|
|
13509
13467
|
throw new Error(message);
|
|
13510
13468
|
}
|
|
13511
|
-
return
|
|
13469
|
+
return json2;
|
|
13512
13470
|
}
|
|
13513
13471
|
async function verifyWebhookSignature2(payload, signatureHeader, webhookSecret) {
|
|
13514
13472
|
const parts = {};
|
|
@@ -13818,19 +13776,6 @@ function createStripeModule(config, db) {
|
|
|
13818
13776
|
}
|
|
13819
13777
|
|
|
13820
13778
|
// src/auth/stripe-plugin.ts
|
|
13821
|
-
function json2(body, status = 200) {
|
|
13822
|
-
return new Response(JSON.stringify(body), {
|
|
13823
|
-
status,
|
|
13824
|
-
headers: { "Content-Type": "application/json" }
|
|
13825
|
-
});
|
|
13826
|
-
}
|
|
13827
|
-
async function parseBody12(request) {
|
|
13828
|
-
try {
|
|
13829
|
-
return await request.json();
|
|
13830
|
-
} catch {
|
|
13831
|
-
return {};
|
|
13832
|
-
}
|
|
13833
|
-
}
|
|
13834
13779
|
function stripe(config) {
|
|
13835
13780
|
return {
|
|
13836
13781
|
id: "kavach-stripe",
|
|
@@ -13846,17 +13791,18 @@ function stripe(config) {
|
|
|
13846
13791
|
async handler(request, endpointCtx) {
|
|
13847
13792
|
const user = await endpointCtx.getUser(request);
|
|
13848
13793
|
if (!user) {
|
|
13849
|
-
return
|
|
13794
|
+
return json({ error: "Authentication required" }, 401);
|
|
13850
13795
|
}
|
|
13851
|
-
const
|
|
13852
|
-
|
|
13796
|
+
const bodyResult = await parseBody(request);
|
|
13797
|
+
if (!bodyResult.ok) return bodyResult.response;
|
|
13798
|
+
const priceId = typeof bodyResult.data.priceId === "string" ? bodyResult.data.priceId.trim() : null;
|
|
13853
13799
|
if (!priceId) {
|
|
13854
|
-
return
|
|
13800
|
+
return json({ error: "Missing required field: priceId" }, 400);
|
|
13855
13801
|
}
|
|
13856
|
-
const successUrl = typeof
|
|
13857
|
-
const cancelUrl = typeof
|
|
13858
|
-
const trialDays = typeof
|
|
13859
|
-
const metadata =
|
|
13802
|
+
const successUrl = typeof bodyResult.data.successUrl === "string" ? bodyResult.data.successUrl : void 0;
|
|
13803
|
+
const cancelUrl = typeof bodyResult.data.cancelUrl === "string" ? bodyResult.data.cancelUrl : void 0;
|
|
13804
|
+
const trialDays = typeof bodyResult.data.trialDays === "number" ? bodyResult.data.trialDays : void 0;
|
|
13805
|
+
const metadata = bodyResult.data.metadata != null && typeof bodyResult.data.metadata === "object" && !Array.isArray(bodyResult.data.metadata) ? bodyResult.data.metadata : void 0;
|
|
13860
13806
|
try {
|
|
13861
13807
|
const result = await module.createCheckoutSession(user.id, priceId, {
|
|
13862
13808
|
successUrl,
|
|
@@ -13864,9 +13810,9 @@ function stripe(config) {
|
|
|
13864
13810
|
trialDays,
|
|
13865
13811
|
metadata
|
|
13866
13812
|
});
|
|
13867
|
-
return
|
|
13813
|
+
return json(result);
|
|
13868
13814
|
} catch (err2) {
|
|
13869
|
-
return
|
|
13815
|
+
return json(
|
|
13870
13816
|
{
|
|
13871
13817
|
error: err2 instanceof Error ? err2.message : "Failed to create checkout session"
|
|
13872
13818
|
},
|
|
@@ -13885,18 +13831,19 @@ function stripe(config) {
|
|
|
13885
13831
|
async handler(request, endpointCtx) {
|
|
13886
13832
|
const user = await endpointCtx.getUser(request);
|
|
13887
13833
|
if (!user) {
|
|
13888
|
-
return
|
|
13834
|
+
return json({ error: "Authentication required" }, 401);
|
|
13889
13835
|
}
|
|
13890
|
-
const
|
|
13891
|
-
|
|
13836
|
+
const bodyResult = await parseBody(request);
|
|
13837
|
+
if (!bodyResult.ok) return bodyResult.response;
|
|
13838
|
+
const returnUrl = typeof bodyResult.data.returnUrl === "string" ? bodyResult.data.returnUrl.trim() : null;
|
|
13892
13839
|
if (!returnUrl) {
|
|
13893
|
-
return
|
|
13840
|
+
return json({ error: "Missing required field: returnUrl" }, 400);
|
|
13894
13841
|
}
|
|
13895
13842
|
try {
|
|
13896
13843
|
const result = await module.createPortalSession(user.id, returnUrl);
|
|
13897
|
-
return
|
|
13844
|
+
return json(result);
|
|
13898
13845
|
} catch (err2) {
|
|
13899
|
-
return
|
|
13846
|
+
return json(
|
|
13900
13847
|
{
|
|
13901
13848
|
error: err2 instanceof Error ? err2.message : "Failed to create portal session"
|
|
13902
13849
|
},
|
|
@@ -13915,10 +13862,10 @@ function stripe(config) {
|
|
|
13915
13862
|
async handler(request, endpointCtx) {
|
|
13916
13863
|
const user = await endpointCtx.getUser(request);
|
|
13917
13864
|
if (!user) {
|
|
13918
|
-
return
|
|
13865
|
+
return json({ error: "Authentication required" }, 401);
|
|
13919
13866
|
}
|
|
13920
13867
|
const subscription = await module.getSubscription(user.id);
|
|
13921
|
-
return
|
|
13868
|
+
return json({ subscription });
|
|
13922
13869
|
}
|
|
13923
13870
|
});
|
|
13924
13871
|
ctx.addEndpoint({
|
|
@@ -14020,13 +13967,13 @@ async function generateBackupCodes(count) {
|
|
|
14020
13967
|
}
|
|
14021
13968
|
return { plain, hashed };
|
|
14022
13969
|
}
|
|
14023
|
-
function
|
|
13970
|
+
function jsonResponse14(body, status = 200) {
|
|
14024
13971
|
return new Response(JSON.stringify(body), {
|
|
14025
13972
|
status,
|
|
14026
13973
|
headers: { "Content-Type": "application/json" }
|
|
14027
13974
|
});
|
|
14028
13975
|
}
|
|
14029
|
-
async function
|
|
13976
|
+
async function parseBody7(request) {
|
|
14030
13977
|
try {
|
|
14031
13978
|
return await request.json();
|
|
14032
13979
|
} catch {
|
|
@@ -14124,50 +14071,50 @@ function createTotpModule(config, db) {
|
|
|
14124
14071
|
const method = request.method.toUpperCase();
|
|
14125
14072
|
if (method !== "POST") return null;
|
|
14126
14073
|
if (path === "/auth/2fa/setup") {
|
|
14127
|
-
const body = await
|
|
14074
|
+
const body = await parseBody7(request);
|
|
14128
14075
|
const userId = typeof body.userId === "string" ? body.userId : null;
|
|
14129
|
-
if (!userId) return
|
|
14076
|
+
if (!userId) return jsonResponse14({ error: "userId required" }, 400);
|
|
14130
14077
|
try {
|
|
14131
14078
|
const result = await setup(userId);
|
|
14132
|
-
return
|
|
14079
|
+
return jsonResponse14(result);
|
|
14133
14080
|
} catch (err2) {
|
|
14134
|
-
return
|
|
14081
|
+
return jsonResponse14({ error: err2 instanceof Error ? err2.message : "Setup failed" }, 500);
|
|
14135
14082
|
}
|
|
14136
14083
|
}
|
|
14137
14084
|
if (path === "/auth/2fa/enable") {
|
|
14138
|
-
const body = await
|
|
14085
|
+
const body = await parseBody7(request);
|
|
14139
14086
|
const userId = typeof body.userId === "string" ? body.userId : null;
|
|
14140
14087
|
const code = typeof body.code === "string" ? body.code : null;
|
|
14141
|
-
if (!userId || !code) return
|
|
14088
|
+
if (!userId || !code) return jsonResponse14({ error: "userId and code required" }, 400);
|
|
14142
14089
|
const result = await enable(userId, code);
|
|
14143
|
-
return
|
|
14090
|
+
return jsonResponse14(result);
|
|
14144
14091
|
}
|
|
14145
14092
|
if (path === "/auth/2fa/verify") {
|
|
14146
|
-
const body = await
|
|
14093
|
+
const body = await parseBody7(request);
|
|
14147
14094
|
const userId = typeof body.userId === "string" ? body.userId : null;
|
|
14148
14095
|
const code = typeof body.code === "string" ? body.code : null;
|
|
14149
|
-
if (!userId || !code) return
|
|
14096
|
+
if (!userId || !code) return jsonResponse14({ error: "userId and code required" }, 400);
|
|
14150
14097
|
const result = await verify(userId, code);
|
|
14151
|
-
return
|
|
14098
|
+
return jsonResponse14(result);
|
|
14152
14099
|
}
|
|
14153
14100
|
if (path === "/auth/2fa/disable") {
|
|
14154
|
-
const body = await
|
|
14101
|
+
const body = await parseBody7(request);
|
|
14155
14102
|
const userId = typeof body.userId === "string" ? body.userId : null;
|
|
14156
14103
|
const code = typeof body.code === "string" ? body.code : null;
|
|
14157
|
-
if (!userId || !code) return
|
|
14104
|
+
if (!userId || !code) return jsonResponse14({ error: "userId and code required" }, 400);
|
|
14158
14105
|
const result = await disable(userId, code);
|
|
14159
|
-
return
|
|
14106
|
+
return jsonResponse14(result);
|
|
14160
14107
|
}
|
|
14161
14108
|
if (path === "/auth/2fa/backup-codes") {
|
|
14162
|
-
const body = await
|
|
14109
|
+
const body = await parseBody7(request);
|
|
14163
14110
|
const userId = typeof body.userId === "string" ? body.userId : null;
|
|
14164
14111
|
const code = typeof body.code === "string" ? body.code : null;
|
|
14165
|
-
if (!userId || !code) return
|
|
14112
|
+
if (!userId || !code) return jsonResponse14({ error: "userId and code required" }, 400);
|
|
14166
14113
|
try {
|
|
14167
14114
|
const result = await regenerateBackupCodes(userId, code);
|
|
14168
|
-
return
|
|
14115
|
+
return jsonResponse14(result);
|
|
14169
14116
|
} catch (err2) {
|
|
14170
|
-
return
|
|
14117
|
+
return jsonResponse14(
|
|
14171
14118
|
{ error: err2 instanceof Error ? err2.message : "Failed to regenerate codes" },
|
|
14172
14119
|
400
|
|
14173
14120
|
);
|
|
@@ -14187,13 +14134,13 @@ function createTotpModule(config, db) {
|
|
|
14187
14134
|
}
|
|
14188
14135
|
|
|
14189
14136
|
// src/auth/totp-plugin.ts
|
|
14190
|
-
function
|
|
14137
|
+
function jsonResponse15(body, status = 200) {
|
|
14191
14138
|
return new Response(JSON.stringify(body), {
|
|
14192
14139
|
status,
|
|
14193
14140
|
headers: { "Content-Type": "application/json" }
|
|
14194
14141
|
});
|
|
14195
14142
|
}
|
|
14196
|
-
async function
|
|
14143
|
+
async function parseBody8(request) {
|
|
14197
14144
|
try {
|
|
14198
14145
|
return await request.json();
|
|
14199
14146
|
} catch {
|
|
@@ -14215,13 +14162,13 @@ function twoFactor(config) {
|
|
|
14215
14162
|
async handler(request, endpointCtx) {
|
|
14216
14163
|
const user = await endpointCtx.getUser(request);
|
|
14217
14164
|
if (!user) {
|
|
14218
|
-
return
|
|
14165
|
+
return jsonResponse15({ error: "Authentication required" }, 401);
|
|
14219
14166
|
}
|
|
14220
14167
|
try {
|
|
14221
14168
|
const setup = await module.setup(user.id);
|
|
14222
|
-
return
|
|
14169
|
+
return jsonResponse15(setup);
|
|
14223
14170
|
} catch (err2) {
|
|
14224
|
-
return
|
|
14171
|
+
return jsonResponse15(
|
|
14225
14172
|
{ error: err2 instanceof Error ? err2.message : "Enrollment failed" },
|
|
14226
14173
|
500
|
|
14227
14174
|
);
|
|
@@ -14238,23 +14185,23 @@ function twoFactor(config) {
|
|
|
14238
14185
|
async handler(request, endpointCtx) {
|
|
14239
14186
|
const user = await endpointCtx.getUser(request);
|
|
14240
14187
|
if (!user) {
|
|
14241
|
-
return
|
|
14188
|
+
return jsonResponse15({ error: "Authentication required" }, 401);
|
|
14242
14189
|
}
|
|
14243
|
-
const body = await
|
|
14190
|
+
const body = await parseBody8(request);
|
|
14244
14191
|
const code = typeof body.code === "string" ? body.code : null;
|
|
14245
14192
|
if (!code) {
|
|
14246
|
-
return
|
|
14193
|
+
return jsonResponse15({ error: "Missing required field: code" }, 400);
|
|
14247
14194
|
}
|
|
14248
14195
|
const enabled = await module.isEnabled(user.id);
|
|
14249
14196
|
if (!enabled) {
|
|
14250
14197
|
const result2 = await module.enable(user.id, code);
|
|
14251
14198
|
if (!result2.enabled) {
|
|
14252
|
-
return
|
|
14199
|
+
return jsonResponse15({ error: "Invalid TOTP code" }, 400);
|
|
14253
14200
|
}
|
|
14254
|
-
return
|
|
14201
|
+
return jsonResponse15({ valid: true, activated: true });
|
|
14255
14202
|
}
|
|
14256
14203
|
const result = await module.verify(user.id, code);
|
|
14257
|
-
return
|
|
14204
|
+
return jsonResponse15(result);
|
|
14258
14205
|
}
|
|
14259
14206
|
});
|
|
14260
14207
|
ctx.addEndpoint({
|
|
@@ -14267,18 +14214,18 @@ function twoFactor(config) {
|
|
|
14267
14214
|
async handler(request, endpointCtx) {
|
|
14268
14215
|
const user = await endpointCtx.getUser(request);
|
|
14269
14216
|
if (!user) {
|
|
14270
|
-
return
|
|
14217
|
+
return jsonResponse15({ error: "Authentication required" }, 401);
|
|
14271
14218
|
}
|
|
14272
|
-
const body = await
|
|
14219
|
+
const body = await parseBody8(request);
|
|
14273
14220
|
const code = typeof body.code === "string" ? body.code : null;
|
|
14274
14221
|
if (!code) {
|
|
14275
|
-
return
|
|
14222
|
+
return jsonResponse15({ error: "Missing required field: code" }, 400);
|
|
14276
14223
|
}
|
|
14277
14224
|
const result = await module.disable(user.id, code);
|
|
14278
14225
|
if (!result.disabled) {
|
|
14279
|
-
return
|
|
14226
|
+
return jsonResponse15({ error: "Invalid TOTP code" }, 400);
|
|
14280
14227
|
}
|
|
14281
|
-
return
|
|
14228
|
+
return jsonResponse15(result);
|
|
14282
14229
|
}
|
|
14283
14230
|
});
|
|
14284
14231
|
ctx.addEndpoint({
|
|
@@ -14291,10 +14238,10 @@ function twoFactor(config) {
|
|
|
14291
14238
|
async handler(request, endpointCtx) {
|
|
14292
14239
|
const user = await endpointCtx.getUser(request);
|
|
14293
14240
|
if (!user) {
|
|
14294
|
-
return
|
|
14241
|
+
return jsonResponse15({ error: "Authentication required" }, 401);
|
|
14295
14242
|
}
|
|
14296
14243
|
const enabled = await module.isEnabled(user.id);
|
|
14297
|
-
return
|
|
14244
|
+
return jsonResponse15({ enabled });
|
|
14298
14245
|
}
|
|
14299
14246
|
});
|
|
14300
14247
|
ctx.addEndpoint({
|
|
@@ -14307,18 +14254,18 @@ function twoFactor(config) {
|
|
|
14307
14254
|
async handler(request, endpointCtx) {
|
|
14308
14255
|
const user = await endpointCtx.getUser(request);
|
|
14309
14256
|
if (!user) {
|
|
14310
|
-
return
|
|
14257
|
+
return jsonResponse15({ error: "Authentication required" }, 401);
|
|
14311
14258
|
}
|
|
14312
|
-
const body = await
|
|
14259
|
+
const body = await parseBody8(request);
|
|
14313
14260
|
const code = typeof body.code === "string" ? body.code : null;
|
|
14314
14261
|
if (!code) {
|
|
14315
|
-
return
|
|
14262
|
+
return jsonResponse15({ error: "Missing required field: code" }, 400);
|
|
14316
14263
|
}
|
|
14317
14264
|
try {
|
|
14318
14265
|
const result = await module.regenerateBackupCodes(user.id, code);
|
|
14319
|
-
return
|
|
14266
|
+
return jsonResponse15(result);
|
|
14320
14267
|
} catch (err2) {
|
|
14321
|
-
return
|
|
14268
|
+
return jsonResponse15(
|
|
14322
14269
|
{ error: err2 instanceof Error ? err2.message : "Failed to regenerate backup codes" },
|
|
14323
14270
|
400
|
|
14324
14271
|
);
|
|
@@ -14437,7 +14384,7 @@ async function hashPassword(password) {
|
|
|
14437
14384
|
async function verifyPassword(stored, candidate) {
|
|
14438
14385
|
return pbkdf2Verify(candidate, stored);
|
|
14439
14386
|
}
|
|
14440
|
-
function
|
|
14387
|
+
function jsonResponse16(body, status = 200) {
|
|
14441
14388
|
return new Response(JSON.stringify(body), {
|
|
14442
14389
|
status,
|
|
14443
14390
|
headers: { "Content-Type": "application/json" }
|
|
@@ -14561,12 +14508,12 @@ function createUsernameAuthModule(config, db, sessionManager) {
|
|
|
14561
14508
|
try {
|
|
14562
14509
|
body = await request.json();
|
|
14563
14510
|
} catch {
|
|
14564
|
-
return
|
|
14511
|
+
return jsonResponse16({ error: "Invalid JSON body" }, 400);
|
|
14565
14512
|
}
|
|
14566
14513
|
const b = body;
|
|
14567
14514
|
if (pathname === "/auth/username/sign-up") {
|
|
14568
14515
|
if (typeof b.username !== "string" || typeof b.password !== "string") {
|
|
14569
|
-
return
|
|
14516
|
+
return jsonResponse16({ error: "Missing required fields: username, password" }, 400);
|
|
14570
14517
|
}
|
|
14571
14518
|
try {
|
|
14572
14519
|
const result = await signUp({
|
|
@@ -14574,21 +14521,21 @@ function createUsernameAuthModule(config, db, sessionManager) {
|
|
|
14574
14521
|
password: b.password,
|
|
14575
14522
|
name: typeof b.name === "string" ? b.name : void 0
|
|
14576
14523
|
});
|
|
14577
|
-
return
|
|
14524
|
+
return jsonResponse16(result, 201);
|
|
14578
14525
|
} catch (err2) {
|
|
14579
|
-
return
|
|
14526
|
+
return jsonResponse16({ error: err2 instanceof Error ? err2.message : "Sign-up failed" }, 400);
|
|
14580
14527
|
}
|
|
14581
14528
|
}
|
|
14582
14529
|
if (pathname === "/auth/username/sign-in") {
|
|
14583
14530
|
if (typeof b.username !== "string" || typeof b.password !== "string") {
|
|
14584
|
-
return
|
|
14531
|
+
return jsonResponse16({ error: "Missing required fields: username, password" }, 400);
|
|
14585
14532
|
}
|
|
14586
14533
|
try {
|
|
14587
14534
|
const result = await signIn({ username: b.username, password: b.password });
|
|
14588
|
-
return
|
|
14535
|
+
return jsonResponse16(result);
|
|
14589
14536
|
} catch (err2) {
|
|
14590
14537
|
if (err2 instanceof Error && err2.message === "Password reset required") {
|
|
14591
|
-
return
|
|
14538
|
+
return jsonResponse16(
|
|
14592
14539
|
{
|
|
14593
14540
|
error: {
|
|
14594
14541
|
code: "PASSWORD_RESET_REQUIRED",
|
|
@@ -14598,21 +14545,21 @@ function createUsernameAuthModule(config, db, sessionManager) {
|
|
|
14598
14545
|
403
|
|
14599
14546
|
);
|
|
14600
14547
|
}
|
|
14601
|
-
return
|
|
14548
|
+
return jsonResponse16({ error: "Invalid username or password" }, 401);
|
|
14602
14549
|
}
|
|
14603
14550
|
}
|
|
14604
14551
|
if (pathname === "/auth/username/change-password") {
|
|
14605
14552
|
if (typeof b.userId !== "string" || typeof b.current !== "string" || typeof b.newPassword !== "string") {
|
|
14606
|
-
return
|
|
14553
|
+
return jsonResponse16(
|
|
14607
14554
|
{ error: "Missing required fields: userId, current, newPassword" },
|
|
14608
14555
|
400
|
|
14609
14556
|
);
|
|
14610
14557
|
}
|
|
14611
14558
|
try {
|
|
14612
14559
|
const result = await changePassword(b.userId, b.current, b.newPassword);
|
|
14613
|
-
return
|
|
14560
|
+
return jsonResponse16(result);
|
|
14614
14561
|
} catch (err2) {
|
|
14615
|
-
return
|
|
14562
|
+
return jsonResponse16(
|
|
14616
14563
|
{ error: err2 instanceof Error ? err2.message : "Change password failed" },
|
|
14617
14564
|
400
|
|
14618
14565
|
);
|
|
@@ -14620,13 +14567,13 @@ function createUsernameAuthModule(config, db, sessionManager) {
|
|
|
14620
14567
|
}
|
|
14621
14568
|
if (pathname === "/auth/username/change-username") {
|
|
14622
14569
|
if (typeof b.userId !== "string" || typeof b.newUsername !== "string") {
|
|
14623
|
-
return
|
|
14570
|
+
return jsonResponse16({ error: "Missing required fields: userId, newUsername" }, 400);
|
|
14624
14571
|
}
|
|
14625
14572
|
try {
|
|
14626
14573
|
const result = await changeUsername(b.userId, b.newUsername);
|
|
14627
|
-
return
|
|
14574
|
+
return jsonResponse16(result);
|
|
14628
14575
|
} catch (err2) {
|
|
14629
|
-
return
|
|
14576
|
+
return jsonResponse16(
|
|
14630
14577
|
{ error: err2 instanceof Error ? err2.message : "Change username failed" },
|
|
14631
14578
|
400
|
|
14632
14579
|
);
|