@astram/guard 0.10.2 → 0.11.1
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/auth/account.controller.d.ts +76 -19
- package/dist/auth/account.controller.d.ts.map +1 -1
- package/dist/auth/account.controller.js +270 -53
- package/dist/auth/auth.controller.d.ts +3 -1
- package/dist/auth/auth.controller.d.ts.map +1 -1
- package/dist/auth/auth.controller.js +10 -2
- package/dist/auth/auth.module.d.ts +8 -0
- package/dist/auth/auth.module.d.ts.map +1 -1
- package/dist/auth/auth.module.js +80 -7
- package/dist/auth/confirm-email.controller.d.ts +33 -0
- package/dist/auth/confirm-email.controller.d.ts.map +1 -0
- package/dist/auth/confirm-email.controller.js +205 -0
- package/dist/auth/email-address.d.ts +30 -0
- package/dist/auth/email-address.d.ts.map +1 -0
- package/dist/auth/email-address.js +49 -0
- package/dist/auth/email-change-messages.d.ts +26 -0
- package/dist/auth/email-change-messages.d.ts.map +1 -0
- package/dist/auth/email-change-messages.js +56 -0
- package/dist/auth/link-base-url.d.ts +24 -0
- package/dist/auth/link-base-url.d.ts.map +1 -0
- package/dist/auth/link-base-url.js +93 -0
- package/dist/auth/ports/email-address-changer.d.ts +65 -0
- package/dist/auth/ports/email-address-changer.d.ts.map +1 -0
- package/dist/auth/ports/email-address-changer.js +8 -0
- package/dist/auth/ports/email-change-token-store.d.ts +39 -0
- package/dist/auth/ports/email-change-token-store.d.ts.map +1 -0
- package/dist/auth/ports/email-change-token-store.js +12 -0
- package/dist/auth/tokens.d.ts +39 -1
- package/dist/auth/tokens.d.ts.map +1 -1
- package/dist/auth/tokens.js +40 -2
- package/dist/authentication/mariadb-email-address-changer.d.ts +33 -0
- package/dist/authentication/mariadb-email-address-changer.d.ts.map +1 -0
- package/dist/authentication/mariadb-email-address-changer.js +97 -0
- package/dist/authentication/mariadb-email-change-token-store.d.ts +25 -0
- package/dist/authentication/mariadb-email-change-token-store.d.ts.map +1 -0
- package/dist/authentication/mariadb-email-change-token-store.js +72 -0
- package/dist/authentication/mariadb-mfa-device-store.d.ts.map +1 -1
- package/dist/authentication/mariadb-mfa-device-store.js +8 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -1
- package/dist/module/shield.module.d.ts +11 -0
- package/dist/module/shield.module.d.ts.map +1 -1
- package/dist/module/shield.module.js +6 -0
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.controller.d.ts","sourceRoot":"","sources":["../../src/auth/auth.controller.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"auth.controller.d.ts","sourceRoot":"","sources":["../../src/auth/auth.controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -216,7 +216,8 @@ let AuthController = class AuthController {
|
|
|
216
216
|
rootIds;
|
|
217
217
|
sessionRecords;
|
|
218
218
|
sessionRestrictions;
|
|
219
|
-
|
|
219
|
+
emailChangeTokens;
|
|
220
|
+
constructor(authenticator, sessionAuthority, pendingMfa, tokenBinder, profileResolver, credentialResolver, resetTokenStore, emailSender, credentialWriter, sessionSweeper, passwordHasher, resetLinkBaseUrl, recoveryCodes, loginLimiter, loginIpLimiter, trustProxyCheck, recoveryLimiter, forgotLimiter, forgotHintLimiter, resetLimiter, auditSink, sessionFactsProjector, scopeSubtreeResolver, membershipChecker, permissionFacts, mfaLimiter, mfaStepUp, clock, lifetimes, rootIds, sessionRecords, sessionRestrictions, emailChangeTokens) {
|
|
220
221
|
this.authenticator = authenticator;
|
|
221
222
|
this.sessionAuthority = sessionAuthority;
|
|
222
223
|
this.pendingMfa = pendingMfa;
|
|
@@ -249,6 +250,7 @@ let AuthController = class AuthController {
|
|
|
249
250
|
this.rootIds = rootIds;
|
|
250
251
|
this.sessionRecords = sessionRecords;
|
|
251
252
|
this.sessionRestrictions = sessionRestrictions;
|
|
253
|
+
this.emailChangeTokens = emailChangeTokens;
|
|
252
254
|
}
|
|
253
255
|
/**
|
|
254
256
|
* The access-token binding TTL for an establishment (security hardening #8):
|
|
@@ -1093,6 +1095,10 @@ let AuthController = class AuthController {
|
|
|
1093
1095
|
}
|
|
1094
1096
|
// Burn any other outstanding reset tokens for the principal (single active reset).
|
|
1095
1097
|
await store.invalidateAllForPrincipal(principalId);
|
|
1098
|
+
// Phase 12 (A8): a pending self-service email change dies with the old password too.
|
|
1099
|
+
await this.emailChangeTokens?.invalidateAllForPrincipal(principalId).catch((err) => {
|
|
1100
|
+
sweepError ??= err;
|
|
1101
|
+
});
|
|
1096
1102
|
if (sweepError !== undefined)
|
|
1097
1103
|
throw sweepError;
|
|
1098
1104
|
}
|
|
@@ -1763,7 +1769,9 @@ exports.AuthController = AuthController = __decorate([
|
|
|
1763
1769
|
__param(30, (0, common_1.Inject)(mariadb_session_record_store_js_1.SHIELD_SESSION_RECORD_STORE)),
|
|
1764
1770
|
__param(31, (0, common_2.Optional)()),
|
|
1765
1771
|
__param(31, (0, common_1.Inject)(tokens_js_1.SHIELD_SESSION_RESTRICTIONS)),
|
|
1766
|
-
|
|
1772
|
+
__param(32, (0, common_2.Optional)()),
|
|
1773
|
+
__param(32, (0, common_1.Inject)(tokens_js_1.SHIELD_EMAIL_CHANGE_TOKEN_STORE)),
|
|
1774
|
+
__metadata("design:paramtypes", [Object, Object, pending_mfa_store_js_1.PendingMfaStore, Function, Object, Object, Object, Object, Object, Object, Object, String, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object])
|
|
1767
1775
|
], AuthController);
|
|
1768
1776
|
/**
|
|
1769
1777
|
* Narrow an `authenticated` result to its guaranteed principal. The core engine
|
|
@@ -218,6 +218,14 @@ export interface AuthModuleOptions {
|
|
|
218
218
|
* target node before re-binding the session. Only meaningful under `auth: true`.
|
|
219
219
|
*/
|
|
220
220
|
readonly membershipChecker?: MembershipChecker;
|
|
221
|
+
/**
|
|
222
|
+
* The base URL of the app's confirm-email page (Phase 12, KI #14). The mailed link is
|
|
223
|
+
* `${emailChangeLinkBaseUrl}#t=<token>` (the token in the fragment, never the query). Option >
|
|
224
|
+
* env `EMAIL_CHANGE_LINK_BASE_URL` > dev default `http://localhost:5173/confirm-email`. Outside
|
|
225
|
+
* `NODE_ENV` development/test an unset or loopback base fails the boot unless
|
|
226
|
+
* `emailSender: 'disabled'` (Phase 13 #1; `RESET_LINK_BASE_URL` follows the same rule).
|
|
227
|
+
*/
|
|
228
|
+
readonly emailChangeLinkBaseUrl?: string;
|
|
221
229
|
}
|
|
222
230
|
/**
|
|
223
231
|
* The no-override default sender decision (pure apart from the warning). Exported for
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.module.d.ts","sourceRoot":"","sources":["../../src/auth/auth.module.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AAEH,OAAO,EAAkB,KAAK,aAAa,EAAiB,MAAM,gBAAgB,CAAC;AAanF,OAAO,KAAK,EAGV,kBAAkB,EAOlB,QAAQ,EACR,cAAc,EAEf,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.module.d.ts","sourceRoot":"","sources":["../../src/auth/auth.module.ts"],"names":[],"mappings":"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"}
|
package/dist/auth/auth.module.js
CHANGED
|
@@ -118,6 +118,9 @@ const mariadb_profile_resolver_js_1 = require("../authentication/mariadb-profile
|
|
|
118
118
|
const mariadb_credential_writer_js_1 = require("../authentication/mariadb-credential-writer.js");
|
|
119
119
|
const mariadb_profile_writer_js_1 = require("../authentication/mariadb-profile-writer.js");
|
|
120
120
|
const mariadb_reset_token_store_js_1 = require("../authentication/mariadb-reset-token-store.js");
|
|
121
|
+
const mariadb_email_change_token_store_js_1 = require("../authentication/mariadb-email-change-token-store.js");
|
|
122
|
+
const mariadb_email_address_changer_js_1 = require("../authentication/mariadb-email-address-changer.js");
|
|
123
|
+
const link_base_url_js_1 = require("./link-base-url.js");
|
|
121
124
|
const mariadb_recovery_code_store_js_1 = require("../authentication/mariadb-recovery-code-store.js");
|
|
122
125
|
const mariadb_mfa_device_store_js_1 = require("../authentication/mariadb-mfa-device-store.js");
|
|
123
126
|
const mariadb_session_sweeper_js_1 = require("../authentication/mariadb-session-sweeper.js");
|
|
@@ -130,6 +133,7 @@ const index_js_1 = require("../session/index.js");
|
|
|
130
133
|
const persistence_module_js_1 = require("./persistence.module.js");
|
|
131
134
|
const auth_controller_js_1 = require("./auth.controller.js");
|
|
132
135
|
const account_controller_js_1 = require("./account.controller.js");
|
|
136
|
+
const confirm_email_controller_js_1 = require("./confirm-email.controller.js");
|
|
133
137
|
const pending_mfa_store_js_1 = require("./pending-mfa-store.js");
|
|
134
138
|
const mfa_step_up_js_1 = require("./mfa-step-up.js");
|
|
135
139
|
const tokens_js_1 = require("./tokens.js");
|
|
@@ -647,6 +651,55 @@ function makeResetTokenStoreProvider(override) {
|
|
|
647
651
|
inject: [tokens_js_1.SHIELD_DB_POOL],
|
|
648
652
|
};
|
|
649
653
|
}
|
|
654
|
+
/**
|
|
655
|
+
* The self-service email-change token store (Phase 12, KI #14) — always the framework default
|
|
656
|
+
* over `shield_identity.email_change_token` (identity migration 020); no consumer override seam.
|
|
657
|
+
*/
|
|
658
|
+
const emailChangeTokenStoreProvider = {
|
|
659
|
+
provide: tokens_js_1.SHIELD_EMAIL_CHANGE_TOKEN_STORE,
|
|
660
|
+
useFactory: (pool) => new mariadb_email_change_token_store_js_1.MariaDbEmailChangeTokenStore(pool),
|
|
661
|
+
inject: [tokens_js_1.SHIELD_DB_POOL],
|
|
662
|
+
};
|
|
663
|
+
/** The ONE email read/write path for an email change (Phase 12): the framework default. */
|
|
664
|
+
const emailAddressChangerProvider = {
|
|
665
|
+
provide: tokens_js_1.SHIELD_EMAIL_ADDRESS_CHANGER,
|
|
666
|
+
useFactory: (pool) => new mariadb_email_address_changer_js_1.MariaDbEmailAddressChanger(pool),
|
|
667
|
+
inject: [tokens_js_1.SHIELD_DB_POOL],
|
|
668
|
+
};
|
|
669
|
+
/**
|
|
670
|
+
* The per-account email-change REQUEST limiter (Phase 12) — `email-change:pid:<id>`, 3 per 60-min
|
|
671
|
+
* window, so the route cannot spam an inbox. `consume` fails closed (503).
|
|
672
|
+
*/
|
|
673
|
+
const emailChangeLimiterProvider = {
|
|
674
|
+
provide: tokens_js_1.SHIELD_EMAIL_CHANGE_LIMITER,
|
|
675
|
+
useFactory: (redis) => new attempt_limiter_js_1.AttemptLimiter(redis, { maxAttempts: 3, windowMs: 60 * 60 * 1000 }),
|
|
676
|
+
inject: [tokens_js_1.SHIELD_REDIS],
|
|
677
|
+
};
|
|
678
|
+
/**
|
|
679
|
+
* The `POST /auth/confirm-email` per-IP limiter (Phase 12, A6) — `confirm-email:ip:<ip>`, 10 per
|
|
680
|
+
* 15-min window (like reset-password); enforced only with an explicit `trust proxy`.
|
|
681
|
+
*/
|
|
682
|
+
const confirmEmailLimiterProvider = {
|
|
683
|
+
provide: tokens_js_1.SHIELD_CONFIRM_EMAIL_LIMITER,
|
|
684
|
+
useFactory: (redis) => new attempt_limiter_js_1.AttemptLimiter(redis, { maxAttempts: 10, windowMs: 15 * 60 * 1000 }),
|
|
685
|
+
inject: [tokens_js_1.SHIELD_REDIS],
|
|
686
|
+
};
|
|
687
|
+
/**
|
|
688
|
+
* The confirm-link base (Phase 13 #1): `ShieldModule.forRoot({ emailChangeLinkBaseUrl })` >
|
|
689
|
+
* `EMAIL_CHANGE_LINK_BASE_URL` > dev default, read when the module compiles. Outside development/test
|
|
690
|
+
* (and unless `emailSender: 'disabled'`) an unset or loopback base FAILS the boot — see
|
|
691
|
+
* {@link resolveAuthLinkBaseUrls}.
|
|
692
|
+
*/
|
|
693
|
+
function makeEmailChangeLinkBaseUrlProvider(base, mailDisabled) {
|
|
694
|
+
return {
|
|
695
|
+
provide: tokens_js_1.SHIELD_EMAIL_CHANGE_LINK_BASE_URL,
|
|
696
|
+
useFactory: () => (0, link_base_url_js_1.resolveAuthLinkBaseUrls)({
|
|
697
|
+
env: process.env,
|
|
698
|
+
emailChangeLinkBaseUrlOption: base,
|
|
699
|
+
mailDisabled,
|
|
700
|
+
}).emailChangeLinkBaseUrl,
|
|
701
|
+
};
|
|
702
|
+
}
|
|
650
703
|
/**
|
|
651
704
|
* Bind the {@link EmailSender} (security hardening #6):
|
|
652
705
|
* - a supplied sender → used verbatim (any environment);
|
|
@@ -763,12 +816,20 @@ function makeMembershipCheckerProvider(override) {
|
|
|
763
816
|
* (`${base}?token=<rawToken>`). A plain string VALUE (not a seam) resolved from the
|
|
764
817
|
* environment (`RESET_LINK_BASE_URL`) with a dev default, so the framework owns the
|
|
765
818
|
* URL composition and {@link SHIELD_EMAIL_SENDER} stays URL-agnostic. Always
|
|
766
|
-
* provided so the `@Optional()` injection resolves.
|
|
819
|
+
* provided so the `@Optional()` injection resolves. Read when the module compiles (not at import);
|
|
820
|
+
* outside development/test (and unless `emailSender: 'disabled'`) an unset or loopback base FAILS
|
|
821
|
+
* the boot (Phase 13 #1, {@link resolveAuthLinkBaseUrls}).
|
|
767
822
|
*/
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
823
|
+
function makeResetLinkBaseUrlProvider(emailChangeLinkBaseUrl, mailDisabled) {
|
|
824
|
+
return {
|
|
825
|
+
provide: tokens_js_1.SHIELD_RESET_LINK_BASE_URL,
|
|
826
|
+
useFactory: () => (0, link_base_url_js_1.resolveAuthLinkBaseUrls)({
|
|
827
|
+
env: process.env,
|
|
828
|
+
emailChangeLinkBaseUrlOption: emailChangeLinkBaseUrl,
|
|
829
|
+
mailDisabled,
|
|
830
|
+
}).resetLinkBaseUrl,
|
|
831
|
+
};
|
|
832
|
+
}
|
|
772
833
|
/**
|
|
773
834
|
* The auth composition module. Imports {@link PersistenceModule} (the live
|
|
774
835
|
* connections) and provides the adapters + composed engines. Exports the two
|
|
@@ -791,6 +852,10 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
791
852
|
// through `ShieldModule.forRoot` — guard NEVER imports `@astram/identity`.
|
|
792
853
|
const identityActionKeys = options.identityActionKeys ?? [];
|
|
793
854
|
const identityControlPlaneKeys = options.identityControlPlaneKeys ?? [];
|
|
855
|
+
// The link-base boot rule (Phase 13 #1) applies only with a real sender: `'disabled'` delivers
|
|
856
|
+
// nothing, and an omitted sender outside development/test already fails the boot through
|
|
857
|
+
// `resolveDefaultEmailSender` (that clearer error is kept; in development/test the rule is off).
|
|
858
|
+
const mailDisabled = options.emailSender === undefined || options.emailSender === 'disabled';
|
|
794
859
|
return {
|
|
795
860
|
module: AuthModule_1,
|
|
796
861
|
imports: [persistence_module_js_1.PersistenceModule.forRoot()],
|
|
@@ -800,7 +865,8 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
800
865
|
// injects the two composed engines (below) + PendingMfaStore.
|
|
801
866
|
// AuthFactsController serves the framework-default `/identity/auth-facts` +
|
|
802
867
|
// `/identity/permissions-version` (over the SHIELD_PERMISSION_FACTS adapter below).
|
|
803
|
-
|
|
868
|
+
// ConfirmEmailController (Phase 12): `POST /auth/confirm-email`, beside AuthController.
|
|
869
|
+
controllers: [auth_controller_js_1.AuthController, confirm_email_controller_js_1.ConfirmEmailController, account_controller_js_1.AccountController, auth_facts_controller_js_1.AuthFactsController],
|
|
804
870
|
providers: [
|
|
805
871
|
passwordHasherProvider,
|
|
806
872
|
makeCredentialResolverProvider(options.credentialResolver),
|
|
@@ -809,6 +875,11 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
809
875
|
// The account/credential-surface seams (null when absent → route 501).
|
|
810
876
|
makeCredentialWriterProvider(options.credentialWriter),
|
|
811
877
|
makeResetTokenStoreProvider(options.resetTokenStore),
|
|
878
|
+
emailChangeTokenStoreProvider,
|
|
879
|
+
emailAddressChangerProvider,
|
|
880
|
+
emailChangeLimiterProvider,
|
|
881
|
+
confirmEmailLimiterProvider,
|
|
882
|
+
makeEmailChangeLinkBaseUrlProvider(options.emailChangeLinkBaseUrl, mailDisabled),
|
|
812
883
|
makeEmailSenderProvider(options.emailSender),
|
|
813
884
|
makeRecoveryCodeStoreProvider(options.recoveryCodeStore),
|
|
814
885
|
makeProfileWriterProvider(options.profileWriter),
|
|
@@ -848,7 +919,7 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
848
919
|
},
|
|
849
920
|
// The reset-link base URL value the forgot-password handler composes into the
|
|
850
921
|
// emailed link (framework-owned URL composition; EmailSender stays dumb).
|
|
851
|
-
|
|
922
|
+
makeResetLinkBaseUrlProvider(options.emailChangeLinkBaseUrl, mailDisabled),
|
|
852
923
|
// The resolved session lifetimes (#8) — one source for authority, binder, controller.
|
|
853
924
|
sessionLifetimesProvider,
|
|
854
925
|
sessionRecordStoreProvider,
|
|
@@ -912,6 +983,8 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
912
983
|
// and the account/auth controllers see them app-wide through @Global Shield.
|
|
913
984
|
tokens_js_1.SHIELD_CREDENTIAL_WRITER,
|
|
914
985
|
tokens_js_1.SHIELD_RESET_TOKEN_STORE,
|
|
986
|
+
// Phase 12: identity's admin email change invalidates pending self-service tokens.
|
|
987
|
+
tokens_js_1.SHIELD_EMAIL_CHANGE_TOKEN_STORE,
|
|
915
988
|
tokens_js_1.SHIELD_EMAIL_SENDER,
|
|
916
989
|
tokens_js_1.SHIELD_RECOVERY_CODE_STORE,
|
|
917
990
|
tokens_js_1.SHIELD_PROFILE_WRITER,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AttemptLimiter } from './rate-limit/attempt-limiter.js';
|
|
2
|
+
import { TrustProxyBootCheck } from './rate-limit/trust-proxy-check.js';
|
|
3
|
+
import type { AuditSink } from './ports/audit-sink.js';
|
|
4
|
+
import type { EmailSender } from './ports/email-sender.js';
|
|
5
|
+
import type { EmailChangeTokenStore } from './ports/email-change-token-store.js';
|
|
6
|
+
import type { EmailAddressChanger } from './ports/email-address-changer.js';
|
|
7
|
+
import type { ResetTokenStore } from './ports/reset-token-store.js';
|
|
8
|
+
import type { SessionSweeper } from './ports/session-sweeper.js';
|
|
9
|
+
export declare class ConfirmEmailController {
|
|
10
|
+
private readonly tokens?;
|
|
11
|
+
private readonly addresses?;
|
|
12
|
+
private readonly sweeper?;
|
|
13
|
+
private readonly resetTokens?;
|
|
14
|
+
private readonly emailSender?;
|
|
15
|
+
private readonly auditSink?;
|
|
16
|
+
private readonly limiter?;
|
|
17
|
+
private readonly trustProxyCheck?;
|
|
18
|
+
constructor(tokens?: (EmailChangeTokenStore | null) | undefined, addresses?: (EmailAddressChanger | null) | undefined, sweeper?: (SessionSweeper | null) | undefined, resetTokens?: (ResetTokenStore | null) | undefined, emailSender?: (EmailSender | null) | undefined, auditSink?: (AuditSink | null) | undefined, limiter?: (AttemptLimiter | null) | undefined, trustProxyCheck?: (Pick<TrustProxyBootCheck, "perIpLimitingEnabled"> | null) | undefined);
|
|
19
|
+
confirmEmail(body: {
|
|
20
|
+
token?: unknown;
|
|
21
|
+
}, ip: string): Promise<{
|
|
22
|
+
ok: true;
|
|
23
|
+
}>;
|
|
24
|
+
/**
|
|
25
|
+
* Best-effort auth audit (A9): masked address, a reason code; never a token. Attributed to the
|
|
26
|
+
* token's user and the tenant of the session that requested the change (KI #14 re-review P1-2:
|
|
27
|
+
* the confirm request may carry no session, and `audit_log.al_tenant_id` is NOT NULL). Only a
|
|
28
|
+
* consumed token has a binding, so an unknown / expired / used token is never audited: there is
|
|
29
|
+
* no user or tenant to attribute it to (the per-IP limiter bounds that path).
|
|
30
|
+
*/
|
|
31
|
+
private audit;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=confirm-email.controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"confirm-email.controller.d.ts","sourceRoot":"","sources":["../../src/auth/confirm-email.controller.ts"],"names":[],"mappings":"AA2CA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAEV,qBAAqB,EACtB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,KAAK,EAAE,mBAAmB,EAAsB,MAAM,kCAAkC,CAAC;AAChG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAcjE,qBACa,sBAAsB;IAI/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAGxB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;IAG3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;IAGzB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IAG7B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IAG7B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;IAG3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;IAGzB,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC;gBArBhB,MAAM,CAAC,GAAE,qBAAqB,GAAG,IAAI,aAAA,EAGrC,SAAS,CAAC,GAAE,mBAAmB,GAAG,IAAI,aAAA,EAGtC,OAAO,CAAC,GAAE,cAAc,GAAG,IAAI,aAAA,EAG/B,WAAW,CAAC,GAAE,eAAe,GAAG,IAAI,aAAA,EAGpC,WAAW,CAAC,GAAE,WAAW,GAAG,IAAI,aAAA,EAGhC,SAAS,CAAC,GAAE,SAAS,GAAG,IAAI,aAAA,EAG5B,OAAO,CAAC,GAAE,cAAc,GAAG,IAAI,aAAA,EAG/B,eAAe,CAAC,GAAE,IAAI,CAAC,mBAAmB,EAAE,sBAAsB,CAAC,GAAG,IAAI,aAAA;IAMvF,YAAY,CACR,IAAI,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,EAE3B,EAAE,EAAE,MAAM,GACf,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,CAAC;IA6ExB;;;;;;OAMG;IACH,OAAO,CAAC,KAAK;CAkBd"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.ConfirmEmailController = void 0;
|
|
16
|
+
/**
|
|
17
|
+
* `POST /auth/confirm-email` — the CONFIRM half of the verified self-service email change (security
|
|
18
|
+
* hardening Phase 12, KI #14). `@Public()`: the link may be opened on a device with no session; the
|
|
19
|
+
* single-use token IS the credential. The request half is `PATCH /account/profile`.
|
|
20
|
+
*
|
|
21
|
+
* Flow:
|
|
22
|
+
* 1. per-IP budget `confirm-email:ip:<ip>` (A6; enforced only with an explicit `trust proxy`);
|
|
23
|
+
* 2. consume the token atomically (sha256; single use) — unknown / expired / used → 400;
|
|
24
|
+
* 3. ONE transaction (`EmailAddressChanger.change`): lock the user + credential rows, refuse a
|
|
25
|
+
* stale binding or a non-active user (400, A3) and a collision (409), write `u_email`, move the
|
|
26
|
+
* login hint that equalled the old email, burn the user's pending email-change tokens;
|
|
27
|
+
* 4. after commit: revoke every session of the user EXCEPT the requesting one (A7, durable revoke
|
|
28
|
+
* + Redis unbind through the session sweeper), burn outstanding reset tokens, notify the OLD
|
|
29
|
+
* address (A10, no link), audit (A9).
|
|
30
|
+
*
|
|
31
|
+
* Outcomes: 200 `{ ok: true }`; 400 `invalid_token`; 409 `email_unavailable`; 429
|
|
32
|
+
* `too_many_requests`; 503 `authentication_unavailable` (limiter / store outage, or a follow-up
|
|
33
|
+
* after the committed change failed); 501 `not_available` when a seam is unplugged.
|
|
34
|
+
*
|
|
35
|
+
* The token is never logged, echoed, or placed in an audit row; the body is never logged.
|
|
36
|
+
*/
|
|
37
|
+
const common_1 = require("@nestjs/common");
|
|
38
|
+
const node_crypto_1 = require("node:crypto");
|
|
39
|
+
const classification_decorator_js_1 = require("../enforcement/classification.decorator.js");
|
|
40
|
+
const tokens_js_1 = require("./tokens.js");
|
|
41
|
+
const trust_proxy_check_js_1 = require("./rate-limit/trust-proxy-check.js");
|
|
42
|
+
const email_address_js_1 = require("./email-address.js");
|
|
43
|
+
const email_change_messages_js_1 = require("./email-change-messages.js");
|
|
44
|
+
function fail(status, error) {
|
|
45
|
+
throw new common_1.HttpException({ error }, status);
|
|
46
|
+
}
|
|
47
|
+
function invalidToken() {
|
|
48
|
+
fail(common_1.HttpStatus.BAD_REQUEST, 'invalid_token');
|
|
49
|
+
}
|
|
50
|
+
function unavailable() {
|
|
51
|
+
fail(common_1.HttpStatus.SERVICE_UNAVAILABLE, 'authentication_unavailable');
|
|
52
|
+
}
|
|
53
|
+
let ConfirmEmailController = class ConfirmEmailController {
|
|
54
|
+
tokens;
|
|
55
|
+
addresses;
|
|
56
|
+
sweeper;
|
|
57
|
+
resetTokens;
|
|
58
|
+
emailSender;
|
|
59
|
+
auditSink;
|
|
60
|
+
limiter;
|
|
61
|
+
trustProxyCheck;
|
|
62
|
+
constructor(tokens, addresses, sweeper, resetTokens, emailSender, auditSink, limiter, trustProxyCheck) {
|
|
63
|
+
this.tokens = tokens;
|
|
64
|
+
this.addresses = addresses;
|
|
65
|
+
this.sweeper = sweeper;
|
|
66
|
+
this.resetTokens = resetTokens;
|
|
67
|
+
this.emailSender = emailSender;
|
|
68
|
+
this.auditSink = auditSink;
|
|
69
|
+
this.limiter = limiter;
|
|
70
|
+
this.trustProxyCheck = trustProxyCheck;
|
|
71
|
+
}
|
|
72
|
+
async confirmEmail(body, ip) {
|
|
73
|
+
const tokens = this.tokens;
|
|
74
|
+
const addresses = this.addresses;
|
|
75
|
+
if (tokens == null || addresses == null)
|
|
76
|
+
fail(common_1.HttpStatus.NOT_IMPLEMENTED, 'not_available');
|
|
77
|
+
// A6: count every attempt first (fail closed); a blank ip is its own bucket, never a shared one.
|
|
78
|
+
const limiter = this.trustProxyCheck?.perIpLimitingEnabled === true ? this.limiter : null;
|
|
79
|
+
const ipKey = `confirm-email:ip:${ip || 'unknown-ip'}`;
|
|
80
|
+
if (limiter != null) {
|
|
81
|
+
let allowed;
|
|
82
|
+
try {
|
|
83
|
+
({ allowed } = await limiter.consume(ipKey));
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
unavailable();
|
|
87
|
+
}
|
|
88
|
+
if (!allowed)
|
|
89
|
+
fail(common_1.HttpStatus.TOO_MANY_REQUESTS, 'too_many_requests');
|
|
90
|
+
}
|
|
91
|
+
const token = typeof body?.token === 'string' ? body.token : '';
|
|
92
|
+
if (token === '')
|
|
93
|
+
invalidToken();
|
|
94
|
+
let binding;
|
|
95
|
+
try {
|
|
96
|
+
binding = await tokens.consume((0, node_crypto_1.createHash)('sha256').update(token).digest('hex'));
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
unavailable();
|
|
100
|
+
}
|
|
101
|
+
if (binding === null)
|
|
102
|
+
invalidToken();
|
|
103
|
+
// A valid single-use token proved the caller: give the attempt back.
|
|
104
|
+
await limiter?.refund(ipKey).catch(() => undefined);
|
|
105
|
+
let outcome;
|
|
106
|
+
try {
|
|
107
|
+
outcome = await addresses.change({
|
|
108
|
+
principalId: binding.principalId,
|
|
109
|
+
newEmail: binding.newEmail,
|
|
110
|
+
expectedOldEmail: binding.oldEmail,
|
|
111
|
+
requireActive: true,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
unavailable();
|
|
116
|
+
}
|
|
117
|
+
const maskedNew = (0, email_address_js_1.maskEmail)(binding.newEmail);
|
|
118
|
+
if (outcome.status !== 'changed') {
|
|
119
|
+
const reason = outcome.status === 'unavailable' ? 'email_unavailable' : outcome.status;
|
|
120
|
+
this.audit(binding, 'account.email_change_refused', { reason, newEmail: maskedNew });
|
|
121
|
+
if (outcome.status === 'unavailable')
|
|
122
|
+
fail(common_1.HttpStatus.CONFLICT, 'email_unavailable');
|
|
123
|
+
invalidToken();
|
|
124
|
+
}
|
|
125
|
+
// After commit. Each follow-up is attempted; a failure is surfaced (503) only after the rest
|
|
126
|
+
// ran, so the notice and the audit always go out for a committed change.
|
|
127
|
+
let followUpFailed = false;
|
|
128
|
+
if (this.sweeper != null) {
|
|
129
|
+
await this.sweeper
|
|
130
|
+
.revokeOthersForPrincipal(binding.principalId, binding.sessionId)
|
|
131
|
+
.catch(() => {
|
|
132
|
+
followUpFailed = true;
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
if (this.resetTokens != null) {
|
|
136
|
+
await this.resetTokens.invalidateAllForPrincipal(binding.principalId).catch(() => {
|
|
137
|
+
followUpFailed = true;
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
if (outcome.oldEmail !== null && outcome.oldEmail !== '' && this.emailSender != null) {
|
|
141
|
+
await this.emailSender
|
|
142
|
+
.send((0, email_change_messages_js_1.emailChangedNoticeMessage)(outcome.oldEmail, maskedNew, Date.now()))
|
|
143
|
+
.catch(() => undefined); // best-effort: the change is committed
|
|
144
|
+
}
|
|
145
|
+
this.audit(binding, 'account.email_changed', {
|
|
146
|
+
newEmail: maskedNew,
|
|
147
|
+
loginHintMoved: outcome.hintMoved,
|
|
148
|
+
});
|
|
149
|
+
if (followUpFailed)
|
|
150
|
+
unavailable();
|
|
151
|
+
return { ok: true };
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Best-effort auth audit (A9): masked address, a reason code; never a token. Attributed to the
|
|
155
|
+
* token's user and the tenant of the session that requested the change (KI #14 re-review P1-2:
|
|
156
|
+
* the confirm request may carry no session, and `audit_log.al_tenant_id` is NOT NULL). Only a
|
|
157
|
+
* consumed token has a binding, so an unknown / expired / used token is never audited: there is
|
|
158
|
+
* no user or tenant to attribute it to (the per-IP limiter bounds that path).
|
|
159
|
+
*/
|
|
160
|
+
audit(binding, action, attributes) {
|
|
161
|
+
void this.auditSink
|
|
162
|
+
?.record({
|
|
163
|
+
type: 'authentication',
|
|
164
|
+
occurredAtMs: Date.now(),
|
|
165
|
+
actorId: binding.principalId,
|
|
166
|
+
tenantId: binding.tenantId,
|
|
167
|
+
action,
|
|
168
|
+
attributes,
|
|
169
|
+
})
|
|
170
|
+
.catch(() => {
|
|
171
|
+
/* best-effort: an audit failure never changes the response */
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
exports.ConfirmEmailController = ConfirmEmailController;
|
|
176
|
+
__decorate([
|
|
177
|
+
(0, classification_decorator_js_1.Public)(),
|
|
178
|
+
(0, common_1.Post)('confirm-email'),
|
|
179
|
+
(0, common_1.HttpCode)(common_1.HttpStatus.OK),
|
|
180
|
+
__param(0, (0, common_1.Body)()),
|
|
181
|
+
__param(1, (0, common_1.Ip)()),
|
|
182
|
+
__metadata("design:type", Function),
|
|
183
|
+
__metadata("design:paramtypes", [Object, String]),
|
|
184
|
+
__metadata("design:returntype", Promise)
|
|
185
|
+
], ConfirmEmailController.prototype, "confirmEmail", null);
|
|
186
|
+
exports.ConfirmEmailController = ConfirmEmailController = __decorate([
|
|
187
|
+
(0, common_1.Controller)('auth'),
|
|
188
|
+
__param(0, (0, common_1.Optional)()),
|
|
189
|
+
__param(0, (0, common_1.Inject)(tokens_js_1.SHIELD_EMAIL_CHANGE_TOKEN_STORE)),
|
|
190
|
+
__param(1, (0, common_1.Optional)()),
|
|
191
|
+
__param(1, (0, common_1.Inject)(tokens_js_1.SHIELD_EMAIL_ADDRESS_CHANGER)),
|
|
192
|
+
__param(2, (0, common_1.Optional)()),
|
|
193
|
+
__param(2, (0, common_1.Inject)(tokens_js_1.SHIELD_SESSION_SWEEPER)),
|
|
194
|
+
__param(3, (0, common_1.Optional)()),
|
|
195
|
+
__param(3, (0, common_1.Inject)(tokens_js_1.SHIELD_RESET_TOKEN_STORE)),
|
|
196
|
+
__param(4, (0, common_1.Optional)()),
|
|
197
|
+
__param(4, (0, common_1.Inject)(tokens_js_1.SHIELD_EMAIL_SENDER)),
|
|
198
|
+
__param(5, (0, common_1.Optional)()),
|
|
199
|
+
__param(5, (0, common_1.Inject)(tokens_js_1.SHIELD_AUDIT_SINK)),
|
|
200
|
+
__param(6, (0, common_1.Optional)()),
|
|
201
|
+
__param(6, (0, common_1.Inject)(tokens_js_1.SHIELD_CONFIRM_EMAIL_LIMITER)),
|
|
202
|
+
__param(7, (0, common_1.Optional)()),
|
|
203
|
+
__param(7, (0, common_1.Inject)(trust_proxy_check_js_1.TrustProxyBootCheck)),
|
|
204
|
+
__metadata("design:paramtypes", [Object, Object, Object, Object, Object, Object, Object, Object])
|
|
205
|
+
], ConfirmEmailController);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email-address helpers shared by the self-service email change (guard `PATCH /account/profile`,
|
|
3
|
+
* `POST /auth/confirm-email`) and the administrator path (`@astram/identity` `UserService`), so
|
|
4
|
+
* the two never drift (security hardening Phase 12, KI #14). Exported from the guard main entry;
|
|
5
|
+
* identity imports them (identity may import guard, never the reverse — ADR-0003/ADR-0026).
|
|
6
|
+
*
|
|
7
|
+
* Pure functions, no I/O.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* The canonical stored form of an email address (A5): trim → NFKC → trim → lower-case. Applied
|
|
11
|
+
* ONCE at the edge; that one value is what the token row, the collision check and the final write
|
|
12
|
+
* use. The second trim removes spaces NFKC itself produces (a no-break space folds to a space).
|
|
13
|
+
*
|
|
14
|
+
* Not the limiter-key canonicalisation (`canonicalHint`, which keys Redis budgets): that one stays
|
|
15
|
+
* unchanged so existing limiter keys do not move.
|
|
16
|
+
*/
|
|
17
|
+
export declare function canonicalEmail(raw: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* A deliberately minimal email shape check (non-empty, a single `@`, a `.` in the domain, no
|
|
20
|
+
* whitespace). A full RFC-5322 validator adds no security value here, only false rejections; the
|
|
21
|
+
* authoritative check is the confirm link reaching the inbox.
|
|
22
|
+
*/
|
|
23
|
+
export declare function isEmailShaped(value: string): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* The masked form used in audit rows and the old-address notice (A9, A10): the first character of
|
|
26
|
+
* the local part, `***`, then the domain (`alice@example.com` → `a***@example.com`). A value with no
|
|
27
|
+
* usable `@` is never echoed (`***`).
|
|
28
|
+
*/
|
|
29
|
+
export declare function maskEmail(value: string): string;
|
|
30
|
+
//# sourceMappingURL=email-address.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email-address.d.ts","sourceRoot":"","sources":["../../src/auth/email-address.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAElD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAMpD;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAI/C"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Email-address helpers shared by the self-service email change (guard `PATCH /account/profile`,
|
|
4
|
+
* `POST /auth/confirm-email`) and the administrator path (`@astram/identity` `UserService`), so
|
|
5
|
+
* the two never drift (security hardening Phase 12, KI #14). Exported from the guard main entry;
|
|
6
|
+
* identity imports them (identity may import guard, never the reverse — ADR-0003/ADR-0026).
|
|
7
|
+
*
|
|
8
|
+
* Pure functions, no I/O.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.canonicalEmail = canonicalEmail;
|
|
12
|
+
exports.isEmailShaped = isEmailShaped;
|
|
13
|
+
exports.maskEmail = maskEmail;
|
|
14
|
+
/**
|
|
15
|
+
* The canonical stored form of an email address (A5): trim → NFKC → trim → lower-case. Applied
|
|
16
|
+
* ONCE at the edge; that one value is what the token row, the collision check and the final write
|
|
17
|
+
* use. The second trim removes spaces NFKC itself produces (a no-break space folds to a space).
|
|
18
|
+
*
|
|
19
|
+
* Not the limiter-key canonicalisation (`canonicalHint`, which keys Redis budgets): that one stays
|
|
20
|
+
* unchanged so existing limiter keys do not move.
|
|
21
|
+
*/
|
|
22
|
+
function canonicalEmail(raw) {
|
|
23
|
+
return raw.trim().normalize('NFKC').trim().toLowerCase();
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* A deliberately minimal email shape check (non-empty, a single `@`, a `.` in the domain, no
|
|
27
|
+
* whitespace). A full RFC-5322 validator adds no security value here, only false rejections; the
|
|
28
|
+
* authoritative check is the confirm link reaching the inbox.
|
|
29
|
+
*/
|
|
30
|
+
function isEmailShaped(value) {
|
|
31
|
+
if (/\s/.test(value))
|
|
32
|
+
return false;
|
|
33
|
+
const at = value.indexOf('@');
|
|
34
|
+
if (at <= 0 || at !== value.lastIndexOf('@') || at === value.length - 1)
|
|
35
|
+
return false;
|
|
36
|
+
const domain = value.slice(at + 1);
|
|
37
|
+
return domain.includes('.') && !domain.startsWith('.') && !domain.endsWith('.');
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The masked form used in audit rows and the old-address notice (A9, A10): the first character of
|
|
41
|
+
* the local part, `***`, then the domain (`alice@example.com` → `a***@example.com`). A value with no
|
|
42
|
+
* usable `@` is never echoed (`***`).
|
|
43
|
+
*/
|
|
44
|
+
function maskEmail(value) {
|
|
45
|
+
const at = value.lastIndexOf('@');
|
|
46
|
+
if (at <= 0 || at === value.length - 1)
|
|
47
|
+
return '***';
|
|
48
|
+
return `${value.slice(0, 1)}***@${value.slice(at + 1)}`;
|
|
49
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two emails of the verified self-service email change (security hardening Phase 12, KI #14).
|
|
3
|
+
* The framework composes them; the {@link EmailSender} only delivers. Both carry HTML and text.
|
|
4
|
+
*
|
|
5
|
+
* - {@link confirmEmailChangeMessage}: to the NEW address, with the confirm link. The raw token is
|
|
6
|
+
* in the link's FRAGMENT (`#t=`, A4) — it is never placed in a query string, logged, or returned.
|
|
7
|
+
* - {@link emailChangedNoticeMessage}: to the OLD address after a confirmed change (A10). No link;
|
|
8
|
+
* the time, the masked new address, and what to do if it wasn't the owner.
|
|
9
|
+
*/
|
|
10
|
+
import type { EmailMessage } from './ports/email-sender.js';
|
|
11
|
+
/** Email-change token time-to-live: 30 minutes (same as a password reset). */
|
|
12
|
+
export declare const EMAIL_CHANGE_TTL_MS: number;
|
|
13
|
+
/** The default confirm-page base (a dev Vite origin), like the reset-link default. */
|
|
14
|
+
export declare const DEFAULT_EMAIL_CHANGE_LINK_BASE_URL = "http://localhost:5173/confirm-email";
|
|
15
|
+
/**
|
|
16
|
+
* What to do if the change was not the owner's (A10, reworded in Phase 13 #2a). After an
|
|
17
|
+
* attacker's change the login hint may have moved with the email (it follows when it equalled the
|
|
18
|
+
* old address), so a reset from the login page may not reach the victim: the administrator comes
|
|
19
|
+
* first, and the reset advice is qualified rather than given as the remedy.
|
|
20
|
+
*/
|
|
21
|
+
export declare const NOT_YOU_SENTENCE: string;
|
|
22
|
+
/** The confirm link: the token rides in the fragment, never the query (A4). */
|
|
23
|
+
export declare function emailChangeLink(base: string, rawToken: string): string;
|
|
24
|
+
export declare function confirmEmailChangeMessage(to: string, link: string): EmailMessage;
|
|
25
|
+
export declare function emailChangedNoticeMessage(to: string, maskedNewEmail: string, atMs: number): EmailMessage;
|
|
26
|
+
//# sourceMappingURL=email-change-messages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email-change-messages.d.ts","sourceRoot":"","sources":["../../src/auth/email-change-messages.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAE5D,8EAA8E;AAC9E,eAAO,MAAM,mBAAmB,QAAiB,CAAC;AAElD,sFAAsF;AACtF,eAAO,MAAM,kCAAkC,wCAAwC,CAAC;AAExF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,QAE8C,CAAC;AAU5E,+EAA+E;AAC/E,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEtE;AAED,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,YAAY,CAehF;AAED,wBAAgB,yBAAyB,CACvC,EAAE,EAAE,MAAM,EACV,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,MAAM,GACX,YAAY,CAYd"}
|