@absolutejs/auth 0.60.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.
- package/dist/credentials/config.d.ts +15 -0
- package/dist/credentials/login.d.ts +2 -2
- package/dist/credentials/register.d.ts +1 -1
- package/dist/credentials/routes.d.ts +1 -1
- package/dist/csrf.d.ts +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +42 -4
- package/dist/index.js.map +9 -8
- package/dist/server.js +41 -4
- package/dist/server.js.map +9 -8
- package/package.json +1 -1
|
@@ -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();
|
|
@@ -5333,6 +5342,8 @@ var evaluatePassword = async (password, policy = {}) => {
|
|
|
5333
5342
|
var isPasswordCompromised = (password) => isPasswordBreached(password);
|
|
5334
5343
|
|
|
5335
5344
|
// src/credentials/login.ts
|
|
5345
|
+
var dummyPasswordHashPromise;
|
|
5346
|
+
var dummyPasswordHash = () => dummyPasswordHashPromise ??= hashPassword("absolutejs-auth-timing-equalizer");
|
|
5336
5347
|
var credentialsLogin = ({
|
|
5337
5348
|
authSessionStore,
|
|
5338
5349
|
checkBreachesOnLogin,
|
|
@@ -5347,7 +5358,8 @@ var credentialsLogin = ({
|
|
|
5347
5358
|
passwordVerifier,
|
|
5348
5359
|
rehashOnLogin = false,
|
|
5349
5360
|
requireEmailVerification = false,
|
|
5350
|
-
sessionDurationMs = DEFAULT_CREDENTIAL_SESSION_TTL_MS
|
|
5361
|
+
sessionDurationMs = DEFAULT_CREDENTIAL_SESSION_TTL_MS,
|
|
5362
|
+
trustedOrigins
|
|
5351
5363
|
}) => new Elysia11().use(sessionStore()).post(loginRoute, async ({
|
|
5352
5364
|
body: { email, password },
|
|
5353
5365
|
cookie: { user_session_id },
|
|
@@ -5355,6 +5367,9 @@ var credentialsLogin = ({
|
|
|
5355
5367
|
status,
|
|
5356
5368
|
store: { session, unregisteredSession }
|
|
5357
5369
|
}) => withSpan("auth.credentials.login", undefined, async (span) => {
|
|
5370
|
+
if (!isTrustedOrigin(request, trustedOrigins)) {
|
|
5371
|
+
return status("Forbidden", "Request origin is not allowed");
|
|
5372
|
+
}
|
|
5358
5373
|
const headerBag = {};
|
|
5359
5374
|
request.headers.forEach((value, key) => {
|
|
5360
5375
|
headerBag[key] = value;
|
|
@@ -5374,7 +5389,13 @@ var credentialsLogin = ({
|
|
|
5374
5389
|
const credential = await credentialStore.getCredentialByEmail(normalizedEmail);
|
|
5375
5390
|
const user = await getUserByEmail(normalizedEmail);
|
|
5376
5391
|
const verifyHash = passwordVerifier ?? verifyPassword;
|
|
5377
|
-
|
|
5392
|
+
let passwordValid;
|
|
5393
|
+
if (credential === undefined) {
|
|
5394
|
+
await verifyPassword(password, await dummyPasswordHash());
|
|
5395
|
+
passwordValid = false;
|
|
5396
|
+
} else {
|
|
5397
|
+
passwordValid = await verifyHash(password, credential.passwordHash);
|
|
5398
|
+
}
|
|
5378
5399
|
if (!credential || !user || credential.status !== "active" || !passwordValid) {
|
|
5379
5400
|
await lockoutGuard?.recordFailure(normalizedEmail);
|
|
5380
5401
|
await onCredentialsLoginError?.({
|
|
@@ -5506,19 +5527,26 @@ var credentialsRegister = ({
|
|
|
5506
5527
|
credentialStore,
|
|
5507
5528
|
onCreateCredentialUser,
|
|
5508
5529
|
onCredentialsLoginSuccess,
|
|
5530
|
+
onExistingAccount,
|
|
5509
5531
|
onRegistrationSuccess,
|
|
5510
5532
|
onSendEmail,
|
|
5511
5533
|
passwordPolicy,
|
|
5512
5534
|
registerRoute = "/auth/register",
|
|
5513
5535
|
requireEmailVerification = false,
|
|
5536
|
+
revealRegistrationConflicts = false,
|
|
5514
5537
|
sessionDurationMs = DEFAULT_CREDENTIAL_SESSION_TTL_MS,
|
|
5538
|
+
trustedOrigins,
|
|
5515
5539
|
verificationTokenDurationMs = DEFAULT_VERIFICATION_TOKEN_TTL_MS
|
|
5516
5540
|
}) => new Elysia13().use(sessionStore()).post(registerRoute, async ({
|
|
5517
5541
|
body: { email, password, ...extraFields },
|
|
5518
5542
|
cookie: { user_session_id },
|
|
5543
|
+
request,
|
|
5519
5544
|
status,
|
|
5520
5545
|
store: { session }
|
|
5521
5546
|
}) => withSpan("auth.credentials.register", undefined, async () => {
|
|
5547
|
+
if (!isTrustedOrigin(request, trustedOrigins)) {
|
|
5548
|
+
return status("Forbidden", "Request origin is not allowed");
|
|
5549
|
+
}
|
|
5522
5550
|
const normalizedEmail = email.trim().toLowerCase();
|
|
5523
5551
|
if (!normalizedEmail.includes("@")) {
|
|
5524
5552
|
return status("Bad Request", "A valid email is required");
|
|
@@ -5532,7 +5560,13 @@ var credentialsRegister = ({
|
|
|
5532
5560
|
}
|
|
5533
5561
|
const existing = await credentialStore.getCredentialByEmail(normalizedEmail);
|
|
5534
5562
|
if (existing) {
|
|
5535
|
-
|
|
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
|
+
});
|
|
5536
5570
|
}
|
|
5537
5571
|
const created = await onCreateCredentialUser({
|
|
5538
5572
|
...extraFields,
|
|
@@ -37323,6 +37357,9 @@ var buildAuthApplications = async (configuration) => {
|
|
|
37323
37357
|
await webhookDispatch?.(event);
|
|
37324
37358
|
}
|
|
37325
37359
|
}) : undefined;
|
|
37360
|
+
if (auditEmit === undefined) {
|
|
37361
|
+
console.warn("[@absolutejs/auth] No audit sink configured (pass `audit` or `webhooks`). " + "Privileged flows (impersonation, role/permission changes, sign-out) will " + "run WITHOUT an audit trail.");
|
|
37362
|
+
}
|
|
37326
37363
|
const lockoutGuard = lockout ? createLockoutGuard(lockout) : undefined;
|
|
37327
37364
|
const resolvedAgentAuth = agentAuth === undefined ? undefined : {
|
|
37328
37365
|
...agentAuth,
|
|
@@ -37705,6 +37742,7 @@ export {
|
|
|
37705
37742
|
isValidUser,
|
|
37706
37743
|
isValidProviderOption,
|
|
37707
37744
|
isUserSessionId,
|
|
37745
|
+
isTrustedOrigin,
|
|
37708
37746
|
isSafeLocalPath,
|
|
37709
37747
|
isRevocableProviderOption,
|
|
37710
37748
|
isRevocableOAuth2Client,
|
|
@@ -38046,5 +38084,5 @@ export {
|
|
|
38046
38084
|
AGENT_CLAIM_GRANT_TYPE
|
|
38047
38085
|
};
|
|
38048
38086
|
|
|
38049
|
-
//# debugId=
|
|
38087
|
+
//# debugId=A62F57FCBD818EE864756E2164756E21
|
|
38050
38088
|
//# sourceMappingURL=index.js.map
|