@atlasauth/angular 0.7.0 → 0.8.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 +12 -0
- package/dist/esm2022/lib/atlas.service.mjs +8 -4
- package/dist/esm2022/lib/components/attempt-flow.component.mjs +16 -2
- package/dist/esm2022/lib/components/flow-controller.mjs +13 -6
- package/dist/esm2022/lib/components/sign-in.component.mjs +43 -3
- package/dist/esm2022/lib/components/sign-up.component.mjs +4 -2
- package/dist/esm2022/lib/i18n.mjs +3 -1
- package/dist/esm2022/lib/version.mjs +2 -2
- package/dist/fesm2022/atlasauth-angular.mjs +81 -13
- package/dist/fesm2022/atlasauth-angular.mjs.map +1 -1
- package/dist/lib/atlas.service.d.ts +2 -1
- package/dist/lib/components/attempt-flow.component.d.ts +4 -2
- package/dist/lib/components/flow-controller.d.ts +1 -1
- package/dist/lib/components/sign-in.component.d.ts +9 -1
- package/dist/lib/version.d.ts +1 -1
- package/package.json +2 -2
|
@@ -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 { TokenCache, SignOutNotifier, resolveFrontendApi, catalogLocale, configureReferralCapture, FapiClient, SignOutGuard, localizedErrorMessage, getTemplateToken, getProviderToken, signOutReasonFromClient, readRedirectResult, completeOAuthPopupLanding, mapOAuthPopupPayload, stripRedirectParams, closeOAuthPopupAfterSignIn, SDK_HEADER, sdkHeaderValue, reportSdkAdvisory, readExpiry, msUntilRefresh, nextStep, createCaptchaController, atlasCaptchaLabels, flowFromPending, initialFlow, fetchInstanceSettings, offeredFirstFactors, prepareFactor, signInCaptchaOptions, flowNeedsCaptcha, planFirstFactor, advanceWithCaptcha, createSignUp, shouldKeepPolling, pollAttempt } from '@atlasauth/js';
|
|
7
|
+
import { TokenCache, SignOutNotifier, resolveFrontendApi, withDevBrowser, catalogLocale, configureReferralCapture, FapiClient, SignOutGuard, localizedErrorMessage, getTemplateToken, getProviderToken, signOutReasonFromClient, readRedirectResult, completeOAuthPopupLanding, mapOAuthPopupPayload, stripRedirectParams, closeOAuthPopupAfterSignIn, SDK_HEADER, sdkHeaderValue, reportSdkAdvisory, readExpiry, msUntilRefresh, nextStep, createCaptchaController, atlasCaptchaLabels, flowFromPending, initialFlow, fetchInstanceSettings, offeredFirstFactors, prepareFactor, signInCaptchaOptions, flowNeedsCaptcha, planFirstFactor, advanceWithCaptcha, createSignUp, shouldKeepPolling, pollAttempt, signUp } 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';
|
|
@@ -171,6 +171,8 @@ const EN_US = {
|
|
|
171
171
|
'signIn.codeSentTo': 'We sent a code to {email}. Enter it below.',
|
|
172
172
|
'signIn.resendCode': 'Resend code',
|
|
173
173
|
'signIn.magicLinkSent': 'Check your email for a sign-in link.',
|
|
174
|
+
'signIn.signedIn': 'You\'re signed in.',
|
|
175
|
+
'signIn.sessionNotKept': 'You signed in, but this browser didn\'t keep the session. If it blocks third-party cookies, allow cookies for this site and try again.',
|
|
174
176
|
'signIn.checkOtherTab': 'You can close this tab — you are signed in where you started.',
|
|
175
177
|
'signUp.title': 'Create your account',
|
|
176
178
|
'signUp.submit': 'Continue',
|
|
@@ -291,7 +293,7 @@ function withFallback(locale) {
|
|
|
291
293
|
}
|
|
292
294
|
|
|
293
295
|
/** This package's version, sent as `X-Atlas-SDK` (kept in step with package.json by a test). */
|
|
294
|
-
const ATLAS_ANGULAR_VERSION = '0.
|
|
296
|
+
const ATLAS_ANGULAR_VERSION = '0.8.0';
|
|
295
297
|
const ATLAS_ANGULAR_SDK = { name: '@atlasauth/angular', version: ATLAS_ANGULAR_VERSION };
|
|
296
298
|
|
|
297
299
|
/**
|
|
@@ -398,7 +400,8 @@ class AtlasService {
|
|
|
398
400
|
this.baseUrl = resolveFrontendApi(options.frontendApi, {
|
|
399
401
|
component: 'provideAtlas() / AtlasModule.forRoot()',
|
|
400
402
|
});
|
|
401
|
-
|
|
403
|
+
// Development instances keep the session in a header where the browser blocks the cookie.
|
|
404
|
+
this.doFetch = withDevBrowser(options.publishableKey, options.fetchImpl ?? ((...args) => fetch(...args)));
|
|
402
405
|
this.catalog = options.localization ? withFallback(options.localization) : EN_US;
|
|
403
406
|
this.errorCatalog = options.localization ?? EN_US;
|
|
404
407
|
this.requestLocale = catalogLocale(options.localization);
|
|
@@ -591,6 +594,7 @@ class AtlasService {
|
|
|
591
594
|
await this.reload();
|
|
592
595
|
}
|
|
593
596
|
/** Re-read `/v1/client`: refresh user, session, memberships and status. */
|
|
597
|
+
/** Re-boot from the Frontend API. Resolves true when a session now exists in this browser. */
|
|
594
598
|
async reload() {
|
|
595
599
|
try {
|
|
596
600
|
const response = await this.call('/v1/client');
|
|
@@ -601,7 +605,7 @@ class AtlasService {
|
|
|
601
605
|
// refresh cookie; the guard re-tries its revoke in the background.
|
|
602
606
|
if (!body.session || !body.user || this.guard.refuse(body.session.id)) {
|
|
603
607
|
this.clearLocalState(body.session ? 'other_tab' : signOutReasonFromClient(body));
|
|
604
|
-
return;
|
|
608
|
+
return false;
|
|
605
609
|
}
|
|
606
610
|
this.notifier.clear();
|
|
607
611
|
this._user.next(body.user);
|
|
@@ -611,6 +615,7 @@ class AtlasService {
|
|
|
611
615
|
this.applyToken(body.jwt, body.session.id);
|
|
612
616
|
this._status.next('signed_in');
|
|
613
617
|
this.scheduleRefresh();
|
|
618
|
+
return true;
|
|
614
619
|
}
|
|
615
620
|
catch {
|
|
616
621
|
// A failed boot is NOT signed-out. Rendering a sign-in form because the
|
|
@@ -618,6 +623,7 @@ class AtlasService {
|
|
|
618
623
|
// needless re-authentication. Only promote loading → signed_out.
|
|
619
624
|
if (this._status.value === 'loading')
|
|
620
625
|
this._status.next('signed_out');
|
|
626
|
+
return false;
|
|
621
627
|
}
|
|
622
628
|
}
|
|
623
629
|
// --- Internals --------------------------------------------------------------
|
|
@@ -1078,6 +1084,8 @@ class AttemptFlowComponent {
|
|
|
1078
1084
|
attempt = null;
|
|
1079
1085
|
busy = false;
|
|
1080
1086
|
errors = [];
|
|
1087
|
+
/** `FlowState.sessionKept`: whether this browser kept the session the sign-in created. */
|
|
1088
|
+
sessionKept = undefined;
|
|
1081
1089
|
titleKey = 'signIn.title';
|
|
1082
1090
|
/**
|
|
1083
1091
|
* The sign-in captcha, drawn above the submit button on the identifier step
|
|
@@ -1192,6 +1200,12 @@ class AttemptFlowComponent {
|
|
|
1192
1200
|
case 'await_oauth':
|
|
1193
1201
|
return [msg('signIn.magicLinkSent')];
|
|
1194
1202
|
case 'done':
|
|
1203
|
+
// Signed in only when this browser holds the session; a blocked cookie is said
|
|
1204
|
+
// plainly. "Check the other tab" is for a sign-in completed on another device.
|
|
1205
|
+
if (this.sessionKept === true)
|
|
1206
|
+
return [msg('signIn.signedIn')];
|
|
1207
|
+
if (this.sessionKept === false)
|
|
1208
|
+
return [{ kind: 'alert', name: '', labelKey: 'signIn.sessionNotKept', type: 'text' }];
|
|
1195
1209
|
return [msg('signIn.checkOtherTab')];
|
|
1196
1210
|
case 'restart':
|
|
1197
1211
|
return [msg('error.generic')];
|
|
@@ -1202,7 +1216,7 @@ class AttemptFlowComponent {
|
|
|
1202
1216
|
}
|
|
1203
1217
|
}
|
|
1204
1218
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: AttemptFlowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1205
|
-
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", titleKey: "titleKey", captcha: "captcha", firstFactors: "firstFactors", preparedFactor: "preparedFactor" }, outputs: { submitFlow: "submitFlow", chooseFactor: "chooseFactor" }, ngImport: i0, template: `
|
|
1219
|
+
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" }, outputs: { submitFlow: "submitFlow", chooseFactor: "chooseFactor" }, ngImport: i0, template: `
|
|
1206
1220
|
<div [class]="cls('card', 'atlas-card')">
|
|
1207
1221
|
<h1 [class]="cls('headerTitle', 'atlas-title')">{{ t(titleKey) }}</h1>
|
|
1208
1222
|
|
|
@@ -1229,6 +1243,8 @@ class AttemptFlowComponent {
|
|
|
1229
1243
|
<span [class]="cls('formFieldError', 'atlas-error')">{{ errorText(error) }}</span>
|
|
1230
1244
|
}
|
|
1231
1245
|
</label>
|
|
1246
|
+
} @else if (f.kind === 'alert') {
|
|
1247
|
+
<p [class]="cls('formFieldError', 'atlas-error')" role="alert">{{ t(f.labelKey) }}</p>
|
|
1232
1248
|
} @else {
|
|
1233
1249
|
<p>{{ t(f.labelKey) }}</p>
|
|
1234
1250
|
}
|
|
@@ -1294,6 +1310,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
1294
1310
|
<span [class]="cls('formFieldError', 'atlas-error')">{{ errorText(error) }}</span>
|
|
1295
1311
|
}
|
|
1296
1312
|
</label>
|
|
1313
|
+
} @else if (f.kind === 'alert') {
|
|
1314
|
+
<p [class]="cls('formFieldError', 'atlas-error')" role="alert">{{ t(f.labelKey) }}</p>
|
|
1297
1315
|
} @else {
|
|
1298
1316
|
<p>{{ t(f.labelKey) }}</p>
|
|
1299
1317
|
}
|
|
@@ -1331,6 +1349,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
1331
1349
|
type: Input
|
|
1332
1350
|
}], errors: [{
|
|
1333
1351
|
type: Input
|
|
1352
|
+
}], sessionKept: [{
|
|
1353
|
+
type: Input
|
|
1334
1354
|
}], titleKey: [{
|
|
1335
1355
|
type: Input
|
|
1336
1356
|
}], captcha: [{
|
|
@@ -1520,17 +1540,23 @@ class AtlasFlowController {
|
|
|
1520
1540
|
await this.finish(next);
|
|
1521
1541
|
}
|
|
1522
1542
|
async finish(next) {
|
|
1523
|
-
this.setState(next);
|
|
1524
1543
|
if (next.attempt?.status === 'complete') {
|
|
1544
|
+
// Stays busy until this browser actually holds the session: the completion
|
|
1545
|
+
// alone says nothing about whether the cookie was kept.
|
|
1546
|
+
this.setState({ ...next, busy: true });
|
|
1547
|
+
let exchanged = true;
|
|
1525
1548
|
if (next.ticket) {
|
|
1526
|
-
await this.client.post('/v1/client/tickets/exchange', {
|
|
1549
|
+
const res = await this.client.post('/v1/client/tickets/exchange', {
|
|
1527
1550
|
attempt_id: next.attempt.id,
|
|
1528
1551
|
ticket: next.ticket,
|
|
1529
1552
|
});
|
|
1553
|
+
exchanged = res.ok;
|
|
1530
1554
|
}
|
|
1531
|
-
await this.reload();
|
|
1555
|
+
const kept = exchanged && (await this.reload());
|
|
1556
|
+
this.setState({ ...next, busy: false, sessionKept: kept });
|
|
1532
1557
|
}
|
|
1533
1558
|
else {
|
|
1559
|
+
this.setState(next);
|
|
1534
1560
|
// A magic-link step may now be pollable (a link opened on another device).
|
|
1535
1561
|
this.syncPolling();
|
|
1536
1562
|
}
|
|
@@ -1555,11 +1581,12 @@ class AtlasFlowController {
|
|
|
1555
1581
|
return;
|
|
1556
1582
|
// The ticket is exchanged by a normal request, so no session token ever
|
|
1557
1583
|
// appears in a URL.
|
|
1558
|
-
await this.client.post('/v1/client/tickets/exchange', {
|
|
1584
|
+
const res = await this.client.post('/v1/client/tickets/exchange', {
|
|
1559
1585
|
attempt_id: next.attempt.id,
|
|
1560
1586
|
ticket,
|
|
1561
1587
|
});
|
|
1562
|
-
await this.reload();
|
|
1588
|
+
const kept = res.ok && (await this.reload());
|
|
1589
|
+
this.setState({ ...this.state(), sessionKept: kept });
|
|
1563
1590
|
this.stopPolling();
|
|
1564
1591
|
})();
|
|
1565
1592
|
}, 2_000);
|
|
@@ -1592,6 +1619,31 @@ class SignInComponent {
|
|
|
1592
1619
|
atlas = inject(AtlasService);
|
|
1593
1620
|
/** Where to send the user once the flow completes. Reserved for host routing. */
|
|
1594
1621
|
afterUrl;
|
|
1622
|
+
/**
|
|
1623
|
+
* Where "No account? Sign up" goes. Default: the instance's hosted sign-up
|
|
1624
|
+
* page (carrying the visitor's referral). `null` hides it. Always shown, never
|
|
1625
|
+
* only after a failed sign-in, so it says nothing about whether an email has an account.
|
|
1626
|
+
*/
|
|
1627
|
+
signUpUrl;
|
|
1628
|
+
t(key) {
|
|
1629
|
+
return translate(this.atlas.localization, key);
|
|
1630
|
+
}
|
|
1631
|
+
signUpHref() {
|
|
1632
|
+
if (this.signUpUrl === null)
|
|
1633
|
+
return null;
|
|
1634
|
+
if (this.signUpUrl)
|
|
1635
|
+
return this.signUpUrl;
|
|
1636
|
+
try {
|
|
1637
|
+
return signUp({
|
|
1638
|
+
frontendApi: this.atlas.frontendApi,
|
|
1639
|
+
redirectUrl: this.afterUrl ?? (typeof window !== 'undefined' ? window.location.href : undefined),
|
|
1640
|
+
navigate: false,
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
catch {
|
|
1644
|
+
return null;
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1595
1647
|
flow = new AtlasFlowController(this.atlas.createFapiClient(), () => this.atlas.reload(),
|
|
1596
1648
|
// Resume mid-flow if an OAuth redirect came back owing a second factor.
|
|
1597
1649
|
this.atlas.pendingAttempt, (key) => translate(this.atlas.localization, key));
|
|
@@ -1608,11 +1660,12 @@ class SignInComponent {
|
|
|
1608
1660
|
this.flow.destroy();
|
|
1609
1661
|
}
|
|
1610
1662
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: SignInComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1611
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
1663
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: SignInComponent, isStandalone: true, selector: "atlas-sign-in", inputs: { afterUrl: "afterUrl", signUpUrl: "signUpUrl" }, ngImport: i0, template: `
|
|
1612
1664
|
<atlas-attempt-flow
|
|
1613
1665
|
[attempt]="flow.state().attempt"
|
|
1614
1666
|
[busy]="flow.state().busy"
|
|
1615
1667
|
[errors]="flow.state().errors"
|
|
1668
|
+
[sessionKept]="flow.state().sessionKept"
|
|
1616
1669
|
titleKey="signIn.title"
|
|
1617
1670
|
[captcha]="flow.captchaDrawn() ? flow.captcha : null"
|
|
1618
1671
|
[firstFactors]="flow.firstFactors()"
|
|
@@ -1620,7 +1673,12 @@ class SignInComponent {
|
|
|
1620
1673
|
(submitFlow)="flow.submit($event)"
|
|
1621
1674
|
(chooseFactor)="flow.prepare($event)"
|
|
1622
1675
|
></atlas-attempt-flow>
|
|
1623
|
-
|
|
1676
|
+
@if (flow.state().attempt?.status !== 'complete' && signUpHref(); as href) {
|
|
1677
|
+
<p class="atlas-footer" data-atlas-sign-up-prompt="">
|
|
1678
|
+
{{ t('signIn.noAccount') }} <a class="atlas-link" [href]="href">{{ t('signIn.signUpLink') }}</a>
|
|
1679
|
+
</p>
|
|
1680
|
+
}
|
|
1681
|
+
`, isInline: true, dependencies: [{ kind: "component", type: AttemptFlowComponent, selector: "atlas-attempt-flow", inputs: ["attempt", "busy", "errors", "sessionKept", "titleKey", "captcha", "firstFactors", "preparedFactor"], outputs: ["submitFlow", "chooseFactor"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1624
1682
|
}
|
|
1625
1683
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: SignInComponent, decorators: [{
|
|
1626
1684
|
type: Component,
|
|
@@ -1634,6 +1692,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
1634
1692
|
[attempt]="flow.state().attempt"
|
|
1635
1693
|
[busy]="flow.state().busy"
|
|
1636
1694
|
[errors]="flow.state().errors"
|
|
1695
|
+
[sessionKept]="flow.state().sessionKept"
|
|
1637
1696
|
titleKey="signIn.title"
|
|
1638
1697
|
[captcha]="flow.captchaDrawn() ? flow.captcha : null"
|
|
1639
1698
|
[firstFactors]="flow.firstFactors()"
|
|
@@ -1641,10 +1700,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
1641
1700
|
(submitFlow)="flow.submit($event)"
|
|
1642
1701
|
(chooseFactor)="flow.prepare($event)"
|
|
1643
1702
|
></atlas-attempt-flow>
|
|
1703
|
+
@if (flow.state().attempt?.status !== 'complete' && signUpHref(); as href) {
|
|
1704
|
+
<p class="atlas-footer" data-atlas-sign-up-prompt="">
|
|
1705
|
+
{{ t('signIn.noAccount') }} <a class="atlas-link" [href]="href">{{ t('signIn.signUpLink') }}</a>
|
|
1706
|
+
</p>
|
|
1707
|
+
}
|
|
1644
1708
|
`,
|
|
1645
1709
|
}]
|
|
1646
1710
|
}], propDecorators: { afterUrl: [{
|
|
1647
1711
|
type: Input
|
|
1712
|
+
}], signUpUrl: [{
|
|
1713
|
+
type: Input
|
|
1648
1714
|
}] } });
|
|
1649
1715
|
|
|
1650
1716
|
/**
|
|
@@ -2111,6 +2177,7 @@ class SignUpComponent {
|
|
|
2111
2177
|
[attempt]="flow.state().attempt"
|
|
2112
2178
|
[busy]="flow.state().busy"
|
|
2113
2179
|
[errors]="flow.state().errors"
|
|
2180
|
+
[sessionKept]="flow.state().sessionKept"
|
|
2114
2181
|
titleKey="signUp.title"
|
|
2115
2182
|
(submitFlow)="flow.submit($event)"
|
|
2116
2183
|
></atlas-attempt-flow>
|
|
@@ -2123,7 +2190,7 @@ class SignUpComponent {
|
|
|
2123
2190
|
(submitSignUp)="flow.create($event.values, $event.consent)"
|
|
2124
2191
|
></atlas-sign-up-form>
|
|
2125
2192
|
}
|
|
2126
|
-
`, isInline: true, dependencies: [{ kind: "component", type: AttemptFlowComponent, selector: "atlas-attempt-flow", inputs: ["attempt", "busy", "errors", "titleKey", "captcha", "firstFactors", "preparedFactor"], outputs: ["submitFlow", "chooseFactor"] }, { kind: "component", type: SignUpFormComponent, selector: "atlas-sign-up-form", inputs: ["settings", "busy", "errors", "captcha"], outputs: ["submitSignUp"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2193
|
+
`, isInline: true, dependencies: [{ kind: "component", type: AttemptFlowComponent, selector: "atlas-attempt-flow", inputs: ["attempt", "busy", "errors", "sessionKept", "titleKey", "captcha", "firstFactors", "preparedFactor"], outputs: ["submitFlow", "chooseFactor"] }, { kind: "component", type: SignUpFormComponent, selector: "atlas-sign-up-form", inputs: ["settings", "busy", "errors", "captcha"], outputs: ["submitSignUp"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2127
2194
|
}
|
|
2128
2195
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: SignUpComponent, decorators: [{
|
|
2129
2196
|
type: Component,
|
|
@@ -2138,6 +2205,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
2138
2205
|
[attempt]="flow.state().attempt"
|
|
2139
2206
|
[busy]="flow.state().busy"
|
|
2140
2207
|
[errors]="flow.state().errors"
|
|
2208
|
+
[sessionKept]="flow.state().sessionKept"
|
|
2141
2209
|
titleKey="signUp.title"
|
|
2142
2210
|
(submitFlow)="flow.submit($event)"
|
|
2143
2211
|
></atlas-attempt-flow>
|