@absolutejs/auth 0.61.0 → 0.62.0

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.
@@ -41,6 +41,13 @@ export type CredentialsConfig<UserType> = {
41
41
  onEmailVerified?: (context: {
42
42
  email: string;
43
43
  }) => void | Promise<void>;
44
+ /** Called (in the enumeration-safe default) when someone tries to register an
45
+ * email that already exists — send the real owner a "you already have an
46
+ * account, sign in" email out-of-band. Not called when
47
+ * `revealRegistrationConflicts` is true. */
48
+ onExistingAccount?: (context: {
49
+ email: string;
50
+ }) => void | Promise<void>;
44
51
  onPasswordReset?: (context: {
45
52
  email: string;
46
53
  }) => void | Promise<void>;
@@ -55,9 +62,17 @@ export type CredentialsConfig<UserType> = {
55
62
  * rejected until the email is verified. Default false = auto-login on register and
56
63
  * verification acts as a soft, later gate. */
57
64
  requireEmailVerification?: boolean;
65
+ /** When true, `POST /register` returns 409 "Email is already registered" for a
66
+ * known email. Default false = enumeration-safe: return the same generic
67
+ * response a new pending registration does and call `onExistingAccount`
68
+ * instead of confirming the account exists. */
69
+ revealRegistrationConflicts?: boolean;
58
70
  resetPasswordRoute?: RouteString;
59
71
  resetTokenDurationMs?: number;
60
72
  sessionDurationMs?: number;
73
+ /** When set, login/register reject requests whose `Origin` header is not in
74
+ * this list (defense against login/registration CSRF). Omit to disable. */
75
+ trustedOrigins?: readonly string[];
61
76
  verificationTokenDurationMs?: number;
62
77
  verifyEmailRoute?: RouteString;
63
78
  };
@@ -1,6 +1,6 @@
1
1
  import { Elysia } from 'elysia';
2
2
  import { type CredentialRouteProps } from './config';
3
- export declare const credentialsLogin: <UserType>({ authSessionStore, checkBreachesOnLogin, cookieSecure, credentialStore, getUserByEmail, isMfaRequired, lockoutGuard, loginRoute, onCredentialsLoginError, onCredentialsLoginSuccess, passwordVerifier, rehashOnLogin, requireEmailVerification, sessionDurationMs }: CredentialRouteProps<UserType>) => Elysia<"", {
3
+ export declare const credentialsLogin: <UserType>({ authSessionStore, checkBreachesOnLogin, cookieSecure, credentialStore, getUserByEmail, isMfaRequired, lockoutGuard, loginRoute, onCredentialsLoginError, onCredentialsLoginSuccess, passwordVerifier, rehashOnLogin, requireEmailVerification, sessionDurationMs, trustedOrigins }: CredentialRouteProps<UserType>) => Elysia<"", {
4
4
  decorator: {};
5
5
  store: {
6
6
  session: import("..").SessionRecord<UserType>;
@@ -30,7 +30,7 @@ export declare const credentialsLogin: <UserType>({ authSessionStore, checkBreac
30
30
  headers: unknown;
31
31
  response: {
32
32
  401: "Invalid email or password";
33
- 403: {
33
+ 403: "Request origin is not allowed" | {
34
34
  readonly status: "email_not_verified";
35
35
  };
36
36
  200: {
@@ -1,6 +1,6 @@
1
1
  import { Elysia } from 'elysia';
2
2
  import { type CredentialRouteProps } from './config';
3
- export declare const credentialsRegister: <UserType>({ authSessionStore, cookieSecure, credentialStore, onCreateCredentialUser, onCredentialsLoginSuccess, onRegistrationSuccess, onSendEmail, passwordPolicy, registerRoute, requireEmailVerification, sessionDurationMs, verificationTokenDurationMs }: CredentialRouteProps<UserType>) => Elysia<"", {
3
+ export declare const credentialsRegister: <UserType>({ authSessionStore, cookieSecure, credentialStore, onCreateCredentialUser, onCredentialsLoginSuccess, onExistingAccount, onRegistrationSuccess, onSendEmail, passwordPolicy, registerRoute, requireEmailVerification, revealRegistrationConflicts, sessionDurationMs, trustedOrigins, verificationTokenDurationMs }: CredentialRouteProps<UserType>) => Elysia<"", {
4
4
  decorator: {};
5
5
  store: {
6
6
  session: import("..").SessionRecord<UserType>;
@@ -98,7 +98,7 @@ export declare const credentialRoutes: <UserType>(config: CredentialRouteProps<U
98
98
  headers: unknown;
99
99
  response: {
100
100
  401: "Invalid email or password";
101
- 403: {
101
+ 403: "Request origin is not allowed" | {
102
102
  readonly status: "email_not_verified";
103
103
  };
104
104
  200: {
package/dist/csrf.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare const isTrustedOrigin: (request: Request, trustedOrigins?: readonly string[]) => boolean;
package/dist/index.d.ts CHANGED
@@ -815,7 +815,7 @@ export declare const createAuthApplications: <UserType>(configuration: AuthConfi
815
815
  headers: unknown;
816
816
  response: {
817
817
  401: "Invalid email or password";
818
- 403: {
818
+ 403: "Request origin is not allowed" | {
819
819
  readonly status: "email_not_verified";
820
820
  };
821
821
  200: {
@@ -3468,6 +3468,7 @@ export { resolveAuthHtmxRenderers } from './htmx/renderers';
3468
3468
  export type { AuthHtmxConfig, AuthHtmxConnectorTarget, AuthHtmxProviderData, AuthHtmxProviderInfo, AuthHtmxRenderOverrides, AuthHtmxRenderersConfig, AuthHtmxUser, AuthIdentityPayload, LinkedProviderPayload } from './htmx/types';
3469
3469
  export * from './utils';
3470
3470
  export * from './redirect';
3471
+ export * from './csrf';
3471
3472
  export { buildClientProviders, resolveClientProviderEntry, resolveProviderClientConfiguration } from './providers/clients';
3472
3473
  export type { OAuth2TokenResponse, OAuth2Client, OAuth2ClientForConfig, CustomProviderCredentials, ProviderConfig, ProviderOption, PKCEProvider, OIDCProvider, RefreshableProvider, RevocableProvider, ScopeRequiredProvider, ProvidersMap, ProviderConfiguration, CredentialsFor } from 'citra';
3473
3474
  export { providers, providerOptions, createCustomOAuth2Client, defineProvider, refreshableProviderOptions, revocableProviderOptions, oidcProviderOptions, pkceProviderOptions, scopeRequiredProviderOptions, decodeJWT, extractPropFromIdentity, isValidProviderOption, isRefreshableOAuth2Client, isRefreshableProviderOption, isOIDCProviderOption, isPKCEProviderOption, isRevocableProviderOption, isRevocableOAuth2Client } from 'citra';
package/dist/index.js CHANGED
@@ -5145,6 +5145,15 @@ init_constants();
5145
5145
  init_crypto();
5146
5146
  import { Elysia as Elysia11, t as t8 } from "elysia";
5147
5147
 
5148
+ // src/csrf.ts
5149
+ var isTrustedOrigin = (request, trustedOrigins) => {
5150
+ if (trustedOrigins === undefined || trustedOrigins.length === 0) {
5151
+ return true;
5152
+ }
5153
+ const origin = request.headers.get("origin");
5154
+ return origin !== null && trustedOrigins.includes(origin);
5155
+ };
5156
+
5148
5157
  // src/credentials/import.ts
5149
5158
  init_crypto();
5150
5159
  var normalizeEmail = (email) => email.trim().toLowerCase();
@@ -5349,7 +5358,8 @@ var credentialsLogin = ({
5349
5358
  passwordVerifier,
5350
5359
  rehashOnLogin = false,
5351
5360
  requireEmailVerification = false,
5352
- sessionDurationMs = DEFAULT_CREDENTIAL_SESSION_TTL_MS
5361
+ sessionDurationMs = DEFAULT_CREDENTIAL_SESSION_TTL_MS,
5362
+ trustedOrigins
5353
5363
  }) => new Elysia11().use(sessionStore()).post(loginRoute, async ({
5354
5364
  body: { email, password },
5355
5365
  cookie: { user_session_id },
@@ -5357,6 +5367,9 @@ var credentialsLogin = ({
5357
5367
  status,
5358
5368
  store: { session, unregisteredSession }
5359
5369
  }) => withSpan("auth.credentials.login", undefined, async (span) => {
5370
+ if (!isTrustedOrigin(request, trustedOrigins)) {
5371
+ return status("Forbidden", "Request origin is not allowed");
5372
+ }
5360
5373
  const headerBag = {};
5361
5374
  request.headers.forEach((value, key) => {
5362
5375
  headerBag[key] = value;
@@ -5514,19 +5527,26 @@ var credentialsRegister = ({
5514
5527
  credentialStore,
5515
5528
  onCreateCredentialUser,
5516
5529
  onCredentialsLoginSuccess,
5530
+ onExistingAccount,
5517
5531
  onRegistrationSuccess,
5518
5532
  onSendEmail,
5519
5533
  passwordPolicy,
5520
5534
  registerRoute = "/auth/register",
5521
5535
  requireEmailVerification = false,
5536
+ revealRegistrationConflicts = false,
5522
5537
  sessionDurationMs = DEFAULT_CREDENTIAL_SESSION_TTL_MS,
5538
+ trustedOrigins,
5523
5539
  verificationTokenDurationMs = DEFAULT_VERIFICATION_TOKEN_TTL_MS
5524
5540
  }) => new Elysia13().use(sessionStore()).post(registerRoute, async ({
5525
5541
  body: { email, password, ...extraFields },
5526
5542
  cookie: { user_session_id },
5543
+ request,
5527
5544
  status,
5528
5545
  store: { session }
5529
5546
  }) => withSpan("auth.credentials.register", undefined, async () => {
5547
+ if (!isTrustedOrigin(request, trustedOrigins)) {
5548
+ return status("Forbidden", "Request origin is not allowed");
5549
+ }
5530
5550
  const normalizedEmail = email.trim().toLowerCase();
5531
5551
  if (!normalizedEmail.includes("@")) {
5532
5552
  return status("Bad Request", "A valid email is required");
@@ -5540,7 +5560,13 @@ var credentialsRegister = ({
5540
5560
  }
5541
5561
  const existing = await credentialStore.getCredentialByEmail(normalizedEmail);
5542
5562
  if (existing) {
5543
- return status("Conflict", "Email is already registered");
5563
+ if (revealRegistrationConflicts) {
5564
+ return status("Conflict", "Email is already registered");
5565
+ }
5566
+ await onExistingAccount?.({ email: normalizedEmail });
5567
+ return status("Created", {
5568
+ status: "verification_required"
5569
+ });
5544
5570
  }
5545
5571
  const created = await onCreateCredentialUser({
5546
5572
  ...extraFields,
@@ -37716,6 +37742,7 @@ export {
37716
37742
  isValidUser,
37717
37743
  isValidProviderOption,
37718
37744
  isUserSessionId,
37745
+ isTrustedOrigin,
37719
37746
  isSafeLocalPath,
37720
37747
  isRevocableProviderOption,
37721
37748
  isRevocableOAuth2Client,
@@ -38057,5 +38084,5 @@ export {
38057
38084
  AGENT_CLAIM_GRANT_TYPE
38058
38085
  };
38059
38086
 
38060
- //# debugId=60AF00D018199BB164756E2164756E21
38087
+ //# debugId=A62F57FCBD818EE864756E2164756E21
38061
38088
  //# sourceMappingURL=index.js.map