@atlasauth/svelte 0.8.0 → 0.10.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/accessors.d.ts +66 -1
  3. package/dist/accessors.js +110 -3
  4. package/dist/appearance.d.ts +1 -1
  5. package/dist/components/AttemptFlow.svelte +188 -4
  6. package/dist/components/AttemptFlow.svelte.d.ts +28 -1
  7. package/dist/components/ImpersonationBanner.svelte +38 -0
  8. package/dist/components/ImpersonationBanner.svelte.d.ts +19 -0
  9. package/dist/components/OrganizationAuditLog.svelte +45 -0
  10. package/dist/components/OrganizationAuditLog.svelte.d.ts +31 -0
  11. package/dist/components/OrganizationProfile.svelte +308 -12
  12. package/dist/components/OrganizationProfile.svelte.d.ts +2 -0
  13. package/dist/components/PasskeySignUpStep.svelte +23 -0
  14. package/dist/components/PasskeySignUpStep.svelte.d.ts +24 -0
  15. package/dist/components/PolicyAcceptStep.svelte +52 -0
  16. package/dist/components/PolicyAcceptStep.svelte.d.ts +30 -0
  17. package/dist/components/PolicyClickwrap.svelte +23 -0
  18. package/dist/components/PolicyClickwrap.svelte.d.ts +19 -0
  19. package/dist/components/PolicyConsent.svelte +23 -0
  20. package/dist/components/PolicyConsent.svelte.d.ts +24 -0
  21. package/dist/components/PolicyGate.svelte +83 -0
  22. package/dist/components/PolicyGate.svelte.d.ts +24 -0
  23. package/dist/components/RecoveryCodes.svelte +55 -0
  24. package/dist/components/RecoveryCodes.svelte.d.ts +25 -0
  25. package/dist/components/SignIn.svelte +21 -1
  26. package/dist/components/SignUp.svelte +22 -3
  27. package/dist/components/SignUpForm.svelte +84 -19
  28. package/dist/components/SignUpForm.svelte.d.ts +6 -1
  29. package/dist/context.d.ts +9 -1
  30. package/dist/context.js +18 -1
  31. package/dist/i18n.js +58 -1
  32. package/dist/index.d.ts +8 -1
  33. package/dist/index.js +8 -1
  34. package/dist/policies.d.ts +6 -0
  35. package/dist/policies.js +17 -0
  36. package/dist/types.d.ts +6 -0
  37. package/dist/version.d.ts +1 -1
  38. package/dist/version.js +1 -1
  39. package/package.json +4 -4
@@ -0,0 +1,83 @@
1
+ <script>import { onDestroy } from "svelte";
2
+ import {
3
+ FapiClient,
4
+ PolicyGateController,
5
+ formatPolicyDate,
6
+ injectPolicyStyles,
7
+ joinTitles,
8
+ mountPolicyAcceptStep
9
+ } from "@atlasauth/js";
10
+ import { useAtlas } from "../context";
11
+ import { classFor } from "../appearance";
12
+ import { usePolicyUi } from "../policies";
13
+ import { ATLAS_SVELTE_SDK } from "../version";
14
+ export let onAccepted = void 0;
15
+ const atlas = useAtlas();
16
+ const status = atlas.status;
17
+ const ui = usePolicyUi();
18
+ const gate = new PolicyGateController(
19
+ new FapiClient({ publishableKey: atlas.publishableKey, baseUrl: atlas.frontendApi, locale: atlas.locale, sdk: ATLAS_SVELTE_SDK }),
20
+ atlas.locale ?? void 0
21
+ );
22
+ let pending = gate.pending;
23
+ let grace = gate.graceEndsAt;
24
+ let controller = null;
25
+ let error = "";
26
+ let step = null;
27
+ const off = gate.subscribe(() => {
28
+ pending = gate.pending;
29
+ grace = gate.graceEndsAt;
30
+ });
31
+ onDestroy(() => {
32
+ off();
33
+ step?.destroy();
34
+ });
35
+ $: if ($status === "signed_in") void gate.load();
36
+ async function begin() {
37
+ controller = await gate.begin();
38
+ }
39
+ function host(node, c) {
40
+ injectPolicyStyles(node.ownerDocument);
41
+ step = mountPolicyAcceptStep(node, c, {
42
+ ...ui,
43
+ onSubmit: async () => {
44
+ step?.setBusy(true);
45
+ const res = await gate.accept(c);
46
+ step?.setBusy(false);
47
+ if (!res.ok) {
48
+ error = res.errors[0]?.message ?? ui.t("policy.requiredGeneric");
49
+ return;
50
+ }
51
+ controller = null;
52
+ onAccepted?.();
53
+ },
54
+ onDecline: () => {
55
+ controller = null;
56
+ }
57
+ });
58
+ return {
59
+ destroy() {
60
+ step?.destroy();
61
+ step = null;
62
+ }
63
+ };
64
+ }
65
+ </script>
66
+
67
+ {#if pending.length > 0 && !controller}
68
+ <div class={classFor('alert', atlas.appearance, 'atlas-policy-banner')} role="status" data-atlas-policy-banner="">
69
+ <span>
70
+ <strong>{ui.t('policy.bannerTitle')}. </strong>
71
+ {ui.t('policy.bannerBody', { policies: joinTitles(pending.map((p) => p.title), ui.t('policy.and')) })}
72
+ {#if grace}{' '}{ui.t('policy.bannerGrace', { date: formatPolicyDate(grace) })}{/if}
73
+ </span>
74
+ <button type="button" class={ui.classes.link} on:click={begin}>{ui.t('policy.bannerReview')}</button>
75
+ </div>
76
+ {/if}
77
+ {#if controller}
78
+ <div class={classFor('card', atlas.appearance, 'atlas-card')} data-atlas-policy-gate="">
79
+ <div use:host={controller} />
80
+ {#if error}<p class={ui.classes.error} role="alert">{error}</p>{/if}
81
+ </div>
82
+ {/if}
83
+ <slot />
@@ -0,0 +1,24 @@
1
+ import { SvelteComponent } from "svelte";
2
+ declare const __propDef: {
3
+ props: {
4
+ /**
5
+ * In-app re-acceptance. For a signed-in user, a banner when a policy needs
6
+ * re-acceptance; "Review and accept" opens the unskippable dialog / checkboxes
7
+ * and posts `/v1/client/me/policies/accept`. Renders its slot always.
8
+ */ onAccepted?: (() => void) | undefined;
9
+ };
10
+ events: {
11
+ [evt: string]: CustomEvent<any>;
12
+ };
13
+ slots: {
14
+ default: {};
15
+ };
16
+ exports?: {} | undefined;
17
+ bindings?: string | undefined;
18
+ };
19
+ export type PolicyGateProps = typeof __propDef.props;
20
+ export type PolicyGateEvents = typeof __propDef.events;
21
+ export type PolicyGateSlots = typeof __propDef.slots;
22
+ export default class PolicyGate extends SvelteComponent<PolicyGateProps, PolicyGateEvents, PolicyGateSlots> {
23
+ }
24
+ export {};
@@ -0,0 +1,55 @@
1
+ <script>import { canDownloadRecoveryCodes, copyRecoveryCodes, downloadRecoveryCodes } from "@atlasauth/js";
2
+ import { useAtlas } from "../context";
3
+ import { classFor } from "../appearance";
4
+ import { translate } from "../i18n";
5
+ export let codes;
6
+ export let busy = false;
7
+ export let onSaved;
8
+ const atlas = useAtlas();
9
+ const cls = (key, base) => classFor(key, atlas.appearance, base);
10
+ const t = (key) => translate(atlas.catalog, key);
11
+ const canCopy = typeof navigator !== "undefined" && !!navigator.clipboard?.writeText;
12
+ const canDownload = canDownloadRecoveryCodes();
13
+ let copied = false;
14
+ $: if (codes) copied = false;
15
+ async function copy() {
16
+ copied = await copyRecoveryCodes(codes);
17
+ }
18
+ </script>
19
+
20
+ <div class={cls('card', 'atlas-card')} data-atlas="recovery-codes">
21
+ <h1 class={cls('headerTitle', 'atlas-title')}>{t('mfa.recoveryCodesTitle')}</h1>
22
+ <p class="atlas-text">{t('mfa.recoveryCodesHelp')}</p>
23
+ <ul
24
+ class="atlas-recovery-codes"
25
+ data-atlas="recovery-codes-list"
26
+ style="list-style:none;padding:0;margin:8px 0 12px;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;text-align:center"
27
+ >
28
+ {#each codes as code (code)}
29
+ <li><code>{code}</code></li>
30
+ {/each}
31
+ </ul>
32
+ <div class="atlas-alternatives">
33
+ {#if canCopy}
34
+ <button class={cls('formButtonSecondary', 'atlas-button-secondary')} type="button" data-atlas="recovery-codes-copy" on:click={() => void copy()}>
35
+ {t('mfa.recoveryCodesCopy')}
36
+ </button>
37
+ {/if}
38
+ {#if canDownload}
39
+ <button
40
+ class={cls('formButtonSecondary', 'atlas-button-secondary')}
41
+ type="button"
42
+ data-atlas="recovery-codes-download"
43
+ on:click={() => downloadRecoveryCodes(codes)}
44
+ >
45
+ {t('mfa.recoveryCodesDownload')}
46
+ </button>
47
+ {/if}
48
+ </div>
49
+ {#if copied}
50
+ <p class="atlas-text" aria-live="polite" data-atlas="recovery-codes-copied">{t('mfa.recoveryCodesCopied')}</p>
51
+ {/if}
52
+ <button class={cls('formButtonPrimary', 'atlas-button')} type="button" data-atlas="recovery-codes-saved" disabled={busy} on:click={onSaved}>
53
+ {t('mfa.recoveryCodesSaved')}
54
+ </button>
55
+ </div>
@@ -0,0 +1,25 @@
1
+ import { SvelteComponent } from "svelte";
2
+ declare const __propDef: {
3
+ props: {
4
+ /**
5
+ * The recovery codes a mid-sign-in MFA enrolment came with, shown once before
6
+ * the sign-in finishes: copy them, download them as a .txt file, then
7
+ * "I've saved my codes" (`onSaved`) exchanges the ticket. The codes live only
8
+ * in the flow's memory and are forgotten once saved.
9
+ */ codes: string[];
10
+ busy?: boolean;
11
+ onSaved: () => void;
12
+ };
13
+ events: {
14
+ [evt: string]: CustomEvent<any>;
15
+ };
16
+ slots: {};
17
+ exports?: {} | undefined;
18
+ bindings?: string | undefined;
19
+ };
20
+ export type RecoveryCodesProps = typeof __propDef.props;
21
+ export type RecoveryCodesEvents = typeof __propDef.events;
22
+ export type RecoveryCodesSlots = typeof __propDef.slots;
23
+ export default class RecoveryCodes extends SvelteComponent<RecoveryCodesProps, RecoveryCodesEvents, RecoveryCodesSlots> {
24
+ }
25
+ export {};
@@ -1,10 +1,11 @@
1
1
  <script>import { onDestroy } from "svelte";
2
- import { signUp as hostedSignUp } from "@atlasauth/js";
2
+ import { lastUsedMethodLabel, pendingRecoveryCodes, signUp as hostedSignUp } from "@atlasauth/js";
3
3
  import { signIn } from "../accessors";
4
4
  import { useAtlas } from "../context";
5
5
  import { classFor } from "../appearance";
6
6
  import { translate } from "../i18n";
7
7
  import AttemptFlow from "./AttemptFlow.svelte";
8
+ import RecoveryCodes from "./RecoveryCodes.svelte";
8
9
  export let afterUrl = void 0;
9
10
  export let signUpUrl = void 0;
10
11
  const atlas = useAtlas();
@@ -26,11 +27,24 @@ $: promptHref = $flowState.attempt?.status !== "complete" ? signUpHref(signUpUrl
26
27
  const flow = signIn();
27
28
  const flowState = flow.state;
28
29
  const settings = flow.settings;
30
+ const lastMethod = flow.lastMethod;
31
+ const enrollmentState = flow.enrollmentState;
29
32
  onDestroy(() => flow.destroy());
33
+ $: recoveryCodes = pendingRecoveryCodes($flowState);
34
+ $: lastLabel = !$flowState.attempt ? lastUsedMethodLabel($lastMethod, t) : null;
30
35
  const drawn = (..._deps) => flow.captcha.needed ? flow.captcha : null;
31
36
  $: captcha = drawn($settings, $flowState);
32
37
  </script>
33
38
 
39
+ {#if lastLabel}
40
+ <p class={classFor('badge', atlas.appearance, 'atlas-text atlas-last-used-hint')} data-atlas="last-used" data-atlas-method={$lastMethod}>
41
+ {t('signIn.lastUsed')}: {lastLabel}
42
+ </p>
43
+ {/if}
44
+
45
+ {#if recoveryCodes}
46
+ <RecoveryCodes codes={recoveryCodes} busy={$flowState.busy} onSaved={flow.saveRecoveryCodes} />
47
+ {:else}
34
48
  <AttemptFlow
35
49
  attempt={$flowState.attempt}
36
50
  busy={$flowState.busy}
@@ -39,9 +53,15 @@ $: captcha = drawn($settings, $flowState);
39
53
  titleKey="signIn.title"
40
54
  onSubmit={flow.submit}
41
55
  onPrepare={flow.prepare}
56
+ onEmailSecondFactor={$settings?.account.emailSecondFactor ? flow.sendEmailSecondFactor : null}
57
+ onSmsSecondFactor={$settings && flow.mfaChoices().sms ? flow.sendSmsSecondFactor : null}
58
+ enrollment={flow.enrollment}
59
+ enrollmentState={$enrollmentState}
42
60
  emailDelivery={$settings?.emailDelivery ?? null}
43
61
  {captcha}
62
+ onPolicyDecline={flow.declinePolicy}
44
63
  />
64
+ {/if}
45
65
 
46
66
  {#if promptHref}
47
67
  <p class={classFor('footerAction', atlas.appearance, 'atlas-footer')} data-atlas-sign-up-prompt="">
@@ -1,17 +1,31 @@
1
- <script>import { onDestroy } from "svelte";
1
+ <script>import { onDestroy, onMount } from "svelte";
2
+ import { awaitingPasskey, passkeysSupported } from "@atlasauth/js";
2
3
  import { signUp } from "../accessors";
3
4
  import AttemptFlow from "./AttemptFlow.svelte";
5
+ import PasskeySignUpStep from "./PasskeySignUpStep.svelte";
4
6
  import SignUpForm from "./SignUpForm.svelte";
5
7
  export let afterUrl = void 0;
6
8
  const flow = signUp();
7
9
  const flowState = flow.state;
8
10
  const settings = flow.settings;
9
11
  onDestroy(() => flow.destroy());
12
+ onMount(() => {
13
+ void flow.loadPolicies();
14
+ });
10
15
  const drawn = (..._deps) => flow.signUpCaptcha.needed ? flow.signUpCaptcha : null;
11
16
  $: captcha = drawn($settings, $flowState);
17
+ let mode = "password";
18
+ $: offer = $settings?.account.passkeySignUp ?? null;
19
+ $: canPasskey = !!offer && passkeysSupported();
20
+ $: usingPasskey = mode === "passkey" && canPasskey;
21
+ function switchMode() {
22
+ mode = usingPasskey ? "password" : "passkey";
23
+ }
12
24
  </script>
13
25
 
14
- {#if $flowState.attempt}
26
+ {#if $flowState.attempt && awaitingPasskey($flowState.attempt)}
27
+ <PasskeySignUpStep busy={$flowState.busy} errors={$flowState.errors} onCreate={flow.createPasskey} />
28
+ {:else if $flowState.attempt}
15
29
  <AttemptFlow
16
30
  attempt={$flowState.attempt}
17
31
  busy={$flowState.busy}
@@ -19,6 +33,8 @@ $: captcha = drawn($settings, $flowState);
19
33
  sessionKept={$flowState.sessionKept}
20
34
  titleKey="signUp.title"
21
35
  onSubmit={flow.submit}
36
+ emailVerification={$settings?.account.emailVerification ?? 'code'}
37
+ onResend={flow.resendSignUp}
22
38
  />
23
39
  {:else}
24
40
  <SignUpForm
@@ -26,6 +42,9 @@ $: captcha = drawn($settings, $flowState);
26
42
  busy={$flowState.busy}
27
43
  errors={$flowState.errors}
28
44
  {captcha}
29
- onSubmit={flow.create}
45
+ policies={flow.policies}
46
+ onSubmit={usingPasskey ? flow.createWithPasskey : flow.create}
47
+ passkey={usingPasskey && offer ? { email: offer.email } : null}
48
+ onSwitchMode={canPasskey ? switchMode : undefined}
30
49
  />
31
50
  {/if}
@@ -1,26 +1,50 @@
1
1
  <script>import {
2
- localizedErrorMessage
2
+ confirmPolicies,
3
+ localizedErrorMessage,
4
+ policyTranslator
3
5
  } from "@atlasauth/js";
6
+ import { onDestroy } from "svelte";
4
7
  import { useAtlas } from "../context";
5
8
  import { classFor } from "../appearance";
6
9
  import { translate } from "../i18n";
7
10
  import CaptchaSlot from "./CaptchaSlot.svelte";
11
+ import PolicyClickwrap from "./PolicyClickwrap.svelte";
12
+ import PolicyConsent from "./PolicyConsent.svelte";
8
13
  export let settings = null;
9
14
  export let busy = false;
10
15
  export let errors = [];
11
16
  export let captcha = null;
12
17
  export let onSubmit;
18
+ export let passkey = null;
19
+ export let onSwitchMode = void 0;
20
+ export let policies = null;
13
21
  const atlas = useAtlas();
14
22
  const cls = (key, base) => classFor(key, atlas.appearance, base);
15
23
  const t = (key, vars) => translate(atlas.catalog, key, vars);
16
24
  const errorText = (error) => localizedErrorMessage(error, atlas.errorCatalog ?? atlas.catalog, atlas.catalog);
17
25
  let values = {};
18
26
  let agreed = false;
19
- $: legal = settings?.legal;
27
+ let declined = null;
28
+ let policyTick = 0;
29
+ let watched = null;
30
+ let unwatch = null;
31
+ $: if (policies !== watched) {
32
+ unwatch?.();
33
+ watched = policies;
34
+ unwatch = policies ? policies.subscribe(() => policyTick += 1) : null;
35
+ }
36
+ onDestroy(() => unwatch?.());
37
+ const hasAny = (_tick, c) => !!c && c.policies.length > 0;
38
+ const okFor = (_tick, c) => !c || c.policies.length === 0 || c.canSubmit();
39
+ $: hasPolicies = hasAny(policyTick, policies);
40
+ $: legal = hasPolicies ? void 0 : settings?.legal;
20
41
  $: showLegal = !!legal && (!!legal.termsUrl || !!legal.privacyUrl);
21
- $: consentOk = !legal?.required || agreed;
42
+ $: policyOk = okFor(policyTick, policies);
43
+ $: consentOk = (!legal?.required || agreed) && policyOk;
22
44
  $: fields = settings?.signUpFields ?? [];
23
- $: emailUnavailable = settings?.emailDelivery?.passwordSignUp === false;
45
+ $: emailUnavailable = settings?.emailDelivery?.passwordSignUp === false && !(passkey && passkey.email !== "required");
46
+ $: showEmail = !passkey || passkey.email !== "off";
47
+ $: emailRequired = !passkey || passkey.email === "required";
24
48
  const label = (f) => f.required ? f.label : t("signUp.optionalField", { label: f.label });
25
49
  const inputType = (f) => ["email", "tel", "url", "number", "date"].includes(f.type) ? f.type : "text";
26
50
  const picked = (key) => values[key] ? values[key].split(", ") : [];
@@ -34,6 +58,14 @@ function toggle(f, option, on) {
34
58
  function handleSubmit(event) {
35
59
  event.preventDefault();
36
60
  if (!consentOk) return;
61
+ declined = null;
62
+ if (hasPolicies && policies) {
63
+ void confirmPolicies(policies, { t: policyTranslator(t) }).then((res) => {
64
+ if (res.ok) onSubmit({ ...values }, true);
65
+ else declined = res.declined;
66
+ });
67
+ return;
68
+ }
37
69
  onSubmit({ ...values }, legal?.required ? agreed : true);
38
70
  }
39
71
  </script>
@@ -123,22 +155,29 @@ function handleSubmit(event) {
123
155
  {/if}
124
156
  {/each}
125
157
 
126
- <label class={cls('formField', 'atlas-field')}>
127
- <span class={cls('formFieldLabel', 'atlas-field-label')}>{t('signUp.emailLabel')}</span>
128
- <input
129
- class={cls('formFieldInput', 'atlas-input')}
130
- type="email"
131
- name="email"
132
- autocomplete="email"
133
- required
134
- value={values.email ?? ''}
135
- on:input={(e) => (values.email = e.currentTarget.value)}
136
- />
137
- {#each errors.filter((e) => e.param === 'email') as error (error.code)}
138
- <span class={cls('formFieldError', 'atlas-error')}>{errorText(error)}</span>
139
- {/each}
140
- </label>
158
+ {#if showEmail}
159
+ <label class={cls('formField', 'atlas-field')}>
160
+ <span class={cls('formFieldLabel', 'atlas-field-label')}
161
+ >{emailRequired ? t('signUp.emailLabel') : t('signUp.optionalField', { label: t('signUp.emailLabel') })}</span
162
+ >
163
+ <input
164
+ class={cls('formFieldInput', 'atlas-input')}
165
+ type="email"
166
+ name="email"
167
+ autocomplete="email"
168
+ required={emailRequired}
169
+ value={values.email ?? ''}
170
+ on:input={(e) => (values.email = e.currentTarget.value)}
171
+ />
172
+ {#each errors.filter((e) => e.param === 'email') as error (error.code)}
173
+ <span class={cls('formFieldError', 'atlas-error')}>{errorText(error)}</span>
174
+ {/each}
175
+ </label>
176
+ {/if}
141
177
 
178
+ {#if passkey}
179
+ <p class="atlas-text">{t('signUp.passkeyIntro')}</p>
180
+ {:else}
142
181
  <label class={cls('formField', 'atlas-field')}>
143
182
  <span class={cls('formFieldLabel', 'atlas-field-label')}>{t('signUp.passwordLabel')}</span>
144
183
  <input
@@ -154,6 +193,7 @@ function handleSubmit(event) {
154
193
  <span class={cls('formFieldError', 'atlas-error')}>{errorText(error)}</span>
155
194
  {/each}
156
195
  </label>
196
+ {/if}
157
197
 
158
198
  {#if showLegal && legal}
159
199
  <label class={cls('formField', 'atlas-consent')}>
@@ -176,6 +216,15 @@ function handleSubmit(event) {
176
216
  </label>
177
217
  {/if}
178
218
 
219
+ {#if hasPolicies && policies}
220
+ <PolicyConsent controller={policies} />
221
+ {#if declined}
222
+ <span class={cls('formFieldError', 'atlas-error')} role="alert" data-atlas-policy-declined="">
223
+ {t('policy.declined', { policy: declined })}
224
+ </span>
225
+ {/if}
226
+ {/if}
227
+
179
228
  {#if captcha}
180
229
  <CaptchaSlot controller={captcha} />
181
230
  {/if}
@@ -187,6 +236,22 @@ function handleSubmit(event) {
187
236
  >
188
237
  {t('signUp.submit')}
189
238
  </button>
239
+ {#if hasPolicies && policies}
240
+ <PolicyClickwrap controller={policies} />
241
+ {/if}
242
+ {#if onSwitchMode}
243
+ <div class="atlas-alternatives">
244
+ <button
245
+ type="button"
246
+ class={cls('formButtonSecondary', 'atlas-button-secondary')}
247
+ data-atlas-sign-up-mode={passkey ? 'password' : 'passkey'}
248
+ disabled={busy}
249
+ on:click={() => onSwitchMode?.()}
250
+ >
251
+ {t(passkey ? 'signUp.passwordOption' : 'signUp.passkeyOption')}
252
+ </button>
253
+ </div>
254
+ {/if}
190
255
  </form>
191
256
  {/if}
192
257
  </div>
@@ -1,5 +1,5 @@
1
1
  import { SvelteComponent } from "svelte";
2
- import { type CaptchaController, type FieldError, type InstanceSettings } from '@atlasauth/js';
2
+ import { type CaptchaController, type FieldError, type InstanceSettings, type PolicyAcceptanceController } from '@atlasauth/js';
3
3
  declare const __propDef: {
4
4
  props: {
5
5
  /**
@@ -16,6 +16,11 @@ declare const __propDef: {
16
16
  errors?: FieldError[];
17
17
  captcha?: CaptchaController | null;
18
18
  onSubmit: (values: Record<string, string>, consent: boolean) => void;
19
+ /** Passkey-only sign-up: no password field; the email per the instance's mode. */ passkey?: {
20
+ email: "off" | "optional" | "required";
21
+ } | null;
22
+ /** Toggle between the password and passkey forms (only when both are offered). */ onSwitchMode?: (() => void) | undefined;
23
+ /** The instance's policies (terms, privacy, …); they replace the legacy consent row. */ policies?: PolicyAcceptanceController | null;
19
24
  };
20
25
  events: {
21
26
  [evt: string]: CustomEvent<any>;
package/dist/context.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type Readable } from 'svelte/store';
2
- import { type ProviderToken, type SignOutOptions, type SignOutResult, type SignOutEvent, type SignOutListener, type ReferralConsent } from '@atlasauth/js';
2
+ import { type ProviderToken, type SignOutOptions, type SignOutResult, type EndImpersonationResult, type SignOutEvent, type SignOutListener, type ReferralConsent } from '@atlasauth/js';
3
3
  import { type Catalog } from './i18n';
4
4
  import { type Appearance, type InstanceDesign } from './appearance';
5
5
  import type { AtlasOrganizationMembership, AtlasSession, AtlasUser, SessionClaims } from './types';
@@ -95,6 +95,7 @@ export interface AtlasClient extends AtlasStores {
95
95
  pendingAttempt: {
96
96
  id: string;
97
97
  status: string;
98
+ policyToken?: string;
98
99
  } | null;
99
100
  /** The session JWT; with `{ template }` a short-lived named-template token (e.g. `'chatly'`). */
100
101
  getToken(options?: {
@@ -126,6 +127,13 @@ export interface AtlasClient extends AtlasStores {
126
127
  */
127
128
  signOut(options?: SignOutOptions): Promise<SignOutResult>;
128
129
  setActiveOrganization(organizationId: string | null): Promise<void>;
130
+ /**
131
+ * End the active impersonation ("return to admin"): the impersonator's own
132
+ * session on this browser becomes active again with no sign-in
133
+ * (`resumed: true`); with none, another account signed in here is adopted,
134
+ * or this browser is signed out.
135
+ */
136
+ endImpersonation(): Promise<EndImpersonationResult>;
129
137
  /** Re-boot from the Frontend API. Resolves true when a session now exists in this browser. */
130
138
  reload(): Promise<boolean>;
131
139
  /**
package/dist/context.js CHANGED
@@ -52,7 +52,7 @@ export function createAtlasClient(options) {
52
52
  return null;
53
53
  const result = readRedirectResult(window.location.search);
54
54
  return result?.status && !result.ticket
55
- ? { id: result.attemptId, status: result.status }
55
+ ? { id: result.attemptId, status: result.status, ...(result.policyToken ? { policyToken: result.policyToken } : {}) }
56
56
  : null;
57
57
  })();
58
58
  async function call(path, init) {
@@ -235,6 +235,22 @@ export function createAtlasClient(options) {
235
235
  applyToken(body.jwt, current.sessionId);
236
236
  await reload();
237
237
  }
238
+ async function endImpersonation() {
239
+ const outcome = await guard.endImpersonation(cache.peek()?.sessionId ?? get(session)?.id ?? null);
240
+ if (!outcome.ended)
241
+ return outcome;
242
+ if (!outcome.activeSessionId) {
243
+ if (timer)
244
+ clearTimeout(timer);
245
+ timer = null;
246
+ clearSession('user');
247
+ return outcome;
248
+ }
249
+ if (outcome.jwt)
250
+ applyToken(outcome.jwt, outcome.activeSessionId);
251
+ await reload();
252
+ return outcome;
253
+ }
238
254
  async function boot() {
239
255
  if (booted)
240
256
  return;
@@ -322,6 +338,7 @@ export function createAtlasClient(options) {
322
338
  getProviderToken,
323
339
  signOut,
324
340
  setActiveOrganization,
341
+ endImpersonation,
325
342
  reload,
326
343
  boot,
327
344
  loadInstanceDesign,
package/dist/i18n.js CHANGED
@@ -9,7 +9,7 @@
9
9
  * A missing key renders the KEY, not an empty string. A blank button is a bug
10
10
  * nobody can report; a button reading `signIn.submit` is one anyone can.
11
11
  */
12
- import { EMAIL_UNAVAILABLE_MESSAGE } from '@atlasauth/js';
12
+ import { EMAIL_UNAVAILABLE_MESSAGE, POLICY_STRINGS } from '@atlasauth/js';
13
13
  export const EN_US = {
14
14
  'signIn.title': 'Sign in',
15
15
  'signIn.subtitle': 'to continue to {application}',
@@ -18,6 +18,7 @@ export const EN_US = {
18
18
  'signIn.submit': 'Continue',
19
19
  'signIn.forgotPassword': 'Forgot password?',
20
20
  'signIn.noAccount': 'No account?',
21
+ 'signIn.lastUsed': 'Last used',
21
22
  'signIn.signUpLink': 'Sign up',
22
23
  'signIn.orDivider': 'or',
23
24
  'signIn.socialButton': 'Continue with {provider}',
@@ -49,6 +50,13 @@ export const EN_US = {
49
50
  'signUp.and': 'and',
50
51
  // Shown instead of the email + password form when the app cannot send email.
51
52
  'signUp.emailUnavailable': EMAIL_UNAVAILABLE_MESSAGE,
53
+ 'signUp.passkeyOption': 'Sign up with a passkey instead',
54
+ 'signUp.passwordOption': 'Sign up with a password instead',
55
+ 'signUp.passkeyIntro': "No password needed: you'll sign in with a passkey saved on this device or in your password manager.",
56
+ 'signUp.passkeyPrompt': 'Create a passkey to finish setting up your account.',
57
+ 'signUp.passkeyCreate': 'Create passkey',
58
+ 'signUp.passkeyCancelled': "The passkey wasn't created. Try again when you're ready.",
59
+ 'signUp.linkSentTo': 'We sent a confirmation link to {email}. Open it on any device; this page continues on its own.',
52
60
  'captcha.prompt': 'Complete the security check to continue.',
53
61
  'captcha.required': 'Please complete the security check, then try again.',
54
62
  'captcha.failed': "The security check didn't pass. Please try again.",
@@ -66,6 +74,24 @@ export const EN_US = {
66
74
  'mfa.usePasskey': 'Use a passkey instead',
67
75
  'mfa.useBackupCode': 'Use a recovery code',
68
76
  'mfa.submit': 'Verify',
77
+ 'mfa.enrollChoose': 'Set up a second step for signing in.',
78
+ 'mfa.enrollTotpOption': 'Use an authenticator app',
79
+ 'mfa.enrollSmsOption': 'Use text messages',
80
+ 'mfa.enrollScan': 'Scan this QR code with your authenticator app, or enter the setup key.',
81
+ 'mfa.enrollQrAlt': 'QR code for your authenticator app',
82
+ 'mfa.enrollSetupKey': 'Setup key',
83
+ 'mfa.enrollPreparing': 'Setting up…',
84
+ 'mfa.phoneLabel': 'Mobile number',
85
+ 'mfa.phoneHint': 'Include the country code, for example +14155550101.',
86
+ 'mfa.sendCode': 'Text me a code',
87
+ 'mfa.codeTextedTo': 'Enter the code we texted to {phone}.',
88
+ 'mfa.smsCodeOption': 'Text me a code instead',
89
+ 'mfa.recoveryCodesTitle': 'Save your recovery codes',
90
+ 'mfa.recoveryCodesHelp': "Each code works once. If you can't use your second step, sign in with one of these. Keep them somewhere safe: they won't be shown again.",
91
+ 'mfa.recoveryCodesCopy': 'Copy codes',
92
+ 'mfa.recoveryCodesCopied': 'Copied.',
93
+ 'mfa.recoveryCodesDownload': 'Download as .txt',
94
+ 'mfa.recoveryCodesSaved': "I've saved my codes",
69
95
  'reset.title': 'Reset your password',
70
96
  'reset.submit': 'Send reset code',
71
97
  'reset.newPasswordLabel': 'New password',
@@ -93,6 +119,37 @@ export const EN_US = {
93
119
  'organization.members': 'Members',
94
120
  'organization.invite': 'Invite member',
95
121
  'organization.roleLabel': 'Role',
122
+ 'organization.invitations': 'Invitations',
123
+ 'organization.settings': 'Settings',
124
+ 'organization.emailLabel': 'Email address',
125
+ 'organization.sendInvite': 'Send invitation',
126
+ 'organization.noInvitations': 'No pending invitations.',
127
+ 'organization.remove': 'Remove',
128
+ 'organization.revoke': 'Revoke',
129
+ 'organization.resend': 'Resend',
130
+ 'organization.you': '(you)',
131
+ 'organization.leave': 'Leave organization',
132
+ 'organization.leaveConfirm': 'Leave {name}? You will lose access until you are invited again.',
133
+ 'organization.delete': 'Delete organization',
134
+ 'organization.deleteConfirm': 'Delete {name} for every member? This cannot be undone.',
135
+ 'organization.confirm': 'Confirm',
136
+ 'organization.cancel': 'Cancel',
137
+ 'organization.unnamedMember': 'Unnamed member',
138
+ 'organization.passwordPrompt': 'Enter your password to confirm.',
139
+ 'organization.roles': 'Roles',
140
+ 'organization.roleName': 'Role name',
141
+ 'organization.roleKey': 'Role key (for example org:auditor)',
142
+ 'organization.rolePermissions': 'Permissions',
143
+ 'organization.createRole': 'Create role',
144
+ 'organization.saveRole': 'Save role',
145
+ 'organization.editRole': 'Edit',
146
+ 'organization.deleteRole': 'Delete',
147
+ 'organization.noCustomRoles': 'This organization has no roles of its own yet.',
148
+ 'impersonation.banner': 'You are signed in as {name} on someone else\'s behalf.',
149
+ 'impersonation.returnToAdmin': 'Return to admin',
150
+ 'impersonation.end': 'End impersonation',
151
+ // Policy acceptance (terms, privacy, any policy).
152
+ ...POLICY_STRINGS,
96
153
  'error.generic': 'Something went wrong. Please try again.',
97
154
  'error.network': 'We could not reach the server. Check your connection.',
98
155
  // Server errors, by the stable `reason` the Frontend API sends. Shown instead
package/dist/index.d.ts CHANGED
@@ -16,7 +16,7 @@ export * from './i18n';
16
16
  export * from './types';
17
17
  export * from './fapi';
18
18
  export { ATLAS_KEY, initAtlas, createAtlasClient, useAtlas, type AuthStatus, type AtlasInitOptions, type AtlasStores, type AtlasClient, } from './context';
19
- export { getUser, getSession, getAuth, getOrganization, getToken, signIn, signUp, signOut, type UseUser, type UseSession, type UseAuth, type UseOrganization, type FlowController, } from './accessors';
19
+ export { getUser, getSession, getAuth, getOrganization, getOrganizationAuditLog, getToken, signIn, signUp, signOut, type UseUser, type UseSession, type UseAuth, type UseOrganization, type OrganizationAuditLogStore, type FlowController, } from './accessors';
20
20
  export { default as AtlasProvider } from './components/AtlasProvider.svelte';
21
21
  export { default as SignedIn } from './components/SignedIn.svelte';
22
22
  export { default as SignedOut } from './components/SignedOut.svelte';
@@ -29,6 +29,13 @@ export { default as UserButton } from './components/UserButton.svelte';
29
29
  export { default as UserProfile } from './components/UserProfile.svelte';
30
30
  export { default as OrganizationSwitcher } from './components/OrganizationSwitcher.svelte';
31
31
  export { default as OrganizationProfile } from './components/OrganizationProfile.svelte';
32
+ export { default as OrganizationAuditLog } from './components/OrganizationAuditLog.svelte';
33
+ export type { OrganizationAuditLogEntry, OrganizationAuditLogState } from '@atlasauth/js';
34
+ export { default as ImpersonationBanner } from './components/ImpersonationBanner.svelte';
35
+ export { default as PolicyConsent } from './components/PolicyConsent.svelte';
36
+ export { default as PolicyClickwrap } from './components/PolicyClickwrap.svelte';
37
+ export { default as PolicyAcceptStep } from './components/PolicyAcceptStep.svelte';
38
+ export { default as PolicyGate } from './components/PolicyGate.svelte';
32
39
  export type { InstanceSettings, EmailDelivery, PublicSignUpField, CaptchaController, CaptchaFlowKey, CaptchaWidgetMode, FlowCaptcha, } from '@atlasauth/js';
33
40
  export { SignOutError } from '@atlasauth/js';
34
41
  export type { SignOutOptions, SignOutResult } from '@atlasauth/js';