@codixus/server 0.1.3 → 0.1.5

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/index.js CHANGED
@@ -137,7 +137,7 @@ var BanService = class {
137
137
  };
138
138
 
139
139
  // src/auth/guard.ts
140
- function createGuard(jwt) {
140
+ function createGuard(jwt, banService) {
141
141
  return function guard(options = {}) {
142
142
  return async (req, res, next) => {
143
143
  const authHeader = req.headers.authorization;
@@ -152,9 +152,14 @@ function createGuard(jwt) {
152
152
  const token = authHeader.substring(7);
153
153
  try {
154
154
  const payload = await jwt.verify(token);
155
+ const deviceId = payload.deviceId;
156
+ if (banService && await banService.isBanned(deviceId)) {
157
+ res.status(403).json({ success: false, error: "DEVICE_BANNED" });
158
+ return;
159
+ }
155
160
  req.user = {
156
161
  uid: payload.sub,
157
- deviceId: payload.deviceId
162
+ deviceId
158
163
  };
159
164
  next();
160
165
  } catch (error) {
@@ -755,12 +760,12 @@ function createRateLimit(config = {}) {
755
760
  }
756
761
  entry.count++;
757
762
  const remaining = Math.max(0, maxRequests - entry.count);
758
- res.setHeader("X-RateLimit-Limit", maxRequests);
759
- res.setHeader("X-RateLimit-Remaining", remaining);
760
- res.setHeader("X-RateLimit-Reset", Math.ceil(entry.resetAt / 1e3));
763
+ res.setHeader("X-RateLimit-Limit", String(maxRequests));
764
+ res.setHeader("X-RateLimit-Remaining", String(remaining));
765
+ res.setHeader("X-RateLimit-Reset", String(Math.ceil(entry.resetAt / 1e3)));
761
766
  if (entry.count > maxRequests) {
762
767
  const retryAfter = Math.ceil((entry.resetAt - now) / 1e3);
763
- res.setHeader("Retry-After", retryAfter);
768
+ res.setHeader("Retry-After", String(retryAfter));
764
769
  res.status(429).json({
765
770
  success: false,
766
771
  error: message,
@@ -780,6 +785,31 @@ function defaultKeyGenerator(req) {
780
785
  // src/db/rest.ts
781
786
  import { Router as Router2 } from "express";
782
787
  import crypto4 from "crypto";
788
+ var RestErrorCode = {
789
+ CreateDisabled: "CREATE_DISABLED",
790
+ UpdateDisabled: "UPDATE_DISABLED",
791
+ DeleteDisabled: "DELETE_DISABLED",
792
+ ReadForbidden: "READ_FORBIDDEN",
793
+ WriteForbidden: "WRITE_FORBIDDEN",
794
+ DeleteForbidden: "DELETE_FORBIDDEN",
795
+ NotFound: "NOT_FOUND",
796
+ InvalidFilter: "INVALID_FILTER",
797
+ InvalidSort: "INVALID_SORT",
798
+ InvalidCursor: "INVALID_CURSOR",
799
+ InvalidUpdate: "INVALID_UPDATE",
800
+ InvalidBody: "INVALID_BODY",
801
+ AuthRequired: "AUTH_REQUIRED"
802
+ };
803
+ var DEFAULT_ALLOWED_OPS = /* @__PURE__ */ new Set([
804
+ "$eq",
805
+ "$ne",
806
+ "$in",
807
+ "$nin",
808
+ "$gt",
809
+ "$gte",
810
+ "$lt",
811
+ "$lte"
812
+ ]);
783
813
  var SAFE_UPDATE_OPS = /* @__PURE__ */ new Set([
784
814
  "$set",
785
815
  "$unset",
@@ -794,41 +824,199 @@ var SAFE_UPDATE_OPS = /* @__PURE__ */ new Set([
794
824
  "$currentDate",
795
825
  "$setOnInsert"
796
826
  ]);
797
- var BLOCKED_OPS = /* @__PURE__ */ new Set([
798
- "$where",
799
- "$function",
800
- "$accumulator",
801
- "$expr",
802
- "$jsonSchema",
803
- "$comment"
804
- ]);
805
- function sanitizeObject(obj, depth = 0) {
806
- if (depth > 10) throw new Error("Object too deeply nested");
807
- if (obj === null || obj === void 0) return {};
808
- if (typeof obj !== "object" || Array.isArray(obj)) throw new Error("Expected object");
827
+ var RestError = class extends Error {
828
+ constructor(code, message) {
829
+ super(message);
830
+ this.code = code;
831
+ }
832
+ };
833
+ function param(req, name) {
834
+ const val = req.params[name];
835
+ return Array.isArray(val) ? val[0] : val ?? "";
836
+ }
837
+ function fail(res, status, code, error) {
838
+ res.status(status).json({ success: false, error, code });
839
+ }
840
+ function handleCaught(res, err) {
841
+ if (err instanceof RestError) {
842
+ const status = err.code === RestErrorCode.AuthRequired ? 401 : 400;
843
+ fail(res, status, err.code, err.message);
844
+ return;
845
+ }
846
+ const message = err instanceof Error ? err.message : String(err);
847
+ fail(res, 400, RestErrorCode.InvalidBody, message);
848
+ }
849
+ function buildFilterValidator(queryableFields, allowedOps) {
850
+ return function sanitizeFilter(obj, depth = 0) {
851
+ if (depth > 10)
852
+ throw new RestError(RestErrorCode.InvalidFilter, "Filter too deeply nested");
853
+ if (obj === null || obj === void 0) return {};
854
+ if (typeof obj !== "object" || Array.isArray(obj))
855
+ throw new RestError(RestErrorCode.InvalidFilter, "Filter must be an object");
856
+ const result = {};
857
+ for (const [key, value] of Object.entries(obj)) {
858
+ if (key.startsWith("$")) {
859
+ if (!allowedOps.has(key)) {
860
+ throw new RestError(
861
+ RestErrorCode.InvalidFilter,
862
+ `Operator "${key}" is not allowed`
863
+ );
864
+ }
865
+ result[key] = sanitizeFilterValue(value, allowedOps, depth + 1);
866
+ } else {
867
+ if (queryableFields && !queryableFields.has(key)) {
868
+ throw new RestError(
869
+ RestErrorCode.InvalidFilter,
870
+ `Field "${key}" is not queryable`
871
+ );
872
+ }
873
+ result[key] = sanitizeFilterValue(value, allowedOps, depth + 1);
874
+ }
875
+ }
876
+ return result;
877
+ };
878
+ }
879
+ function sanitizeFilterValue(value, allowedOps, depth) {
880
+ if (depth > 10)
881
+ throw new RestError(RestErrorCode.InvalidFilter, "Filter too deeply nested");
882
+ if (value === null) return null;
883
+ if (Array.isArray(value)) {
884
+ return value.map((v) => sanitizeFilterValue(v, allowedOps, depth + 1));
885
+ }
886
+ if (value instanceof Date) return value;
887
+ if (typeof value !== "object") return value;
809
888
  const result = {};
810
- for (const [key, value] of Object.entries(obj)) {
811
- if (BLOCKED_OPS.has(key)) throw new Error(`Operator "${key}" is not allowed`);
812
- if (value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date)) {
813
- result[key] = sanitizeObject(value, depth + 1);
814
- } else {
815
- result[key] = value;
889
+ for (const [k, v] of Object.entries(value)) {
890
+ if (k.startsWith("$") && !allowedOps.has(k)) {
891
+ throw new RestError(
892
+ RestErrorCode.InvalidFilter,
893
+ `Operator "${k}" is not allowed`
894
+ );
816
895
  }
896
+ result[k] = sanitizeFilterValue(v, allowedOps, depth + 1);
817
897
  }
818
898
  return result;
819
899
  }
820
- function sanitizeUpdate(obj) {
821
- if (!obj || typeof obj !== "object") throw new Error("Update must be an object");
822
- for (const key of Object.keys(obj)) {
823
- if (key.startsWith("$") && !SAFE_UPDATE_OPS.has(key)) {
824
- throw new Error(`Update operator "${key}" is not allowed`);
900
+ function assertNoNestedOperators(value, path, depth = 0) {
901
+ if (depth > 10) return;
902
+ if (value === null || typeof value !== "object") return;
903
+ if (value instanceof Date) return;
904
+ if (Array.isArray(value)) {
905
+ for (const v of value) assertNoNestedOperators(v, path, depth + 1);
906
+ return;
907
+ }
908
+ for (const [k, v] of Object.entries(value)) {
909
+ if (k.startsWith("$")) {
910
+ throw new RestError(
911
+ RestErrorCode.InvalidUpdate,
912
+ `Nested operator "${k}" is not allowed inside update value for "${path}"`
913
+ );
825
914
  }
915
+ assertNoNestedOperators(v, path, depth + 1);
826
916
  }
827
- return sanitizeObject(obj);
828
917
  }
829
- function param(req, name) {
830
- const val = req.params[name];
831
- return Array.isArray(val) ? val[0] : val ?? "";
918
+ function sanitizeUpdate(obj, writableFields) {
919
+ if (!obj || typeof obj !== "object" || Array.isArray(obj))
920
+ throw new RestError(RestErrorCode.InvalidUpdate, "Update must be an object");
921
+ const input = obj;
922
+ const hasOperators = Object.keys(input).some((k) => k.startsWith("$"));
923
+ const wrapped = hasOperators ? input : { $set: input };
924
+ const result = {};
925
+ for (const [op, fields] of Object.entries(wrapped)) {
926
+ if (!op.startsWith("$")) {
927
+ throw new RestError(
928
+ RestErrorCode.InvalidUpdate,
929
+ "Update must use $-prefixed operators"
930
+ );
931
+ }
932
+ if (!SAFE_UPDATE_OPS.has(op)) {
933
+ throw new RestError(
934
+ RestErrorCode.InvalidUpdate,
935
+ `Update operator "${op}" is not allowed`
936
+ );
937
+ }
938
+ if (!fields || typeof fields !== "object" || Array.isArray(fields)) {
939
+ throw new RestError(
940
+ RestErrorCode.InvalidUpdate,
941
+ `Operator "${op}" requires an object`
942
+ );
943
+ }
944
+ const validatedFields = {};
945
+ for (const [field, value] of Object.entries(
946
+ fields
947
+ )) {
948
+ if (field.startsWith("$")) {
949
+ throw new RestError(
950
+ RestErrorCode.InvalidUpdate,
951
+ `Nested operator "${field}" is not allowed`
952
+ );
953
+ }
954
+ if (writableFields) {
955
+ if (!writableFields.has(field)) {
956
+ throw new RestError(
957
+ RestErrorCode.InvalidUpdate,
958
+ `Field "${field}" is not writable`
959
+ );
960
+ }
961
+ }
962
+ if (!writableFields && field.includes(".")) {
963
+ throw new RestError(
964
+ RestErrorCode.InvalidUpdate,
965
+ `Dotted-path writes require explicit writableFields`
966
+ );
967
+ }
968
+ assertNoNestedOperators(value, field);
969
+ validatedFields[field] = value;
970
+ }
971
+ result[op] = validatedFields;
972
+ }
973
+ return result;
974
+ }
975
+ function projectDoc(doc, publicFields) {
976
+ if (!doc || !publicFields) return doc;
977
+ const out = {};
978
+ for (const f of publicFields) {
979
+ if (f in doc) out[f] = doc[f];
980
+ }
981
+ if ("_id" in doc) out._id = doc._id;
982
+ return out;
983
+ }
984
+ function encodeCursor(key, payload) {
985
+ const iv = crypto4.randomBytes(12);
986
+ const cipher = crypto4.createCipheriv("aes-256-gcm", key, iv);
987
+ const plaintext = Buffer.from(JSON.stringify(payload), "utf8");
988
+ const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
989
+ const tag = cipher.getAuthTag();
990
+ return [
991
+ iv.toString("base64url"),
992
+ tag.toString("base64url"),
993
+ ciphertext.toString("base64url")
994
+ ].join(".");
995
+ }
996
+ function decodeCursor(key, token) {
997
+ const parts = token.split(".");
998
+ if (parts.length !== 3)
999
+ throw new RestError(RestErrorCode.InvalidCursor, "Invalid cursor");
1000
+ const [ivB64, tagB64, ctB64] = parts;
1001
+ let iv, tag, ct;
1002
+ try {
1003
+ iv = Buffer.from(ivB64, "base64url");
1004
+ tag = Buffer.from(tagB64, "base64url");
1005
+ ct = Buffer.from(ctB64, "base64url");
1006
+ } catch {
1007
+ throw new RestError(RestErrorCode.InvalidCursor, "Invalid cursor encoding");
1008
+ }
1009
+ if (iv.length !== 12 || tag.length !== 16) {
1010
+ throw new RestError(RestErrorCode.InvalidCursor, "Invalid cursor");
1011
+ }
1012
+ try {
1013
+ const decipher = crypto4.createDecipheriv("aes-256-gcm", key, iv);
1014
+ decipher.setAuthTag(tag);
1015
+ const plaintext = Buffer.concat([decipher.update(ct), decipher.final()]);
1016
+ return JSON.parse(plaintext.toString("utf8"));
1017
+ } catch {
1018
+ throw new RestError(RestErrorCode.InvalidCursor, "Invalid cursor signature");
1019
+ }
832
1020
  }
833
1021
  function createRestRouter(model2, options = {}) {
834
1022
  const {
@@ -837,8 +1025,64 @@ function createRestRouter(model2, options = {}) {
837
1025
  allowDelete = true,
838
1026
  maxLimit = 100,
839
1027
  defaultLimit = 20,
840
- defaultSort
1028
+ defaultSort,
1029
+ ownershipField,
1030
+ queryableFields,
1031
+ sortableFields,
1032
+ writableFields,
1033
+ publicFields,
1034
+ allowedOperators,
1035
+ allowRead,
1036
+ allowWrite
841
1037
  } = options;
1038
+ const queryableSet = queryableFields ? new Set(queryableFields) : null;
1039
+ const sortableSet = sortableFields ? new Set(sortableFields) : null;
1040
+ const writableSet = writableFields ? new Set(writableFields) : null;
1041
+ const publicSet = publicFields ? new Set(publicFields) : null;
1042
+ const opsSet = allowedOperators ? new Set(allowedOperators) : DEFAULT_ALLOWED_OPS;
1043
+ const sanitizeFilter = buildFilterValidator(queryableSet, opsSet);
1044
+ const cursorKey = crypto4.randomBytes(32);
1045
+ function scopedFilter(req, filter) {
1046
+ if (!ownershipField) return filter;
1047
+ const owner = req.user?.deviceId;
1048
+ if (!owner)
1049
+ throw new RestError(
1050
+ RestErrorCode.AuthRequired,
1051
+ "Authentication required"
1052
+ );
1053
+ if (ownershipField in filter) {
1054
+ throw new RestError(
1055
+ RestErrorCode.InvalidFilter,
1056
+ `Field "${ownershipField}" cannot be used in filter (auto-scoped to caller)`
1057
+ );
1058
+ }
1059
+ return { ...filter, [ownershipField]: owner };
1060
+ }
1061
+ function validateSort(sort) {
1062
+ if (!sort) return void 0;
1063
+ if (typeof sort !== "object" || Array.isArray(sort))
1064
+ throw new RestError(RestErrorCode.InvalidSort, "Sort must be an object");
1065
+ const out = {};
1066
+ for (const [field, direction] of Object.entries(sort)) {
1067
+ if (field.startsWith("$"))
1068
+ throw new RestError(
1069
+ RestErrorCode.InvalidSort,
1070
+ `Invalid sort field "${field}"`
1071
+ );
1072
+ if (sortableSet && !sortableSet.has(field))
1073
+ throw new RestError(
1074
+ RestErrorCode.InvalidSort,
1075
+ `Field "${field}" is not sortable`
1076
+ );
1077
+ if (direction !== 1 && direction !== -1)
1078
+ throw new RestError(
1079
+ RestErrorCode.InvalidSort,
1080
+ `Sort direction for "${field}" must be 1 or -1`
1081
+ );
1082
+ out[field] = direction;
1083
+ }
1084
+ return out;
1085
+ }
842
1086
  const router = Router2();
843
1087
  router.get("/", async (req, res) => {
844
1088
  try {
@@ -846,107 +1090,172 @@ function createRestRouter(model2, options = {}) {
846
1090
  const sortStr = req.query.sort;
847
1091
  const limitStr = req.query.limit;
848
1092
  const skipStr = req.query.skip;
849
- const cursor = req.query.cursor;
850
- let filter = {};
851
- if (filterStr) {
852
- filter = sanitizeObject(JSON.parse(filterStr));
853
- }
854
- const sort = sortStr ? JSON.parse(sortStr) : defaultSort;
1093
+ const cursorToken = req.query.cursor;
1094
+ const rawFilter = filterStr ? JSON.parse(filterStr) : {};
1095
+ let filter = sanitizeFilter(rawFilter);
1096
+ filter = scopedFilter(req, filter);
1097
+ const rawSort = sortStr ? JSON.parse(sortStr) : defaultSort;
1098
+ const sort = validateSort(rawSort);
855
1099
  const limit = Math.min(Number(limitStr) || defaultLimit, maxLimit);
856
1100
  const skip = Number(skipStr) || 0;
857
1101
  let query = model2.find(filter);
858
1102
  if (sort) query = query.sort(sort);
859
- if (cursor && sort) {
860
- const sortField = Object.keys(sort)[0];
861
- const cursorValue = Number.isNaN(Number(cursor)) ? cursor : Number(cursor);
862
- query = query.startAfter({ [sortField]: cursorValue });
1103
+ if (cursorToken && sort) {
1104
+ const decoded = decodeCursor(cursorKey, cursorToken);
1105
+ query = query.startAfter(decoded);
863
1106
  }
864
1107
  if (skip) query = query.skip(skip);
865
1108
  query = query.limit(limit);
866
1109
  const docs = await query.exec();
867
1110
  let nextCursor = null;
868
1111
  if (docs.length === limit && sort) {
869
- const sortField = Object.keys(sort)[0];
870
- nextCursor = docs[docs.length - 1]?.[sortField] ?? null;
1112
+ const last = docs[docs.length - 1];
1113
+ const cursorPayload = {};
1114
+ for (const field of Object.keys(sort)) {
1115
+ cursorPayload[field] = last[field];
1116
+ }
1117
+ nextCursor = encodeCursor(cursorKey, cursorPayload);
871
1118
  }
872
- res.json({ success: true, data: docs, nextCursor });
1119
+ const projected = publicSet ? docs.map((d) => projectDoc(d, publicSet)) : docs;
1120
+ res.json({ success: true, data: projected, nextCursor });
873
1121
  } catch (error) {
874
- res.status(400).json({ success: false, error: error.message });
1122
+ handleCaught(res, error);
875
1123
  }
876
1124
  });
877
1125
  router.get("/:id", async (req, res) => {
878
- const id = param(req, "id");
879
- const doc = await model2.findById(id);
880
- if (!doc) {
881
- res.status(404).json({ success: false, error: "Not found" });
882
- return;
1126
+ try {
1127
+ const id = param(req, "id");
1128
+ const doc = await model2.findById(id);
1129
+ if (!doc) {
1130
+ fail(res, 404, RestErrorCode.NotFound, "Not found");
1131
+ return;
1132
+ }
1133
+ if (ownershipField) {
1134
+ const owner = req.user?.deviceId;
1135
+ if (!owner || doc[ownershipField] !== owner) {
1136
+ fail(res, 404, RestErrorCode.NotFound, "Not found");
1137
+ return;
1138
+ }
1139
+ }
1140
+ if (allowRead && !await allowRead(req, doc)) {
1141
+ fail(res, 403, RestErrorCode.ReadForbidden, "Read not allowed");
1142
+ return;
1143
+ }
1144
+ res.json({ success: true, data: projectDoc(doc, publicSet) });
1145
+ } catch (error) {
1146
+ handleCaught(res, error);
883
1147
  }
884
- res.json({ success: true, data: doc });
885
1148
  });
886
1149
  router.post("/", async (req, res) => {
887
1150
  if (!allowCreate) {
888
- res.status(403).json({ success: false, error: "Create not allowed" });
1151
+ fail(res, 403, RestErrorCode.CreateDisabled, "Create not allowed");
889
1152
  return;
890
1153
  }
891
1154
  try {
892
- const data = req.body;
1155
+ const data = { ...req.body };
1156
+ if (ownershipField) {
1157
+ const owner = req.user?.deviceId;
1158
+ if (!owner) {
1159
+ fail(res, 401, RestErrorCode.AuthRequired, "Authentication required");
1160
+ return;
1161
+ }
1162
+ data[ownershipField] = owner;
1163
+ }
893
1164
  if (!data._id) data._id = crypto4.randomUUID();
894
1165
  const doc = await model2.create(data);
895
- res.status(201).json({ success: true, data: doc });
1166
+ res.status(201).json({ success: true, data: projectDoc(doc, publicSet) });
896
1167
  } catch (error) {
897
- res.status(400).json({ success: false, error: error.message });
1168
+ handleCaught(res, error);
898
1169
  }
899
1170
  });
900
1171
  router.post("/count", async (req, res) => {
901
1172
  try {
902
- const filter = req.body.filter ? sanitizeObject(req.body.filter) : {};
1173
+ const rawFilter = req.body.filter ?? {};
1174
+ let filter = sanitizeFilter(rawFilter);
1175
+ filter = scopedFilter(req, filter);
903
1176
  const count = await model2.count(filter);
904
1177
  res.json({ success: true, count });
905
1178
  } catch (error) {
906
- res.status(400).json({ success: false, error: error.message });
1179
+ handleCaught(res, error);
907
1180
  }
908
1181
  });
909
1182
  router.patch("/:id", async (req, res) => {
910
1183
  if (!allowUpdate) {
911
- res.status(403).json({ success: false, error: "Update not allowed" });
1184
+ fail(res, 403, RestErrorCode.UpdateDisabled, "Update not allowed");
912
1185
  return;
913
1186
  }
914
1187
  const id = param(req, "id");
915
1188
  try {
916
- const update = sanitizeUpdate(req.body.update ?? req.body);
1189
+ const update = sanitizeUpdate(req.body.update ?? req.body, writableSet);
1190
+ if (ownershipField || allowWrite) {
1191
+ const existing = await model2.findById(id);
1192
+ if (!existing) {
1193
+ fail(res, 404, RestErrorCode.NotFound, "Not found");
1194
+ return;
1195
+ }
1196
+ if (ownershipField) {
1197
+ const owner = req.user?.deviceId;
1198
+ if (!owner || existing[ownershipField] !== owner) {
1199
+ fail(res, 404, RestErrorCode.NotFound, "Not found");
1200
+ return;
1201
+ }
1202
+ }
1203
+ if (allowWrite && !await allowWrite(req, existing, update)) {
1204
+ fail(res, 403, RestErrorCode.WriteForbidden, "Write not allowed");
1205
+ return;
1206
+ }
1207
+ }
917
1208
  const success = await model2.updateById(id, update);
918
1209
  if (!success) {
919
- res.status(404).json({ success: false, error: "Not found" });
1210
+ fail(res, 404, RestErrorCode.NotFound, "Not found");
920
1211
  return;
921
1212
  }
922
1213
  const doc = await model2.findById(id);
923
- res.json({ success: true, data: doc });
1214
+ res.json({ success: true, data: projectDoc(doc, publicSet) });
924
1215
  } catch (error) {
925
- res.status(400).json({ success: false, error: error.message });
1216
+ handleCaught(res, error);
926
1217
  }
927
1218
  });
928
1219
  router.delete("/:id", async (req, res) => {
1220
+ if (allowDelete === false) {
1221
+ fail(res, 403, RestErrorCode.DeleteDisabled, "Delete not allowed");
1222
+ return;
1223
+ }
929
1224
  const id = param(req, "id");
930
- if (typeof allowDelete === "function") {
931
- const doc = await model2.findById(id);
932
- if (!doc) {
933
- res.status(404).json({ success: false, error: "Not found" });
934
- return;
1225
+ try {
1226
+ const needsDoc = !!ownershipField || typeof allowDelete === "function";
1227
+ if (needsDoc) {
1228
+ const doc = await model2.findById(id);
1229
+ if (!doc) {
1230
+ fail(res, 404, RestErrorCode.NotFound, "Not found");
1231
+ return;
1232
+ }
1233
+ if (ownershipField) {
1234
+ const owner = req.user?.deviceId;
1235
+ if (!owner || doc[ownershipField] !== owner) {
1236
+ fail(res, 404, RestErrorCode.NotFound, "Not found");
1237
+ return;
1238
+ }
1239
+ }
1240
+ if (typeof allowDelete === "function" && !await allowDelete(req, doc)) {
1241
+ fail(
1242
+ res,
1243
+ 403,
1244
+ RestErrorCode.DeleteForbidden,
1245
+ "Delete not allowed"
1246
+ );
1247
+ return;
1248
+ }
935
1249
  }
936
- if (!allowDelete(req, doc)) {
937
- res.status(403).json({ success: false, error: "Delete not allowed" });
1250
+ const success = await model2.deleteById(id);
1251
+ if (!success) {
1252
+ fail(res, 404, RestErrorCode.NotFound, "Not found");
938
1253
  return;
939
1254
  }
940
- } else if (!allowDelete) {
941
- res.status(403).json({ success: false, error: "Delete not allowed" });
942
- return;
943
- }
944
- const success = await model2.deleteById(id);
945
- if (!success) {
946
- res.status(404).json({ success: false, error: "Not found" });
947
- return;
1255
+ res.json({ success: true });
1256
+ } catch (error) {
1257
+ handleCaught(res, error);
948
1258
  }
949
- res.json({ success: true });
950
1259
  });
951
1260
  return router;
952
1261
  }
@@ -976,7 +1285,7 @@ var CodixusServer = class {
976
1285
  async connect() {
977
1286
  this._db = await this.connection.connect();
978
1287
  this.banService = new BanService(this._db);
979
- this.guardFn = createGuard(this.jwt);
1288
+ this.guardFn = createGuard(this.jwt, this.banService);
980
1289
  for (const m of getModelRegistry()) {
981
1290
  m._bind(this._db, this.instanceId);
982
1291
  await m._ensureIndexes();
@@ -1070,6 +1379,7 @@ export {
1070
1379
  ErrorCodes,
1071
1380
  Model,
1072
1381
  Query,
1382
+ RestErrorCode,
1073
1383
  SubCollection,
1074
1384
  model,
1075
1385
  validate