@spfn/auth 0.2.0-beta.72 → 0.2.0-beta.74

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/server.js CHANGED
@@ -8777,7 +8777,56 @@ async function persistNativeLogin(identity, params) {
8777
8777
  // src/server/routes/auth/index.ts
8778
8778
  init_esm();
8779
8779
  import { Transactional } from "@spfn/core/db";
8780
- import { rateLimit } from "@spfn/core/middleware";
8780
+ import { rateLimitPolicy } from "@spfn/core/middleware";
8781
+
8782
+ // src/server/lib/rate-limit-keys.ts
8783
+ import { getClientIp } from "@spfn/core/middleware";
8784
+ async function readJsonBody(c) {
8785
+ try {
8786
+ return await c.req.json();
8787
+ } catch {
8788
+ return {};
8789
+ }
8790
+ }
8791
+ function accountKey(body) {
8792
+ if (typeof body.email === "string" && body.email.trim()) {
8793
+ return `email:${body.email.trim().toLowerCase()}`;
8794
+ }
8795
+ if (typeof body.phone === "string" && body.phone.trim()) {
8796
+ return `phone:${body.phone.trim()}`;
8797
+ }
8798
+ return void 0;
8799
+ }
8800
+ function targetKey(body) {
8801
+ if (typeof body.target !== "string" || !body.target.trim()) {
8802
+ return void 0;
8803
+ }
8804
+ const type = typeof body.targetType === "string" ? body.targetType : "target";
8805
+ const value = type === "email" ? body.target.trim().toLowerCase() : body.target.trim();
8806
+ return `${type}:${value}`;
8807
+ }
8808
+ function byIpAndAccount(options = {}) {
8809
+ return async (c) => {
8810
+ const body = await readJsonBody(c);
8811
+ const account = accountKey(body);
8812
+ return [
8813
+ { key: `ip:${getClientIp(c)}`, limit: options.ipLimit },
8814
+ account ? `acct:${account}` : void 0
8815
+ ];
8816
+ };
8817
+ }
8818
+ function byIpAndTarget(options = {}) {
8819
+ return async (c) => {
8820
+ const body = await readJsonBody(c);
8821
+ const target = targetKey(body);
8822
+ return [
8823
+ { key: `ip:${getClientIp(c)}`, limit: options.ipLimit },
8824
+ target ? `tgt:${target}` : void 0
8825
+ ];
8826
+ };
8827
+ }
8828
+
8829
+ // src/server/routes/auth/index.ts
8781
8830
  import { defineRouter, route } from "@spfn/core/route";
8782
8831
  var sendVerificationCode = route.post("/_auth/codes").input({
8783
8832
  body: Type.Object({
@@ -8787,7 +8836,7 @@ var sendVerificationCode = route.post("/_auth/codes").input({
8787
8836
  targetType: TargetTypeSchema,
8788
8837
  purpose: VerificationPurposeSchema
8789
8838
  })
8790
- }).use([rateLimit({ limit: 5, windowMs: 6e4 })]).skip(["auth"]).handler(async (c) => {
8839
+ }).use([rateLimitPolicy("auth-code-send", { limit: 5, windowMs: 6e4, by: byIpAndTarget({ ipLimit: 20 }) })]).skip(["auth"]).handler(async (c) => {
8791
8840
  const { body } = await c.data();
8792
8841
  return await sendVerificationCodeService(body);
8793
8842
  });
@@ -8805,7 +8854,7 @@ var verifyCode = route.post("/_auth/codes/verify").input({
8805
8854
  }),
8806
8855
  purpose: VerificationPurposeSchema
8807
8856
  })
8808
- }).use([rateLimit({ limit: 10, windowMs: 6e4 })]).skip(["auth"]).handler(async (c) => {
8857
+ }).use([rateLimitPolicy("auth-code-verify", { limit: 10, windowMs: 6e4, by: byIpAndTarget({ ipLimit: 50 }) })]).skip(["auth"]).handler(async (c) => {
8809
8858
  const { body } = await c.data();
8810
8859
  return await verifyCodeService(body);
8811
8860
  });
@@ -8832,7 +8881,7 @@ var register = route.post("/_auth/register").input({
8832
8881
  fingerprint: Type.String({ description: "Key fingerprint" }),
8833
8882
  algorithm: Type.Union(KEY_ALGORITHM.map((algo) => Type.Literal(algo)), { description: "Signature algorithm" })
8834
8883
  })
8835
- }).use([Transactional()]).skip(["auth"]).handler(async (c) => {
8884
+ }).use([rateLimitPolicy("auth-register", { limit: 10, windowMs: 6e4, by: byIpAndAccount({ ipLimit: 100 }) }), Transactional()]).skip(["auth"]).handler(async (c) => {
8836
8885
  const { body } = await c.data();
8837
8886
  return await registerService(body);
8838
8887
  });
@@ -8857,7 +8906,7 @@ var login = route.post("/_auth/login").input({
8857
8906
  algorithm: Type.Union(KEY_ALGORITHM.map((algo) => Type.Literal(algo)), { description: "Signature algorithm" }),
8858
8907
  oldKeyId: Type.Optional(Type.String({ description: "Previous key ID for rotation" }))
8859
8908
  })
8860
- }).use([rateLimit({ limit: 10, windowMs: 6e4 }), Transactional()]).skip(["auth"]).handler(async (c) => {
8909
+ }).use([rateLimitPolicy("auth-login", { limit: 10, windowMs: 6e4, by: byIpAndAccount({ ipLimit: 100 }) }), Transactional()]).skip(["auth"]).handler(async (c) => {
8861
8910
  const { body } = await c.data();
8862
8911
  return await loginService(body);
8863
8912
  });
@@ -8897,7 +8946,7 @@ var changePassword = route.put("/_auth/password").input({
8897
8946
  })),
8898
8947
  newPassword: PasswordSchema
8899
8948
  })
8900
- }).handler(async (c) => {
8949
+ }).use([rateLimitPolicy("auth-password-change", { limit: 10, windowMs: 6e4 })]).handler(async (c) => {
8901
8950
  const { body } = await c.data();
8902
8951
  const user = getUser(c);
8903
8952
  await changePasswordService({
@@ -9257,6 +9306,7 @@ function extractOTTHeader(header) {
9257
9306
  init_types();
9258
9307
  init_esm();
9259
9308
  import { Transactional as Transactional2 } from "@spfn/core/db";
9309
+ import { rateLimitPolicy as rateLimitPolicy2 } from "@spfn/core/middleware";
9260
9310
  import { defineRouter as defineRouter2, route as route2 } from "@spfn/core/route";
9261
9311
  var INVITATION_STATUSES2 = ["pending", "accepted", "expired", "cancelled"];
9262
9312
  var getInvitation = route2.get("/_auth/invitations/:token").input({
@@ -9266,7 +9316,7 @@ var getInvitation = route2.get("/_auth/invitations/:token").input({
9266
9316
  description: "Invitation token (UUID v4)"
9267
9317
  })
9268
9318
  })
9269
- }).skip(["auth"]).handler(async (c) => {
9319
+ }).use([rateLimitPolicy2("auth-invitation-lookup", { limit: 30, windowMs: 6e4 })]).skip(["auth"]).handler(async (c) => {
9270
9320
  const { params } = await c.data();
9271
9321
  const token = params.token;
9272
9322
  const validation = await validateInvitation(token);
@@ -9305,7 +9355,7 @@ var acceptInvitation2 = route2.post("/_auth/invitations/accept").input({
9305
9355
  fingerprint: Type.String({ description: "Key fingerprint" }),
9306
9356
  algorithm: Type.Union(KEY_ALGORITHM.map((algo) => Type.Literal(algo)), { description: "Signature algorithm" })
9307
9357
  })
9308
- }).use([Transactional2()]).skip(["auth"]).handler(async (c) => {
9358
+ }).use([rateLimitPolicy2("auth-invitation-accept", { limit: 10, windowMs: 6e4 }), Transactional2()]).skip(["auth"]).handler(async (c) => {
9309
9359
  const { body } = await c.data();
9310
9360
  return await acceptInvitation({
9311
9361
  token: body.token,
@@ -9455,6 +9505,7 @@ var invitationRouter = defineRouter2({
9455
9505
 
9456
9506
  // src/server/routes/users/index.ts
9457
9507
  init_esm();
9508
+ import { rateLimitPolicy as rateLimitPolicy3 } from "@spfn/core/middleware";
9458
9509
  import { defineRouter as defineRouter3, route as route3 } from "@spfn/core/route";
9459
9510
  var getUserProfile = route3.get("/_auth/users/profile").handler(async (c) => {
9460
9511
  const { userId } = getAuth(c);
@@ -9486,7 +9537,7 @@ var checkUsername = route3.get("/_auth/users/username/check").input({
9486
9537
  query: Type.Object({
9487
9538
  username: Type.String({ minLength: 1 })
9488
9539
  })
9489
- }).handler(async (c) => {
9540
+ }).use([rateLimitPolicy3("auth-username-check", { limit: 30, windowMs: 6e4 })]).handler(async (c) => {
9490
9541
  const { query } = await c.data();
9491
9542
  return { available: await checkUsernameAvailableService(query.username) };
9492
9543
  });
@@ -9711,7 +9762,7 @@ var deleteCookie = (c, name, opt) => {
9711
9762
  init_types();
9712
9763
  import { Transactional as Transactional3 } from "@spfn/core/db";
9713
9764
  import { ValidationError as ValidationError7 } from "@spfn/core/errors";
9714
- import { rateLimit as rateLimit2 } from "@spfn/core/middleware";
9765
+ import { rateLimitPolicy as rateLimitPolicy4 } from "@spfn/core/middleware";
9715
9766
  import { defineRouter as defineRouter4, route as route4 } from "@spfn/core/route";
9716
9767
  var providerParams = Type.Object({
9717
9768
  provider: Type.Union(SOCIAL_PROVIDERS.map((p) => Type.Literal(p)), {
@@ -9724,7 +9775,7 @@ var oauthGoogleStart = route4.get("/_auth/oauth/google").input({
9724
9775
  description: "Encrypted OAuth state (returnUrl, publicKey, keyId, fingerprint, algorithm)"
9725
9776
  })
9726
9777
  })
9727
- }).use([rateLimit2({ limit: 20, windowMs: 6e4 })]).skip(["auth"]).handler(async (c) => {
9778
+ }).use([rateLimitPolicy4("oauth-start", { limit: 20, windowMs: 6e4 })]).skip(["auth"]).handler(async (c) => {
9728
9779
  const { query } = await c.data();
9729
9780
  if (!isGoogleOAuthEnabled()) {
9730
9781
  return c.redirect(buildOAuthErrorUrl("Google OAuth is not configured"));
@@ -9747,7 +9798,7 @@ var oauthGoogleCallback = route4.get("/_auth/oauth/google/callback").input({
9747
9798
  description: "Error description from Google"
9748
9799
  }))
9749
9800
  })
9750
- }).use([Transactional3()]).skip(["auth"]).handler(async (c) => {
9801
+ }).use([rateLimitPolicy4("oauth-callback", { limit: 30, windowMs: 6e4 }), Transactional3()]).skip(["auth"]).handler(async (c) => {
9751
9802
  const { query } = await c.data();
9752
9803
  if (query.error) {
9753
9804
  const errorMessage = query.error_description || query.error;
@@ -9795,7 +9846,7 @@ var oauthStart = route4.post("/_auth/oauth/start").input({
9795
9846
  description: "Custom metadata passed to authRegisterEvent (e.g. referral code, UTM params)"
9796
9847
  }))
9797
9848
  })
9798
- }).use([rateLimit2({ limit: 20, windowMs: 6e4 })]).skip(["auth"]).handler(async (c) => {
9849
+ }).use([rateLimitPolicy4("oauth-start", { limit: 20, windowMs: 6e4 })]).skip(["auth"]).handler(async (c) => {
9799
9850
  const { body } = await c.data();
9800
9851
  const nonce = generateOAuthNonce();
9801
9852
  setCookie(c.raw, COOKIE_NAMES.OAUTH_CSRF, nonce, {
@@ -9822,7 +9873,7 @@ var getGoogleOAuthUrl = route4.post("/_auth/oauth/google/url").input({
9822
9873
  description: "Encrypted OAuth state (injected by interceptor)"
9823
9874
  }))
9824
9875
  })
9825
- }).skip(["auth"]).handler(async (c) => {
9876
+ }).use([rateLimitPolicy4("oauth-start", { limit: 20, windowMs: 6e4 })]).skip(["auth"]).handler(async (c) => {
9826
9877
  const { body } = await c.data();
9827
9878
  if (!isGoogleOAuthEnabled()) {
9828
9879
  throw new ValidationError7({ message: "Google OAuth is not configured" });
@@ -9840,7 +9891,7 @@ var oauthFinalize = route4.post("/_auth/oauth/finalize").input({
9840
9891
  keyId: Type.String({ description: "Key ID from OAuth state" }),
9841
9892
  returnUrl: Type.Optional(Type.String({ description: "URL to redirect after login" }))
9842
9893
  })
9843
- }).skip(["auth"]).handler(async (c) => {
9894
+ }).use([rateLimitPolicy4("oauth-start", { limit: 20, windowMs: 6e4 })]).skip(["auth"]).handler(async (c) => {
9844
9895
  const { body } = await c.data();
9845
9896
  return {
9846
9897
  success: true,
@@ -9856,7 +9907,7 @@ var oauthProviderStart = route4.get("/_auth/oauth/:provider").input({
9856
9907
  description: "Encrypted OAuth state (returnUrl, publicKey, keyId, fingerprint, algorithm)"
9857
9908
  })
9858
9909
  })
9859
- }).use([rateLimit2({ limit: 20, windowMs: 6e4 })]).skip(["auth"]).handler(async (c) => {
9910
+ }).use([rateLimitPolicy4("oauth-start", { limit: 20, windowMs: 6e4 })]).skip(["auth"]).handler(async (c) => {
9860
9911
  const { params, query } = await c.data();
9861
9912
  const provider = getOAuthProvider(params.provider);
9862
9913
  if (!provider?.isEnabled()) {
@@ -9880,7 +9931,7 @@ var oauthProviderCallback = route4.get("/_auth/oauth/:provider/callback").input(
9880
9931
  description: "Error description from provider"
9881
9932
  }))
9882
9933
  })
9883
- }).use([Transactional3()]).skip(["auth"]).handler(async (c) => {
9934
+ }).use([rateLimitPolicy4("oauth-callback", { limit: 30, windowMs: 6e4 }), Transactional3()]).skip(["auth"]).handler(async (c) => {
9884
9935
  const { params, query } = await c.data();
9885
9936
  if (query.error) {
9886
9937
  const errorMessage = query.error_description || query.error;
@@ -9914,7 +9965,7 @@ var getProviderOAuthUrl = route4.post("/_auth/oauth/:provider/url").input({
9914
9965
  description: "Encrypted OAuth state (injected by interceptor)"
9915
9966
  }))
9916
9967
  })
9917
- }).skip(["auth"]).handler(async (c) => {
9968
+ }).use([rateLimitPolicy4("oauth-start", { limit: 20, windowMs: 6e4 })]).skip(["auth"]).handler(async (c) => {
9918
9969
  const { params, body } = await c.data();
9919
9970
  const provider = requireEnabledProvider(params.provider);
9920
9971
  if (!body.state) {
@@ -9944,7 +9995,7 @@ var oauthNative = route4.post("/_auth/oauth/:provider/native").input({
9944
9995
  description: "Custom metadata passed to auth events (e.g. referral code, UTM params)"
9945
9996
  }))
9946
9997
  })
9947
- }).skip(["auth"]).handler(async (c) => {
9998
+ }).use([rateLimitPolicy4("oauth-native", { limit: 20, windowMs: 6e4 })]).skip(["auth"]).handler(async (c) => {
9948
9999
  const { params, body } = await c.data();
9949
10000
  return await oauthNativeService({ provider: params.provider, ...body });
9950
10001
  });