strapi-plugin-oidc 1.8.4 → 1.8.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.
@@ -345,9 +345,149 @@ function clearAuthCookies(strapi2, ctx) {
345
345
  ctx.cookies.set(name, "", rootPathOptions);
346
346
  }
347
347
  }
348
- const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
349
- function isValidEmail(email) {
350
- return EMAIL_REGEX.test(email);
348
+ class OidcError extends Error {
349
+ kind;
350
+ cause;
351
+ constructor(kind, message, cause) {
352
+ super(message);
353
+ this.name = "OidcError";
354
+ this.kind = kind;
355
+ this.cause = cause;
356
+ }
357
+ }
358
+ const OIDC_ERROR_DISPATCH = {
359
+ nonce_mismatch: { action: "nonce_mismatch", code: errorCodes.NONCE_MISMATCH },
360
+ token_exchange_failed: {
361
+ action: "token_exchange_failed",
362
+ code: errorCodes.TOKEN_EXCHANGE_FAILED
363
+ },
364
+ id_token_parse_failed: {
365
+ action: "login_failure",
366
+ code: errorCodes.ID_TOKEN_PARSE_FAILED,
367
+ key: "id_token_parse_failed"
368
+ },
369
+ userinfo_fetch_failed: {
370
+ action: "login_failure",
371
+ code: errorCodes.USERINFO_FETCH_FAILED,
372
+ key: "userinfo_fetch_failed"
373
+ },
374
+ user_creation_failed: {
375
+ action: "login_failure",
376
+ code: errorCodes.USER_CREATION_FAILED,
377
+ key: "user_creation_failed"
378
+ },
379
+ whitelist_rejected: {
380
+ action: "whitelist_rejected",
381
+ code: errorCodes.WHITELIST_CHECK_FAILED,
382
+ key: "whitelist_rejected"
383
+ },
384
+ invalid_email: {
385
+ action: "login_failure",
386
+ code: errorCodes.TOKEN_EXCHANGE_FAILED,
387
+ key: "sign_in_unknown"
388
+ },
389
+ email_not_verified: {
390
+ action: "email_not_verified",
391
+ code: errorCodes.EMAIL_NOT_VERIFIED,
392
+ key: "email_not_verified"
393
+ },
394
+ id_token_invalid: {
395
+ action: "id_token_invalid",
396
+ code: errorCodes.ID_TOKEN_INVALID,
397
+ key: "id_token_invalid"
398
+ },
399
+ unknown: {
400
+ action: "login_failure",
401
+ code: errorCodes.TOKEN_EXCHANGE_FAILED,
402
+ key: "sign_in_unknown"
403
+ }
404
+ };
405
+ function toMessage(e) {
406
+ return e instanceof Error ? e.message : String(e);
407
+ }
408
+ const REQUIRED_CONFIG_KEYS = [
409
+ "OIDC_DISCOVERY_URL",
410
+ "OIDC_CLIENT_ID",
411
+ "OIDC_CLIENT_SECRET",
412
+ "OIDC_REDIRECT_URI",
413
+ "OIDC_SCOPE",
414
+ "OIDC_FAMILY_NAME_FIELD",
415
+ "OIDC_GIVEN_NAME_FIELD",
416
+ // Populated at bootstrap from OIDC_DISCOVERY_URL — checked here as a runtime safety net
417
+ "OIDC_TOKEN_ENDPOINT",
418
+ "OIDC_USERINFO_ENDPOINT",
419
+ "OIDC_AUTHORIZATION_ENDPOINT"
420
+ ];
421
+ const jwksCache = /* @__PURE__ */ new Map();
422
+ let jwksDisabledWarned = false;
423
+ function getJwks(uri) {
424
+ let jwks = jwksCache.get(uri);
425
+ if (!jwks) {
426
+ jwks = jose.createRemoteJWKSet(new URL(uri));
427
+ jwksCache.set(uri, jwks);
428
+ }
429
+ return jwks;
430
+ }
431
+ async function verifyIdToken(idToken, config2) {
432
+ const jwksUri = config2.OIDC_JWKS_URI;
433
+ const issuer = config2.OIDC_ISSUER;
434
+ if (!jwksUri) {
435
+ if (!jwksDisabledWarned) {
436
+ jwksDisabledWarned = true;
437
+ strapi.log.warn(errorMessages.JWKS_URI_NOT_CONFIGURED);
438
+ }
439
+ return null;
440
+ }
441
+ try {
442
+ const jwks = getJwks(jwksUri);
443
+ const { payload } = await jose.jwtVerify(idToken, jwks, {
444
+ issuer: issuer || void 0,
445
+ audience: config2.OIDC_CLIENT_ID
446
+ });
447
+ return payload;
448
+ } catch (e) {
449
+ if (e instanceof jose.errors.JWTClaimValidationFailed || e instanceof jose.errors.JWSSignatureVerificationFailed || e instanceof jose.errors.JWTExpired || e instanceof jose.errors.JWTInvalid || e instanceof jose.errors.JWSInvalid) {
450
+ const msg = toMessage(e);
451
+ throw new OidcError("id_token_invalid", msg, e);
452
+ }
453
+ throw e;
454
+ }
455
+ }
456
+ function configValidation() {
457
+ const config2 = strapi.config.get("plugin::strapi-plugin-oidc");
458
+ const missing = REQUIRED_CONFIG_KEYS.filter((key) => !config2[key]);
459
+ if (missing.length === 0) {
460
+ return config2;
461
+ }
462
+ throw new Error(errorMessages.MISSING_CONFIG(missing.join(", ")));
463
+ }
464
+ async function oidcSignIn(ctx) {
465
+ const { OIDC_CLIENT_ID, OIDC_REDIRECT_URI, OIDC_SCOPE, OIDC_AUTHORIZATION_ENDPOINT } = configValidation();
466
+ const { code_verifier: codeVerifier, code_challenge: codeChallenge } = await pkceChallenge__default.default();
467
+ const state = node_crypto.randomBytes(32).toString("base64url");
468
+ const nonce = node_crypto.randomBytes(32).toString("base64url");
469
+ const cookieOptions = {
470
+ httpOnly: true,
471
+ maxAge: 6e5,
472
+ secure: shouldMarkSecure(strapi, ctx),
473
+ sameSite: "lax"
474
+ };
475
+ ctx.cookies.set("oidc_code_verifier", codeVerifier, cookieOptions);
476
+ ctx.cookies.set("oidc_state", state, cookieOptions);
477
+ ctx.cookies.set("oidc_nonce", nonce, cookieOptions);
478
+ const params = new URLSearchParams({
479
+ response_type: "code",
480
+ client_id: OIDC_CLIENT_ID,
481
+ redirect_uri: OIDC_REDIRECT_URI,
482
+ scope: OIDC_SCOPE,
483
+ code_challenge: codeChallenge,
484
+ code_challenge_method: "S256",
485
+ state,
486
+ nonce
487
+ });
488
+ const authorizationUrl = `${OIDC_AUTHORIZATION_ENDPOINT}?${params.toString()}`;
489
+ ctx.set("Location", authorizationUrl);
490
+ return ctx.send({}, 302);
351
491
  }
352
492
  const en = {
353
493
  "global.plugins.strapi-plugin-oidc": "OIDC Plugin",
@@ -510,63 +650,6 @@ const authPageMessages = (locale) => ({
510
650
  errorTitle: t(locale, "auth.page.error.title", "Authentication Failed"),
511
651
  returnToLogin: t(locale, "auth.page.error.returnToLogin", "Return to Login")
512
652
  });
513
- class OidcError extends Error {
514
- kind;
515
- cause;
516
- constructor(kind, message, cause) {
517
- super(message);
518
- this.name = "OidcError";
519
- this.kind = kind;
520
- this.cause = cause;
521
- }
522
- }
523
- const OIDC_ERROR_DISPATCH = {
524
- nonce_mismatch: { action: "nonce_mismatch", code: errorCodes.NONCE_MISMATCH },
525
- token_exchange_failed: {
526
- action: "token_exchange_failed",
527
- code: errorCodes.TOKEN_EXCHANGE_FAILED
528
- },
529
- id_token_parse_failed: {
530
- action: "login_failure",
531
- code: errorCodes.ID_TOKEN_PARSE_FAILED,
532
- key: "id_token_parse_failed"
533
- },
534
- userinfo_fetch_failed: {
535
- action: "login_failure",
536
- code: errorCodes.USERINFO_FETCH_FAILED,
537
- key: "userinfo_fetch_failed"
538
- },
539
- user_creation_failed: {
540
- action: "login_failure",
541
- code: errorCodes.USER_CREATION_FAILED,
542
- key: "user_creation_failed"
543
- },
544
- whitelist_rejected: {
545
- action: "whitelist_rejected",
546
- code: errorCodes.WHITELIST_CHECK_FAILED,
547
- key: "whitelist_rejected"
548
- },
549
- invalid_email: {
550
- action: "login_failure",
551
- code: errorCodes.TOKEN_EXCHANGE_FAILED,
552
- key: "sign_in_unknown"
553
- },
554
- email_not_verified: {
555
- action: "email_not_verified",
556
- code: errorCodes.EMAIL_NOT_VERIFIED,
557
- key: "email_not_verified"
558
- },
559
- id_token_invalid: {
560
- action: "id_token_invalid",
561
- code: errorCodes.ID_TOKEN_INVALID,
562
- key: "id_token_invalid"
563
- },
564
- unknown: {
565
- action: "login_failure",
566
- code: errorCodes.TOKEN_EXCHANGE_FAILED,
567
- key: "sign_in_unknown"
568
- }
569
- };
570
653
  const TRUSTED_IP_HEADERS = /* @__PURE__ */ new Set([
571
654
  "cf-connecting-ip",
572
655
  "true-client-ip",
@@ -597,128 +680,9 @@ function getClientIp(ctx) {
597
680
  }
598
681
  return ctx.ip;
599
682
  }
600
- function toMessage(e) {
601
- return e instanceof Error ? e.message : String(e);
602
- }
603
- const REQUIRED_CONFIG_KEYS = [
604
- "OIDC_DISCOVERY_URL",
605
- "OIDC_CLIENT_ID",
606
- "OIDC_CLIENT_SECRET",
607
- "OIDC_REDIRECT_URI",
608
- "OIDC_SCOPE",
609
- "OIDC_FAMILY_NAME_FIELD",
610
- "OIDC_GIVEN_NAME_FIELD",
611
- // Populated at bootstrap from OIDC_DISCOVERY_URL — checked here as a runtime safety net
612
- "OIDC_TOKEN_ENDPOINT",
613
- "OIDC_USERINFO_ENDPOINT",
614
- "OIDC_AUTHORIZATION_ENDPOINT"
615
- ];
616
- const LOGOUT_USERINFO_TIMEOUT_MS = 1500;
617
- const jwksCache = /* @__PURE__ */ new Map();
618
- let jwksDisabledWarned = false;
619
- function getJwks(uri) {
620
- let jwks = jwksCache.get(uri);
621
- if (!jwks) {
622
- jwks = jose.createRemoteJWKSet(new URL(uri));
623
- jwksCache.set(uri, jwks);
624
- }
625
- return jwks;
626
- }
627
- async function verifyIdToken(idToken, config2) {
628
- const jwksUri = config2.OIDC_JWKS_URI;
629
- const issuer = config2.OIDC_ISSUER;
630
- if (!jwksUri) {
631
- if (!jwksDisabledWarned) {
632
- jwksDisabledWarned = true;
633
- strapi.log.warn(errorMessages.JWKS_URI_NOT_CONFIGURED);
634
- }
635
- return null;
636
- }
637
- try {
638
- const jwks = getJwks(jwksUri);
639
- const { payload } = await jose.jwtVerify(idToken, jwks, {
640
- issuer: issuer || void 0,
641
- audience: config2.OIDC_CLIENT_ID
642
- });
643
- return payload;
644
- } catch (e) {
645
- if (e instanceof jose.errors.JWTClaimValidationFailed || e instanceof jose.errors.JWSSignatureVerificationFailed || e instanceof jose.errors.JWTExpired || e instanceof jose.errors.JWTInvalid || e instanceof jose.errors.JWSInvalid) {
646
- const msg = toMessage(e);
647
- throw new OidcError("id_token_invalid", msg, e);
648
- }
649
- throw e;
650
- }
651
- }
652
- function configValidation() {
653
- const config2 = strapi.config.get("plugin::strapi-plugin-oidc");
654
- const missing = REQUIRED_CONFIG_KEYS.filter((key) => !config2[key]);
655
- if (missing.length === 0) {
656
- return config2;
657
- }
658
- throw new Error(errorMessages.MISSING_CONFIG(missing.join(", ")));
659
- }
660
- async function oidcSignIn(ctx) {
661
- const { OIDC_CLIENT_ID, OIDC_REDIRECT_URI, OIDC_SCOPE, OIDC_AUTHORIZATION_ENDPOINT } = configValidation();
662
- const { code_verifier: codeVerifier, code_challenge: codeChallenge } = await pkceChallenge__default.default();
663
- const state = node_crypto.randomBytes(32).toString("base64url");
664
- const nonce = node_crypto.randomBytes(32).toString("base64url");
665
- const cookieOptions = {
666
- httpOnly: true,
667
- maxAge: 6e5,
668
- secure: shouldMarkSecure(strapi, ctx),
669
- sameSite: "lax"
670
- };
671
- ctx.cookies.set("oidc_code_verifier", codeVerifier, cookieOptions);
672
- ctx.cookies.set("oidc_state", state, cookieOptions);
673
- ctx.cookies.set("oidc_nonce", nonce, cookieOptions);
674
- const params = new URLSearchParams({
675
- response_type: "code",
676
- client_id: OIDC_CLIENT_ID,
677
- redirect_uri: OIDC_REDIRECT_URI,
678
- scope: OIDC_SCOPE,
679
- code_challenge: codeChallenge,
680
- code_challenge_method: "S256",
681
- state,
682
- nonce
683
- });
684
- const authorizationUrl = `${OIDC_AUTHORIZATION_ENDPOINT}?${params.toString()}`;
685
- ctx.set("Location", authorizationUrl);
686
- return ctx.send({}, 302);
687
- }
688
- async function exchangeTokenAndFetchUserInfo(config2, params, expectedNonce) {
689
- const response = await fetch(config2.OIDC_TOKEN_ENDPOINT, {
690
- method: "POST",
691
- body: params,
692
- headers: {
693
- "Content-Type": "application/x-www-form-urlencoded"
694
- }
695
- });
696
- if (!response.ok) {
697
- throw new OidcError("token_exchange_failed", errorMessages.TOKEN_EXCHANGE_FAILED);
698
- }
699
- const tokenData = await response.json();
700
- if (tokenData.id_token) {
701
- const verifiedPayload = await verifyIdToken(tokenData.id_token, config2);
702
- try {
703
- const idTokenPayload = verifiedPayload ?? JSON.parse(
704
- Buffer.from(tokenData.id_token.split(".")[1], "base64url").toString("utf8")
705
- );
706
- if (idTokenPayload.nonce !== expectedNonce) {
707
- throw new OidcError("nonce_mismatch", errorMessages.NONCE_MISMATCH);
708
- }
709
- } catch (e) {
710
- if (e instanceof OidcError) throw e;
711
- throw new OidcError("id_token_parse_failed", errorMessages.ID_TOKEN_PARSE_FAILED, e);
712
- }
713
- }
714
- const userResponse = await fetch(config2.OIDC_USERINFO_ENDPOINT, {
715
- headers: { Authorization: `Bearer ${tokenData.access_token}` }
716
- });
717
- if (!userResponse.ok) {
718
- throw new OidcError("userinfo_fetch_failed", errorMessages.USERINFO_FETCH_FAILED);
719
- }
720
- const userInfo = await userResponse.json();
721
- return { userInfo, accessToken: tokenData.access_token };
683
+ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
684
+ function isValidEmail(email) {
685
+ return EMAIL_REGEX.test(email);
722
686
  }
723
687
  function collectGroupMapRoleNames(userInfo, config2) {
724
688
  const rawGroups = userInfo[config2.OIDC_GROUP_FIELD];
@@ -898,6 +862,61 @@ function classifyOidcError(e, userInfo) {
898
862
  params
899
863
  };
900
864
  }
865
+ async function handleCallbackError(e, userInfo, auditLog2, oauthService2, ctx) {
866
+ const errorInfo = classifyOidcError(e, userInfo);
867
+ const message = toMessage(e);
868
+ await auditLog2.log({
869
+ action: errorInfo.action,
870
+ email: userInfo?.email,
871
+ ip: getClientIp(ctx),
872
+ detailsKey: errorInfo.action,
873
+ detailsParams: errorInfo.action === "login_failure" ? { message } : void 0
874
+ });
875
+ strapi.log.error({
876
+ code: errorInfo.code,
877
+ phase: "oidc_callback",
878
+ message: e instanceof Error ? e.message : "Unknown sign-in error",
879
+ detail: errorInfo.key ? getErrorDetail(errorInfo.key, errorInfo.params) : void 0,
880
+ email: userInfo?.email
881
+ });
882
+ const locale = negotiateLocale(ctx.request.headers["accept-language"]);
883
+ ctx.send(oauthService2.renderSignUpError(userFacingMessages(locale).signInError, locale));
884
+ }
885
+ async function exchangeTokenAndFetchUserInfo(config2, params, expectedNonce) {
886
+ const response = await fetch(config2.OIDC_TOKEN_ENDPOINT, {
887
+ method: "POST",
888
+ body: params,
889
+ headers: {
890
+ "Content-Type": "application/x-www-form-urlencoded"
891
+ }
892
+ });
893
+ if (!response.ok) {
894
+ throw new OidcError("token_exchange_failed", errorMessages.TOKEN_EXCHANGE_FAILED);
895
+ }
896
+ const tokenData = await response.json();
897
+ if (tokenData.id_token) {
898
+ const verifiedPayload = await verifyIdToken(tokenData.id_token, config2);
899
+ try {
900
+ const idTokenPayload = verifiedPayload ?? JSON.parse(
901
+ Buffer.from(tokenData.id_token.split(".")[1], "base64url").toString("utf8")
902
+ );
903
+ if (idTokenPayload.nonce !== expectedNonce) {
904
+ throw new OidcError("nonce_mismatch", errorMessages.NONCE_MISMATCH);
905
+ }
906
+ } catch (e) {
907
+ if (e instanceof OidcError) throw e;
908
+ throw new OidcError("id_token_parse_failed", errorMessages.ID_TOKEN_PARSE_FAILED, e);
909
+ }
910
+ }
911
+ const userResponse = await fetch(config2.OIDC_USERINFO_ENDPOINT, {
912
+ headers: { Authorization: `Bearer ${tokenData.access_token}` }
913
+ });
914
+ if (!userResponse.ok) {
915
+ throw new OidcError("userinfo_fetch_failed", errorMessages.USERINFO_FETCH_FAILED);
916
+ }
917
+ const userInfo = await userResponse.json();
918
+ return { userInfo, accessToken: tokenData.access_token };
919
+ }
901
920
  function readAndClearPkceCookies(ctx) {
902
921
  const oidcState = ctx.cookies.get("oidc_state");
903
922
  const codeVerifier = ctx.cookies.get("oidc_code_verifier");
@@ -931,26 +950,6 @@ async function logSuccessfulAuth(auditLog2, ctx, user, userCreated, rolesUpdated
931
950
  }
932
951
  await Promise.all(entries);
933
952
  }
934
- async function handleCallbackError(e, userInfo, auditLog2, oauthService2, ctx) {
935
- const errorInfo = classifyOidcError(e, userInfo);
936
- const message = toMessage(e);
937
- await auditLog2.log({
938
- action: errorInfo.action,
939
- email: userInfo?.email,
940
- ip: getClientIp(ctx),
941
- detailsKey: errorInfo.action,
942
- detailsParams: errorInfo.action === "login_failure" ? { message } : void 0
943
- });
944
- strapi.log.error({
945
- code: errorInfo.code,
946
- phase: "oidc_callback",
947
- message: e instanceof Error ? e.message : "Unknown sign-in error",
948
- detail: errorInfo.key ? getErrorDetail(errorInfo.key, errorInfo.params) : void 0,
949
- email: userInfo?.email
950
- });
951
- const locale = negotiateLocale(ctx.request.headers["accept-language"]);
952
- ctx.send(oauthService2.renderSignUpError(userFacingMessages(locale).signInError, locale));
953
- }
954
953
  async function oidcSignInCallback(ctx) {
955
954
  const config2 = configValidation();
956
955
  const oauthService2 = getOauthService();
@@ -1018,6 +1017,7 @@ async function oidcSignInCallback(ctx) {
1018
1017
  await handleCallbackError(e, userInfo, auditLog2, oauthService2, ctx);
1019
1018
  }
1020
1019
  }
1020
+ const LOGOUT_USERINFO_TIMEOUT_MS = 1500;
1021
1021
  async function isProviderSessionExpired(userinfoEndpoint, accessToken) {
1022
1022
  try {
1023
1023
  const response = await fetch(userinfoEndpoint, {
@@ -2061,6 +2061,7 @@ function translateDetails(key, params) {
2061
2061
  if (!translation) return null;
2062
2062
  return interpolate(translation, params);
2063
2063
  }
2064
+ const DAY_MS = 864e5;
2064
2065
  const STRING_OP_MAP = {
2065
2066
  $eq: (v) => v,
2066
2067
  $contains: (v) => ({ $containsi: v }),
@@ -2073,9 +2074,11 @@ const DATE_OP_MAP = {
2073
2074
  $lt: (v) => ({ $lt: v }),
2074
2075
  $lte: (v) => ({ $lte: v }),
2075
2076
  $between: (v) => ({ $between: v })
2076
- // $in is handled separately: each ISO day-start is expanded to a [day, day+1) range.
2077
2077
  };
2078
- const DAY_MS = 864e5;
2078
+ const ACTION_OP_MAP = {
2079
+ $eq: (v) => v,
2080
+ $in: (v) => ({ $in: v })
2081
+ };
2079
2082
  function nextDayIso(iso) {
2080
2083
  return new Date(new Date(iso).getTime() + DAY_MS).toISOString();
2081
2084
  }
@@ -2083,10 +2086,6 @@ function expandCreatedAtInToDayRanges(days) {
2083
2086
  const ranges = days.map((d) => ({ createdAt: { $gte: d, $lt: nextDayIso(d) } }));
2084
2087
  return ranges.length === 1 ? ranges[0] : { $or: ranges };
2085
2088
  }
2086
- const ACTION_OP_MAP = {
2087
- $eq: (v) => v,
2088
- $in: (v) => ({ $in: v })
2089
- };
2090
2089
  function mapFieldFilter(conditions, field, filter, opMap) {
2091
2090
  for (const [op, value] of Object.entries(filter)) {
2092
2091
  const transform = opMap[op];
@@ -2095,20 +2094,26 @@ function mapFieldFilter(conditions, field, filter, opMap) {
2095
2094
  if (result !== void 0) conditions.push({ [field]: result });
2096
2095
  }
2097
2096
  }
2097
+ function buildDateConditions(conditions, createdAt) {
2098
+ if (!createdAt) return;
2099
+ const { $in: inDays, ...rest } = createdAt;
2100
+ if (Array.isArray(inDays) && inDays.length > 0) {
2101
+ conditions.push(expandCreatedAtInToDayRanges(inDays));
2102
+ }
2103
+ for (const [op, value] of Object.entries(rest)) {
2104
+ const transform = DATE_OP_MAP[op];
2105
+ if (transform) {
2106
+ const result = transform(value);
2107
+ if (result !== void 0) conditions.push({ createdAt: result });
2108
+ }
2109
+ }
2110
+ }
2098
2111
  function buildWhereClause(filters) {
2099
2112
  const conditions = [];
2100
2113
  if (filters.action) mapFieldFilter(conditions, "action", filters.action, ACTION_OP_MAP);
2101
2114
  if (filters.email) mapFieldFilter(conditions, "email", filters.email, STRING_OP_MAP);
2102
2115
  if (filters.ip) mapFieldFilter(conditions, "ip", filters.ip, STRING_OP_MAP);
2103
- if (filters.createdAt) {
2104
- const { $in: inDays, ...rest } = filters.createdAt;
2105
- if (Array.isArray(inDays) && inDays.length > 0) {
2106
- conditions.push(expandCreatedAtInToDayRanges(inDays));
2107
- }
2108
- if (Object.keys(rest).length > 0) {
2109
- mapFieldFilter(conditions, "createdAt", rest, DATE_OP_MAP);
2110
- }
2111
- }
2116
+ if (filters.createdAt) buildDateConditions(conditions, filters.createdAt);
2112
2117
  if (conditions.length === 0) return {};
2113
2118
  if (conditions.length === 1) return conditions[0];
2114
2119
  return { $and: conditions };