@atlasauth/angular 0.13.0 → 0.15.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/CHANGELOG.md +18 -0
- package/dist/esm2022/lib/components/attempt-flow.component.mjs +96 -5
- package/dist/esm2022/lib/components/flow-controller.mjs +64 -10
- package/dist/esm2022/lib/components/sign-in.component.mjs +8 -2
- package/dist/esm2022/lib/components/sign-up.component.mjs +97 -8
- package/dist/esm2022/lib/i18n.mjs +12 -1
- package/dist/esm2022/lib/version.mjs +2 -2
- package/dist/fesm2022/atlasauth-angular.mjs +271 -21
- package/dist/fesm2022/atlasauth-angular.mjs.map +1 -1
- package/dist/lib/components/attempt-flow.component.d.ts +46 -6
- package/dist/lib/components/flow-controller.d.ts +21 -4
- package/dist/lib/components/sign-up.component.d.ts +9 -1
- package/dist/lib/version.d.ts +1 -1
- package/package.json +6 -6
|
@@ -4,7 +4,7 @@ import { isPlatformBrowser, DOCUMENT } from '@angular/common';
|
|
|
4
4
|
import { toSignal } from '@angular/core/rxjs-interop';
|
|
5
5
|
import { BehaviorSubject, Subject, combineLatest, firstValueFrom } from 'rxjs';
|
|
6
6
|
import { map, distinctUntilChanged, filter, take } from 'rxjs/operators';
|
|
7
|
-
import { createOrganizationAuditLog, POLICY_STRINGS, TokenCache, SignOutNotifier, resolveFrontendApi, withDevBrowser, catalogLocale, configureReferralCapture, FapiClient, SignOutGuard, localizedErrorMessage, reauthenticate, getTemplateToken, getProviderToken, signOutReasonFromClient, readRedirectResult, completeOAuthPopupLanding, mapOAuthPopupPayload, stripRedirectParams, closeOAuthPopupAfterSignIn, SDK_HEADER, sdkHeaderValue, reportSdkAdvisory, readExpiry, msUntilRefresh, policyTranslator, injectPolicyStyles, mountPolicyClickwrap, mountPolicyConsent, controllerForPending, mountPolicyAcceptStep, joinTitles, formatPolicyDate, PolicyGateController, groupSetupKey, enrollmentSubmitKey, nextStep, awaitingSignUpLink, canDownloadRecoveryCodes, copyRecoveryCodes, downloadRecoveryCodes, createCaptchaController, atlasCaptchaLabels, PolicyAcceptanceController, flowFromPending, initialFlow, createMfaEnrollment, createPasskeySignInController, startSignInWithAtlas, mfaChoicesFrom, fetchInstanceSettings, fetchLastSignInMethod, offeredFirstFactors, prepareFactor, signInCaptchaOptions, flowNeedsCaptcha, planFirstFactor, advanceWithCaptcha, loadPolicies, createSignUp, prepareSecondFactorCode, resendSignUpVerification, createPasskeySignUp, finishPasskeySignUp, pendingRecoveryCodes, acknowledgeRecoveryCodes, shouldKeepPolling, pollAttempt, lastUsedMethodLabel, signUp, passkeysSupported, confirmPolicies, awaitingPasskey, listCibaRequests, getCibaPin, decideCibaRequest, setCibaPin, clearCibaPin, OrganizationProfileController, describeAuditAction, actorFromClaims } from '@atlasauth/js';
|
|
7
|
+
import { createOrganizationAuditLog, POLICY_STRINGS, TokenCache, SignOutNotifier, resolveFrontendApi, withDevBrowser, catalogLocale, configureReferralCapture, FapiClient, SignOutGuard, localizedErrorMessage, reauthenticate, getTemplateToken, getProviderToken, signOutReasonFromClient, readRedirectResult, completeOAuthPopupLanding, mapOAuthPopupPayload, stripRedirectParams, closeOAuthPopupAfterSignIn, SDK_HEADER, sdkHeaderValue, reportSdkAdvisory, readExpiry, msUntilRefresh, policyTranslator, injectPolicyStyles, mountPolicyClickwrap, mountPolicyConsent, controllerForPending, mountPolicyAcceptStep, joinTitles, formatPolicyDate, PolicyGateController, groupSetupKey, enrollmentSubmitKey, nextStep, awaitingSignUpLink, canDownloadRecoveryCodes, copyRecoveryCodes, downloadRecoveryCodes, createCaptchaController, atlasCaptchaLabels, PolicyAcceptanceController, flowFromPending, initialFlow, createMfaEnrollment, createPasskeySignInController, startSignInWithAtlas, mfaChoicesFrom, fetchInstanceSettings, fetchLastSignInMethod, offeredFirstFactors, preparePasswordHoldProof, prepareFactor, signInCaptchaOptions, flowNeedsCaptcha, planFirstFactor, attemptPasswordHoldProof, advanceWithCaptcha, loadPolicies, createSignUp, prepareSecondFactorCode, resendSignUpVerification, createPasskeySignUp, finishPasskeySignUp, pendingRecoveryCodes, acknowledgeRecoveryCodes, pendingExistingAccountNotice, acknowledgeExistingAccount, shouldKeepPolling, pollAttempt, lastUsedMethodLabel, signUp, passkeysSupported, confirmPolicies, existingAccountResetUrl, signUpProvedByLink, signInAfterProofUrl, awaitingPasskey, listCibaRequests, getCibaPin, decideCibaRequest, setCibaPin, clearCibaPin, OrganizationProfileController, describeAuditAction, actorFromClaims } from '@atlasauth/js';
|
|
8
8
|
export { FapiClient, SIGN_OUT_EVENT, SignOutError, advance, flowFromPending, getProviderToken, initialFlow, onAtlasSignOut, pollAttempt, prepareFactor, requestForStep, shouldKeepPolling } from '@atlasauth/js';
|
|
9
9
|
import { evaluate } from '@atlasauth/authz';
|
|
10
10
|
export { evaluate, hasFromClaims } from '@atlasauth/authz';
|
|
@@ -207,6 +207,13 @@ const EN_US = {
|
|
|
207
207
|
'signIn.continueWithAtlas': 'Continue with Atlas',
|
|
208
208
|
'signIn.emailCodeLabel': 'Verification code',
|
|
209
209
|
'signIn.useEmailCode': 'Email me a code',
|
|
210
|
+
'signIn.useEmailLink': 'Email me a sign-in link',
|
|
211
|
+
'signIn.linkSentTo': 'We sent a sign-in link to {email}. Open it on any device to continue.',
|
|
212
|
+
'signIn.resendLink': 'Resend link',
|
|
213
|
+
'signIn.passwordOption': 'Use your password instead',
|
|
214
|
+
'signIn.passwordHoldReleased': 'Your email is verified. Enter your password to sign in.',
|
|
215
|
+
'signUp.existingAccount': 'You already had an account. Your existing password still applies.',
|
|
216
|
+
'signUp.emailConfirmed': 'Your email is confirmed. Sign in to continue.',
|
|
210
217
|
'signIn.codeSentTo': 'We sent a code to {email}. Enter it below.',
|
|
211
218
|
'signIn.resendCode': 'Resend code',
|
|
212
219
|
'signIn.sending': 'Sending…',
|
|
@@ -366,6 +373,9 @@ const EN_US = {
|
|
|
366
373
|
'error.email_delivery_unavailable': "This app can't send email right now. Try another sign-in method or contact the app owner.",
|
|
367
374
|
'error.strategy_disabled': 'This sign-in method is not available for this app.',
|
|
368
375
|
'error.sign_up_restricted': 'Sign-up on this app is by invitation only.',
|
|
376
|
+
'error.sign_up_denied': 'Sign-up is not available for this account.',
|
|
377
|
+
'error.password_not_yet_usable': 'Verify your email address before signing in with this password.',
|
|
378
|
+
'error.account_deleted': 'The account for this address was deleted, so it can’t be used to sign up again.',
|
|
369
379
|
'error.waitlisted': "You're on the waitlist. We'll email you when a place opens up.",
|
|
370
380
|
'error.identifier_not_allowed': 'This email or phone number is not allowed on this app.',
|
|
371
381
|
'error.disposable_email_blocked': 'Sign-up with a disposable or temporary email address is not allowed.',
|
|
@@ -374,6 +384,7 @@ const EN_US = {
|
|
|
374
384
|
'error.sign_ups_disabled': 'Sign-ups are temporarily disabled.',
|
|
375
385
|
'error.high_risk_blocked': 'This sign-in was blocked because it looked unusual.',
|
|
376
386
|
'error.sms_region_blocked': 'Text messages are not available for this phone number.',
|
|
387
|
+
'error.github_team_required': 'Your GitHub account is not a member of a GitHub organization or team allowed to sign in here.',
|
|
377
388
|
};
|
|
378
389
|
/**
|
|
379
390
|
* Look up a key and interpolate `{placeholders}`.
|
|
@@ -392,7 +403,7 @@ function withFallback(locale) {
|
|
|
392
403
|
}
|
|
393
404
|
|
|
394
405
|
/** This package's version, sent as `X-Atlas-SDK` (kept in step with package.json by a test). */
|
|
395
|
-
const ATLAS_ANGULAR_VERSION = '0.
|
|
406
|
+
const ATLAS_ANGULAR_VERSION = '0.15.0';
|
|
396
407
|
const ATLAS_ANGULAR_SDK = { name: '@atlasauth/angular', version: ATLAS_ANGULAR_VERSION };
|
|
397
408
|
|
|
398
409
|
/**
|
|
@@ -1484,10 +1495,16 @@ class AttemptFlowComponent {
|
|
|
1484
1495
|
firstFactors = null;
|
|
1485
1496
|
/** The first factor already sent (`AtlasFlowController.preparedFactor()`): its code is collected. */
|
|
1486
1497
|
preparedFactor = null;
|
|
1498
|
+
/** `FlowState.passwordHold`: where the proof went, and whether the password signs in now. */
|
|
1499
|
+
passwordHold = null;
|
|
1500
|
+
/** `AtlasFlowController.holdProof()`: the emailed proof a password hold still waits on. */
|
|
1501
|
+
holdProof = null;
|
|
1487
1502
|
/** Emits the collected field values when the form is submitted. */
|
|
1488
1503
|
submitFlow = new EventEmitter();
|
|
1489
1504
|
/** Emits when the user picks a first factor the server must send first (an emailed code). */
|
|
1490
1505
|
chooseFactor = new EventEmitter();
|
|
1506
|
+
/** Emits on "Use your password instead", from an emailed factor already sent. */
|
|
1507
|
+
usePassword = new EventEmitter();
|
|
1491
1508
|
/**
|
|
1492
1509
|
* Offer "Email me a code instead" at the second factor (the instance's
|
|
1493
1510
|
* `account.emailSecondFactor`); `emailSecondFactor` asks for it and
|
|
@@ -1548,6 +1565,11 @@ class AttemptFlowComponent {
|
|
|
1548
1565
|
});
|
|
1549
1566
|
return;
|
|
1550
1567
|
}
|
|
1568
|
+
// Only what is on screen: a code typed earlier must not turn a password into a code attempt.
|
|
1569
|
+
if (this.step().kind === 'collect_first_factor') {
|
|
1570
|
+
this.submitFlow.emit(this.preparedFactor === 'email_code' ? { code: this.values['code'] ?? '' } : { password: this.values['password'] ?? '' });
|
|
1571
|
+
return;
|
|
1572
|
+
}
|
|
1551
1573
|
this.submitFlow.emit(this.values);
|
|
1552
1574
|
}
|
|
1553
1575
|
/** The shared enrolment step is drawn (a controller was passed and the flow is enrolling). */
|
|
@@ -1619,7 +1641,13 @@ class AttemptFlowComponent {
|
|
|
1619
1641
|
codeSentText() {
|
|
1620
1642
|
if (this.step().kind !== 'collect_first_factor' || this.preparedFactor !== 'email_code')
|
|
1621
1643
|
return null;
|
|
1622
|
-
return this.t('signIn.codeSentTo', { email: this.attempt?.identifier ?? '' });
|
|
1644
|
+
return this.t('signIn.codeSentTo', { email: this.passwordHold?.email ?? this.attempt?.identifier ?? '' });
|
|
1645
|
+
}
|
|
1646
|
+
/** The "we sent a link to …" line, once a link was sent. */
|
|
1647
|
+
linkSentText() {
|
|
1648
|
+
if (this.step().kind !== 'collect_first_factor' || this.preparedFactor !== 'email_link')
|
|
1649
|
+
return null;
|
|
1650
|
+
return this.t('signIn.linkSentTo', { email: this.passwordHold?.email ?? this.attempt?.identifier ?? '' });
|
|
1623
1651
|
}
|
|
1624
1652
|
/** The attempt is `needs_policy_acceptance`. */
|
|
1625
1653
|
awaitingPolicies() {
|
|
@@ -1630,7 +1658,7 @@ class AttemptFlowComponent {
|
|
|
1630
1658
|
// Nothing to submit on a first-factor step that only offers an emailed code
|
|
1631
1659
|
// until that code is sent.
|
|
1632
1660
|
if (kind === 'collect_first_factor')
|
|
1633
|
-
return this.fields().
|
|
1661
|
+
return this.fields().some((f) => f.kind === 'input');
|
|
1634
1662
|
if (this.linkOnly())
|
|
1635
1663
|
return false;
|
|
1636
1664
|
// Enrolment: nothing to submit while choosing a method or fetching the setup.
|
|
@@ -1648,12 +1676,17 @@ class AttemptFlowComponent {
|
|
|
1648
1676
|
if (this.preparedFactor === 'email_code') {
|
|
1649
1677
|
return [{ kind: 'input', name: 'code', labelKey: 'signIn.emailCodeLabel', type: 'text' }];
|
|
1650
1678
|
}
|
|
1679
|
+
if (this.preparedFactor === 'email_link')
|
|
1680
|
+
return [];
|
|
1651
1681
|
// The password field unless the only offer is an emailed code (chosen
|
|
1652
1682
|
// with the button below the form).
|
|
1653
1683
|
const offered = this.offered();
|
|
1654
1684
|
return offered.includes('email_code') && !offered.includes('password')
|
|
1655
1685
|
? []
|
|
1656
|
-
: [
|
|
1686
|
+
: [
|
|
1687
|
+
...(this.passwordHold?.released ? [released()] : []),
|
|
1688
|
+
{ kind: 'input', name: 'password', labelKey: 'signIn.passwordLabel', type: 'password' },
|
|
1689
|
+
];
|
|
1657
1690
|
}
|
|
1658
1691
|
case 'collect_second_factor':
|
|
1659
1692
|
return [{ kind: 'input', name: 'code', labelKey: 'mfa.codeLabel', type: 'text' }];
|
|
@@ -1697,7 +1730,7 @@ class AttemptFlowComponent {
|
|
|
1697
1730
|
}
|
|
1698
1731
|
}
|
|
1699
1732
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: AttemptFlowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1700
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: AttemptFlowComponent, isStandalone: true, selector: "atlas-attempt-flow", inputs: { attempt: "attempt", busy: "busy", errors: "errors", sessionKept: "sessionKept", titleKey: "titleKey", captcha: "captcha", firstFactors: "firstFactors", preparedFactor: "preparedFactor", offerEmailSecondFactor: "offerEmailSecondFactor", emailedSecondFactor: "emailedSecondFactor", offerSmsSecondFactor: "offerSmsSecondFactor", textedSecondFactor: "textedSecondFactor", enrollment: "enrollment", enrollmentState: "enrollmentState", emailVerification: "emailVerification" }, outputs: { submitFlow: "submitFlow", chooseFactor: "chooseFactor", emailSecondFactor: "emailSecondFactor", smsSecondFactor: "smsSecondFactor", resend: "resend", policyDeclined: "policyDeclined" }, ngImport: i0, template: `
|
|
1733
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: AttemptFlowComponent, isStandalone: true, selector: "atlas-attempt-flow", inputs: { attempt: "attempt", busy: "busy", errors: "errors", sessionKept: "sessionKept", titleKey: "titleKey", captcha: "captcha", firstFactors: "firstFactors", preparedFactor: "preparedFactor", passwordHold: "passwordHold", holdProof: "holdProof", offerEmailSecondFactor: "offerEmailSecondFactor", emailedSecondFactor: "emailedSecondFactor", offerSmsSecondFactor: "offerSmsSecondFactor", textedSecondFactor: "textedSecondFactor", enrollment: "enrollment", enrollmentState: "enrollmentState", emailVerification: "emailVerification" }, outputs: { submitFlow: "submitFlow", chooseFactor: "chooseFactor", usePassword: "usePassword", emailSecondFactor: "emailSecondFactor", smsSecondFactor: "smsSecondFactor", resend: "resend", policyDeclined: "policyDeclined" }, ngImport: i0, template: `
|
|
1701
1734
|
<div [class]="cls('card', 'atlas-card')">
|
|
1702
1735
|
<h1 [class]="cls('headerTitle', 'atlas-title')">{{ t(titleKey) }}</h1>
|
|
1703
1736
|
|
|
@@ -1709,6 +1742,9 @@ class AttemptFlowComponent {
|
|
|
1709
1742
|
@if (codeSentText(); as sentText) {
|
|
1710
1743
|
<p class="atlas-text" data-atlas-code-sent="">{{ sentText }}</p>
|
|
1711
1744
|
}
|
|
1745
|
+
@if (linkSentText(); as sentLink) {
|
|
1746
|
+
<p class="atlas-text" data-atlas-link-sent="">{{ sentLink }}</p>
|
|
1747
|
+
}
|
|
1712
1748
|
@if (linkOnly()) {
|
|
1713
1749
|
<!-- Nothing to type: the flow polls until the emailed link is opened (anywhere). -->
|
|
1714
1750
|
<p class="atlas-text" data-atlas-link-sent="" aria-live="polite">
|
|
@@ -1817,6 +1853,8 @@ class AttemptFlowComponent {
|
|
|
1817
1853
|
<span [class]="cls('formFieldError', 'atlas-error')">{{ errorText(error) }}</span>
|
|
1818
1854
|
}
|
|
1819
1855
|
</label>
|
|
1856
|
+
} @else if (f.kind === 'released') {
|
|
1857
|
+
<p class="atlas-text" data-atlas-password-released="">{{ t(f.labelKey) }}</p>
|
|
1820
1858
|
} @else if (f.kind === 'alert') {
|
|
1821
1859
|
<p [class]="cls('formFieldError', 'atlas-error')" role="alert">{{ t(f.labelKey) }}</p>
|
|
1822
1860
|
} @else {
|
|
@@ -1898,6 +1936,31 @@ class AttemptFlowComponent {
|
|
|
1898
1936
|
{{ t(preparedFactor === 'email_code' ? 'signIn.resendCode' : 'signIn.useEmailCode') }}
|
|
1899
1937
|
</button>
|
|
1900
1938
|
}
|
|
1939
|
+
|
|
1940
|
+
@if (holdProof === 'email_link' && step().kind === 'collect_first_factor') {
|
|
1941
|
+
<button
|
|
1942
|
+
[class]="cls('formButtonSecondary', 'atlas-button-secondary')"
|
|
1943
|
+
[disabled]="busy"
|
|
1944
|
+
type="button"
|
|
1945
|
+
[attr.data-strategy]="preparedFactor === 'email_link' ? null : 'email_link'"
|
|
1946
|
+
[attr.data-atlas-resend]="preparedFactor === 'email_link' ? '' : null"
|
|
1947
|
+
(click)="chooseFactor.emit('email_link')"
|
|
1948
|
+
>
|
|
1949
|
+
{{ t(preparedFactor === 'email_link' ? 'signIn.resendLink' : 'signIn.useEmailLink') }}
|
|
1950
|
+
</button>
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
@if (preparedFactor && offered().includes('password')) {
|
|
1954
|
+
<button
|
|
1955
|
+
[class]="cls('formButtonSecondary', 'atlas-button-secondary')"
|
|
1956
|
+
[disabled]="busy"
|
|
1957
|
+
type="button"
|
|
1958
|
+
data-strategy="password"
|
|
1959
|
+
(click)="usePassword.emit()"
|
|
1960
|
+
>
|
|
1961
|
+
{{ t('signIn.passwordOption') }}
|
|
1962
|
+
</button>
|
|
1963
|
+
}
|
|
1901
1964
|
</form>
|
|
1902
1965
|
</div>
|
|
1903
1966
|
`, isInline: true, dependencies: [{ kind: "directive", type: AtlasCaptchaSlotDirective, selector: "[atlasCaptchaSlot]", inputs: ["atlasCaptchaSlot"] }, { kind: "component", type: PolicyAcceptStepComponent, selector: "atlas-policy-accept-step", inputs: ["attempt", "busy"], outputs: ["submitFlow", "declined"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
@@ -1921,6 +1984,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
1921
1984
|
@if (codeSentText(); as sentText) {
|
|
1922
1985
|
<p class="atlas-text" data-atlas-code-sent="">{{ sentText }}</p>
|
|
1923
1986
|
}
|
|
1987
|
+
@if (linkSentText(); as sentLink) {
|
|
1988
|
+
<p class="atlas-text" data-atlas-link-sent="">{{ sentLink }}</p>
|
|
1989
|
+
}
|
|
1924
1990
|
@if (linkOnly()) {
|
|
1925
1991
|
<!-- Nothing to type: the flow polls until the emailed link is opened (anywhere). -->
|
|
1926
1992
|
<p class="atlas-text" data-atlas-link-sent="" aria-live="polite">
|
|
@@ -2029,6 +2095,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
2029
2095
|
<span [class]="cls('formFieldError', 'atlas-error')">{{ errorText(error) }}</span>
|
|
2030
2096
|
}
|
|
2031
2097
|
</label>
|
|
2098
|
+
} @else if (f.kind === 'released') {
|
|
2099
|
+
<p class="atlas-text" data-atlas-password-released="">{{ t(f.labelKey) }}</p>
|
|
2032
2100
|
} @else if (f.kind === 'alert') {
|
|
2033
2101
|
<p [class]="cls('formFieldError', 'atlas-error')" role="alert">{{ t(f.labelKey) }}</p>
|
|
2034
2102
|
} @else {
|
|
@@ -2110,6 +2178,31 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
2110
2178
|
{{ t(preparedFactor === 'email_code' ? 'signIn.resendCode' : 'signIn.useEmailCode') }}
|
|
2111
2179
|
</button>
|
|
2112
2180
|
}
|
|
2181
|
+
|
|
2182
|
+
@if (holdProof === 'email_link' && step().kind === 'collect_first_factor') {
|
|
2183
|
+
<button
|
|
2184
|
+
[class]="cls('formButtonSecondary', 'atlas-button-secondary')"
|
|
2185
|
+
[disabled]="busy"
|
|
2186
|
+
type="button"
|
|
2187
|
+
[attr.data-strategy]="preparedFactor === 'email_link' ? null : 'email_link'"
|
|
2188
|
+
[attr.data-atlas-resend]="preparedFactor === 'email_link' ? '' : null"
|
|
2189
|
+
(click)="chooseFactor.emit('email_link')"
|
|
2190
|
+
>
|
|
2191
|
+
{{ t(preparedFactor === 'email_link' ? 'signIn.resendLink' : 'signIn.useEmailLink') }}
|
|
2192
|
+
</button>
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
@if (preparedFactor && offered().includes('password')) {
|
|
2196
|
+
<button
|
|
2197
|
+
[class]="cls('formButtonSecondary', 'atlas-button-secondary')"
|
|
2198
|
+
[disabled]="busy"
|
|
2199
|
+
type="button"
|
|
2200
|
+
data-strategy="password"
|
|
2201
|
+
(click)="usePassword.emit()"
|
|
2202
|
+
>
|
|
2203
|
+
{{ t('signIn.passwordOption') }}
|
|
2204
|
+
</button>
|
|
2205
|
+
}
|
|
2113
2206
|
</form>
|
|
2114
2207
|
</div>
|
|
2115
2208
|
`,
|
|
@@ -2130,10 +2223,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
2130
2223
|
type: Input
|
|
2131
2224
|
}], preparedFactor: [{
|
|
2132
2225
|
type: Input
|
|
2226
|
+
}], passwordHold: [{
|
|
2227
|
+
type: Input
|
|
2228
|
+
}], holdProof: [{
|
|
2229
|
+
type: Input
|
|
2133
2230
|
}], submitFlow: [{
|
|
2134
2231
|
type: Output
|
|
2135
2232
|
}], chooseFactor: [{
|
|
2136
2233
|
type: Output
|
|
2234
|
+
}], usePassword: [{
|
|
2235
|
+
type: Output
|
|
2137
2236
|
}], offerEmailSecondFactor: [{
|
|
2138
2237
|
type: Input
|
|
2139
2238
|
}], emailedSecondFactor: [{
|
|
@@ -2161,6 +2260,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
2161
2260
|
function msg(labelKey) {
|
|
2162
2261
|
return { kind: 'message', name: '', labelKey, type: 'text' };
|
|
2163
2262
|
}
|
|
2263
|
+
function released() {
|
|
2264
|
+
return { kind: 'released', name: '', labelKey: 'signIn.passwordHoldReleased', type: 'text' };
|
|
2265
|
+
}
|
|
2164
2266
|
|
|
2165
2267
|
/**
|
|
2166
2268
|
* The recovery codes a mid-sign-in MFA enrolment came with, shown once before
|
|
@@ -2296,6 +2398,7 @@ class AtlasFlowController {
|
|
|
2296
2398
|
client;
|
|
2297
2399
|
reload;
|
|
2298
2400
|
t;
|
|
2401
|
+
holdExistingAccountNotice;
|
|
2299
2402
|
state;
|
|
2300
2403
|
/**
|
|
2301
2404
|
* The instance's public sign-in / sign-up settings (captcha, sign-up fields,
|
|
@@ -2358,10 +2461,13 @@ class AtlasFlowController {
|
|
|
2358
2461
|
identifier = '';
|
|
2359
2462
|
constructor(client, reload, pending,
|
|
2360
2463
|
/** Resolves the captcha messages' catalog keys. Defaults to en-US. */
|
|
2361
|
-
t = (key) => translate(EN_US, key)
|
|
2464
|
+
t = (key) => translate(EN_US, key),
|
|
2465
|
+
/** Wait for {@link continueAfterExistingAccount} (`<atlas-sign-up>` draws the notice). */
|
|
2466
|
+
holdExistingAccountNotice = false) {
|
|
2362
2467
|
this.client = client;
|
|
2363
2468
|
this.reload = reload;
|
|
2364
2469
|
this.t = t;
|
|
2470
|
+
this.holdExistingAccountNotice = holdExistingAccountNotice;
|
|
2365
2471
|
// Seeded from a redirect that came back mid-flow (e.g. OAuth needing a second
|
|
2366
2472
|
// factor) so the user resumes at the demanded step instead of starting over.
|
|
2367
2473
|
this.state = signal(pending ? flowFromPending(pending) : initialFlow);
|
|
@@ -2428,9 +2534,27 @@ class AtlasFlowController {
|
|
|
2428
2534
|
firstFactors() {
|
|
2429
2535
|
const attempt = this.state().attempt;
|
|
2430
2536
|
const step = attempt ? nextStep(attempt) : null;
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2537
|
+
if (step?.kind !== 'collect_first_factor')
|
|
2538
|
+
return [];
|
|
2539
|
+
const offered = offeredFirstFactors(step.strategies, this.settings()?.emailDelivery);
|
|
2540
|
+
const proof = this.holdProof();
|
|
2541
|
+
return proof && !offered.includes(proof) ? [...offered, proof] : offered;
|
|
2542
|
+
}
|
|
2543
|
+
/**
|
|
2544
|
+
* The emailed proof a password hold still waits on (`FlowState.passwordHold`): the
|
|
2545
|
+
* code, or a link where the instance verifies by link only. Null with no hold, once
|
|
2546
|
+
* proven, or while the app cannot deliver email.
|
|
2547
|
+
*/
|
|
2548
|
+
holdProof() {
|
|
2549
|
+
const hold = this.state().passwordHold;
|
|
2550
|
+
if (!hold || hold.released || this.settings()?.emailDelivery?.available === false)
|
|
2551
|
+
return null;
|
|
2552
|
+
return this.settings()?.account?.emailVerification === 'link' ? 'email_link' : 'email_code';
|
|
2553
|
+
}
|
|
2554
|
+
/** Back from the emailed factor to the password field. */
|
|
2555
|
+
usePassword() {
|
|
2556
|
+
this.preparedFactor.set(null);
|
|
2557
|
+
this.state.update((current) => ({ ...current, prepared: null, pollSecret: null, errors: [] }));
|
|
2434
2558
|
}
|
|
2435
2559
|
/**
|
|
2436
2560
|
* True when email + password sign-up cannot complete because the app cannot
|
|
@@ -2447,16 +2571,21 @@ class AtlasFlowController {
|
|
|
2447
2571
|
/**
|
|
2448
2572
|
* Ask the server to email a sign-in code for the current `needs_first_factor`
|
|
2449
2573
|
* step (`POST …/prepare_first_factor`); the form then collects the code, which
|
|
2450
|
-
* {@link submit} posts as the `email_code` strategy.
|
|
2574
|
+
* {@link submit} posts as the `email_code` strategy. While a password hold waits
|
|
2575
|
+
* on it ({@link holdProof}), this emails that proof instead.
|
|
2451
2576
|
*/
|
|
2452
2577
|
async prepare(strategy) {
|
|
2453
2578
|
if (!this.firstFactors().includes(strategy))
|
|
2454
2579
|
return;
|
|
2580
|
+
const proof = this.holdProof();
|
|
2455
2581
|
this.state.update((current) => ({ ...current, busy: true, errors: [] }));
|
|
2456
|
-
const next =
|
|
2582
|
+
const next = strategy === proof
|
|
2583
|
+
? await preparePasswordHoldProof(this.client, this.state(), proof)
|
|
2584
|
+
: await prepareFactor(this.client, this.state(), strategy);
|
|
2457
2585
|
this.setState(next);
|
|
2458
2586
|
if (!next.errors.length)
|
|
2459
2587
|
this.preparedFactor.set(strategy);
|
|
2588
|
+
this.syncPolling();
|
|
2460
2589
|
}
|
|
2461
2590
|
/** Keep both captchas in step with the settings and the flow's current step. */
|
|
2462
2591
|
syncCaptcha() {
|
|
@@ -2472,8 +2601,12 @@ class AtlasFlowController {
|
|
|
2472
2601
|
this.signUpCaptchaDrawn.set(this.signUpCaptcha.needed);
|
|
2473
2602
|
}
|
|
2474
2603
|
setState(next) {
|
|
2475
|
-
const
|
|
2604
|
+
const previous = this.state();
|
|
2605
|
+
const previousId = previous.attempt?.id ?? null;
|
|
2476
2606
|
this.state.set(next);
|
|
2607
|
+
// A proven hold goes back to the password field.
|
|
2608
|
+
if (next.passwordHold?.released && !previous.passwordHold?.released)
|
|
2609
|
+
this.preparedFactor.set(null);
|
|
2477
2610
|
// A prepared factor belongs to the first-factor step it was sent for.
|
|
2478
2611
|
if (next.attempt?.status !== 'needs_first_factor')
|
|
2479
2612
|
this.preparedFactor.set(null);
|
|
@@ -2509,6 +2642,11 @@ class AtlasFlowController {
|
|
|
2509
2642
|
*/
|
|
2510
2643
|
async submit(values) {
|
|
2511
2644
|
this.state.update((current) => ({ ...current, busy: true, errors: [] }));
|
|
2645
|
+
// A code that lifts a password hold goes to its own attempt, not this sign-in.
|
|
2646
|
+
if (this.preparedFactor() === 'email_code' && this.holdProof() === 'email_code') {
|
|
2647
|
+
this.setState(await attemptPasswordHoldProof(this.client, this.state(), values.code ?? ''));
|
|
2648
|
+
return;
|
|
2649
|
+
}
|
|
2512
2650
|
const out = await advanceWithCaptcha(this.client, this.state(), values, {
|
|
2513
2651
|
captcha: this.captcha,
|
|
2514
2652
|
identifier: this.identifier,
|
|
@@ -2604,10 +2742,24 @@ class AtlasFlowController {
|
|
|
2604
2742
|
async saveRecoveryCodes() {
|
|
2605
2743
|
await this.finish(acknowledgeRecoveryCodes(this.state()));
|
|
2606
2744
|
}
|
|
2745
|
+
/** The address belonged to an existing account: the notice is up until {@link continueAfterExistingAccount}. */
|
|
2746
|
+
existingAccountNotice() {
|
|
2747
|
+
return pendingExistingAccountNotice(this.state());
|
|
2748
|
+
}
|
|
2749
|
+
/** The user read the notice: finish the sign-up. */
|
|
2750
|
+
async continueAfterExistingAccount() {
|
|
2751
|
+
await this.finish(acknowledgeExistingAccount(this.state()));
|
|
2752
|
+
}
|
|
2607
2753
|
async finish(next) {
|
|
2608
2754
|
// An enrolment's recovery codes are shown first; `saveRecoveryCodes` finishes.
|
|
2609
2755
|
if (pendingRecoveryCodes(next)) {
|
|
2610
|
-
|
|
2756
|
+
// The ticket dies in 60 seconds and saving the codes can take longer: redeem it now,
|
|
2757
|
+
// and `saveRecoveryCodes` finishes. A refused exchange keeps the ticket, so the save reports it.
|
|
2758
|
+
this.setState({ ...next, busy: true });
|
|
2759
|
+
const res = next.ticket
|
|
2760
|
+
? await this.client.post('/v1/client/tickets/exchange', { attempt_id: next.attempt.id, ticket: next.ticket })
|
|
2761
|
+
: null;
|
|
2762
|
+
this.setState({ ...next, busy: false, ticket: res?.ok ? null : next.ticket });
|
|
2611
2763
|
return;
|
|
2612
2764
|
}
|
|
2613
2765
|
if (next.attempt?.status === 'complete') {
|
|
@@ -2622,6 +2774,10 @@ class AtlasFlowController {
|
|
|
2622
2774
|
});
|
|
2623
2775
|
exchanged = res.ok;
|
|
2624
2776
|
}
|
|
2777
|
+
if (exchanged && this.holdExistingAccountNotice && pendingExistingAccountNotice(next)) {
|
|
2778
|
+
this.setState({ ...next, ticket: null, busy: false });
|
|
2779
|
+
return;
|
|
2780
|
+
}
|
|
2625
2781
|
const kept = exchanged && (await this.reload());
|
|
2626
2782
|
this.setState({ ...next, busy: false, sessionKept: kept });
|
|
2627
2783
|
}
|
|
@@ -2781,6 +2937,9 @@ class SignInComponent {
|
|
|
2781
2937
|
[captcha]="flow.captchaDrawn() ? flow.captcha : null"
|
|
2782
2938
|
[firstFactors]="flow.firstFactors()"
|
|
2783
2939
|
[preparedFactor]="flow.preparedFactor()"
|
|
2940
|
+
[passwordHold]="flow.state().passwordHold ?? null"
|
|
2941
|
+
[holdProof]="flow.holdProof()"
|
|
2942
|
+
(usePassword)="flow.usePassword()"
|
|
2784
2943
|
[offerEmailSecondFactor]="flow.settings()?.account?.emailSecondFactor === true"
|
|
2785
2944
|
[emailedSecondFactor]="flow.emailSecondFactor()"
|
|
2786
2945
|
(submitFlow)="submitIdentifier($event)"
|
|
@@ -2810,7 +2969,7 @@ class SignInComponent {
|
|
|
2810
2969
|
{{ t('signIn.noAccount') }} <a class="atlas-link" [href]="href">{{ t('signIn.signUpLink') }}</a>
|
|
2811
2970
|
</p>
|
|
2812
2971
|
}
|
|
2813
|
-
`, isInline: true, dependencies: [{ kind: "component", type: AttemptFlowComponent, selector: "atlas-attempt-flow", inputs: ["attempt", "busy", "errors", "sessionKept", "titleKey", "captcha", "firstFactors", "preparedFactor", "offerEmailSecondFactor", "emailedSecondFactor", "offerSmsSecondFactor", "textedSecondFactor", "enrollment", "enrollmentState", "emailVerification"], outputs: ["submitFlow", "chooseFactor", "emailSecondFactor", "smsSecondFactor", "resend", "policyDeclined"] }, { kind: "component", type: RecoveryCodesComponent, selector: "atlas-recovery-codes", inputs: ["codes", "busy"], outputs: ["saved"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2972
|
+
`, isInline: true, dependencies: [{ kind: "component", type: AttemptFlowComponent, selector: "atlas-attempt-flow", inputs: ["attempt", "busy", "errors", "sessionKept", "titleKey", "captcha", "firstFactors", "preparedFactor", "passwordHold", "holdProof", "offerEmailSecondFactor", "emailedSecondFactor", "offerSmsSecondFactor", "textedSecondFactor", "enrollment", "enrollmentState", "emailVerification"], outputs: ["submitFlow", "chooseFactor", "usePassword", "emailSecondFactor", "smsSecondFactor", "resend", "policyDeclined"] }, { kind: "component", type: RecoveryCodesComponent, selector: "atlas-recovery-codes", inputs: ["codes", "busy"], outputs: ["saved"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2814
2973
|
}
|
|
2815
2974
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: SignInComponent, decorators: [{
|
|
2816
2975
|
type: Component,
|
|
@@ -2845,6 +3004,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
2845
3004
|
[captcha]="flow.captchaDrawn() ? flow.captcha : null"
|
|
2846
3005
|
[firstFactors]="flow.firstFactors()"
|
|
2847
3006
|
[preparedFactor]="flow.preparedFactor()"
|
|
3007
|
+
[passwordHold]="flow.state().passwordHold ?? null"
|
|
3008
|
+
[holdProof]="flow.holdProof()"
|
|
3009
|
+
(usePassword)="flow.usePassword()"
|
|
2848
3010
|
[offerEmailSecondFactor]="flow.settings()?.account?.emailSecondFactor === true"
|
|
2849
3011
|
[emailedSecondFactor]="flow.emailSecondFactor()"
|
|
2850
3012
|
(submitFlow)="submitIdentifier($event)"
|
|
@@ -3455,7 +3617,11 @@ class SignUpComponent {
|
|
|
3455
3617
|
atlas = inject(AtlasService);
|
|
3456
3618
|
/** Where to send the user once the flow completes. Reserved for host routing. */
|
|
3457
3619
|
afterUrl;
|
|
3458
|
-
|
|
3620
|
+
/** Where the existing-account notice's "Forgot password?" goes. Default: the hosted sign-in page; `null` hides it. */
|
|
3621
|
+
resetPasswordUrl;
|
|
3622
|
+
/** Where "Sign in" goes once an emailed link confirmed the address and made no session here. Default: the hosted sign-in page; `null` hides it. */
|
|
3623
|
+
signInUrl;
|
|
3624
|
+
flow = new AtlasFlowController(this.atlas.createFapiClient(), () => this.atlas.reload(), this.atlas.pendingAttempt, (key) => translate(this.atlas.localization, key), true);
|
|
3459
3625
|
/** Password or passkey-only sign-up (the latter when the instance offers it). */
|
|
3460
3626
|
mode = signal('password');
|
|
3461
3627
|
canPasskey() {
|
|
@@ -3469,6 +3635,16 @@ class SignUpComponent {
|
|
|
3469
3635
|
this.flow.state.update((current) => ({ ...current, errors: [] }));
|
|
3470
3636
|
this.mode.set(this.passkeyForm() ? 'password' : 'passkey');
|
|
3471
3637
|
}
|
|
3638
|
+
resetHref() {
|
|
3639
|
+
return existingAccountResetUrl({ url: this.resetPasswordUrl, frontendApi: this.atlas.frontendApi, redirectUrl: this.afterUrl });
|
|
3640
|
+
}
|
|
3641
|
+
/** A link confirmed the address and made no session here. Says the same for any address. */
|
|
3642
|
+
emailConfirmed() {
|
|
3643
|
+
return signUpProvedByLink(this.flow.state().attempt);
|
|
3644
|
+
}
|
|
3645
|
+
signInHref() {
|
|
3646
|
+
return signInAfterProofUrl({ url: this.signInUrl, frontendApi: this.atlas.frontendApi, redirectUrl: this.afterUrl });
|
|
3647
|
+
}
|
|
3472
3648
|
awaitingPasskey() {
|
|
3473
3649
|
return awaitingPasskey(this.flow.state().attempt);
|
|
3474
3650
|
}
|
|
@@ -3493,8 +3669,26 @@ class SignUpComponent {
|
|
|
3493
3669
|
this.flow.destroy();
|
|
3494
3670
|
}
|
|
3495
3671
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: SignUpComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3496
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: SignUpComponent, isStandalone: true, selector: "atlas-sign-up", inputs: { afterUrl: "afterUrl" }, ngImport: i0, template: `
|
|
3497
|
-
@if (
|
|
3672
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: SignUpComponent, isStandalone: true, selector: "atlas-sign-up", inputs: { afterUrl: "afterUrl", resetPasswordUrl: "resetPasswordUrl", signInUrl: "signInUrl" }, ngImport: i0, template: `
|
|
3673
|
+
@if (flow.recoveryCodes(); as codes) {
|
|
3674
|
+
<atlas-recovery-codes [codes]="codes" [busy]="flow.state().busy" (saved)="flow.saveRecoveryCodes()"></atlas-recovery-codes>
|
|
3675
|
+
} @else if (flow.existingAccountNotice()) {
|
|
3676
|
+
<div [class]="cls('card', 'atlas-card')" data-atlas-existing-account="">
|
|
3677
|
+
<h1 [class]="cls('headerTitle', 'atlas-title')">{{ t('signUp.title') }}</h1>
|
|
3678
|
+
<p class="atlas-text">{{ t('signUp.existingAccount') }}</p>
|
|
3679
|
+
@if (resetHref(); as href) {
|
|
3680
|
+
<p class="atlas-text"><a class="atlas-link" [href]="href">{{ t('signIn.forgotPassword') }}</a></p>
|
|
3681
|
+
}
|
|
3682
|
+
<button
|
|
3683
|
+
[class]="cls('formButtonPrimary', 'atlas-button')"
|
|
3684
|
+
type="button"
|
|
3685
|
+
[disabled]="flow.state().busy"
|
|
3686
|
+
(click)="flow.continueAfterExistingAccount()"
|
|
3687
|
+
>
|
|
3688
|
+
{{ t('signUp.submit') }}
|
|
3689
|
+
</button>
|
|
3690
|
+
</div>
|
|
3691
|
+
} @else if (awaitingPasskey()) {
|
|
3498
3692
|
<div [class]="cls('card', 'atlas-card')" data-atlas-passkey-sign-up="">
|
|
3499
3693
|
<h1 [class]="cls('headerTitle', 'atlas-title')">{{ t('signUp.title') }}</h1>
|
|
3500
3694
|
@for (error of flow.state().errors; track error.code) {
|
|
@@ -3510,6 +3704,14 @@ class SignUpComponent {
|
|
|
3510
3704
|
{{ t('signUp.passkeyCreate') }}
|
|
3511
3705
|
</button>
|
|
3512
3706
|
</div>
|
|
3707
|
+
} @else if (emailConfirmed()) {
|
|
3708
|
+
<div [class]="cls('card', 'atlas-card')" data-atlas-email-confirmed="">
|
|
3709
|
+
<h1 [class]="cls('headerTitle', 'atlas-title')">{{ t('signUp.title') }}</h1>
|
|
3710
|
+
<p class="atlas-text" role="status">{{ t('signUp.emailConfirmed') }}</p>
|
|
3711
|
+
@if (signInHref(); as href) {
|
|
3712
|
+
<a [class]="cls('formButtonPrimary', 'atlas-button')" [href]="href">{{ t('signIn.title') }}</a>
|
|
3713
|
+
}
|
|
3714
|
+
</div>
|
|
3513
3715
|
} @else if (flow.state().attempt) {
|
|
3514
3716
|
<atlas-attempt-flow
|
|
3515
3717
|
[attempt]="flow.state().attempt"
|
|
@@ -3520,6 +3722,15 @@ class SignUpComponent {
|
|
|
3520
3722
|
[emailVerification]="flow.settings()?.account?.emailVerification ?? 'code'"
|
|
3521
3723
|
(submitFlow)="flow.submit($event)"
|
|
3522
3724
|
(resend)="flow.resendSignUp()"
|
|
3725
|
+
[offerEmailSecondFactor]="flow.settings()?.account?.emailSecondFactor === true"
|
|
3726
|
+
[emailedSecondFactor]="flow.emailSecondFactor()"
|
|
3727
|
+
[offerSmsSecondFactor]="flow.settings() !== null && flow.mfaChoices().sms"
|
|
3728
|
+
[textedSecondFactor]="flow.smsSecondFactor()"
|
|
3729
|
+
[enrollment]="flow.enrollment"
|
|
3730
|
+
[enrollmentState]="flow.enrollmentState()"
|
|
3731
|
+
(emailSecondFactor)="flow.sendEmailSecondFactor()"
|
|
3732
|
+
(smsSecondFactor)="flow.sendSmsSecondFactor()"
|
|
3733
|
+
(policyDeclined)="flow.declinePolicy($event)"
|
|
3523
3734
|
></atlas-attempt-flow>
|
|
3524
3735
|
} @else {
|
|
3525
3736
|
<atlas-sign-up-form
|
|
@@ -3536,7 +3747,7 @@ class SignUpComponent {
|
|
|
3536
3747
|
"
|
|
3537
3748
|
></atlas-sign-up-form>
|
|
3538
3749
|
}
|
|
3539
|
-
`, isInline: true, dependencies: [{ kind: "component", type: AttemptFlowComponent, selector: "atlas-attempt-flow", inputs: ["attempt", "busy", "errors", "sessionKept", "titleKey", "captcha", "firstFactors", "preparedFactor", "offerEmailSecondFactor", "emailedSecondFactor", "offerSmsSecondFactor", "textedSecondFactor", "enrollment", "enrollmentState", "emailVerification"], outputs: ["submitFlow", "chooseFactor", "emailSecondFactor", "smsSecondFactor", "resend", "policyDeclined"] }, { kind: "component", type: SignUpFormComponent, selector: "atlas-sign-up-form", inputs: ["settings", "busy", "errors", "captcha", "passkey", "switchable", "policies"], outputs: ["switchMode", "submitSignUp"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3750
|
+
`, isInline: true, dependencies: [{ kind: "component", type: AttemptFlowComponent, selector: "atlas-attempt-flow", inputs: ["attempt", "busy", "errors", "sessionKept", "titleKey", "captcha", "firstFactors", "preparedFactor", "passwordHold", "holdProof", "offerEmailSecondFactor", "emailedSecondFactor", "offerSmsSecondFactor", "textedSecondFactor", "enrollment", "enrollmentState", "emailVerification"], outputs: ["submitFlow", "chooseFactor", "usePassword", "emailSecondFactor", "smsSecondFactor", "resend", "policyDeclined"] }, { kind: "component", type: RecoveryCodesComponent, selector: "atlas-recovery-codes", inputs: ["codes", "busy"], outputs: ["saved"] }, { kind: "component", type: SignUpFormComponent, selector: "atlas-sign-up-form", inputs: ["settings", "busy", "errors", "captcha", "passkey", "switchable", "policies"], outputs: ["switchMode", "submitSignUp"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3540
3751
|
}
|
|
3541
3752
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: SignUpComponent, decorators: [{
|
|
3542
3753
|
type: Component,
|
|
@@ -3544,9 +3755,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
3544
3755
|
selector: 'atlas-sign-up',
|
|
3545
3756
|
standalone: true,
|
|
3546
3757
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
3547
|
-
imports: [AttemptFlowComponent, SignUpFormComponent],
|
|
3758
|
+
imports: [AttemptFlowComponent, RecoveryCodesComponent, SignUpFormComponent],
|
|
3548
3759
|
template: `
|
|
3549
|
-
@if (
|
|
3760
|
+
@if (flow.recoveryCodes(); as codes) {
|
|
3761
|
+
<atlas-recovery-codes [codes]="codes" [busy]="flow.state().busy" (saved)="flow.saveRecoveryCodes()"></atlas-recovery-codes>
|
|
3762
|
+
} @else if (flow.existingAccountNotice()) {
|
|
3763
|
+
<div [class]="cls('card', 'atlas-card')" data-atlas-existing-account="">
|
|
3764
|
+
<h1 [class]="cls('headerTitle', 'atlas-title')">{{ t('signUp.title') }}</h1>
|
|
3765
|
+
<p class="atlas-text">{{ t('signUp.existingAccount') }}</p>
|
|
3766
|
+
@if (resetHref(); as href) {
|
|
3767
|
+
<p class="atlas-text"><a class="atlas-link" [href]="href">{{ t('signIn.forgotPassword') }}</a></p>
|
|
3768
|
+
}
|
|
3769
|
+
<button
|
|
3770
|
+
[class]="cls('formButtonPrimary', 'atlas-button')"
|
|
3771
|
+
type="button"
|
|
3772
|
+
[disabled]="flow.state().busy"
|
|
3773
|
+
(click)="flow.continueAfterExistingAccount()"
|
|
3774
|
+
>
|
|
3775
|
+
{{ t('signUp.submit') }}
|
|
3776
|
+
</button>
|
|
3777
|
+
</div>
|
|
3778
|
+
} @else if (awaitingPasskey()) {
|
|
3550
3779
|
<div [class]="cls('card', 'atlas-card')" data-atlas-passkey-sign-up="">
|
|
3551
3780
|
<h1 [class]="cls('headerTitle', 'atlas-title')">{{ t('signUp.title') }}</h1>
|
|
3552
3781
|
@for (error of flow.state().errors; track error.code) {
|
|
@@ -3562,6 +3791,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
3562
3791
|
{{ t('signUp.passkeyCreate') }}
|
|
3563
3792
|
</button>
|
|
3564
3793
|
</div>
|
|
3794
|
+
} @else if (emailConfirmed()) {
|
|
3795
|
+
<div [class]="cls('card', 'atlas-card')" data-atlas-email-confirmed="">
|
|
3796
|
+
<h1 [class]="cls('headerTitle', 'atlas-title')">{{ t('signUp.title') }}</h1>
|
|
3797
|
+
<p class="atlas-text" role="status">{{ t('signUp.emailConfirmed') }}</p>
|
|
3798
|
+
@if (signInHref(); as href) {
|
|
3799
|
+
<a [class]="cls('formButtonPrimary', 'atlas-button')" [href]="href">{{ t('signIn.title') }}</a>
|
|
3800
|
+
}
|
|
3801
|
+
</div>
|
|
3565
3802
|
} @else if (flow.state().attempt) {
|
|
3566
3803
|
<atlas-attempt-flow
|
|
3567
3804
|
[attempt]="flow.state().attempt"
|
|
@@ -3572,6 +3809,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
3572
3809
|
[emailVerification]="flow.settings()?.account?.emailVerification ?? 'code'"
|
|
3573
3810
|
(submitFlow)="flow.submit($event)"
|
|
3574
3811
|
(resend)="flow.resendSignUp()"
|
|
3812
|
+
[offerEmailSecondFactor]="flow.settings()?.account?.emailSecondFactor === true"
|
|
3813
|
+
[emailedSecondFactor]="flow.emailSecondFactor()"
|
|
3814
|
+
[offerSmsSecondFactor]="flow.settings() !== null && flow.mfaChoices().sms"
|
|
3815
|
+
[textedSecondFactor]="flow.smsSecondFactor()"
|
|
3816
|
+
[enrollment]="flow.enrollment"
|
|
3817
|
+
[enrollmentState]="flow.enrollmentState()"
|
|
3818
|
+
(emailSecondFactor)="flow.sendEmailSecondFactor()"
|
|
3819
|
+
(smsSecondFactor)="flow.sendSmsSecondFactor()"
|
|
3820
|
+
(policyDeclined)="flow.declinePolicy($event)"
|
|
3575
3821
|
></atlas-attempt-flow>
|
|
3576
3822
|
} @else {
|
|
3577
3823
|
<atlas-sign-up-form
|
|
@@ -3592,6 +3838,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
3592
3838
|
}]
|
|
3593
3839
|
}], propDecorators: { afterUrl: [{
|
|
3594
3840
|
type: Input
|
|
3841
|
+
}], resetPasswordUrl: [{
|
|
3842
|
+
type: Input
|
|
3843
|
+
}], signInUrl: [{
|
|
3844
|
+
type: Input
|
|
3595
3845
|
}] } });
|
|
3596
3846
|
|
|
3597
3847
|
/**
|