@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 {
|
|
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([
|
|
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([
|
|
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([
|
|
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 {
|
|
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([
|
|
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([
|
|
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([
|
|
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
|
});
|