wabe 0.6.14 → 0.6.15

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
@@ -17231,14 +17231,14 @@ var require_plugin_crypto = __commonJS((exports) => {
17231
17231
  function _interopDefault(ex) {
17232
17232
  return ex && typeof ex === "object" && "default" in ex ? ex["default"] : ex;
17233
17233
  }
17234
- var crypto3 = _interopDefault(__require("crypto"));
17234
+ var crypto2 = _interopDefault(__require("crypto"));
17235
17235
  var createDigest = (algorithm, hmacKey, counter) => {
17236
- const hmac = crypto3.createHmac(algorithm, Buffer.from(hmacKey, "hex"));
17236
+ const hmac = crypto2.createHmac(algorithm, Buffer.from(hmacKey, "hex"));
17237
17237
  const digest = hmac.update(Buffer.from(counter, "hex")).digest();
17238
17238
  return digest.toString("hex");
17239
17239
  };
17240
17240
  var createRandomBytes = (size, encoding) => {
17241
- return crypto3.randomBytes(size).toString(encoding);
17241
+ return crypto2.randomBytes(size).toString(encoding);
17242
17242
  };
17243
17243
  exports.createDigest = createDigest;
17244
17244
  exports.createRandomBytes = createRandomBytes;
@@ -28905,13 +28905,13 @@ var SecondaryFactor;
28905
28905
  SecondaryFactor2["QRCodeOTP"] = "qrcodeOTP";
28906
28906
  })(SecondaryFactor ||= {});
28907
28907
  // ../wabe/src/authentication/oauth/utils.ts
28908
- import crypto2 from "node:crypto";
28908
+ import crypto from "node:crypto";
28909
28909
  var base64URLencode = (content) => {
28910
- const hasher = crypto2.createHash("sha256").update(content);
28910
+ const hasher = crypto.createHash("sha256").update(content);
28911
28911
  const result = hasher.digest("base64");
28912
28912
  return result.split("=")[0].replaceAll("+", "-").replaceAll("/", "_");
28913
28913
  };
28914
- var generateRandomValues = () => crypto2.randomBytes(60).toString("base64url");
28914
+ var generateRandomValues = () => crypto.randomBytes(60).toString("base64url");
28915
28915
 
28916
28916
  // ../wabe/src/authentication/oauth/Oauth2Client.ts
28917
28917
  class OAuth2Client {
@@ -29048,7 +29048,7 @@ class Google {
29048
29048
  }
29049
29049
  // ../wabe/src/authentication/OTP.ts
29050
29050
  var import_otplib = __toESM(require_otplib(), 1);
29051
- import { createHash } from "node:crypto";
29051
+ import { createHash, randomBytes as randomBytes2 } from "node:crypto";
29052
29052
 
29053
29053
  // ../wabe/src/utils/index.ts
29054
29054
  import { timingSafeEqual } from "node:crypto";
@@ -29149,10 +29149,14 @@ var getNestedProperty = (obj, path) => {
29149
29149
  };
29150
29150
  var firstLetterInUpperCase = (str) => {
29151
29151
  const indexOfFirstLetter = str.search(/[a-z]/i);
29152
+ if (indexOfFirstLetter < 0)
29153
+ return str;
29152
29154
  return str.slice(0, indexOfFirstLetter) + str[indexOfFirstLetter]?.toUpperCase() + str.slice(indexOfFirstLetter + 1);
29153
29155
  };
29154
29156
  var firstLetterInLowerCase = (str) => {
29155
29157
  const indexOfFirstLetter = str.search(/[a-z]/i);
29158
+ if (indexOfFirstLetter < 0)
29159
+ return str;
29156
29160
  return str.slice(0, indexOfFirstLetter) + str[indexOfFirstLetter]?.toLowerCase() + str.slice(indexOfFirstLetter + 1);
29157
29161
  };
29158
29162
  var getClassFromClassName = (className, config) => {
@@ -29186,56 +29190,15 @@ var tokenize = (value) => {
29186
29190
  return tmpValue.replace(/[\W_]+/g, " ").trim();
29187
29191
  };
29188
29192
 
29189
- // ../wabe/src/authentication/OTP.ts
29190
- var ONE_WINDOW = 1;
29191
-
29192
- class OTP {
29193
- secret;
29194
- internalTotp;
29195
- constructor(rootKey) {
29196
- this.secret = rootKey;
29197
- this.internalTotp = import_otplib.totp.clone({
29198
- window: [ONE_WINDOW, 0]
29199
- });
29200
- }
29201
- deriveSecret(userId) {
29202
- const hash = createHash("sha256").update(`${this.secret}:${userId}`).digest();
29203
- return base32Encode(hash, "RFC4648", { padding: false });
29204
- }
29205
- generate(userId) {
29206
- const secret = this.deriveSecret(userId);
29207
- return this.internalTotp.generate(secret);
29208
- }
29209
- verify(otp, userId) {
29210
- const secret = this.deriveSecret(userId);
29211
- return this.internalTotp.verify({ secret, token: otp });
29212
- }
29213
- authenticatorGenerate(userId) {
29214
- const secret = this.deriveSecret(userId);
29215
- return import_otplib.authenticator.generate(secret);
29216
- }
29217
- authenticatorVerify(otp, userId) {
29218
- const secret = this.deriveSecret(userId);
29219
- return import_otplib.authenticator.verify({
29220
- secret,
29221
- token: otp
29222
- });
29223
- }
29224
- generateKeyuri({
29225
- userId,
29226
- emailOrUsername,
29227
- applicationName
29228
- }) {
29229
- const secret = this.deriveSecret(userId);
29230
- return import_otplib.authenticator.keyuri(emailOrUsername, applicationName, secret);
29231
- }
29232
- }
29233
- // ../wabe/src/authentication/Session.ts
29234
- var import_jsonwebtoken = __toESM(require_jsonwebtoken(), 1);
29235
- import crypto4 from "node:crypto";
29236
-
29237
29193
  // ../wabe/src/utils/crypto.ts
29238
- import { randomBytes, createCipheriv, createDecipheriv, createHmac } from "node:crypto";
29194
+ import {
29195
+ randomBytes,
29196
+ createCipheriv,
29197
+ createDecipheriv,
29198
+ createHmac,
29199
+ timingSafeEqual as timingSafeEqual2
29200
+ } from "node:crypto";
29201
+ import * as nodeCrypto from "node:crypto";
29239
29202
  import { promisify } from "node:util";
29240
29203
  var params = {
29241
29204
  parallelism: 1,
@@ -29243,12 +29206,18 @@ var params = {
29243
29206
  memory: 65536,
29244
29207
  passes: 2
29245
29208
  };
29209
+ var getNodeArgon2 = () => {
29210
+ const argon22 = nodeCrypto.argon2;
29211
+ if (!argon22)
29212
+ throw new Error("Argon2 is not supported in this runtime");
29213
+ return argon22;
29214
+ };
29246
29215
  var hashArgon2 = async (text) => {
29247
29216
  if (process.versions.bun)
29248
29217
  return Bun.password.hash(text, { algorithm: "argon2id" });
29249
- const argon2 = promisify(__require("node:crypto").argon2);
29218
+ const argon22 = promisify(getNodeArgon2());
29250
29219
  const nonce = randomBytes(16);
29251
- const result = await argon2("argon2id", {
29220
+ const result = await argon22("argon2id", {
29252
29221
  message: text,
29253
29222
  nonce,
29254
29223
  ...params
@@ -29259,6 +29228,8 @@ var verifyArgon2 = async (password, hash) => {
29259
29228
  if (process.versions.bun)
29260
29229
  return Bun.password.verify(password, hash, "argon2id");
29261
29230
  const [, algorithm, , paramString, nonceHex, storedHashHex] = hash.split("$");
29231
+ if (!algorithm || !paramString || !nonceHex || !storedHashHex)
29232
+ return false;
29262
29233
  const kvPairs = paramString?.split(",");
29263
29234
  const parsedParams = Object.fromEntries(kvPairs?.map((pair) => {
29264
29235
  const [key, value] = pair.split("=");
@@ -29267,15 +29238,18 @@ var verifyArgon2 = async (password, hash) => {
29267
29238
  const memory = parsedParams.m;
29268
29239
  const passes = parsedParams.t;
29269
29240
  const parallelism = parsedParams.p;
29270
- const newDerived = await promisify(__require("node:crypto"))(algorithm, {
29271
- nonce: Buffer.from(nonceHex || "", "base64"),
29241
+ if ([memory, passes, parallelism].some((value) => Number.isNaN(value)))
29242
+ return false;
29243
+ const argon2Async = promisify(getNodeArgon2());
29244
+ const newDerived = await argon2Async(algorithm, {
29245
+ nonce: Buffer.from(nonceHex, "base64"),
29272
29246
  parallelism,
29273
- tagLength: 64,
29247
+ tagLength: params.tagLength,
29274
29248
  memory,
29275
29249
  passes,
29276
29250
  message: password
29277
29251
  });
29278
- const isMatch = crypto.timingSafeEqual(Buffer.from(newDerived), Buffer.from(storedHashHex || "", "base64"));
29252
+ const isMatch = timingSafeEqual2(Buffer.from(newDerived), Buffer.from(storedHashHex, "base64"));
29279
29253
  return isMatch;
29280
29254
  };
29281
29255
  var isArgon2Hash = (value) => typeof value === "string" && value.startsWith("$argon2");
@@ -29319,7 +29293,76 @@ var contextWithRoot = (context) => ({
29319
29293
  });
29320
29294
  var notEmpty = (value) => value !== null && value !== undefined;
29321
29295
 
29296
+ // ../wabe/src/authentication/OTP.ts
29297
+ var ONE_WINDOW = 1;
29298
+ var generateOtpSalt = () => randomBytes2(32).toString("hex");
29299
+ var getOrCreateOtpSalt = async (context, userId) => {
29300
+ const rootContext = contextWithRoot(context);
29301
+ const user = await context.wabe.controllers.database.getObject({
29302
+ className: "User",
29303
+ id: userId,
29304
+ context: rootContext,
29305
+ select: { otpSalt: true }
29306
+ });
29307
+ if (user?.otpSalt)
29308
+ return user.otpSalt;
29309
+ const salt = generateOtpSalt();
29310
+ await context.wabe.controllers.database.updateObject({
29311
+ className: "User",
29312
+ id: userId,
29313
+ context: rootContext,
29314
+ data: { otpSalt: salt },
29315
+ select: {}
29316
+ });
29317
+ return salt;
29318
+ };
29319
+
29320
+ class OTP {
29321
+ secret;
29322
+ internalTotp;
29323
+ constructor(rootKey) {
29324
+ this.secret = rootKey;
29325
+ this.internalTotp = import_otplib.totp.clone({
29326
+ window: [ONE_WINDOW, 0]
29327
+ });
29328
+ }
29329
+ deriveSecret(userId, salt) {
29330
+ const material = salt ? `${this.secret}:${userId}:${salt}` : `${this.secret}:${userId}`;
29331
+ const hash = createHash("sha256").update(material).digest();
29332
+ return base32Encode(hash, "RFC4648", { padding: false });
29333
+ }
29334
+ generate(userId, salt) {
29335
+ const secret = this.deriveSecret(userId, salt);
29336
+ return this.internalTotp.generate(secret);
29337
+ }
29338
+ verify(otp, userId, salt) {
29339
+ const secret = this.deriveSecret(userId, salt);
29340
+ return this.internalTotp.verify({ secret, token: otp });
29341
+ }
29342
+ authenticatorGenerate(userId, salt) {
29343
+ const secret = this.deriveSecret(userId, salt);
29344
+ return import_otplib.authenticator.generate(secret);
29345
+ }
29346
+ authenticatorVerify(otp, userId, salt) {
29347
+ const secret = this.deriveSecret(userId, salt);
29348
+ return import_otplib.authenticator.verify({
29349
+ secret,
29350
+ token: otp
29351
+ });
29352
+ }
29353
+ generateKeyuri({
29354
+ userId,
29355
+ emailOrUsername,
29356
+ applicationName,
29357
+ salt
29358
+ }) {
29359
+ const secret = this.deriveSecret(userId, salt);
29360
+ return import_otplib.authenticator.keyuri(emailOrUsername, applicationName, secret);
29361
+ }
29362
+ }
29322
29363
  // ../wabe/src/authentication/Session.ts
29364
+ var import_jsonwebtoken = __toESM(require_jsonwebtoken(), 1);
29365
+ import crypto3 from "node:crypto";
29323
29366
  var getJwtSecret = (context) => {
29324
29367
  const secret = context.wabe.config.authentication?.session?.jwtSecret;
29325
29368
  if (!secret)
@@ -29338,7 +29381,7 @@ var safeVerify = (token, secret, options = {}) => {
29338
29381
  }
29339
29382
  };
29340
29383
  var getTokenSecret = (context) => context.wabe.config.authentication?.session?.tokenSecret ?? getJwtSecret(context);
29341
- var getTokenEncryptionKey = (context) => crypto4.createHash("sha256").update(getTokenSecret(context)).digest();
29384
+ var getTokenEncryptionKey = (context) => crypto3.createHash("sha256").update(getTokenSecret(context)).digest();
29342
29385
  var getJwtVerifyOptions = (context) => {
29343
29386
  const opts = {};
29344
29387
  const audience = context.wabe.config.authentication?.session?.jwtAudience;
@@ -29434,7 +29477,7 @@ class Session {
29434
29477
  const currentSessionId = session.id;
29435
29478
  const message = `${currentSessionId.length}!${currentSessionId}!${receivedRandomValue?.length}!${receivedRandomValue}`;
29436
29479
  const csrfSecret = context.wabe.config.authentication?.session?.csrfSecret || getJwtSecret(context);
29437
- const expectedHmac = crypto4.createHmac("sha256", csrfSecret).update(message).digest("hex");
29480
+ const expectedHmac = crypto3.createHmac("sha256", csrfSecret).update(message).digest("hex");
29438
29481
  const isHex = /^[0-9a-f]+$/i;
29439
29482
  if (!isHex.test(receivedHmacHex) || receivedHmacHex.length !== expectedHmac.length)
29440
29483
  return {
@@ -29443,7 +29486,7 @@ class Session {
29443
29486
  accessToken: null,
29444
29487
  refreshToken: null
29445
29488
  };
29446
- const isValid = crypto4.timingSafeEqual(Buffer.from(receivedHmacHex, "hex"), Buffer.from(expectedHmac, "hex"));
29489
+ const isValid = crypto3.timingSafeEqual(Buffer.from(receivedHmacHex, "hex"), Buffer.from(expectedHmac, "hex"));
29447
29490
  if (!isValid)
29448
29491
  return {
29449
29492
  sessionId: null,
@@ -29502,7 +29545,7 @@ class Session {
29502
29545
  }) : undefined;
29503
29546
  const secretKey = getJwtSecret(context);
29504
29547
  const signOptions = {
29505
- jwtid: crypto4.randomUUID(),
29548
+ jwtid: crypto3.randomUUID(),
29506
29549
  algorithm: JWT_ALGORITHM
29507
29550
  };
29508
29551
  const audience = context.wabe.config.authentication?.session?.jwtAudience;
@@ -29544,10 +29587,10 @@ class Session {
29544
29587
  if (!res)
29545
29588
  throw new Error("Session not created");
29546
29589
  const sessionId = res.id;
29547
- const randomValue = crypto4.randomBytes(16).toString("hex");
29590
+ const randomValue = crypto3.randomBytes(16).toString("hex");
29548
29591
  const message = `${sessionId.length}!${sessionId}!${randomValue.length}!${randomValue}`;
29549
29592
  const csrfSecret = context.wabe.config.authentication?.session?.csrfSecret || secretKey;
29550
- const hmac = crypto4.createHmac("sha256", csrfSecret).update(message).digest("hex");
29593
+ const hmac = crypto3.createHmac("sha256", csrfSecret).update(message).digest("hex");
29551
29594
  const csrfToken = `${hmac}.${randomValue}`;
29552
29595
  return {
29553
29596
  accessToken: this.accessToken,
@@ -29626,7 +29669,7 @@ class Session {
29626
29669
  }) : undefined;
29627
29670
  const nowSeconds = Math.floor(Date.now() / 1000);
29628
29671
  const signOptions = {
29629
- jwtid: crypto4.randomUUID(),
29672
+ jwtid: crypto3.randomUUID(),
29630
29673
  algorithm: JWT_ALGORITHM
29631
29674
  };
29632
29675
  const audience = context.wabe.config.authentication?.session?.jwtAudience;
@@ -29699,6 +29742,7 @@ var signOutResolver = async (_, __, context) => {
29699
29742
  if (context.wabe.config.authentication?.session?.cookieSession) {
29700
29743
  context.response?.deleteCookie("accessToken");
29701
29744
  context.response?.deleteCookie("refreshToken");
29745
+ context.response?.deleteCookie("csrfToken");
29702
29746
  }
29703
29747
  return true;
29704
29748
  };
@@ -29713,7 +29757,7 @@ var getSessionCookieSameSite = (config) => {
29713
29757
  };
29714
29758
 
29715
29759
  // ../wabe/src/authentication/security.ts
29716
- import crypto5 from "node:crypto";
29760
+ import crypto4 from "node:crypto";
29717
29761
  var DEFAULT_SIGN_IN_RATE_LIMIT = {
29718
29762
  maxAttempts: 10,
29719
29763
  windowMs: 10 * 60 * 1000,
@@ -29729,6 +29773,16 @@ var DEFAULT_VERIFY_CHALLENGE_RATE_LIMIT = {
29729
29773
  windowMs: 10 * 60 * 1000,
29730
29774
  blockDurationMs: 15 * 60 * 1000
29731
29775
  };
29776
+ var DEFAULT_SEND_OTP_CODE_RATE_LIMIT = {
29777
+ maxAttempts: 5,
29778
+ windowMs: 10 * 60 * 1000,
29779
+ blockDurationMs: 30 * 60 * 1000
29780
+ };
29781
+ var DEFAULT_RESET_PASSWORD_RATE_LIMIT = {
29782
+ maxAttempts: 10,
29783
+ windowMs: 10 * 60 * 1000,
29784
+ blockDurationMs: 15 * 60 * 1000
29785
+ };
29732
29786
  var DEFAULT_MFA_CHALLENGE_TTL_MS = 5 * 60 * 1000;
29733
29787
  var rateLimitStorage = new Map;
29734
29788
  var getRateLimitOptions = (context, scope) => {
@@ -29737,17 +29791,21 @@ var getRateLimitOptions = (context, scope) => {
29737
29791
  const scopeConfigMap = {
29738
29792
  signIn: securityConfig?.signInRateLimit,
29739
29793
  signUp: securityConfig?.signUpRateLimit,
29740
- verifyChallenge: securityConfig?.verifyChallengeRateLimit
29794
+ verifyChallenge: securityConfig?.verifyChallengeRateLimit,
29795
+ sendOtpCode: securityConfig?.sendOtpCodeRateLimit,
29796
+ resetPassword: securityConfig?.resetPasswordRateLimit
29741
29797
  };
29742
29798
  const defaultsMap = {
29743
29799
  signIn: DEFAULT_SIGN_IN_RATE_LIMIT,
29744
29800
  signUp: DEFAULT_SIGN_UP_RATE_LIMIT,
29745
- verifyChallenge: DEFAULT_VERIFY_CHALLENGE_RATE_LIMIT
29801
+ verifyChallenge: DEFAULT_VERIFY_CHALLENGE_RATE_LIMIT,
29802
+ sendOtpCode: DEFAULT_SEND_OTP_CODE_RATE_LIMIT,
29803
+ resetPassword: DEFAULT_RESET_PASSWORD_RATE_LIMIT
29746
29804
  };
29747
29805
  const scopeConfig = scopeConfigMap[scope];
29748
29806
  const defaults = defaultsMap[scope];
29749
29807
  return {
29750
- enabled: scopeConfig?.enabled ?? !!wabeConfig?.isProduction,
29808
+ enabled: scopeConfig?.enabled ?? true,
29751
29809
  maxAttempts: scopeConfig?.maxAttempts ?? defaults.maxAttempts,
29752
29810
  windowMs: scopeConfig?.windowMs ?? defaults.windowMs,
29753
29811
  blockDurationMs: scopeConfig?.blockDurationMs ?? defaults.blockDurationMs
@@ -29826,19 +29884,15 @@ var pruneExpiredChallenges = (challenges) => {
29826
29884
  return challenges.filter((challenge) => challenge.expiresAt > now);
29827
29885
  };
29828
29886
  var getUserPendingChallenges = async (context, userId) => {
29829
- try {
29830
- const user = await getDatabaseController(context).getObject({
29831
- className: "User",
29832
- id: userId,
29833
- context: contextWithRoot(context),
29834
- select: {
29835
- pendingChallenges: true
29836
- }
29837
- });
29838
- return parsePendingChallenges(user?.pendingChallenges);
29839
- } catch {
29840
- return null;
29841
- }
29887
+ const user = await getDatabaseController(context).getObject({
29888
+ className: "User",
29889
+ id: userId,
29890
+ context: contextWithRoot(context),
29891
+ select: {
29892
+ pendingChallenges: true
29893
+ }
29894
+ });
29895
+ return parsePendingChallenges(user?.pendingChallenges);
29842
29896
  };
29843
29897
  var saveUserPendingChallenges = async (context, userId, challenges) => getDatabaseController(context).updateObject({
29844
29898
  className: "User",
@@ -29854,9 +29908,9 @@ var saveUserPendingChallenges = async (context, userId, challenges) => getDataba
29854
29908
  select: {}
29855
29909
  });
29856
29910
  var createMfaChallenge = async (context, { userId, provider }) => {
29857
- const token = crypto5.randomUUID();
29911
+ const token = crypto4.randomUUID();
29858
29912
  const expiresAt = Date.now() + getMfaChallengeTTL(context);
29859
- const currentChallenges = await getUserPendingChallenges(context, userId) || [];
29913
+ const currentChallenges = await getUserPendingChallenges(context, userId);
29860
29914
  const nextChallenges = [
29861
29915
  ...pruneExpiredChallenges(currentChallenges),
29862
29916
  {
@@ -29868,24 +29922,35 @@ var createMfaChallenge = async (context, { userId, provider }) => {
29868
29922
  await saveUserPendingChallenges(context, userId, nextChallenges);
29869
29923
  return token;
29870
29924
  };
29925
+ var mfaLocks = new Map;
29871
29926
  var consumeMfaChallenge = async (context, {
29872
29927
  challengeToken,
29873
29928
  userId,
29874
29929
  provider
29875
29930
  }) => {
29876
- const currentChallenges = await getUserPendingChallenges(context, userId);
29877
- if (!currentChallenges)
29878
- return false;
29879
- const activeChallenges = pruneExpiredChallenges(currentChallenges);
29880
- const normalizedProvider = provider.toLowerCase();
29881
- const isValid = activeChallenges.some((challenge) => challenge.token === challengeToken && challenge.provider === normalizedProvider);
29882
- const remainingChallenges = activeChallenges.filter((challenge) => !(challenge.token === challengeToken && challenge.provider === normalizedProvider));
29883
- if (remainingChallenges.length !== currentChallenges.length || isValid) {
29884
- await saveUserPendingChallenges(context, userId, remainingChallenges);
29885
- }
29886
- return isValid;
29931
+ const lockKey = `mfa:${userId}`;
29932
+ const previous = mfaLocks.get(lockKey) ?? Promise.resolve(false);
29933
+ const current = previous.catch(() => false).then(async () => {
29934
+ const currentChallenges = await getUserPendingChallenges(context, userId);
29935
+ if (!currentChallenges)
29936
+ return false;
29937
+ const activeChallenges = pruneExpiredChallenges(currentChallenges);
29938
+ const normalizedProvider = provider.toLowerCase();
29939
+ const isValid = activeChallenges.some((challenge) => challenge.token === challengeToken && challenge.provider === normalizedProvider);
29940
+ const remainingChallenges = activeChallenges.filter((challenge) => !(challenge.token === challengeToken && challenge.provider === normalizedProvider));
29941
+ if (remainingChallenges.length !== currentChallenges.length || isValid) {
29942
+ await saveUserPendingChallenges(context, userId, remainingChallenges);
29943
+ }
29944
+ return isValid;
29945
+ });
29946
+ mfaLocks.set(lockKey, current);
29947
+ try {
29948
+ return await current;
29949
+ } finally {
29950
+ if (mfaLocks.get(lockKey) === current)
29951
+ mfaLocks.delete(lockKey);
29952
+ }
29887
29953
  };
29888
- var shouldRequireMfaChallenge = (context) => context.wabe.config?.authentication?.security?.requireMfaChallengeInProduction ?? !!context.wabe.config?.isProduction;
29889
29954
 
29890
29955
  // ../wabe/src/authentication/utils.ts
29891
29956
  var getAuthenticationMethod = (listOfMethods, context) => {
@@ -29918,18 +29983,16 @@ var verifyChallengeResolver = async (_, {
29918
29983
  });
29919
29984
  if (!result?.userId)
29920
29985
  throw new Error("Invalid challenge");
29921
- if (shouldRequireMfaChallenge(context)) {
29922
- const challengeToken = input.challengeToken;
29923
- if (!challengeToken)
29924
- throw new Error("Invalid challenge");
29925
- const isValidChallenge = await consumeMfaChallenge(context, {
29926
- challengeToken,
29927
- userId: result.userId,
29928
- provider: name
29929
- });
29930
- if (!isValidChallenge)
29931
- throw new Error("Invalid challenge");
29932
- }
29986
+ const challengeToken = input.challengeToken;
29987
+ if (!challengeToken)
29988
+ throw new Error("Invalid challenge");
29989
+ const isValidChallenge = await consumeMfaChallenge(context, {
29990
+ challengeToken,
29991
+ userId: result.userId,
29992
+ provider: name
29993
+ });
29994
+ if (!isValidChallenge)
29995
+ throw new Error("Invalid challenge");
29933
29996
  const session = new Session;
29934
29997
  const { accessToken, refreshToken } = await session.create(result.userId, context);
29935
29998
  if (context.wabe.config.authentication?.session?.cookieSession) {
@@ -29966,14 +30029,26 @@ var meResolver = (_, __, context) => {
29966
30029
  // ../wabe/src/schema/resolvers/resetPassword.ts
29967
30030
  var DUMMY_USER_ID = "00000000-0000-0000-0000-000000000000";
29968
30031
  var resetPasswordResolver = async (_, { input: { email, phone, password, otp } }, context) => {
29969
- if (!email && !phone)
30032
+ const normalizedEmail = email?.trim().toLowerCase();
30033
+ const normalizedPhone = phone?.trim();
30034
+ if (!normalizedEmail && !normalizedPhone)
30035
+ throw new Error("Email or phone is required");
30036
+ if (normalizedEmail && normalizedPhone)
29970
30037
  throw new Error("Email or phone is required");
30038
+ const identifier = normalizedEmail || normalizedPhone;
30039
+ if (!identifier)
30040
+ throw new Error("Email or phone is required");
30041
+ const rateLimitKey = `resetPassword:${identifier}`;
30042
+ if (isRateLimited(context, "resetPassword", rateLimitKey))
30043
+ throw new Error("Too many attempts. Please try again later.");
29971
30044
  const users = await context.wabe.controllers.database.getObjects({
29972
30045
  className: "User",
29973
30046
  where: {
29974
- ...email && { email: { equalTo: email } },
29975
- ...phone && {
29976
- authentication: { phonePassword: { phone: { equalTo: phone } } }
30047
+ ...normalizedEmail && { email: { equalTo: normalizedEmail } },
30048
+ ...normalizedPhone && {
30049
+ authentication: {
30050
+ phonePassword: { phone: { equalTo: normalizedPhone } }
30051
+ }
29977
30052
  }
29978
30053
  },
29979
30054
  select: { id: true, authentication: true },
@@ -29983,21 +30058,24 @@ var resetPasswordResolver = async (_, { input: { email, phone, password, otp } }
29983
30058
  const realUser = users.length > 0 ? users[0] : null;
29984
30059
  const userId = realUser?.id ?? DUMMY_USER_ID;
29985
30060
  const otpClass = new OTP(context.wabe.config.rootKey);
29986
- const isOtpValid = otpClass.verify(otp, userId);
30061
+ const salt = realUser ? await getOrCreateOtpSalt(context, userId) : undefined;
30062
+ const isOtpValid = otpClass.verify(otp, userId, salt);
29987
30063
  if (realUser) {
29988
- if (!isOtpValid)
30064
+ if (!isOtpValid) {
30065
+ registerRateLimitFailure(context, "resetPassword", rateLimitKey);
29989
30066
  throw new Error("Invalid OTP code");
29990
- const providerKey = phone ? "phonePassword" : "emailPassword";
30067
+ }
30068
+ const providerKey = normalizedPhone ? "phonePassword" : "emailPassword";
29991
30069
  await context.wabe.controllers.database.updateObject({
29992
30070
  className: "User",
29993
30071
  id: realUser.id,
29994
30072
  data: {
29995
30073
  authentication: {
29996
30074
  [providerKey]: {
29997
- ...phone && {
30075
+ ...normalizedPhone && {
29998
30076
  phone: realUser.authentication?.phonePassword?.phone
29999
30077
  },
30000
- ...email && { email },
30078
+ ...normalizedEmail && { email: normalizedEmail },
30001
30079
  password
30002
30080
  }
30003
30081
  }
@@ -30013,6 +30091,9 @@ var resetPasswordResolver = async (_, { input: { email, phone, password, otp } }
30013
30091
  select: {},
30014
30092
  context: contextWithRoot(context)
30015
30093
  });
30094
+ clearRateLimit(context, "resetPassword", rateLimitKey);
30095
+ } else {
30096
+ registerRateLimitFailure(context, "resetPassword", rateLimitKey);
30016
30097
  }
30017
30098
  return true;
30018
30099
  };
@@ -30144,11 +30225,17 @@ var sendOtpCodeResolver = async (_, { input }, context) => {
30144
30225
  const emailController = context.wabe.controllers.email;
30145
30226
  if (!emailController)
30146
30227
  throw new Error("Email adapter not defined");
30228
+ const normalizedEmail = input.email.trim().toLowerCase();
30229
+ if (!normalizedEmail)
30230
+ return true;
30231
+ const rateLimitKey = `sendOtpCode:${normalizedEmail}`;
30232
+ if (isRateLimited(context, "sendOtpCode", rateLimitKey))
30233
+ return true;
30147
30234
  const user = await context.wabe.controllers.database.getObjects({
30148
30235
  className: "User",
30149
30236
  where: {
30150
30237
  email: {
30151
- equalTo: input.email
30238
+ equalTo: normalizedEmail
30152
30239
  }
30153
30240
  },
30154
30241
  select: { id: true },
@@ -30161,15 +30248,17 @@ var sendOtpCodeResolver = async (_, { input }, context) => {
30161
30248
  if (!userId)
30162
30249
  return false;
30163
30250
  const otpClass = new OTP(context.wabe.config.rootKey);
30164
- const otp = otpClass.generate(userId);
30251
+ const salt = await getOrCreateOtpSalt(context, userId);
30252
+ const otp = otpClass.generate(userId, salt);
30165
30253
  const mainEmail = context.wabe.config.email?.mainEmail || "noreply@wabe.com";
30166
30254
  const template = context.wabe.config.email?.htmlTemplates?.sendOTPCode;
30167
30255
  await emailController.send({
30168
30256
  from: mainEmail,
30169
- to: [input.email],
30257
+ to: [normalizedEmail],
30170
30258
  subject: template?.subject || "Your OTP code",
30171
30259
  html: template?.fn ? await template.fn({ otp }) : sendOtpCodeTemplate(otp)
30172
30260
  });
30261
+ registerRateLimitFailure(context, "sendOtpCode", rateLimitKey);
30173
30262
  return true;
30174
30263
  };
30175
30264
 
@@ -30307,12 +30396,16 @@ var signInWithResolver = async (_, {
30307
30396
  };
30308
30397
  }
30309
30398
  if (name === "emailPasswordSRP") {
30399
+ const challengeToken = await createMfaChallenge(context, {
30400
+ userId,
30401
+ provider: "emailPasswordSRPChallenge"
30402
+ });
30310
30403
  return {
30311
30404
  accessToken: null,
30312
30405
  refreshToken: null,
30313
30406
  user,
30314
30407
  srp: srp || null,
30315
- challengeToken: null
30408
+ challengeToken
30316
30409
  };
30317
30410
  }
30318
30411
  const session = new Session;
@@ -30731,6 +30824,13 @@ class Schema {
30731
30824
  }
30732
30825
  }
30733
30826
  },
30827
+ otpSalt: {
30828
+ type: "String",
30829
+ protected: {
30830
+ authorizedRoles: ["rootOnly"],
30831
+ protectedOperations: ["create", "read", "update"]
30832
+ }
30833
+ },
30734
30834
  pendingChallenges: {
30735
30835
  type: "Array",
30736
30836
  typeValue: "Object",
@@ -30918,7 +31018,7 @@ var getFile = async (hookObject) => {
30918
31018
  const schema = hookObject.context.wabe.config.schema?.classes?.find((currentClass) => currentClass.name === hookObject.className);
30919
31019
  if (!schema)
30920
31020
  return;
30921
- const urlCacheInSeconds = hookObject.context.wabe.config.file?.urlCacheInSeconds || 3600 * 24;
31021
+ const urlCacheInSeconds = hookObject.context.wabe.config.file?.urlCacheInSeconds ?? 3600 * 24;
30922
31022
  await Promise.all(Object.entries(schema.fields).filter(([_, value]) => value.type === "File").map(async ([fieldName]) => {
30923
31023
  const fileInfo = hookObject.object?.[fieldName];
30924
31024
  if (!fileInfo)
@@ -30932,17 +31032,21 @@ var getFile = async (hookObject) => {
30932
31032
  if (!hookObject.context.wabe.controllers.file)
30933
31033
  throw new Error("No file adapter found");
30934
31034
  const fileUrlFromBucket = await hookObject.context.wabe.controllers.file?.readFile(fileName);
30935
- const newUrl = fileUrlFromBucket || fileInfo.url;
31035
+ if (!fileUrlFromBucket)
31036
+ return;
31037
+ const newUrl = fileUrlFromBucket;
30936
31038
  const newUrlGeneratedAt = new Date;
30937
31039
  hookObject.object[fieldName] = {
30938
31040
  ...fileInfo,
30939
31041
  urlGeneratedAt: newUrlGeneratedAt,
30940
31042
  url: newUrl
30941
31043
  };
31044
+ if (!hookObject.object?.id)
31045
+ return;
30942
31046
  return hookObject.context.wabe.controllers.database.updateObject({
30943
31047
  className: hookObject.className,
30944
31048
  context: hookObject.context,
30945
- id: hookObject.object?.id || "",
31049
+ id: hookObject.object.id,
30946
31050
  data: {
30947
31051
  [fieldName]: {
30948
31052
  ...fileInfo,
@@ -30957,7 +31061,7 @@ var getFile = async (hookObject) => {
30957
31061
  var defaultAfterReadFile = (hookObject) => getFile(hookObject);
30958
31062
 
30959
31063
  // ../wabe/src/file/security.ts
30960
- import crypto6 from "node:crypto";
31064
+ import crypto5 from "node:crypto";
30961
31065
  import path from "node:path";
30962
31066
  var DEFAULT_MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024;
30963
31067
  var DEFAULT_ALLOWED_MIME_TYPES = [
@@ -31018,7 +31122,7 @@ var detectMimeTypeFromContent = async (file) => {
31018
31122
  };
31019
31123
  var getUploadSecurityConfig = (context) => {
31020
31124
  const security = context.wabe.config.file?.security;
31021
- const enabled = security?.enabled ?? context.wabe.config.isProduction;
31125
+ const enabled = security?.enabled ?? true;
31022
31126
  const maxFileSizeBytes = security?.maxFileSizeBytes ?? DEFAULT_MAX_FILE_SIZE_BYTES;
31023
31127
  const allowedMimeTypes = (security?.allowedMimeTypes || DEFAULT_ALLOWED_MIME_TYPES).map(normalizeMimeType);
31024
31128
  const allowedExtensions = (security?.allowedExtensions || DEFAULT_ALLOWED_EXTENSIONS).map((value) => value.trim().toLowerCase());
@@ -31032,7 +31136,7 @@ var getUploadSecurityConfig = (context) => {
31032
31136
  };
31033
31137
  };
31034
31138
  var createRandomizedFile = async (file, extension) => {
31035
- const uniqueName = `${crypto6.randomUUID()}.${extension}`;
31139
+ const uniqueName = `${crypto5.randomUUID()}.${extension}`;
31036
31140
  const content = await file.arrayBuffer();
31037
31141
  return new File([content], uniqueName, {
31038
31142
  type: file.type,
@@ -31052,6 +31156,9 @@ var secureUploadedFile = async (file, context) => {
31052
31156
  if (!mimeType || !allowedMimeTypes.includes(mimeType))
31053
31157
  throw new Error("File MIME type is not allowed");
31054
31158
  const detectedMimeType = await detectMimeTypeFromContent(file);
31159
+ const mimeTypeHasKnownSignature = MIME_SIGNATURES.some((signature) => signature.mimeType === mimeType);
31160
+ if (mimeTypeHasKnownSignature && detectedMimeType !== mimeType)
31161
+ throw new Error("File content does not match MIME type");
31055
31162
  if (detectedMimeType && detectedMimeType !== mimeType)
31056
31163
  throw new Error("File content does not match MIME type");
31057
31164
  if (detectedMimeType && !allowedMimeTypes.includes(detectedMimeType))
@@ -31065,6 +31172,19 @@ var secureUploadedFile = async (file, context) => {
31065
31172
  };
31066
31173
 
31067
31174
  // ../wabe/src/file/hookUploadFile.ts
31175
+ var ALLOWED_URL_PROTOCOLS = ["https:"];
31176
+ var validateFileUrl = (url) => {
31177
+ let parsed;
31178
+ try {
31179
+ parsed = new URL(url);
31180
+ } catch {
31181
+ throw new Error("Invalid file URL");
31182
+ }
31183
+ if (!ALLOWED_URL_PROTOCOLS.includes(parsed.protocol))
31184
+ throw new Error("File URL must use HTTPS");
31185
+ if (parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "[::1]")
31186
+ throw new Error("File URL must not point to localhost");
31187
+ };
31068
31188
  var handleFile = async (hookObject) => {
31069
31189
  const newData = hookObject.getNewData();
31070
31190
  const schema = hookObject.context.wabe.config.schema?.classes?.find((currentClass) => currentClass.name === hookObject.className);
@@ -31077,6 +31197,7 @@ var handleFile = async (hookObject) => {
31077
31197
  if (!file && !url)
31078
31198
  return;
31079
31199
  if (url) {
31200
+ validateFileUrl(url);
31080
31201
  hookObject.upsertNewData(keyName, {
31081
31202
  url,
31082
31203
  isPresignedUrl: false
@@ -31129,7 +31250,9 @@ class HookObject {
31129
31250
  return this.context.user;
31130
31251
  }
31131
31252
  isFieldUpdated(field) {
31132
- return !!(this.newData && !!this.newData[field]);
31253
+ if (!this.newData)
31254
+ return false;
31255
+ return Object.prototype.hasOwnProperty.call(this.newData, field);
31133
31256
  }
31134
31257
  upsertNewData(field, value) {
31135
31258
  if (!this.newData)
@@ -31930,6 +32053,7 @@ var getDefaultHooks = () => [
31930
32053
  ];
31931
32054
 
31932
32055
  // ../wabe/src/database/DatabaseController.ts
32056
+ var DEFAULT_MAX_WHERE_RECURSION_DEPTH = 10;
31933
32057
  var scalarWhereOperators = new Set([
31934
32058
  "equalTo",
31935
32059
  "notEqualTo",
@@ -32158,7 +32282,10 @@ class DatabaseController {
32158
32282
  return [];
32159
32283
  return relationValue.map((value) => this._extractPointerId(value)).filter((id) => typeof id === "string");
32160
32284
  }
32161
- async _getWhereObjectWithPointerOrRelation(className, where, context) {
32285
+ async _getWhereObjectWithPointerOrRelation(className, where, context, depth = 0) {
32286
+ const maxDepth = context.wabe.config.security?.maxWhereRecursionDepth ?? DEFAULT_MAX_WHERE_RECURSION_DEPTH;
32287
+ if (depth > maxDepth)
32288
+ throw new Error(`Query recursion depth exceeded maximum (${maxDepth}). Reduce nested where conditions.`);
32162
32289
  const whereKeys = Object.keys(where);
32163
32290
  const realClass = this._getClass(className, context);
32164
32291
  const newWhereObject = await whereKeys.reduce(async (acc, whereKey) => {
@@ -32166,7 +32293,7 @@ class DatabaseController {
32166
32293
  const typedWhereKey = whereKey;
32167
32294
  const field = realClass?.fields[typedWhereKey];
32168
32295
  if (typedWhereKey === "AND" || typedWhereKey === "OR") {
32169
- const newWhere = await Promise.all(where[typedWhereKey].map((whereObject) => this._getWhereObjectWithPointerOrRelation(className, whereObject, context)));
32296
+ const newWhere = await Promise.all(where[typedWhereKey].map((whereObject) => this._getWhereObjectWithPointerOrRelation(className, whereObject, context, depth + 1)));
32170
32297
  return {
32171
32298
  ...currentAcc,
32172
32299
  [typedWhereKey]: newWhere
@@ -32203,7 +32330,8 @@ class DatabaseController {
32203
32330
  className: fieldTargetClass,
32204
32331
  select: { id: true },
32205
32332
  where: defaultWhere,
32206
- context
32333
+ context,
32334
+ _whereRecursionDepth: depth + 1
32207
32335
  });
32208
32336
  const relationWhere = objects.length > 0 ? objects.map((object) => object?.id).filter(notEmpty) : [];
32209
32337
  const neverMatchWhere = {
@@ -32544,7 +32672,8 @@ class DatabaseController {
32544
32672
  operationType: "beforeRead" /* BeforeRead */,
32545
32673
  id
32546
32674
  });
32547
- const whereWithACLCondition = this._buildWhereWithACL(where || {}, context, "read");
32675
+ const whereWithPointer = await this._getWhereObjectWithPointerOrRelation(className, where || {}, context);
32676
+ const whereWithACLCondition = this._buildWhereWithACL(whereWithPointer || {}, context, "read");
32548
32677
  const selectWithPointersAndRelationsToGetId = this._buildSelectWithPointers({
32549
32678
  adapterSelect,
32550
32679
  pointers
@@ -32586,7 +32715,8 @@ class DatabaseController {
32586
32715
  _skipHooks,
32587
32716
  first,
32588
32717
  offset,
32589
- order
32718
+ order,
32719
+ _whereRecursionDepth
32590
32720
  }) {
32591
32721
  const { pointers, selectWithoutPointers } = this._getSelectMinusPointersAndRelations({
32592
32722
  className,
@@ -32598,7 +32728,7 @@ class DatabaseController {
32598
32728
  context,
32599
32729
  selectWithoutPointers
32600
32730
  });
32601
- const whereWithPointer = await this._getWhereObjectWithPointerOrRelation(className, where || {}, context);
32731
+ const whereWithPointer = await this._getWhereObjectWithPointerOrRelation(className, where || {}, context, _whereRecursionDepth ?? 0);
32602
32732
  const whereWithACLCondition = this._buildWhereWithACL(whereWithPointer || {}, context, "read");
32603
32733
  const selectWithPointersAndRelationsToGetId = this._buildSelectWithPointers({
32604
32734
  adapterSelect,
@@ -32728,10 +32858,11 @@ class DatabaseController {
32728
32858
  })));
32729
32859
  if (this._isEmptySelect(select))
32730
32860
  return [];
32861
+ const selectWithoutPrivateFields = select ? selectFieldsWithoutPrivateFields(select) : undefined;
32731
32862
  return this.getObjects({
32732
32863
  className,
32733
32864
  context: contextWithRoot(context),
32734
- select,
32865
+ select: selectWithoutPrivateFields,
32735
32866
  where: { id: { in: ids } },
32736
32867
  first,
32737
32868
  offset,
@@ -36023,11 +36154,20 @@ var add = async ({
36023
36154
  context
36024
36155
  });
36025
36156
  const idsToAdd = add2.map(getPointerId).filter(notEmpty);
36026
- if (typeOfExecution === "create")
36157
+ if (typeOfExecution === "create") {
36158
+ const linkedObjects = await Promise.all(idsToAdd.map((id2) => context.wabe.controllers.database.getObject({
36159
+ className: targetClass,
36160
+ id: id2,
36161
+ select: { id: true },
36162
+ context
36163
+ })));
36164
+ if (linkedObjects.some((object) => !object))
36165
+ throw new Error("Object not found");
36027
36166
  return idsToAdd.map((id2) => toPointerObject2({
36028
36167
  className: targetClass,
36029
36168
  id: id2
36030
36169
  }));
36170
+ }
36031
36171
  if (typeOfExecution === "update" && id) {
36032
36172
  const currentValue = await context.wabe.controllers.database.getObject({
36033
36173
  className,
@@ -36198,6 +36338,14 @@ var executeRelationOnFields = ({
36198
36338
  const targetClass = getTargetClassFromField2(fieldName);
36199
36339
  if (!linkedId)
36200
36340
  throw new Error(`Invalid link value for ${className}.${fieldName}`);
36341
+ const linkedObject = await context.wabe.controllers.database.getObject({
36342
+ className: targetClass,
36343
+ id: linkedId,
36344
+ select: { id: true },
36345
+ context: contextWithoutGraphQLCall(context)
36346
+ });
36347
+ if (!linkedObject)
36348
+ throw new Error(`Object not found`);
36201
36349
  newAcc[fieldName] = {
36202
36350
  class: targetClass,
36203
36351
  id: linkedId,
@@ -38327,6 +38475,7 @@ class GitHub {
38327
38475
  }
38328
38476
 
38329
38477
  // ../wabe/src/server/routes/authHandler.ts
38478
+ var validProviders = new Set(Object.values(ProviderEnum));
38330
38479
  var oauthHandlerCallback = async (context, wabeContext) => {
38331
38480
  try {
38332
38481
  const state = decodeURIComponent(context.query.state || "");
@@ -38336,6 +38485,8 @@ var oauthHandlerCallback = async (context, wabeContext) => {
38336
38485
  throw new Error("Authentication failed");
38337
38486
  const codeVerifier = context.getCookie("code_verifier");
38338
38487
  const provider = context.getCookie("provider");
38488
+ if (!provider || !validProviders.has(provider))
38489
+ throw new Error("Authentication failed, invalid provider");
38339
38490
  const { signInWith } = await getGraphqlClient(wabeContext.wabe.config.port).request(gql`
38340
38491
  mutation signInWith(
38341
38492
  $authorizationCode: String!
@@ -38372,7 +38523,7 @@ var oauthHandlerCallback = async (context, wabeContext) => {
38372
38523
  context.res.setCookie("refreshToken", refreshToken, {
38373
38524
  httpOnly: isCookieSession,
38374
38525
  path: "/",
38375
- maxAge: (wabeContext.wabe.config.authentication?.session?.accessTokenExpiresInMs || 60 * 15 * 1000) / 1000,
38526
+ maxAge: (wabeContext.wabe.config.authentication?.session?.refreshTokenExpiresInMs || 1000 * 60 * 60 * 24 * 7) / 1000,
38376
38527
  sameSite,
38377
38528
  secure: true
38378
38529
  });
@@ -38441,6 +38592,7 @@ var authHandler = (context, wabeContext, provider) => {
38441
38592
  };
38442
38593
 
38443
38594
  // ../wabe/src/server/routes/index.ts
38595
+ var validProviders2 = new Set(Object.values(ProviderEnum));
38444
38596
  var defaultRoutes = ({
38445
38597
  devDirectory,
38446
38598
  enableBucketRoute
@@ -38451,8 +38603,8 @@ var defaultRoutes = ({
38451
38603
  path: "/auth/oauth",
38452
38604
  handler: (context) => {
38453
38605
  const provider = context.query.provider;
38454
- if (!provider)
38455
- throw new Error("Authentication failed, provider not found");
38606
+ if (!provider || !validProviders2.has(provider))
38607
+ throw new Error("Authentication failed, invalid provider");
38456
38608
  return authHandler(context, context.wabe, provider);
38457
38609
  }
38458
38610
  },
@@ -38484,18 +38636,25 @@ var wabePrimaryTypesToTypescriptTypes = {
38484
38636
  Email: "string",
38485
38637
  Phone: "string",
38486
38638
  Date: "Date",
38487
- File: "{url: string, name: string}",
38488
38639
  Hash: "string"
38489
38640
  };
38641
+ var getIndent = (options) => options?.indent ?? "\t";
38642
+ var getEndChar = (options) => options?.semi ? ";" : options?.comma ? "," : "";
38643
+ var getQuoteChar = (options) => options?.quote === "double" ? '"' : "'";
38644
+ var getFileTypeString = (options) => {
38645
+ const sep2 = options?.semi ? "; " : ", ";
38646
+ return `{ url: string${sep2}name: string }`;
38647
+ };
38490
38648
  var wabeTypesToTypescriptTypes = ({
38491
38649
  field,
38492
- isInput = false
38650
+ isInput = false,
38651
+ formatOptions
38493
38652
  }) => {
38494
38653
  switch (field.type) {
38495
38654
  case "Date":
38496
- if (isInput)
38497
- return "Date";
38498
- return "string";
38655
+ return isInput ? "Date" : "string";
38656
+ case "File":
38657
+ return getFileTypeString(formatOptions);
38499
38658
  case "Boolean":
38500
38659
  case "Int":
38501
38660
  case "Float":
@@ -38503,12 +38662,13 @@ var wabeTypesToTypescriptTypes = ({
38503
38662
  case "Any":
38504
38663
  case "Email":
38505
38664
  case "Phone":
38506
- case "File":
38507
38665
  case "Hash":
38508
38666
  return wabePrimaryTypesToTypescriptTypes[field.type];
38509
38667
  case "Array":
38510
38668
  if (field.typeValue === "Object")
38511
38669
  return `Array<${field.object.name}>`;
38670
+ if (field.typeValue === "File")
38671
+ return `Array<${getFileTypeString(formatOptions)}>`;
38512
38672
  return `Array<${wabePrimaryTypesToTypescriptTypes[field.typeValue]}>`;
38513
38673
  case "Pointer":
38514
38674
  return field.class;
@@ -38520,28 +38680,31 @@ var wabeTypesToTypescriptTypes = ({
38520
38680
  return field.type;
38521
38681
  }
38522
38682
  };
38683
+ var fieldKey = (name, required) => `${name}${required ? "" : "undefined"}`;
38523
38684
  var generateWabeObject = ({
38524
38685
  object,
38525
38686
  isInput = false,
38526
- prefix = ""
38687
+ prefix = "",
38688
+ formatOptions
38527
38689
  }) => {
38528
- const objectName = object.name;
38690
+ const objectNameWithPrefix = `${prefix}${firstLetterUpperCase(object.name)}`;
38529
38691
  return Object.entries(object.fields).reduce((acc, [fieldName, field]) => {
38530
- const type = wabeTypesToTypescriptTypes({ field, isInput });
38531
- const objectNameWithPrefix = `${prefix}${firstLetterUpperCase(objectName)}`;
38692
+ const type = wabeTypesToTypescriptTypes({ field, isInput, formatOptions });
38532
38693
  if (field.type === "Object" || field.type === "Array" && field.typeValue === "Object") {
38533
38694
  const subObject = generateWabeObject({
38534
38695
  object: field.object,
38535
38696
  isInput,
38536
- prefix: objectNameWithPrefix
38697
+ prefix: objectNameWithPrefix,
38698
+ formatOptions
38537
38699
  });
38538
38700
  const isArray = field.type === "Array";
38701
+ const subTypeName = `${objectNameWithPrefix}${firstLetterUpperCase(field.object.name)}`;
38539
38702
  return {
38540
38703
  ...acc,
38541
38704
  ...subObject,
38542
38705
  [objectNameWithPrefix]: {
38543
38706
  ...acc[objectNameWithPrefix],
38544
- [`${fieldName}${field.required ? "" : "undefined"}`]: `${isArray ? "Array<" : ""}${objectNameWithPrefix}${firstLetterUpperCase(field.object.name)}${isArray ? ">" : ""}`
38707
+ [fieldKey(fieldName, field.required)]: isArray ? `Array<${subTypeName}>` : subTypeName
38545
38708
  }
38546
38709
  };
38547
38710
  }
@@ -38549,242 +38712,165 @@ var generateWabeObject = ({
38549
38712
  ...acc,
38550
38713
  [objectNameWithPrefix]: {
38551
38714
  ...acc[objectNameWithPrefix],
38552
- [`${fieldName}${field.required ? "" : "undefined"}`]: `${type}`
38715
+ [fieldKey(fieldName, field.required)]: `${type}`
38553
38716
  }
38554
38717
  };
38555
38718
  }, {});
38556
38719
  };
38557
- var generateWabeTypes = (classes) => {
38558
- const wabeTypes = classes.reduce((acc, classType) => {
38559
- const { name, fields } = classType;
38560
- const objectsToLoad = [];
38561
- const currentClass = Object.entries(fields).reduce((acc2, [name2, field]) => {
38562
- const type = wabeTypesToTypescriptTypes({ field });
38563
- if (field.type === "Object" || field.type === "Array" && field.typeValue === "Object") {
38564
- const wabeObject = generateWabeObject({ object: field.object });
38565
- objectsToLoad.push(wabeObject);
38566
- }
38567
- return {
38568
- ...acc2,
38569
- [`${name2}${field.required ? "" : "undefined"}`]: type
38570
- };
38571
- }, {});
38572
- const objects = mergeNestedStringRecords(objectsToLoad);
38573
- return {
38574
- ...acc,
38575
- ...objects,
38576
- [name]: { id: "string", ...currentClass }
38577
- };
38578
- }, {});
38579
- return wabeTypes;
38580
- };
38581
- var generateWabeWhereTypes = (classes) => {
38582
- const wabeTypes = classes.reduce((acc, classType) => {
38583
- const { name, fields } = classType;
38584
- const completeName = `Where${firstLetterUpperCase(name)}`;
38585
- const objectsToLoad = [];
38586
- const currentClass = Object.entries(fields).reduce((acc2, [name2, field]) => {
38587
- const type = wabeTypesToTypescriptTypes({ field, isInput: true });
38588
- if (field.type === "Object" || field.type === "Array" && field.typeValue === "Object") {
38589
- const wabeObject = generateWabeObject({
38590
- object: field.object,
38591
- isInput: true
38592
- });
38593
- objectsToLoad.push(wabeObject);
38594
- }
38595
- return {
38596
- ...acc2,
38597
- [`${name2}${field.required ? "" : "undefined"}`]: type
38598
- };
38599
- }, {});
38600
- const objects = mergeNestedStringRecords(objectsToLoad);
38601
- return {
38602
- ...acc,
38603
- ...objects,
38604
- [completeName]: { id: "string", ...currentClass }
38605
- };
38606
- }, {});
38607
- return wabeTypes;
38608
- };
38609
- var generateWabeEnumTypes = (enums) => {
38610
- return Object.values(enums).reduce((acc, { name, values }) => {
38611
- return {
38612
- ...acc,
38613
- [name]: values
38614
- };
38615
- }, {});
38616
- };
38617
- var generateWabeScalarTypes = (scalars) => {
38618
- return Object.values(scalars).reduce((acc, { name }) => {
38720
+ var mergeNestedStringRecords = (records) => Object.assign({}, ...records);
38721
+ var generateClassTypes = (classes, formatOptions, namePrefix = "", isInput = false) => classes.reduce((acc, { name, fields }) => {
38722
+ const objectsToLoad = [];
38723
+ const currentClass = Object.entries(fields).reduce((acc2, [fieldName, field]) => {
38724
+ const type = wabeTypesToTypescriptTypes({ field, isInput, formatOptions });
38725
+ if (field.type === "Object" || field.type === "Array" && field.typeValue === "Object") {
38726
+ objectsToLoad.push(generateWabeObject({ object: field.object, isInput, formatOptions }));
38727
+ }
38619
38728
  return {
38620
- ...acc,
38621
- [name]: "string"
38729
+ ...acc2,
38730
+ [fieldKey(fieldName, field.required)]: type
38622
38731
  };
38623
38732
  }, {});
38624
- };
38625
- var generateWabeMutationOrQueryInput = (mutationOrQueryName, resolver, isMutation) => {
38733
+ const completeName = namePrefix ? `${namePrefix}${firstLetterUpperCase(name)}` : name;
38734
+ return {
38735
+ ...acc,
38736
+ ...mergeNestedStringRecords(objectsToLoad),
38737
+ [completeName]: { id: "string", ...currentClass }
38738
+ };
38739
+ }, {});
38740
+ var generateWabeEnumTypes = (enums) => enums.reduce((acc, { name, values }) => ({ ...acc, [name]: values }), {});
38741
+ var generateWabeScalarTypes = (scalars) => scalars.reduce((acc, { name }) => ({ ...acc, [name]: "string" }), {});
38742
+ var generateWabeMutationOrQueryInput = (mutationOrQueryName, resolver, isMutation, formatOptions) => {
38626
38743
  const objectsToLoad = [];
38627
- const mutationNameWithFirstLetterUpperCase = firstLetterUpperCase(mutationOrQueryName);
38628
- const mutationObject = Object.entries((isMutation ? resolver.args?.input : resolver.args) || {}).reduce((acc, [name, field]) => {
38629
- let type = wabeTypesToTypescriptTypes({ field, isInput: true });
38744
+ const upperName = firstLetterUpperCase(mutationOrQueryName);
38745
+ const resolvedArgs = Object.entries((isMutation ? resolver.args?.input : resolver.args) || {}).reduce((acc, [name, field]) => {
38630
38746
  if (field.type === "Object") {
38631
- type = firstLetterInUpperCase(name);
38632
- const wabeObject = generateWabeObject({
38633
- object: {
38634
- ...field.object,
38635
- name: type
38636
- },
38637
- prefix: mutationNameWithFirstLetterUpperCase
38638
- });
38639
- objectsToLoad.push(wabeObject);
38747
+ const typeName = firstLetterInUpperCase(name);
38748
+ objectsToLoad.push(generateWabeObject({
38749
+ object: { ...field.object, name: typeName },
38750
+ prefix: upperName,
38751
+ formatOptions
38752
+ }));
38640
38753
  return {
38641
38754
  ...acc,
38642
- [`${name}${field.required ? "" : "undefined"}`]: `${mutationNameWithFirstLetterUpperCase}${type}`
38755
+ [fieldKey(name, field.required)]: `${upperName}${typeName}`
38643
38756
  };
38644
38757
  }
38645
38758
  return {
38646
38759
  ...acc,
38647
- [`${name}${field.required ? "" : "undefined"}`]: type
38648
- };
38649
- }, {});
38650
- const objects = mergeNestedStringRecords(objectsToLoad);
38651
- return {
38652
- ...isMutation ? {
38653
- [`${firstLetterInUpperCase(mutationOrQueryName)}Input`]: mutationObject
38654
- } : {},
38655
- [`${isMutation ? "Mutation" : "Query"}${firstLetterInUpperCase(mutationOrQueryName)}Args`]: isMutation ? {
38656
- input: `${firstLetterInUpperCase(mutationOrQueryName)}Input`
38657
- } : mutationObject,
38658
- ...objects
38659
- };
38660
- };
38661
- var generateWabeMutationsAndQueriesTypes = (resolver) => {
38662
- const mutationsObject = Object.entries(resolver.mutations || {}).reduce((acc, [mutationName, mutation]) => {
38663
- return {
38664
- ...acc,
38665
- ...generateWabeMutationOrQueryInput(mutationName, mutation, true)
38666
- };
38667
- }, {});
38668
- const queriesObject = Object.entries(resolver.queries || {}).reduce((acc, [queryName, query]) => {
38669
- return {
38670
- ...acc,
38671
- ...generateWabeMutationOrQueryInput(queryName, query, false)
38760
+ [fieldKey(name, field.required)]: wabeTypesToTypescriptTypes({
38761
+ field,
38762
+ isInput: true,
38763
+ formatOptions
38764
+ })
38672
38765
  };
38673
38766
  }, {});
38767
+ const prettyName = firstLetterInUpperCase(mutationOrQueryName);
38674
38768
  return {
38675
- ...mutationsObject,
38676
- ...queriesObject
38769
+ ...isMutation ? { [`${prettyName}Input`]: resolvedArgs } : {},
38770
+ [`${isMutation ? "Mutation" : "Query"}${prettyName}Args`]: isMutation ? { input: `${prettyName}Input` } : resolvedArgs,
38771
+ ...mergeNestedStringRecords(objectsToLoad)
38677
38772
  };
38678
38773
  };
38679
- var normalizeGraphqlSchemaForComparison = (schemaContent) => {
38680
- try {
38681
- return import_graphql7.print(import_graphql7.parse(schemaContent));
38682
- } catch {
38683
- return schemaContent;
38684
- }
38685
- };
38686
- var mergeNestedStringRecords = (records) => {
38687
- return records.reduce((acc, currentRecord) => ({
38774
+ var generateWabeMutationsAndQueriesTypes = (resolver, formatOptions) => ({
38775
+ ...Object.entries(resolver.mutations || {}).reduce((acc, [name, mutation]) => ({
38688
38776
  ...acc,
38689
- ...currentRecord
38690
- }), {});
38691
- };
38692
- var wrapLongGraphqlFieldArguments = ({
38693
- content,
38694
- indent,
38695
- printWidth
38696
- }) => {
38697
- return content.split(`
38698
- `).map((line) => {
38699
- if (line.length <= printWidth)
38700
- return line;
38701
- const fieldWithArgsMatch = line.match(/^(\s*)([_A-Za-z][_0-9A-Za-z]*)\((.+)\):\s*(.+)$/);
38702
- if (!fieldWithArgsMatch)
38703
- return line;
38704
- const [, fieldIndent = "", fieldName = "", argsString = "", returnType = ""] = fieldWithArgsMatch;
38705
- if (!fieldName || !argsString || !returnType)
38706
- return line;
38707
- const args = argsString.split(",").map((arg) => arg.trim()).filter((arg) => arg.length > 0);
38708
- if (args.length <= 1)
38709
- return line;
38710
- return `${fieldIndent}${fieldName}(
38711
- ${args.map((arg) => `${fieldIndent}${indent}${arg}`).join(`
38712
- `)}
38713
- ${fieldIndent}): ${returnType}`;
38714
- }).join(`
38715
- `);
38716
- };
38777
+ ...generateWabeMutationOrQueryInput(name, mutation, true, formatOptions)
38778
+ }), {}),
38779
+ ...Object.entries(resolver.queries || {}).reduce((acc, [name, query]) => ({
38780
+ ...acc,
38781
+ ...generateWabeMutationOrQueryInput(name, query, false, formatOptions)
38782
+ }), {})
38783
+ });
38717
38784
  var wabeClassRecordToString = (wabeClass, options) => {
38718
- const indent = options?.indent ?? "\t";
38719
- const endChar = options?.semi ? ";" : options?.comma ? "," : "";
38785
+ const indent = getIndent(options);
38786
+ const endChar = getEndChar(options);
38720
38787
  return Object.entries(wabeClass).reduce((acc, [className, fields]) => {
38721
- const fieldsLength = Object.keys(fields).length;
38722
- if (fieldsLength === 0)
38788
+ if (Object.keys(fields).length === 0)
38723
38789
  return `${acc}export type ${className} = {}
38724
38790
 
38725
38791
  `;
38792
+ const body = Object.entries(fields).map(([name, type]) => `${indent}${name.replace("undefined", "?")}: ${type}`).join(`${endChar}
38793
+ `);
38726
38794
  return `${acc}export type ${className} = {
38727
- ${Object.entries(fields).map(([fieldName, fieldType]) => `${indent}${fieldName.replace("undefined", "?")}: ${fieldType}`).join(`${endChar}
38728
- `)}${endChar}
38795
+ ${body}${endChar}
38729
38796
  }
38730
38797
 
38731
38798
  `;
38732
38799
  }, "");
38733
38800
  };
38734
38801
  var wabeEnumRecordToString = (wabeEnum, options) => {
38735
- const indent = options?.indent ?? "\t";
38802
+ const indent = getIndent(options);
38736
38803
  const endChar = options?.semi ? ";" : options?.comma ?? true ? "," : "";
38737
- const quoteString = options?.quote === "double" ? '"' : "'";
38804
+ const quote = getQuoteChar(options);
38738
38805
  return Object.entries(wabeEnum).reduce((acc, [enumName, values]) => {
38739
- const valuesLength = Object.keys(values).length;
38806
+ const hasValues = Object.keys(values).length > 0;
38807
+ const body = Object.entries(values).map(([k, v]) => `${indent}${k} = ${quote}${v}${quote}`).join(`${endChar}
38808
+ `);
38740
38809
  return `${acc}export enum ${enumName} {
38741
- ${Object.entries(values).map(([valueName, value]) => `${indent}${valueName} = ${quoteString}${value}${quoteString}`).join(`${endChar}
38742
- `)}${valuesLength > 0 ? endChar : ""}
38810
+ ${body}${hasValues ? endChar : ""}
38743
38811
  }
38744
38812
 
38745
38813
  `;
38746
38814
  }, "");
38747
38815
  };
38748
- var wabeScalarRecordToString = (wabeScalar) => {
38749
- return Object.entries(wabeScalar).reduce((acc, [scalarName, scalarType]) => {
38750
- return `${acc}export type ${scalarName} = ${scalarType}
38816
+ var wabeScalarRecordToString = (wabeScalar) => Object.entries(wabeScalar).reduce((acc, [name, type]) => `${acc}export type ${name} = ${type}
38751
38817
 
38752
- `;
38753
- }, "");
38754
- };
38818
+ `, "");
38819
+ var wrapLongGraphqlFieldArguments = ({
38820
+ content,
38821
+ indent,
38822
+ printWidth
38823
+ }) => content.split(`
38824
+ `).map((line) => {
38825
+ if (line.length <= printWidth)
38826
+ return line;
38827
+ const match = line.match(/^(\s*)([_A-Za-z][_0-9A-Za-z]*)\((.+)\):\s*(.+)$/);
38828
+ if (!match)
38829
+ return line;
38830
+ const [, fieldIndent = "", fieldName = "", argsString = "", returnType = ""] = match;
38831
+ if (!fieldName || !argsString || !returnType)
38832
+ return line;
38833
+ const args = argsString.split(",").map((arg) => arg.trim()).filter((arg) => arg.length > 0);
38834
+ if (args.length <= 1)
38835
+ return line;
38836
+ const wrappedArgs = args.map((arg) => `${fieldIndent}${indent}${arg}`).join(`
38837
+ `);
38838
+ return `${fieldIndent}${fieldName}(
38839
+ ${wrappedArgs}
38840
+ ${fieldIndent}): ${returnType}`;
38841
+ }).join(`
38842
+ `);
38755
38843
  var generateWabeDevTypes = ({
38756
38844
  scalars,
38757
38845
  enums,
38758
38846
  classes,
38759
38847
  options
38760
38848
  }) => {
38761
- const indent = options?.indent ?? "\t";
38762
- const endChar = options?.semi ? ";" : options?.comma ? "," : "";
38763
- const quoteString = options?.quote === "double" ? '"' : "'";
38764
- const listOfScalars = scalars?.map((scalar) => `${quoteString}${scalar.name}${quoteString}`) || [];
38765
- const wabeScalarType = listOfScalars.length > 0 ? `export type WabeSchemaScalars = ${listOfScalars.join(" | ")}` : `export type WabeSchemaScalars = ${quoteString}${quoteString}`;
38766
- const wabeEnumsGlobalTypes = enums?.map((wabeEnum) => `${wabeEnum.name}: ${wabeEnum.name}`) || [];
38767
- const wabeEnumsGlobalTypesString = wabeEnumsGlobalTypes.length > 0 ? `export type WabeSchemaEnums = {
38768
- ${indent}${wabeEnumsGlobalTypes.join(`${endChar}
38769
- ${indent}`)}${endChar}
38770
- }` : "";
38771
- const allNames = classes.map((schema) => `${schema.name}: ${schema.name}`).filter((schema) => schema);
38772
- const globalWabeTypeString = allNames.length > 0 ? `export type WabeSchemaTypes = {
38773
- ${indent}${allNames.join(`${endChar}
38774
- ${indent}`)}${endChar}
38775
- }` : "";
38776
- const allWhereNames = classes.map((schema) => `${schema.name}: Where${firstLetterUpperCase(schema.name)}`).filter((schema) => schema);
38777
- const globalWabeWhereTypeString = allWhereNames.length > 0 ? `export type WabeSchemaWhereTypes = {
38778
- ${indent}${allWhereNames.join(`${endChar}
38849
+ const indent = getIndent(options);
38850
+ const endChar = getEndChar(options);
38851
+ const quote = getQuoteChar(options);
38852
+ const wabeScalarType = scalars && scalars.length > 0 ? `export type WabeSchemaScalars = ${scalars.map((s) => `${quote}${s.name}${quote}`).join(" | ")}` : `export type WabeSchemaScalars = ${quote}${quote}`;
38853
+ const buildTypeMap = (typeName, entries) => entries.length > 0 ? `export type ${typeName} = {
38854
+ ${indent}${entries.join(`${endChar}
38779
38855
  ${indent}`)}${endChar}
38780
38856
  }` : "";
38857
+ const wabeEnumsString = buildTypeMap("WabeSchemaEnums", enums?.map((e) => `${e.name}: ${e.name}`) ?? []);
38858
+ const wabeTypesString = buildTypeMap("WabeSchemaTypes", classes.map((c) => `${c.name}: ${c.name}`));
38859
+ const wabeWhereString = buildTypeMap("WabeSchemaWhereTypes", classes.map((c) => `${c.name}: Where${firstLetterUpperCase(c.name)}`));
38781
38860
  return `${wabeScalarType}
38782
38861
 
38783
- ${wabeEnumsGlobalTypesString}
38862
+ ${wabeEnumsString}
38784
38863
 
38785
- ${globalWabeTypeString}
38864
+ ${wabeTypesString}
38786
38865
 
38787
- ${globalWabeWhereTypeString}`;
38866
+ ${wabeWhereString}`;
38867
+ };
38868
+ var normalizeGraphqlSchemaForComparison = (schemaContent) => {
38869
+ try {
38870
+ return import_graphql7.print(import_graphql7.parse(schemaContent));
38871
+ } catch {
38872
+ return schemaContent;
38873
+ }
38788
38874
  };
38789
38875
  var generateCodegen = async ({
38790
38876
  schema,
@@ -38793,7 +38879,7 @@ var generateCodegen = async ({
38793
38879
  options
38794
38880
  }) => {
38795
38881
  let graphqlSchemaContent = import_graphql7.printSchema(graphqlSchema);
38796
- const indentStr = options?.indent ?? "\t";
38882
+ const indentStr = getIndent(options);
38797
38883
  const printWidth = options?.printWidth ?? 100;
38798
38884
  const shouldEnsureFinalNewline = options?.finalNewline ?? true;
38799
38885
  const shouldUseSemanticComparison = options?.semanticCompare ?? true;
@@ -38817,22 +38903,13 @@ ${indentation}"""`);
38817
38903
  const resolvers = schema.resolvers ?? {};
38818
38904
  const enums = schema.enums ?? [];
38819
38905
  const scalars = schema.scalars ?? [];
38820
- const wabeClasses = generateWabeTypes(classes);
38821
- const wabeWhereTypes = generateWabeWhereTypes(classes);
38822
- const mutationsAndQueries = generateWabeMutationsAndQueriesTypes(resolvers);
38906
+ const wabeClasses = generateClassTypes(classes, options);
38907
+ const wabeWhereTypes = generateClassTypes(classes, options, "Where", true);
38908
+ const mutationsAndQueries = generateWabeMutationsAndQueriesTypes(resolvers, options);
38823
38909
  const wabeEnumsInString = wabeEnumRecordToString(generateWabeEnumTypes(enums), options);
38824
38910
  const wabeScalarsInString = wabeScalarRecordToString(generateWabeScalarTypes(scalars));
38825
- const wabeObjectsInString = wabeClassRecordToString({
38826
- ...wabeClasses,
38827
- ...wabeWhereTypes,
38828
- ...mutationsAndQueries
38829
- }, options);
38830
- const wabeDevTypes = generateWabeDevTypes({
38831
- scalars,
38832
- enums,
38833
- classes,
38834
- options
38835
- });
38911
+ const wabeObjectsInString = wabeClassRecordToString({ ...wabeClasses, ...wabeWhereTypes, ...mutationsAndQueries }, options);
38912
+ const wabeDevTypes = generateWabeDevTypes({ scalars, enums, classes, options });
38836
38913
  const wabeTsContent = `${wabeEnumsInString}${wabeScalarsInString}${wabeObjectsInString}${wabeDevTypes}
38837
38914
  `;
38838
38915
  let shouldWriteGraphqlSchema = true;
@@ -39175,7 +39252,8 @@ class EmailOTP {
39175
39252
  if (!user.email)
39176
39253
  throw new Error("No user email found");
39177
39254
  const otpClass = new OTP(context.wabe.config.rootKey);
39178
- const otp = otpClass.generate(user.id);
39255
+ const salt = await getOrCreateOtpSalt(context, user.id);
39256
+ const otp = otpClass.generate(user.id, salt);
39179
39257
  const template = context.wabe.config.email?.htmlTemplates?.sendOTPCode;
39180
39258
  await emailController.send({
39181
39259
  from: mainEmail,
@@ -39215,10 +39293,10 @@ class EmailOTP {
39215
39293
  });
39216
39294
  const realUser = users.length > 0 ? users[0] : null;
39217
39295
  const userId = realUser?.id ?? DUMMY_USER_ID2;
39218
- const isDevBypass = !context.wabe.config.isProduction && input.otp === "000000" && realUser !== null;
39219
39296
  const otpClass = new OTP(context.wabe.config.rootKey);
39220
- const isOtpValid = otpClass.verify(input.otp, userId);
39221
- if (realUser && (isOtpValid || isDevBypass)) {
39297
+ const salt = realUser ? await getOrCreateOtpSalt(context, userId) : undefined;
39298
+ const isOtpValid = otpClass.verify(input.otp, userId, salt);
39299
+ if (realUser && isOtpValid) {
39222
39300
  clearRateLimit(context, "verifyChallenge", rateLimitKey);
39223
39301
  return { userId: realUser.id };
39224
39302
  }
@@ -39262,10 +39340,10 @@ class QRCodeOTP {
39262
39340
  });
39263
39341
  const realUser = users.length > 0 ? users[0] : null;
39264
39342
  const userId = realUser?.id ?? DUMMY_USER_ID3;
39265
- const isDevBypass = !context.wabe.config.isProduction && input.otp === "000000" && realUser !== null;
39266
39343
  const otpClass = new OTP(context.wabe.config.rootKey);
39267
- const isOtpValid = otpClass.authenticatorVerify(input.otp, userId);
39268
- if (realUser && (isOtpValid || isDevBypass)) {
39344
+ const salt = realUser ? await getOrCreateOtpSalt(context, userId) : undefined;
39345
+ const isOtpValid = otpClass.authenticatorVerify(input.otp, userId, salt);
39346
+ if (realUser && isOtpValid) {
39269
39347
  clearRateLimit(context, "verifyChallenge", rateLimitKey);
39270
39348
  return { userId: realUser.id };
39271
39349
  }
@@ -51773,34 +51851,39 @@ class Wabe {
51773
51851
  this.server.options("/*", (ctx) => {
51774
51852
  return ctx.res.send("OK");
51775
51853
  }, cors(this.config.security?.corsOptions));
51776
- const rateLimitOptions = this.config.security?.rateLimit;
51854
+ const rateLimitOptions = this.config.security?.rateLimit || (this.config.isProduction ? {
51855
+ numberOfRequests: 200,
51856
+ interval: 60000
51857
+ } : undefined);
51777
51858
  if (rateLimitOptions)
51778
51859
  this.server.beforeHandler(rateLimit(rateLimitOptions));
51779
51860
  this.server.beforeHandler(cors(this.config.security?.corsOptions));
51780
51861
  this.server.beforeHandler(this.config.authentication?.sessionHandler || defaultSessionHandler(this));
51781
- const maxDepth = this.config.security?.maxGraphqlDepth ?? 50;
51862
+ const maxDepth = this.config.security?.maxGraphqlDepth ?? (this.config.isProduction ? 15 : 50);
51863
+ const introspectionDisabled = this.config.security?.disableIntrospection ?? this.config.isProduction;
51864
+ const disableGraphQLDashboard = this.config.security?.disableGraphQLDashboard ?? this.config.isProduction;
51782
51865
  await this.server.usePlugin(M({
51783
51866
  schema: this.config.graphqlSchema,
51784
- allowGetRequests: !(this.config.security?.disableGraphQLDashboard ?? false),
51867
+ allowGetRequests: !disableGraphQLDashboard,
51785
51868
  maskedErrors: this.config.security?.hideSensitiveErrorMessage || this.config.isProduction,
51786
- allowIntrospection: !(this.config.security?.disableIntrospection ?? false),
51869
+ allowIntrospection: !introspectionDisabled,
51787
51870
  maxDepth,
51788
51871
  allowMultipleOperations: true,
51789
51872
  graphqlEndpoint: "/graphql",
51790
51873
  plugins: [
51791
51874
  (() => {
51792
- const introspectionDisabled = new WeakSet;
51875
+ const introspectionDisabledRequests = new WeakSet;
51793
51876
  return {
51794
51877
  onRequestParse: ({ request }) => {
51795
- if (!(this.config.security?.disableIntrospection ?? false))
51878
+ if (!introspectionDisabled)
51796
51879
  return;
51797
- introspectionDisabled.add(request);
51880
+ introspectionDisabledRequests.add(request);
51798
51881
  },
51799
51882
  onValidate: ({
51800
51883
  addValidationRule,
51801
51884
  context
51802
51885
  }) => {
51803
- if (introspectionDisabled.has(context.request)) {
51886
+ if (introspectionDisabledRequests.has(context.request)) {
51804
51887
  addValidationRule(import_graphql52.NoSchemaIntrospectionCustomRule);
51805
51888
  }
51806
51889
  }
@@ -51816,6 +51899,9 @@ class Wabe {
51816
51899
  await initializeRoles(this);
51817
51900
  }
51818
51901
  async close() {
51902
+ this.config.crons?.forEach(({ job }) => {
51903
+ job?.stop?.();
51904
+ });
51819
51905
  await this.controllers.database.close();
51820
51906
  await this.server.stop();
51821
51907
  }
@@ -52356,8 +52442,10 @@ export {
52356
52442
  isArgon2Hash,
52357
52443
  initializeHook,
52358
52444
  hashArgon2,
52445
+ getOrCreateOtpSalt,
52359
52446
  getDefaultHooks,
52360
52447
  getDatabaseController,
52448
+ generateOtpSalt,
52361
52449
  generateCodegen,
52362
52450
  encryptDeterministicToken,
52363
52451
  defaultRoutes,