@mintplayer/ng-spark 22.33.0 → 22.35.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.
@@ -1 +1 @@
1
- {"version":3,"file":"mintplayer-ng-spark-auth-account.mjs","names":[],"sources":["../../auth/account/src/account-messages.ts","../../auth/account/src/spark-account-overview.component.ts","../../auth/account/src/spark-account-profile.component.ts","../../auth/account/src/spark-change-password.component.ts","../../auth/account/src/spark-two-factor-setup.component.ts","../../auth/account/src/spark-external-logins.component.ts","../../auth/account/src/spark-personal-data.component.ts"],"sourcesContent":["import { SparkAccountResult } from '@mintplayer/ng-spark/auth/models';\n\n/**\n * The messages to show for a failed account call: the server's validation messages (already written\n * for the user by Identity or a profile contributor), else a translation key for its error code or\n * status. Keys start with `auth.`; the page runs everything through the `t` pipe, which returns\n * anything that is not a key unchanged.\n */\nexport function accountMessages(result: SparkAccountResult<unknown>): string[] {\n if (result.success) return [];\n if (result.errors) {\n const messages = Object.values(result.errors).flat().filter(m => !!m);\n if (messages.length) return messages;\n }\n if (result.error) return [`auth.accountError.${result.error}`];\n if (result.status === 404) return ['auth.accountUnavailable'];\n return ['auth.accountFailed'];\n}\n\n/** The messages for one field of a validation problem, matched case-insensitively. */\nexport function fieldMessages(result: SparkAccountResult<unknown> | null, field: string): string[] {\n if (!result?.errors) return [];\n const key = Object.keys(result.errors).find(k => k.toLowerCase() === field.toLowerCase());\n return key ? result.errors[key] : [];\n}\n","import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core';\nimport { RouterLink } from '@angular/router';\nimport { BsCardComponent, BsCardHeaderComponent } from '@mintplayer/ng-bootstrap/card';\nimport { SparkAuthService } from '@mintplayer/ng-spark/auth/core';\nimport { currentLanguage } from '@mintplayer/ng-spark/models';\nimport {\n SPARK_ACCOUNT_OVERVIEW_LINKS,\n SPARK_AUTH_ROUTE_PATHS,\n SparkAuthCapabilities,\n SparkAuthRoutePaths,\n passkeysSupported,\n} from '@mintplayer/ng-spark/auth/models';\nimport { TranslateKeyPipe } from '@mintplayer/ng-spark/auth/pipes';\n\n/**\n * Whether the server serves a page's feature. Mounting a page on the client is necessary but not\n * sufficient: the route table and the server's switches are configured independently, which is why\n * `/spark/auth/capabilities` exists. Pages without an entry need no switch beyond being mounted.\n */\ntype ServerCheck = (capabilities: SparkAuthCapabilities) => boolean;\n\n/** Under `Disabled` neither `/login` nor `manage/password` is mapped: no password to change, and a\n * second factor would guard a sign-in that does not exist. */\nconst passwordSignIn: ServerCheck = c => c.localCredentials !== 'Disabled';\n\n/** The account pages this app mounted, in display order, with their title keys. */\nconst PAGES: { key: keyof SparkAuthRoutePaths; label: string; server?: ServerCheck }[] = [\n { key: 'profile', label: 'auth.profileTitle' },\n { key: 'changePassword', label: 'auth.changePasswordTitle', server: passwordSignIn },\n { key: 'twoFactorSetup', label: 'auth.twoFactorSetupTitle', server: c => passwordSignIn(c) && c.twoFactor === true },\n { key: 'externalLogins', label: 'auth.externalLoginsTitle', server: c => c.externalLogins === true },\n // `passkeys` reports `SparkPasskeys.Enabled`; the browser must also support the ceremony.\n { key: 'passkeys', label: 'auth.passkeysTitle', server: c => c.passkeys === true && passkeysSupported() },\n { key: 'personalData', label: 'auth.personalDataTitle' },\n];\n\n/**\n * The account area's landing page (#460, D16): who is signed in, and a link per account page that is\n * both mounted (`withAccount()`) and served (`/spark/auth/capabilities`), then the links other route\n * features contribute through `SPARK_ACCOUNT_OVERVIEW_LINKS` (e.g. connected applications, from\n * `@mintplayer/ng-spark/identity-provider`).\n */\n@Component({\n selector: 'spark-account-overview',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [RouterLink, BsCardComponent, BsCardHeaderComponent, TranslateKeyPipe],\n template: `\n <div class=\"d-flex justify-content-center\">\n <bs-card style=\"width: 100%; max-width: 640px;\">\n <bs-card-header>\n <h3 class=\"mb-0\">{{ 'auth.accountTitle' | t }}</h3>\n @if (auth.user(); as user) {\n <small class=\"spark-account-user text-muted text-break\">{{ user.userName }}</small>\n @if (user.email && user.email !== user.userName) {\n <small class=\"spark-account-email text-muted text-break d-block\">{{ user.email }}</small>\n }\n }\n </bs-card-header>\n <div class=\"p-4\">\n <ul class=\"list-unstyled mb-0\">\n @for (page of pages(); track page.key) {\n <li class=\"py-1\"><a class=\"spark-account-link\" [routerLink]=\"page.path\">{{ page.label | t }}</a></li>\n }\n @for (link of extraLinks(); track link.key) {\n <li class=\"py-1\"><a class=\"spark-account-link\" [routerLink]=\"link.path\">{{ link.label }}</a></li>\n }\n </ul>\n </div>\n </bs-card>\n </div>\n `,\n})\nexport class SparkAccountOverviewComponent {\n protected readonly auth = inject(SparkAuthService);\n private readonly paths = inject(SPARK_AUTH_ROUTE_PATHS, { optional: true }) ?? {};\n private readonly capabilities = signal<SparkAuthCapabilities | null>(null);\n\n /**\n * Pages behind a server switch stay hidden until the capabilities arrive, and stay hidden if the\n * call fails: offering a page whose endpoints the server does not map is worse than omitting it.\n */\n protected readonly pages = computed(() => {\n const capabilities = this.capabilities();\n return PAGES\n .filter(page => !!this.paths[page.key])\n .filter(page => !page.server || (capabilities !== null && page.server(capabilities)))\n .map(page => ({ ...page, path: this.paths[page.key]! }));\n });\n\n private readonly accountLinks = inject(SPARK_ACCOUNT_OVERVIEW_LINKS, { optional: true }) ?? [];\n\n /**\n * Links contributed by route features of other entry points (connected applications), after the\n * auth pages. Each carries its own translations; resolved in the app's current language.\n */\n protected readonly extraLinks = computed(() => {\n const lang = currentLanguage();\n return this.accountLinks.map(link => ({\n key: link.key,\n path: link.path,\n label: link.label[lang] ?? link.label['en'] ?? Object.values(link.label)[0] ?? link.key,\n }));\n });\n\n constructor() {\n void this.auth.capabilities()\n .then(capabilities => this.capabilities.set(capabilities))\n .catch(() => { /* stays null; server-switched pages stay hidden */ });\n }\n}\n","import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';\nimport { firstValueFrom } from 'rxjs';\nimport { Color } from '@mintplayer/ng-bootstrap';\nimport { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';\nimport { BsCardComponent, BsCardHeaderComponent } from '@mintplayer/ng-bootstrap/card';\nimport { BsCheckboxComponent } from '@mintplayer/ng-bootstrap/checkbox';\nimport { BsFormComponent, BsFormControlDirective } from '@mintplayer/ng-bootstrap/form';\nimport { BsInputGroupComponent } from '@mintplayer/ng-bootstrap/input-group';\nimport { BsSelectComponent, BsSelectOption, BsSelectValueAccessor } from '@mintplayer/ng-bootstrap/select';\nimport { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';\nimport { SparkAuthService } from '@mintplayer/ng-spark/auth/core';\nimport {\n SPARK_ACCOUNT_PROFILE_FIELDS,\n SparkAccountProfile,\n SparkAccountProfileField,\n SparkAccountResult,\n} from '@mintplayer/ng-spark/auth/models';\nimport { TranslateKeyPipe } from '@mintplayer/ng-spark/auth/pipes';\nimport { accountMessages, fieldMessages } from './account-messages';\n\ninterface CultureOption {\n value: string;\n label: string;\n}\n\n/**\n * The profile page (#460, D16): user name, email (changeable only under the server's opt-in\n * `SparkEmailChange.Enabled`; a change is confirmed from the NEW address first),\n * the preferred mail language (`SparkUser.PreferredCulture`, M8) and the application's own fields\n * (`SPARK_ACCOUNT_PROFILE_FIELDS`, validated server-side by `ISparkProfileContributor<TUser>`).\n *\n * The language list is the application's UI languages (`GET /spark/culture`); a stored culture not in\n * that list (e.g. `nl-BE`) is kept as an option so opening the page never changes it.\n */\n@Component({\n selector: 'spark-account-profile',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [ReactiveFormsModule, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsCheckboxComponent, BsFormComponent, BsFormControlDirective, BsInputGroupComponent, BsSelectComponent, BsSelectValueAccessor, BsSelectOption, BsSpinnerComponent, TranslateKeyPipe],\n template: `\n <div class=\"d-flex justify-content-center\">\n <bs-card style=\"width: 100%; max-width: 640px;\">\n <bs-card-header><h3 class=\"mb-0\">{{ 'auth.profileTitle' | t }}</h3></bs-card-header>\n <div class=\"p-4\">\n @if (loading()) {\n <div class=\"text-center\"><bs-spinner /></div>\n } @else {\n @for (message of messages(); track message) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3 d-block spark-account-error\">{{ message | t }}</bs-alert>\n }\n @if (saved()) {\n <bs-alert [type]=\"colors.success\" class=\"mb-3 d-block spark-account-saved\">{{ 'auth.profileSaved' | t }}</bs-alert>\n }\n\n <bs-form>\n <form [formGroup]=\"form\" (ngSubmit)=\"save()\">\n <div class=\"mb-3\">\n <label for=\"userName\" class=\"form-label\">{{ 'auth.userName' | t }}</label>\n <input type=\"text\" id=\"userName\" formControlName=\"userName\" autocomplete=\"username\" />\n @for (message of userNameErrors(); track message) {\n <div class=\"text-danger small\">{{ message | t }}</div>\n }\n </div>\n\n <div class=\"mb-3\">\n <label for=\"preferredCulture\" class=\"form-label\">{{ 'auth.preferredCulture' | t }}</label>\n <bs-select id=\"preferredCulture\" formControlName=\"preferredCulture\" class=\"w-100\">\n <option [ngValue]=\"''\">{{ 'auth.preferredCultureDefault' | t }}</option>\n @for (culture of cultureOptions(); track culture.value) {\n <option [ngValue]=\"culture.value\">{{ culture.label }}</option>\n }\n </bs-select>\n <small class=\"text-muted\">{{ 'auth.preferredCultureHint' | t }}</small>\n @for (message of fieldErrors('PreferredCulture'); track message) {\n <div class=\"text-danger small\">{{ message | t }}</div>\n }\n </div>\n\n <div formGroupName=\"fields\">\n @for (field of profileFields; track field.name) {\n <div class=\"mb-3 spark-profile-field\">\n @if (field.type === 'checkbox') {\n <bs-checkbox [type]=\"'checkbox'\" [formControlName]=\"field.name\" [name]=\"field.name\">{{ field.label | t }}</bs-checkbox>\n } @else {\n <label [for]=\"'field-' + field.name\" class=\"form-label\">{{ field.label | t }}</label>\n @switch (field.type) {\n @case ('textarea') {\n <textarea [id]=\"'field-' + field.name\" [formControlName]=\"field.name\" rows=\"3\"\n [attr.maxlength]=\"field.maxLength ?? null\" [required]=\"!!field.required\"></textarea>\n }\n @case ('select') {\n <bs-select [id]=\"'field-' + field.name\" [formControlName]=\"field.name\" class=\"w-100\">\n @if (!field.required) {\n <option [ngValue]=\"null\"></option>\n }\n @for (option of field.options ?? []; track option.value) {\n <option [ngValue]=\"option.value\">{{ option.label | t }}</option>\n }\n </bs-select>\n }\n @default {\n <input [type]=\"field.type ?? 'text'\" [id]=\"'field-' + field.name\" [formControlName]=\"field.name\"\n [attr.maxlength]=\"field.maxLength ?? null\" [required]=\"!!field.required\" />\n }\n }\n }\n @if (field.hint) {\n <small class=\"text-muted\">{{ field.hint | t }}</small>\n }\n @for (message of fieldErrors(field.name); track message) {\n <div class=\"text-danger small\">{{ message | t }}</div>\n }\n </div>\n }\n </div>\n\n <button type=\"submit\" class=\"btn btn-primary\" [disabled]=\"busy()\">\n @if (busy()) { <bs-spinner class=\"me-1\" /> }\n {{ 'auth.saveProfile' | t }}\n </button>\n </form>\n </bs-form>\n\n <hr class=\"my-4\" />\n\n <h5>{{ 'auth.email' | t }}</h5>\n <p class=\"spark-current-email\">\n {{ email() }}\n @if (emailConfirmed() === false) {\n <span class=\"text-warning ms-2\">{{ 'auth.emailNotConfirmed' | t }}</span>\n }\n </p>\n @if (emailMessages().length) {\n @for (message of emailMessages(); track message) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3 d-block\">{{ message | t }}</bs-alert>\n }\n }\n @if (emailSent()) {\n <bs-alert [type]=\"colors.info\" class=\"mb-3 d-block spark-email-sent\">{{ 'auth.emailChangeSent' | t }}</bs-alert>\n }\n <!-- Opt-in on the server (SparkEmailChange.Enabled); hidden until it says so. -->\n @if (emailChangeAllowed()) {\n <bs-form>\n <form [formGroup]=\"emailForm\" (ngSubmit)=\"changeEmail()\">\n <label for=\"newEmail\" class=\"form-label\">{{ 'auth.newEmail' | t }}</label>\n <bs-input-group>\n <input type=\"email\" id=\"newEmail\" formControlName=\"newEmail\" autocomplete=\"email\" />\n <button type=\"submit\" class=\"btn btn-outline-primary\" [disabled]=\"busy()\">{{ 'auth.changeEmail' | t }}</button>\n </bs-input-group>\n </form>\n </bs-form>\n }\n }\n </div>\n </bs-card>\n </div>\n `,\n})\nexport class SparkAccountProfileComponent {\n private readonly auth = inject(SparkAuthService);\n private readonly http = inject(HttpClient);\n protected readonly colors = Color;\n\n /** The app's fields, ordered; each becomes a control under `fields`. */\n protected readonly profileFields: SparkAccountProfileField[] = [...(inject(SPARK_ACCOUNT_PROFILE_FIELDS, { optional: true }) ?? [])]\n .map((field, index) => ({ field, index }))\n .sort((a, b) => (a.field.order ?? Infinity) - (b.field.order ?? Infinity) || a.index - b.index)\n .map(x => x.field);\n\n readonly form = new FormGroup({\n userName: new FormControl('', { nonNullable: true }),\n preferredCulture: new FormControl<string>('', { nonNullable: true }),\n fields: new FormGroup<Record<string, FormControl<unknown>>>({}),\n });\n readonly emailForm = new FormGroup({ newEmail: new FormControl('', { nonNullable: true }) });\n\n readonly loading = signal(true);\n readonly busy = signal(false);\n readonly saved = signal(false);\n readonly messages = signal<string[]>([]);\n private readonly lastResult = signal<SparkAccountResult<unknown> | null>(null);\n\n readonly email = signal<string | null>(null);\n readonly emailConfirmed = signal<boolean | null>(null);\n readonly emailSent = signal(false);\n readonly emailMessages = signal<string[]>([]);\n /** Off until the server reports `emailChange`, and off if the capabilities call fails. */\n readonly emailChangeAllowed = signal(false);\n\n private readonly languages = signal<CultureOption[]>([]);\n private readonly storedCulture = signal<string | null>(null);\n\n readonly cultureOptions = computed(() => {\n const options = this.languages();\n const stored = this.storedCulture();\n return stored && !options.some(o => o.value.toLowerCase() === stored.toLowerCase())\n ? [...options, { value: stored, label: stored }]\n : options;\n });\n\n readonly userNameErrors = computed(() => {\n const result = this.lastResult();\n if (!result?.errors) return [];\n return Object.entries(result.errors)\n .filter(([key]) => key.toLowerCase().includes('username'))\n .flatMap(([, messages]) => messages);\n });\n\n constructor() {\n for (const field of this.profileFields) {\n this.form.controls.fields.addControl(field.name, new FormControl<unknown>(field.type === 'checkbox' ? false : null));\n }\n void this.load();\n }\n\n private isInlineError(key: string): boolean {\n const lowered = key.toLowerCase();\n return lowered === 'preferredculture'\n || lowered.includes('username')\n || this.profileFields.some(f => f.name.toLowerCase() === lowered);\n }\n\n protected fieldErrors(name: string): string[] {\n return fieldMessages(this.lastResult(), name);\n }\n\n private async load(): Promise<void> {\n this.loading.set(true);\n const [profile, info] = await Promise.all([\n this.auth.profile(),\n this.auth.accountInfo(),\n this.loadLanguages(),\n this.auth.capabilities()\n .then(capabilities => this.emailChangeAllowed.set(capabilities.emailChange === true))\n .catch(() => { /* stays false */ }),\n ]);\n if (profile.success && profile.value) {\n this.apply(profile.value);\n } else {\n this.messages.set(accountMessages(profile));\n }\n if (info.success && info.value) {\n this.email.set(info.value.email);\n this.emailConfirmed.set(info.value.isEmailConfirmed);\n } else if (profile.value) {\n this.email.set(profile.value.email);\n }\n this.loading.set(false);\n }\n\n private async loadLanguages(): Promise<void> {\n try {\n const culture = await firstValueFrom(this.http.get<{ languages: Record<string, Record<string, string>> }>('/spark/culture'));\n const lang = (globalThis as { __sparkCurrentLanguage?: () => string }).__sparkCurrentLanguage?.() ?? 'en';\n this.languages.set(Object.entries(culture.languages ?? {}).map(([value, label]) => ({\n value,\n label: label?.[lang] ?? label?.['en'] ?? Object.values(label ?? {})[0] ?? value,\n })));\n } catch {\n // No culture endpoint: only \"default\" and the stored value are offered.\n }\n }\n\n private apply(profile: SparkAccountProfile): void {\n this.storedCulture.set(profile.preferredCulture);\n this.form.controls.userName.setValue(profile.userName ?? '');\n this.form.controls.preferredCulture.setValue(profile.preferredCulture ?? '');\n for (const field of this.profileFields) {\n const key = Object.keys(profile.fields ?? {}).find(k => k.toLowerCase() === field.name.toLowerCase());\n const value = key ? profile.fields[key] : null;\n this.form.controls.fields.controls[field.name]?.setValue(field.type === 'checkbox' ? value === true : value ?? null);\n }\n }\n\n async save(): Promise<void> {\n this.saved.set(false);\n this.busy.set(true);\n this.messages.set([]);\n this.lastResult.set(null);\n try {\n const value = this.form.getRawValue();\n const result = await this.auth.updateProfile({\n userName: value.userName.trim() || null,\n preferredCulture: value.preferredCulture || null,\n ...(this.profileFields.length ? { fields: { ...value.fields } } : {}),\n });\n this.lastResult.set(result);\n if (result.success && result.value) {\n this.apply(result.value);\n this.saved.set(true);\n } else {\n // Field errors render next to their control; only the rest goes to the banner.\n this.messages.set(result.errors\n ? Object.entries(result.errors)\n .filter(([key]) => !this.isInlineError(key))\n .flatMap(([, messages]) => messages)\n : accountMessages(result));\n }\n } finally {\n this.busy.set(false);\n }\n }\n\n async changeEmail(): Promise<void> {\n const newEmail = this.emailForm.controls.newEmail.value.trim();\n this.emailSent.set(false);\n this.emailMessages.set([]);\n if (!newEmail) return;\n\n this.busy.set(true);\n try {\n const result = await this.auth.changeEmail(newEmail);\n if (result.success) {\n this.emailSent.set(true);\n this.emailForm.reset();\n } else {\n this.emailMessages.set(accountMessages(result));\n }\n } finally {\n this.busy.set(false);\n }\n }\n}\n","import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';\nimport { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';\nimport { Color } from '@mintplayer/ng-bootstrap';\nimport { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';\nimport { BsCardComponent, BsCardHeaderComponent } from '@mintplayer/ng-bootstrap/card';\nimport { BsFormComponent, BsFormControlDirective } from '@mintplayer/ng-bootstrap/form';\nimport { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';\nimport { SparkAuthService } from '@mintplayer/ng-spark/auth/core';\nimport { TranslateKeyPipe } from '@mintplayer/ng-spark/auth/pipes';\nimport { accountMessages } from './account-messages';\n\n/**\n * Change the password — or set a first one on an account created through an external provider\n * (`POST /spark/auth/manage/password`, #460 D16). The current password is required only when the\n * account has one; the server says so (`CurrentPasswordRequired`) rather than the page guessing.\n */\n@Component({\n selector: 'spark-change-password',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [ReactiveFormsModule, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsFormComponent, BsFormControlDirective, BsSpinnerComponent, TranslateKeyPipe],\n template: `\n <div class=\"d-flex justify-content-center\">\n <bs-card style=\"width: 100%; max-width: 640px;\">\n <bs-card-header><h3 class=\"mb-0\">{{ 'auth.changePasswordTitle' | t }}</h3></bs-card-header>\n <div class=\"p-4\">\n @for (message of messages(); track message) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3 d-block spark-account-error\">{{ message | t }}</bs-alert>\n }\n @if (saved()) {\n <bs-alert [type]=\"colors.success\" class=\"mb-3 d-block spark-account-saved\">{{ 'auth.passwordChanged' | t }}</bs-alert>\n }\n <bs-form>\n <form [formGroup]=\"form\" (ngSubmit)=\"submit()\">\n <div class=\"mb-3\">\n <label for=\"currentPassword\" class=\"form-label\">{{ 'auth.currentPassword' | t }}</label>\n <input type=\"password\" id=\"currentPassword\" formControlName=\"currentPassword\" autocomplete=\"current-password\" />\n <small class=\"text-muted\">{{ 'auth.currentPasswordHint' | t }}</small>\n </div>\n <div class=\"mb-3\">\n <label for=\"newPassword\" class=\"form-label\">{{ 'auth.newPassword' | t }}</label>\n <input type=\"password\" id=\"newPassword\" formControlName=\"newPassword\" autocomplete=\"new-password\" />\n </div>\n <div class=\"mb-3\">\n <label for=\"confirmPassword\" class=\"form-label\">{{ 'auth.confirmPassword' | t }}</label>\n <input type=\"password\" id=\"confirmPassword\" formControlName=\"confirmPassword\" autocomplete=\"new-password\" />\n </div>\n <button type=\"submit\" class=\"btn btn-primary\" [disabled]=\"busy()\">\n @if (busy()) { <bs-spinner class=\"me-1\" /> }\n {{ 'auth.savePassword' | t }}\n </button>\n </form>\n </bs-form>\n </div>\n </bs-card>\n </div>\n `,\n})\nexport class SparkChangePasswordComponent {\n private readonly auth = inject(SparkAuthService);\n protected readonly colors = Color;\n\n readonly form = new FormGroup({\n currentPassword: new FormControl('', { nonNullable: true }),\n newPassword: new FormControl('', { nonNullable: true, validators: [Validators.required] }),\n confirmPassword: new FormControl('', { nonNullable: true, validators: [Validators.required] }),\n });\n\n readonly busy = signal(false);\n readonly saved = signal(false);\n readonly messages = signal<string[]>([]);\n\n async submit(): Promise<void> {\n this.saved.set(false);\n const { currentPassword, newPassword, confirmPassword } = this.form.getRawValue();\n if (!newPassword) {\n this.messages.set(['auth.newPasswordRequired']);\n return;\n }\n if (newPassword !== confirmPassword) {\n this.messages.set(['auth.passwordMismatch']);\n return;\n }\n\n this.busy.set(true);\n this.messages.set([]);\n try {\n const result = await this.auth.setPassword(newPassword, currentPassword || null);\n if (result.success) {\n this.saved.set(true);\n this.form.reset();\n } else {\n this.messages.set(accountMessages(result));\n }\n } finally {\n this.busy.set(false);\n }\n }\n}\n","import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core';\nimport { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';\nimport { Color } from '@mintplayer/ng-bootstrap';\nimport { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';\nimport { BsCardComponent, BsCardHeaderComponent } from '@mintplayer/ng-bootstrap/card';\nimport { BsCheckboxComponent } from '@mintplayer/ng-bootstrap/checkbox';\nimport { BsFormComponent, BsFormControlDirective } from '@mintplayer/ng-bootstrap/form';\nimport { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';\nimport { SparkAuthService, SparkAuthTranslationService } from '@mintplayer/ng-spark/auth/core';\nimport { SparkAuthenticatorUri, SparkAccountResult, SparkTwoFactorRequest, SparkTwoFactorState } from '@mintplayer/ng-spark/auth/models';\nimport { TranslateKeyPipe } from '@mintplayer/ng-spark/auth/pipes';\nimport { accountMessages } from './account-messages';\n\n/**\n * Two-factor authentication (#460, D16): enroll an authenticator app (server-rendered QR SVG, SP-D),\n * see and regenerate recovery codes, forget this browser, reset the key, disable 2FA; and, when the\n * server offers it and 2FA is on, let an external sign-in skip the code (switching that on needs a code).\n *\n * Built on Identity's `POST /manage/2fa` (which creates the key on the first read) and Spark's read-only\n * `GET /manage/2fa/authenticator-uri`. The QR SVG is shown through an `<img>` data URL, never inserted\n * as markup: an image cannot run script, whatever the SVG contains.\n */\n@Component({\n selector: 'spark-two-factor-setup',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [ReactiveFormsModule, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsCheckboxComponent, BsFormComponent, BsFormControlDirective, BsSpinnerComponent, TranslateKeyPipe],\n template: `\n <div class=\"d-flex justify-content-center\">\n <bs-card style=\"width: 100%; max-width: 640px;\">\n <bs-card-header><h3 class=\"mb-0\">{{ 'auth.twoFactorSetupTitle' | t }}</h3></bs-card-header>\n <div class=\"p-4\">\n @for (message of messages(); track message) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3 d-block spark-account-error\">{{ message | t }}</bs-alert>\n }\n @if (loading()) {\n <div class=\"text-center\"><bs-spinner /></div>\n } @else if (state(); as s) {\n @if (recoveryCodes().length) {\n <bs-alert [type]=\"colors.warning\" class=\"mb-3 d-block spark-recovery-codes\">\n <p class=\"mb-2\">{{ 'auth.recoveryCodesShownOnce' | t }}</p>\n <div class=\"d-flex flex-wrap gap-3 font-monospace\">\n @for (code of recoveryCodes(); track code) { <span>{{ code }}</span> }\n </div>\n </bs-alert>\n }\n\n @if (s.isTwoFactorEnabled) {\n <p class=\"spark-2fa-enabled\">{{ 'auth.twoFactorEnabled' | t }}</p>\n <p class=\"text-muted\">{{ 'auth.recoveryCodesLeft' | t }}: {{ s.recoveryCodesLeft }}</p>\n <div class=\"d-flex flex-wrap gap-2\">\n <button type=\"button\" class=\"btn btn-outline-primary spark-2fa-new-codes\" [disabled]=\"busy()\" (click)=\"regenerateRecoveryCodes()\">\n {{ 'auth.newRecoveryCodes' | t }}\n </button>\n @if (s.isMachineRemembered) {\n <button type=\"button\" class=\"btn btn-outline-secondary\" [disabled]=\"busy()\" (click)=\"forgetMachine()\">\n {{ 'auth.forgetBrowser' | t }}\n </button>\n }\n <button type=\"button\" class=\"btn btn-outline-warning\" [disabled]=\"busy()\" (click)=\"resetKey()\">\n {{ 'auth.resetAuthenticator' | t }}\n </button>\n <button type=\"button\" class=\"btn btn-outline-danger spark-2fa-disable\" [disabled]=\"busy()\" (click)=\"disable()\">\n {{ 'auth.disableTwoFactor' | t }}\n </button>\n </div>\n\n @if (bypassOffered()) {\n <hr class=\"my-4\" />\n <div class=\"spark-2fa-external-bypass\">\n <bs-form>\n <bs-checkbox [type]=\"'checkbox'\" [name]=\"'externalTwoFactorBypass'\" [isToggled]=\"bypassChecked()\"\n (isToggledChange)=\"toggleBypass($event === true)\">\n {{ 'auth.externalTwoFactorBypassLabel' | t }}\n </bs-checkbox>\n </bs-form>\n <small class=\"text-muted d-block\">{{ 'auth.externalTwoFactorBypassHint' | t }}</small>\n @if (bypassPending()) {\n <bs-form>\n <form [formGroup]=\"bypassForm\" (ngSubmit)=\"confirmBypass()\" class=\"d-flex gap-2 align-items-end mt-2\">\n <div class=\"flex-grow-1\">\n <label for=\"bypassCode\" class=\"form-label\">{{ 'auth.code' | t }}</label>\n <input type=\"text\" id=\"bypassCode\" formControlName=\"code\" inputmode=\"numeric\" autocomplete=\"one-time-code\" maxlength=\"8\" />\n </div>\n <button type=\"submit\" class=\"btn btn-primary spark-2fa-bypass-confirm\" [disabled]=\"busy()\">{{ 'auth.verify' | t }}</button>\n <button type=\"button\" class=\"btn btn-outline-secondary spark-2fa-bypass-cancel\" [disabled]=\"busy()\" (click)=\"toggleBypass(false)\">{{ 'common.cancel' | t }}</button>\n </form>\n </bs-form>\n }\n </div>\n }\n } @else {\n <p>{{ 'auth.twoFactorSetupIntro' | t }}</p>\n @if (qrDataUrl(); as qr) {\n <div class=\"text-center my-3\">\n <img class=\"spark-2fa-qr\" [src]=\"qr\" width=\"200\" height=\"200\" [attr.alt]=\"'auth.qrCodeAlt' | t\" />\n </div>\n }\n @if (authenticator(); as a) {\n <p class=\"text-muted small mb-1\">{{ 'auth.manualKey' | t }}</p>\n <p class=\"font-monospace spark-2fa-key\">{{ formatKey(a.sharedKey) }}</p>\n }\n <bs-form>\n <form [formGroup]=\"form\" (ngSubmit)=\"enable()\" class=\"d-flex gap-2 align-items-end\">\n <div class=\"flex-grow-1\">\n <label for=\"twoFactorCode\" class=\"form-label\">{{ 'auth.code' | t }}</label>\n <input type=\"text\" id=\"twoFactorCode\" formControlName=\"code\" inputmode=\"numeric\" autocomplete=\"one-time-code\" maxlength=\"8\" />\n </div>\n <button type=\"submit\" class=\"btn btn-primary spark-2fa-enable\" [disabled]=\"busy()\">{{ 'auth.enableTwoFactor' | t }}</button>\n </form>\n </bs-form>\n }\n }\n </div>\n </bs-card>\n </div>\n `,\n})\nexport class SparkTwoFactorSetupComponent {\n private readonly auth = inject(SparkAuthService);\n private readonly translation = inject(SparkAuthTranslationService);\n protected readonly colors = Color;\n\n readonly form = new FormGroup({ code: new FormControl('', { nonNullable: true }) });\n\n readonly loading = signal(true);\n readonly busy = signal(false);\n readonly messages = signal<string[]>([]);\n readonly state = signal<SparkTwoFactorState | null>(null);\n readonly authenticator = signal<SparkAuthenticatorUri | null>(null);\n readonly recoveryCodes = signal<string[]>([]);\n\n /** The server's SVG as an image URL — displayed, never parsed into the DOM. */\n readonly qrDataUrl = computed(() => {\n const svg = this.authenticator()?.qrCodeSvg;\n return svg ? 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg) : null;\n });\n\n readonly bypassForm = new FormGroup({ code: new FormControl('', { nonNullable: true }) });\n /** The server offers the external-login bypass (`capabilities.externalLoginTwoFactorBypass`). */\n private readonly bypassCapability = signal(false);\n /** The account's bypass setting, or null until read (or when the read failed). */\n readonly bypass = signal<boolean | null>(null);\n /** Switched on but not yet confirmed with an authenticator code. */\n readonly bypassPending = signal(false);\n\n /** Shown only when the server offers it AND the account has two-factor on: there is nothing to skip otherwise. */\n readonly bypassOffered = computed(() =>\n this.bypassCapability() && this.bypass() !== null && this.state()?.isTwoFactorEnabled === true);\n readonly bypassChecked = computed(() => this.bypass() === true || this.bypassPending());\n\n constructor() {\n void this.refresh();\n void Promise.resolve()\n .then(() => this.auth.capabilities())\n .then(capabilities => {\n this.bypassCapability.set(capabilities?.externalLoginTwoFactorBypass === true);\n if (this.bypassCapability()) return this.loadBypass();\n return undefined;\n })\n .catch(() => { /* capability unknown: the toggle stays hidden */ });\n }\n\n private async loadBypass(): Promise<void> {\n const result = await this.auth.externalLoginTwoFactor();\n this.bypass.set(result.success && result.value ? result.value.bypass : null);\n }\n\n /** Off: saved at once. On: asks for an authenticator code first ({@link confirmBypass}). */\n async toggleBypass(on: boolean): Promise<void> {\n if (on) {\n if (this.bypass() !== true) this.bypassPending.set(true);\n return;\n }\n if (this.bypassPending()) {\n this.bypassPending.set(false);\n this.bypassForm.reset();\n return;\n }\n if (this.bypass() !== true) return;\n await this.saveBypass(false);\n }\n\n async confirmBypass(): Promise<void> {\n const code = this.bypassForm.controls.code.value.replace(/\\s|-/g, '');\n if (!code) {\n this.messages.set(['auth.enterCodeError']);\n return;\n }\n await this.saveBypass(true, code);\n }\n\n private async saveBypass(bypass: boolean, code?: string): Promise<void> {\n this.busy.set(true);\n this.messages.set([]);\n try {\n const result = await this.auth.setExternalLoginTwoFactor(bypass, code);\n if (result.success && result.value) {\n this.bypass.set(result.value.bypass);\n this.bypassPending.set(false);\n this.bypassForm.reset();\n } else {\n this.messages.set(result.status === 400 && !result.errors ? ['auth.invalidCode'] : accountMessages(result));\n }\n } finally {\n this.busy.set(false);\n }\n }\n\n /** Groups the base32 key in fours, the way authenticator apps show it. */\n protected formatKey(key: string): string {\n return key.replace(/(.{4})/g, '$1 ').trim();\n }\n\n async refresh(): Promise<void> {\n this.loading.set(true);\n const state = await this.auth.twoFactor();\n await this.applyState(state);\n this.loading.set(false);\n }\n\n async enable(): Promise<void> {\n const code = this.form.controls.code.value.replace(/\\s|-/g, '');\n if (!code) {\n this.messages.set(['auth.enterCodeError']);\n return;\n }\n await this.run({ enable: true, twoFactorCode: code });\n if (this.state()?.isTwoFactorEnabled) this.form.reset();\n }\n\n async disable(): Promise<void> {\n if (!confirm(this.translation.t('auth.confirmDisableTwoFactor'))) return;\n await this.run({ enable: false });\n }\n\n async regenerateRecoveryCodes(): Promise<void> {\n await this.run({ resetRecoveryCodes: true });\n }\n\n async forgetMachine(): Promise<void> {\n await this.run({ forgetMachine: true });\n // MapIdentityApi computes isMachineRemembered from the request's own remember-me cookie, which this\n // same response deletes, so it still answers true. The browser is forgotten once the call succeeded.\n const state = this.state();\n if (state?.isMachineRemembered && this.messages().length === 0)\n this.state.set({ ...state, isMachineRemembered: false });\n }\n\n async resetKey(): Promise<void> {\n if (!confirm(this.translation.t('auth.confirmResetAuthenticator'))) return;\n await this.run({ resetSharedKey: true });\n }\n\n private async run(request: SparkTwoFactorRequest): Promise<void> {\n this.busy.set(true);\n this.messages.set([]);\n try {\n await this.applyState(await this.auth.twoFactor(request));\n } finally {\n this.busy.set(false);\n }\n }\n\n private async applyState(result: SparkAccountResult<SparkTwoFactorState>): Promise<void> {\n if (!result.success || !result.value) {\n this.messages.set(result.status === 400 && !result.errors ? ['auth.invalidCode'] : accountMessages(result));\n return;\n }\n const wasEnabled = this.state()?.isTwoFactorEnabled;\n this.state.set(result.value);\n this.recoveryCodes.set(result.value.recoveryCodes ?? []);\n // Disabling two-factor may clear the bypass server-side; read it again when the state flips.\n if (this.bypassCapability() && wasEnabled !== undefined && wasEnabled !== result.value.isTwoFactorEnabled) {\n this.bypassPending.set(false);\n void this.loadBypass();\n }\n if (result.value.isTwoFactorEnabled) {\n this.authenticator.set(null);\n return;\n }\n const uri = await this.auth.authenticatorUri();\n this.authenticator.set(uri.success && uri.value ? uri.value : null);\n if (!uri.success) this.messages.set(accountMessages(uri));\n }\n}\n","import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { Color } from '@mintplayer/ng-bootstrap';\nimport { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';\nimport { BsCardComponent, BsCardHeaderComponent } from '@mintplayer/ng-bootstrap/card';\nimport { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';\nimport { SparkAuthService } from '@mintplayer/ng-spark/auth/core';\nimport {\n SPARK_EXTERNAL_PROVIDERS,\n SparkExternalLoginError,\n SparkExternalLogins,\n SparkExternalProviderPresentation,\n} from '@mintplayer/ng-spark/auth/models';\nimport { TranslateKeyPipe } from '@mintplayer/ng-spark/auth/pipes';\n\n/**\n * Connected logins (#460, D16): the providers attached to the account (remove, unless it is the last\n * way in — the server's `canUnlink`, and its refusal), and the configured providers that could be\n * attached (a popup handshake keyed on the signed-in user).\n *\n * Presentation (icon, display name) comes from the same `externalProvider()` declarations as the\n * sign-in page when they are in scope; the server stays authoritative over which providers exist.\n */\n@Component({\n selector: 'spark-external-logins',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsSpinnerComponent, TranslateKeyPipe],\n template: `\n <div class=\"d-flex justify-content-center\">\n <bs-card style=\"width: 100%; max-width: 640px;\">\n <bs-card-header><h3 class=\"mb-0\">{{ 'auth.externalLoginsTitle' | t }}</h3></bs-card-header>\n <div class=\"p-4\">\n @if (errorKey()) {\n <bs-alert [type]=\"errorIsNotice() ? colors.info : colors.danger\" class=\"mb-3 d-block spark-account-error\">{{ errorKey() | t }}</bs-alert>\n }\n @if (loading()) {\n <div class=\"text-center\"><bs-spinner /></div>\n } @else if (logins(); as l) {\n @for (login of l.linked; track login.provider + login.providerKey) {\n <div class=\"d-flex align-items-center justify-content-between border-bottom py-2 spark-linked-login\">\n <span>\n @if (iconFor(login.provider); as icon) { <i [class]=\"icon\" class=\"me-2\"></i> }\n {{ nameFor(login.provider, login.displayName) }}\n </span>\n <button type=\"button\" class=\"btn btn-outline-danger btn-sm\" [disabled]=\"busy() || !login.canUnlink\"\n [attr.title]=\"login.canUnlink ? null : ('auth.lastCredentialHint' | t)\"\n (click)=\"unlink(login.provider, login.providerKey)\">\n {{ 'auth.removeLogin' | t }}\n </button>\n </div>\n } @empty {\n <p class=\"text-muted\">{{ 'auth.noLinkedLogins' | t }}</p>\n }\n\n @if (l.available.length) {\n <h5 class=\"mt-4\">{{ 'auth.addLogin' | t }}</h5>\n <div class=\"d-flex flex-wrap gap-2\">\n @for (provider of l.available; track provider.provider) {\n <button type=\"button\" class=\"btn btn-outline-primary spark-available-login\" [disabled]=\"busy() || linkPending()\" (click)=\"link(provider.provider)\">\n @if (iconFor(provider.provider); as icon) { <i [class]=\"icon\" class=\"me-2\"></i> }\n {{ nameFor(provider.provider, provider.displayName) }}\n </button>\n }\n </div>\n @if (linkPending()) {\n <div class=\"text-center mt-3 spark-link-pending\"><bs-spinner /></div>\n }\n }\n }\n </div>\n </bs-card>\n </div>\n `,\n})\nexport class SparkExternalLoginsComponent {\n private readonly auth = inject(SparkAuthService);\n private readonly router = inject(Router);\n private readonly presentations: SparkExternalProviderPresentation[] = inject(SPARK_EXTERNAL_PROVIDERS, { optional: true }) ?? [];\n protected readonly colors = Color;\n\n readonly loading = signal(true);\n readonly busy = signal(false);\n readonly errorKey = signal('');\n readonly logins = signal<SparkExternalLogins | null>(null);\n\n /**\n * While a link popup is open. Taken from the service, not from the link() promise: under COOP the\n * popup reads closed long before the result arrives, and the attempt keeps listening (D2).\n */\n readonly linkPending = computed(() => this.auth.externalLoginPending());\n\n /** `link_confirmation_sent` travels as a failure but is news, not an error. */\n readonly errorIsNotice = computed(() => this.errorKey() === 'auth.externalLoginError.link_confirmation_sent');\n\n constructor() {\n // A redirect-mode link (inside an installed web app) comes back here with the outcome in the URL.\n const returned = this.auth.takeExternalLoginResult();\n if (returned) this.showLinkError(returned);\n void this.refresh();\n }\n\n private showLinkError(error: SparkExternalLoginError | undefined): void {\n if (error === 'popup_closed') return;\n this.errorKey.set(`auth.externalLoginError.${error ?? 'link_failed'}`);\n }\n\n private presentation(scheme: string): SparkExternalProviderPresentation | undefined {\n return this.presentations.find(p => p.scheme.toLowerCase() === scheme.toLowerCase());\n }\n\n protected iconFor(scheme: string): string | undefined {\n return this.presentation(scheme)?.iconClass;\n }\n\n protected nameFor(scheme: string, serverName: string): string {\n return this.presentation(scheme)?.displayName ?? serverName ?? scheme;\n }\n\n async refresh(): Promise<void> {\n this.loading.set(true);\n try {\n this.logins.set(await this.auth.externalLogins());\n } catch {\n // 404 = linking is not enabled on this deployment (SparkExternalLinking disabled).\n this.errorKey.set('auth.externalLoginsUnavailable');\n } finally {\n this.loading.set(false);\n }\n }\n\n /**\n * Links another provider. The buttons follow {@link linkPending} rather than this promise, which\n * settles only on the result, a newer attempt or the 10-minute timeout.\n */\n async link(provider: string): Promise<void> {\n this.errorKey.set('');\n const result = await this.auth.linkProvider(provider, { returnUrl: this.router.url });\n if (!result.success) this.showLinkError(result.error);\n await this.refresh();\n }\n\n async unlink(provider: string, providerKey: string): Promise<void> {\n this.errorKey.set('');\n this.busy.set(true);\n try {\n const result = await this.auth.unlinkProvider(provider, providerKey);\n if (!result.success) this.errorKey.set(`auth.unlinkError.${result.error ?? 'unlink_failed'}`);\n await this.refresh();\n } finally {\n this.busy.set(false);\n }\n }\n}\n","import { ChangeDetectionStrategy, Component, inject, OnInit, signal } from '@angular/core';\nimport { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';\nimport { Router } from '@angular/router';\nimport { Color } from '@mintplayer/ng-bootstrap';\nimport { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';\nimport { BsCardComponent, BsCardHeaderComponent } from '@mintplayer/ng-bootstrap/card';\nimport { BsCheckboxComponent } from '@mintplayer/ng-bootstrap/checkbox';\nimport { BsFormComponent, BsFormControlDirective } from '@mintplayer/ng-bootstrap/form';\nimport { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';\nimport { SparkAuthService } from '@mintplayer/ng-spark/auth/core';\nimport { SPARK_AUTH_CONFIG } from '@mintplayer/ng-spark/auth/models';\nimport { TranslateKeyPipe } from '@mintplayer/ng-spark/auth/pipes';\nimport { accountMessages } from './account-messages';\n\n/**\n * Personal data (#460, D8/D16): download everything the account and the application's\n * `ISparkPersonalDataContributor`s hold as JSON, and delete the account.\n *\n * Deletion needs re-authentication: the current password (the field shows only when the account has\n * one, from `account.hasPassword`), or — for an account without one — a sign-in\n * younger than the server's `ReauthenticationMaxAge` (5 minutes). A 403 `reauthentication_required`\n * says which is missing; the page then asks to sign in again. Handlers run first and the account is\n * removed last, so a failure (500 `deletion_failed`) leaves the account intact and retryable.\n */\n@Component({\n selector: 'spark-personal-data',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [ReactiveFormsModule, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsCheckboxComponent, BsFormComponent, BsFormControlDirective, BsSpinnerComponent, TranslateKeyPipe],\n template: `\n <div class=\"d-flex justify-content-center\">\n <bs-card style=\"width: 100%; max-width: 640px;\">\n <bs-card-header><h3 class=\"mb-0\">{{ 'auth.personalDataTitle' | t }}</h3></bs-card-header>\n <div class=\"p-4\">\n <p>{{ 'auth.personalDataDescription' | t }}</p>\n @for (message of downloadMessages(); track message) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3 d-block\">{{ message | t }}</bs-alert>\n }\n <button type=\"button\" class=\"btn btn-outline-primary spark-download-data\" [disabled]=\"busy()\" (click)=\"download()\">\n {{ 'auth.downloadPersonalData' | t }}\n </button>\n\n <hr class=\"my-4\" />\n\n <h5 class=\"text-danger\">{{ 'auth.deleteAccountTitle' | t }}</h5>\n <p>{{ 'auth.deleteAccountDescription' | t }}</p>\n @for (message of deleteMessages(); track message) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3 d-block spark-delete-error\">{{ message | t }}</bs-alert>\n }\n <bs-form>\n <form [formGroup]=\"form\" (ngSubmit)=\"deleteAccount()\">\n @if (hasPassword() !== false) {\n <div class=\"mb-3\">\n <label for=\"deletePassword\" class=\"form-label\">{{ 'auth.password' | t }}</label>\n <input type=\"password\" id=\"deletePassword\" formControlName=\"password\" autocomplete=\"current-password\" />\n <small class=\"text-muted\">{{ 'auth.deletePasswordHint' | t }}</small>\n </div>\n } @else {\n <p class=\"text-muted spark-delete-recent-sign-in\">{{ 'auth.deleteRecentSignIn' | t }}</p>\n }\n <div class=\"mb-3\">\n <bs-checkbox [type]=\"'checkbox'\" formControlName=\"confirm\" [name]=\"'confirmDelete'\">{{ 'auth.deleteAccountConfirm' | t }}</bs-checkbox>\n </div>\n <button type=\"submit\" class=\"btn btn-danger spark-delete-account\" [disabled]=\"busy() || !form.controls.confirm.value\">\n @if (busy()) { <bs-spinner class=\"me-1\" /> }\n {{ 'auth.deleteAccount' | t }}\n </button>\n </form>\n </bs-form>\n </div>\n </bs-card>\n </div>\n `,\n})\nexport class SparkPersonalDataComponent implements OnInit {\n private readonly auth = inject(SparkAuthService);\n private readonly router = inject(Router);\n private readonly config = inject(SPARK_AUTH_CONFIG);\n protected readonly colors = Color;\n\n readonly form = new FormGroup({\n password: new FormControl('', { nonNullable: true }),\n confirm: new FormControl(false, { nonNullable: true }),\n });\n\n readonly busy = signal(false);\n readonly downloadMessages = signal<string[]>([]);\n readonly deleteMessages = signal<string[]>([]);\n /**\n * Whether the account has a password (`account.hasPassword` of the personal data). Null while\n * unknown — an older server, or the read failed — which keeps the password field, as before.\n */\n readonly hasPassword = signal<boolean | null>(null);\n\n async ngOnInit(): Promise<void> {\n try {\n const result = await this.auth.personalData();\n const flag = result.success ? (result.value as { account?: { hasPassword?: unknown } } | null)?.account?.hasPassword : undefined;\n this.hasPassword.set(typeof flag === 'boolean' ? flag : null);\n } catch {\n this.hasPassword.set(null);\n }\n }\n\n async download(): Promise<void> {\n this.busy.set(true);\n this.downloadMessages.set([]);\n try {\n const result = await this.auth.personalData();\n if (!result.success) {\n this.downloadMessages.set(accountMessages(result));\n return;\n }\n const blob = new Blob([JSON.stringify(result.value, null, 2)], { type: 'application/json' });\n const url = URL.createObjectURL(blob);\n const anchor = document.createElement('a');\n anchor.href = url;\n anchor.download = 'personal-data.json';\n anchor.click();\n URL.revokeObjectURL(url);\n } finally {\n this.busy.set(false);\n }\n }\n\n async deleteAccount(): Promise<void> {\n if (!this.form.controls.confirm.value) return;\n this.busy.set(true);\n this.deleteMessages.set([]);\n try {\n const result = await this.auth.deleteAccount(this.form.controls.password.value || null);\n if (result.success) {\n await this.router.navigateByUrl(this.config.defaultRedirectUrl);\n return;\n }\n this.deleteMessages.set(accountMessages(result));\n } finally {\n this.busy.set(false);\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"mintplayer-ng-spark-auth-account.mjs","names":[],"sources":["../../auth/account/src/account-messages.ts","../../auth/account/src/spark-account-overview.component.ts","../../auth/account/src/spark-account-profile.component.ts","../../auth/account/src/spark-change-password.component.ts","../../auth/account/src/spark-two-factor-setup.component.ts","../../auth/account/src/spark-external-logins.component.ts","../../auth/account/src/spark-personal-data.component.ts","../../auth/account/src/spark-connected-agents.component.ts"],"sourcesContent":["import { SparkAccountResult } from '@mintplayer/ng-spark/auth/models';\n\n/**\n * The messages to show for a failed account call: the server's validation messages (already written\n * for the user by Identity or a profile contributor), else a translation key for its error code or\n * status. Keys start with `auth.`; the page runs everything through the `t` pipe, which returns\n * anything that is not a key unchanged.\n */\nexport function accountMessages(result: SparkAccountResult<unknown>): string[] {\n if (result.success) return [];\n if (result.errors) {\n const messages = Object.values(result.errors).flat().filter(m => !!m);\n if (messages.length) return messages;\n }\n if (result.error) return [`auth.accountError.${result.error}`];\n if (result.status === 404) return ['auth.accountUnavailable'];\n return ['auth.accountFailed'];\n}\n\n/** The messages for one field of a validation problem, matched case-insensitively. */\nexport function fieldMessages(result: SparkAccountResult<unknown> | null, field: string): string[] {\n if (!result?.errors) return [];\n const key = Object.keys(result.errors).find(k => k.toLowerCase() === field.toLowerCase());\n return key ? result.errors[key] : [];\n}\n","import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core';\nimport { RouterLink } from '@angular/router';\nimport { BsCardComponent, BsCardHeaderComponent } from '@mintplayer/ng-bootstrap/card';\nimport { SparkAuthService } from '@mintplayer/ng-spark/auth/core';\nimport { currentLanguage } from '@mintplayer/ng-spark/models';\nimport {\n SPARK_ACCOUNT_OVERVIEW_LINKS,\n SPARK_AUTH_ROUTE_PATHS,\n SparkAuthCapabilities,\n SparkAuthRoutePaths,\n passkeysSupported,\n} from '@mintplayer/ng-spark/auth/models';\nimport { TranslateKeyPipe } from '@mintplayer/ng-spark/auth/pipes';\n\n/**\n * Whether the server serves a page's feature. Mounting a page on the client is necessary but not\n * sufficient: the route table and the server's switches are configured independently, which is why\n * `/spark/auth/capabilities` exists. Pages without an entry need no switch beyond being mounted.\n */\ntype ServerCheck = (capabilities: SparkAuthCapabilities) => boolean;\n\n/** Under `Disabled` neither `/login` nor `manage/password` is mapped: no password to change, and a\n * second factor would guard a sign-in that does not exist. */\nconst passwordSignIn: ServerCheck = c => c.localCredentials !== 'Disabled';\n\n/** The account pages this app mounted, in display order, with their title keys. */\nconst PAGES: { key: keyof SparkAuthRoutePaths; label: string; server?: ServerCheck }[] = [\n { key: 'profile', label: 'auth.profileTitle' },\n { key: 'changePassword', label: 'auth.changePasswordTitle', server: passwordSignIn },\n { key: 'twoFactorSetup', label: 'auth.twoFactorSetupTitle', server: c => passwordSignIn(c) && c.twoFactor === true },\n { key: 'externalLogins', label: 'auth.externalLoginsTitle', server: c => c.externalLogins === true },\n // `passkeys` reports `SparkPasskeys.Enabled`; the browser must also support the ceremony.\n { key: 'passkeys', label: 'auth.passkeysTitle', server: c => c.passkeys === true && passkeysSupported() },\n { key: 'personalData', label: 'auth.personalDataTitle' },\n];\n\n/**\n * The account area's landing page (#460, D16): who is signed in, and a link per account page that is\n * both mounted (`withAccount()`) and served (`/spark/auth/capabilities`), then the links other route\n * features contribute through `SPARK_ACCOUNT_OVERVIEW_LINKS` (e.g. connected applications, from\n * `@mintplayer/ng-spark/identity-provider`).\n */\n@Component({\n selector: 'spark-account-overview',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [RouterLink, BsCardComponent, BsCardHeaderComponent, TranslateKeyPipe],\n template: `\n <div class=\"d-flex justify-content-center\">\n <bs-card style=\"width: 100%; max-width: 640px;\">\n <bs-card-header>\n <h3 class=\"mb-0\">{{ 'auth.accountTitle' | t }}</h3>\n @if (auth.user(); as user) {\n <small class=\"spark-account-user text-muted text-break\">{{ user.userName }}</small>\n @if (user.email && user.email !== user.userName) {\n <small class=\"spark-account-email text-muted text-break d-block\">{{ user.email }}</small>\n }\n }\n </bs-card-header>\n <div class=\"p-4\">\n <ul class=\"list-unstyled mb-0\">\n @for (page of pages(); track page.key) {\n <li class=\"py-1\"><a class=\"spark-account-link\" [routerLink]=\"page.path\">{{ page.label | t }}</a></li>\n }\n @for (link of extraLinks(); track link.key) {\n <li class=\"py-1\"><a class=\"spark-account-link\" [routerLink]=\"link.path\">{{ link.label }}</a></li>\n }\n </ul>\n </div>\n </bs-card>\n </div>\n `,\n})\nexport class SparkAccountOverviewComponent {\n protected readonly auth = inject(SparkAuthService);\n private readonly paths = inject(SPARK_AUTH_ROUTE_PATHS, { optional: true }) ?? {};\n private readonly capabilities = signal<SparkAuthCapabilities | null>(null);\n\n /**\n * Pages behind a server switch stay hidden until the capabilities arrive, and stay hidden if the\n * call fails: offering a page whose endpoints the server does not map is worse than omitting it.\n */\n protected readonly pages = computed(() => {\n const capabilities = this.capabilities();\n return PAGES\n .filter(page => !!this.paths[page.key])\n .filter(page => !page.server || (capabilities !== null && page.server(capabilities)))\n .map(page => ({ ...page, path: this.paths[page.key]! }));\n });\n\n private readonly accountLinks = inject(SPARK_ACCOUNT_OVERVIEW_LINKS, { optional: true }) ?? [];\n\n /**\n * Links contributed by route features of other entry points (connected applications), after the\n * auth pages. Each carries its own translations; resolved in the app's current language.\n */\n protected readonly extraLinks = computed(() => {\n const lang = currentLanguage();\n return this.accountLinks.map(link => ({\n key: link.key,\n path: link.path,\n label: link.label[lang] ?? link.label['en'] ?? Object.values(link.label)[0] ?? link.key,\n }));\n });\n\n constructor() {\n void this.auth.capabilities()\n .then(capabilities => this.capabilities.set(capabilities))\n .catch(() => { /* stays null; server-switched pages stay hidden */ });\n }\n}\n","import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';\nimport { firstValueFrom } from 'rxjs';\nimport { Color } from '@mintplayer/ng-bootstrap';\nimport { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';\nimport { BsCardComponent, BsCardHeaderComponent } from '@mintplayer/ng-bootstrap/card';\nimport { BsCheckboxComponent } from '@mintplayer/ng-bootstrap/checkbox';\nimport { BsFormComponent, BsFormControlDirective } from '@mintplayer/ng-bootstrap/form';\nimport { BsInputGroupComponent } from '@mintplayer/ng-bootstrap/input-group';\nimport { BsSelectComponent, BsSelectOption, BsSelectValueAccessor } from '@mintplayer/ng-bootstrap/select';\nimport { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';\nimport { SparkAuthService } from '@mintplayer/ng-spark/auth/core';\nimport {\n SPARK_ACCOUNT_PROFILE_FIELDS,\n SparkAccountProfile,\n SparkAccountProfileField,\n SparkAccountResult,\n} from '@mintplayer/ng-spark/auth/models';\nimport { TranslateKeyPipe } from '@mintplayer/ng-spark/auth/pipes';\nimport { accountMessages, fieldMessages } from './account-messages';\n\ninterface CultureOption {\n value: string;\n label: string;\n}\n\n/**\n * The profile page (#460, D16): user name, email (changeable only under the server's opt-in\n * `SparkEmailChange.Enabled`; a change is confirmed from the NEW address first),\n * the preferred mail language (`SparkUser.PreferredCulture`, M8) and the application's own fields\n * (`SPARK_ACCOUNT_PROFILE_FIELDS`, validated server-side by `ISparkProfileContributor<TUser>`).\n *\n * The language list is the application's UI languages (`GET /spark/culture`); a stored culture not in\n * that list (e.g. `nl-BE`) is kept as an option so opening the page never changes it.\n */\n@Component({\n selector: 'spark-account-profile',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [ReactiveFormsModule, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsCheckboxComponent, BsFormComponent, BsFormControlDirective, BsInputGroupComponent, BsSelectComponent, BsSelectValueAccessor, BsSelectOption, BsSpinnerComponent, TranslateKeyPipe],\n template: `\n <div class=\"d-flex justify-content-center\">\n <bs-card style=\"width: 100%; max-width: 640px;\">\n <bs-card-header><h3 class=\"mb-0\">{{ 'auth.profileTitle' | t }}</h3></bs-card-header>\n <div class=\"p-4\">\n @if (loading()) {\n <div class=\"text-center\"><bs-spinner /></div>\n } @else {\n @for (message of messages(); track message) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3 d-block spark-account-error\">{{ message | t }}</bs-alert>\n }\n @if (saved()) {\n <bs-alert [type]=\"colors.success\" class=\"mb-3 d-block spark-account-saved\">{{ 'auth.profileSaved' | t }}</bs-alert>\n }\n\n <bs-form>\n <form [formGroup]=\"form\" (ngSubmit)=\"save()\">\n <div class=\"mb-3\">\n <label for=\"userName\" class=\"form-label\">{{ 'auth.userName' | t }}</label>\n <input type=\"text\" id=\"userName\" formControlName=\"userName\" autocomplete=\"username\" />\n @for (message of userNameErrors(); track message) {\n <div class=\"text-danger small\">{{ message | t }}</div>\n }\n </div>\n\n <div class=\"mb-3\">\n <label for=\"preferredCulture\" class=\"form-label\">{{ 'auth.preferredCulture' | t }}</label>\n <bs-select id=\"preferredCulture\" formControlName=\"preferredCulture\" class=\"w-100\">\n <option [ngValue]=\"''\">{{ 'auth.preferredCultureDefault' | t }}</option>\n @for (culture of cultureOptions(); track culture.value) {\n <option [ngValue]=\"culture.value\">{{ culture.label }}</option>\n }\n </bs-select>\n <small class=\"text-muted\">{{ 'auth.preferredCultureHint' | t }}</small>\n @for (message of fieldErrors('PreferredCulture'); track message) {\n <div class=\"text-danger small\">{{ message | t }}</div>\n }\n </div>\n\n <div formGroupName=\"fields\">\n @for (field of profileFields; track field.name) {\n <div class=\"mb-3 spark-profile-field\">\n @if (field.type === 'checkbox') {\n <bs-checkbox [type]=\"'checkbox'\" [formControlName]=\"field.name\" [name]=\"field.name\">{{ field.label | t }}</bs-checkbox>\n } @else {\n <label [for]=\"'field-' + field.name\" class=\"form-label\">{{ field.label | t }}</label>\n @switch (field.type) {\n @case ('textarea') {\n <textarea [id]=\"'field-' + field.name\" [formControlName]=\"field.name\" rows=\"3\"\n [attr.maxlength]=\"field.maxLength ?? null\" [required]=\"!!field.required\"></textarea>\n }\n @case ('select') {\n <bs-select [id]=\"'field-' + field.name\" [formControlName]=\"field.name\" class=\"w-100\">\n @if (!field.required) {\n <option [ngValue]=\"null\"></option>\n }\n @for (option of field.options ?? []; track option.value) {\n <option [ngValue]=\"option.value\">{{ option.label | t }}</option>\n }\n </bs-select>\n }\n @default {\n <input [type]=\"field.type ?? 'text'\" [id]=\"'field-' + field.name\" [formControlName]=\"field.name\"\n [attr.maxlength]=\"field.maxLength ?? null\" [required]=\"!!field.required\" />\n }\n }\n }\n @if (field.hint) {\n <small class=\"text-muted\">{{ field.hint | t }}</small>\n }\n @for (message of fieldErrors(field.name); track message) {\n <div class=\"text-danger small\">{{ message | t }}</div>\n }\n </div>\n }\n </div>\n\n <button type=\"submit\" class=\"btn btn-primary\" [disabled]=\"busy()\">\n @if (busy()) { <bs-spinner class=\"me-1\" /> }\n {{ 'auth.saveProfile' | t }}\n </button>\n </form>\n </bs-form>\n\n <hr class=\"my-4\" />\n\n <h5>{{ 'auth.email' | t }}</h5>\n <p class=\"spark-current-email\">\n {{ email() }}\n @if (emailConfirmed() === false) {\n <span class=\"text-warning ms-2\">{{ 'auth.emailNotConfirmed' | t }}</span>\n }\n </p>\n @if (emailMessages().length) {\n @for (message of emailMessages(); track message) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3 d-block\">{{ message | t }}</bs-alert>\n }\n }\n @if (emailSent()) {\n <bs-alert [type]=\"colors.info\" class=\"mb-3 d-block spark-email-sent\">{{ 'auth.emailChangeSent' | t }}</bs-alert>\n }\n <!-- Opt-in on the server (SparkEmailChange.Enabled); hidden until it says so. -->\n @if (emailChangeAllowed()) {\n <bs-form>\n <form [formGroup]=\"emailForm\" (ngSubmit)=\"changeEmail()\">\n <label for=\"newEmail\" class=\"form-label\">{{ 'auth.newEmail' | t }}</label>\n <bs-input-group>\n <input type=\"email\" id=\"newEmail\" formControlName=\"newEmail\" autocomplete=\"email\" />\n <button type=\"submit\" class=\"btn btn-outline-primary\" [disabled]=\"busy()\">{{ 'auth.changeEmail' | t }}</button>\n </bs-input-group>\n </form>\n </bs-form>\n }\n }\n </div>\n </bs-card>\n </div>\n `,\n})\nexport class SparkAccountProfileComponent {\n private readonly auth = inject(SparkAuthService);\n private readonly http = inject(HttpClient);\n protected readonly colors = Color;\n\n /** The app's fields, ordered; each becomes a control under `fields`. */\n protected readonly profileFields: SparkAccountProfileField[] = [...(inject(SPARK_ACCOUNT_PROFILE_FIELDS, { optional: true }) ?? [])]\n .map((field, index) => ({ field, index }))\n .sort((a, b) => (a.field.order ?? Infinity) - (b.field.order ?? Infinity) || a.index - b.index)\n .map(x => x.field);\n\n readonly form = new FormGroup({\n userName: new FormControl('', { nonNullable: true }),\n preferredCulture: new FormControl<string>('', { nonNullable: true }),\n fields: new FormGroup<Record<string, FormControl<unknown>>>({}),\n });\n readonly emailForm = new FormGroup({ newEmail: new FormControl('', { nonNullable: true }) });\n\n readonly loading = signal(true);\n readonly busy = signal(false);\n readonly saved = signal(false);\n readonly messages = signal<string[]>([]);\n private readonly lastResult = signal<SparkAccountResult<unknown> | null>(null);\n\n readonly email = signal<string | null>(null);\n readonly emailConfirmed = signal<boolean | null>(null);\n readonly emailSent = signal(false);\n readonly emailMessages = signal<string[]>([]);\n /** Off until the server reports `emailChange`, and off if the capabilities call fails. */\n readonly emailChangeAllowed = signal(false);\n\n private readonly languages = signal<CultureOption[]>([]);\n private readonly storedCulture = signal<string | null>(null);\n\n readonly cultureOptions = computed(() => {\n const options = this.languages();\n const stored = this.storedCulture();\n return stored && !options.some(o => o.value.toLowerCase() === stored.toLowerCase())\n ? [...options, { value: stored, label: stored }]\n : options;\n });\n\n readonly userNameErrors = computed(() => {\n const result = this.lastResult();\n if (!result?.errors) return [];\n return Object.entries(result.errors)\n .filter(([key]) => key.toLowerCase().includes('username'))\n .flatMap(([, messages]) => messages);\n });\n\n constructor() {\n for (const field of this.profileFields) {\n this.form.controls.fields.addControl(field.name, new FormControl<unknown>(field.type === 'checkbox' ? false : null));\n }\n void this.load();\n }\n\n private isInlineError(key: string): boolean {\n const lowered = key.toLowerCase();\n return lowered === 'preferredculture'\n || lowered.includes('username')\n || this.profileFields.some(f => f.name.toLowerCase() === lowered);\n }\n\n protected fieldErrors(name: string): string[] {\n return fieldMessages(this.lastResult(), name);\n }\n\n private async load(): Promise<void> {\n this.loading.set(true);\n const [profile, info] = await Promise.all([\n this.auth.profile(),\n this.auth.accountInfo(),\n this.loadLanguages(),\n this.auth.capabilities()\n .then(capabilities => this.emailChangeAllowed.set(capabilities.emailChange === true))\n .catch(() => { /* stays false */ }),\n ]);\n if (profile.success && profile.value) {\n this.apply(profile.value);\n } else {\n this.messages.set(accountMessages(profile));\n }\n if (info.success && info.value) {\n this.email.set(info.value.email);\n this.emailConfirmed.set(info.value.isEmailConfirmed);\n } else if (profile.value) {\n this.email.set(profile.value.email);\n }\n this.loading.set(false);\n }\n\n private async loadLanguages(): Promise<void> {\n try {\n const culture = await firstValueFrom(this.http.get<{ languages: Record<string, Record<string, string>> }>('/spark/culture'));\n const lang = (globalThis as { __sparkCurrentLanguage?: () => string }).__sparkCurrentLanguage?.() ?? 'en';\n this.languages.set(Object.entries(culture.languages ?? {}).map(([value, label]) => ({\n value,\n label: label?.[lang] ?? label?.['en'] ?? Object.values(label ?? {})[0] ?? value,\n })));\n } catch {\n // No culture endpoint: only \"default\" and the stored value are offered.\n }\n }\n\n private apply(profile: SparkAccountProfile): void {\n this.storedCulture.set(profile.preferredCulture);\n this.form.controls.userName.setValue(profile.userName ?? '');\n this.form.controls.preferredCulture.setValue(profile.preferredCulture ?? '');\n for (const field of this.profileFields) {\n const key = Object.keys(profile.fields ?? {}).find(k => k.toLowerCase() === field.name.toLowerCase());\n const value = key ? profile.fields[key] : null;\n this.form.controls.fields.controls[field.name]?.setValue(field.type === 'checkbox' ? value === true : value ?? null);\n }\n }\n\n async save(): Promise<void> {\n this.saved.set(false);\n this.busy.set(true);\n this.messages.set([]);\n this.lastResult.set(null);\n try {\n const value = this.form.getRawValue();\n const result = await this.auth.updateProfile({\n userName: value.userName.trim() || null,\n preferredCulture: value.preferredCulture || null,\n ...(this.profileFields.length ? { fields: { ...value.fields } } : {}),\n });\n this.lastResult.set(result);\n if (result.success && result.value) {\n this.apply(result.value);\n this.saved.set(true);\n } else {\n // Field errors render next to their control; only the rest goes to the banner.\n this.messages.set(result.errors\n ? Object.entries(result.errors)\n .filter(([key]) => !this.isInlineError(key))\n .flatMap(([, messages]) => messages)\n : accountMessages(result));\n }\n } finally {\n this.busy.set(false);\n }\n }\n\n async changeEmail(): Promise<void> {\n const newEmail = this.emailForm.controls.newEmail.value.trim();\n this.emailSent.set(false);\n this.emailMessages.set([]);\n if (!newEmail) return;\n\n this.busy.set(true);\n try {\n const result = await this.auth.changeEmail(newEmail);\n if (result.success) {\n this.emailSent.set(true);\n this.emailForm.reset();\n } else {\n this.emailMessages.set(accountMessages(result));\n }\n } finally {\n this.busy.set(false);\n }\n }\n}\n","import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';\nimport { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';\nimport { Color } from '@mintplayer/ng-bootstrap';\nimport { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';\nimport { BsCardComponent, BsCardHeaderComponent } from '@mintplayer/ng-bootstrap/card';\nimport { BsFormComponent, BsFormControlDirective } from '@mintplayer/ng-bootstrap/form';\nimport { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';\nimport { SparkAuthService } from '@mintplayer/ng-spark/auth/core';\nimport { TranslateKeyPipe } from '@mintplayer/ng-spark/auth/pipes';\nimport { accountMessages } from './account-messages';\n\n/**\n * Change the password — or set a first one on an account created through an external provider\n * (`POST /spark/auth/manage/password`, #460 D16). The current password is required only when the\n * account has one; the server says so (`CurrentPasswordRequired`) rather than the page guessing.\n */\n@Component({\n selector: 'spark-change-password',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [ReactiveFormsModule, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsFormComponent, BsFormControlDirective, BsSpinnerComponent, TranslateKeyPipe],\n template: `\n <div class=\"d-flex justify-content-center\">\n <bs-card style=\"width: 100%; max-width: 640px;\">\n <bs-card-header><h3 class=\"mb-0\">{{ 'auth.changePasswordTitle' | t }}</h3></bs-card-header>\n <div class=\"p-4\">\n @for (message of messages(); track message) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3 d-block spark-account-error\">{{ message | t }}</bs-alert>\n }\n @if (saved()) {\n <bs-alert [type]=\"colors.success\" class=\"mb-3 d-block spark-account-saved\">{{ 'auth.passwordChanged' | t }}</bs-alert>\n }\n <bs-form>\n <form [formGroup]=\"form\" (ngSubmit)=\"submit()\">\n <div class=\"mb-3\">\n <label for=\"currentPassword\" class=\"form-label\">{{ 'auth.currentPassword' | t }}</label>\n <input type=\"password\" id=\"currentPassword\" formControlName=\"currentPassword\" autocomplete=\"current-password\" />\n <small class=\"text-muted\">{{ 'auth.currentPasswordHint' | t }}</small>\n </div>\n <div class=\"mb-3\">\n <label for=\"newPassword\" class=\"form-label\">{{ 'auth.newPassword' | t }}</label>\n <input type=\"password\" id=\"newPassword\" formControlName=\"newPassword\" autocomplete=\"new-password\" />\n </div>\n <div class=\"mb-3\">\n <label for=\"confirmPassword\" class=\"form-label\">{{ 'auth.confirmPassword' | t }}</label>\n <input type=\"password\" id=\"confirmPassword\" formControlName=\"confirmPassword\" autocomplete=\"new-password\" />\n </div>\n <button type=\"submit\" class=\"btn btn-primary\" [disabled]=\"busy()\">\n @if (busy()) { <bs-spinner class=\"me-1\" /> }\n {{ 'auth.savePassword' | t }}\n </button>\n </form>\n </bs-form>\n </div>\n </bs-card>\n </div>\n `,\n})\nexport class SparkChangePasswordComponent {\n private readonly auth = inject(SparkAuthService);\n protected readonly colors = Color;\n\n readonly form = new FormGroup({\n currentPassword: new FormControl('', { nonNullable: true }),\n newPassword: new FormControl('', { nonNullable: true, validators: [Validators.required] }),\n confirmPassword: new FormControl('', { nonNullable: true, validators: [Validators.required] }),\n });\n\n readonly busy = signal(false);\n readonly saved = signal(false);\n readonly messages = signal<string[]>([]);\n\n async submit(): Promise<void> {\n this.saved.set(false);\n const { currentPassword, newPassword, confirmPassword } = this.form.getRawValue();\n if (!newPassword) {\n this.messages.set(['auth.newPasswordRequired']);\n return;\n }\n if (newPassword !== confirmPassword) {\n this.messages.set(['auth.passwordMismatch']);\n return;\n }\n\n this.busy.set(true);\n this.messages.set([]);\n try {\n const result = await this.auth.setPassword(newPassword, currentPassword || null);\n if (result.success) {\n this.saved.set(true);\n this.form.reset();\n } else {\n this.messages.set(accountMessages(result));\n }\n } finally {\n this.busy.set(false);\n }\n }\n}\n","import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core';\nimport { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';\nimport { Color } from '@mintplayer/ng-bootstrap';\nimport { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';\nimport { BsCardComponent, BsCardHeaderComponent } from '@mintplayer/ng-bootstrap/card';\nimport { BsCheckboxComponent } from '@mintplayer/ng-bootstrap/checkbox';\nimport { BsFormComponent, BsFormControlDirective } from '@mintplayer/ng-bootstrap/form';\nimport { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';\nimport { SparkAuthService, SparkAuthTranslationService } from '@mintplayer/ng-spark/auth/core';\nimport { SparkAuthenticatorUri, SparkAccountResult, SparkTwoFactorRequest, SparkTwoFactorState } from '@mintplayer/ng-spark/auth/models';\nimport { TranslateKeyPipe } from '@mintplayer/ng-spark/auth/pipes';\nimport { accountMessages } from './account-messages';\n\n/**\n * Two-factor authentication (#460, D16): enroll an authenticator app (server-rendered QR SVG, SP-D),\n * see and regenerate recovery codes, forget this browser, reset the key, disable 2FA; and, when the\n * server offers it and 2FA is on, let an external sign-in skip the code (switching that on needs a code).\n *\n * Built on Identity's `POST /manage/2fa` (which creates the key on the first read) and Spark's read-only\n * `GET /manage/2fa/authenticator-uri`. The QR SVG is shown through an `<img>` data URL, never inserted\n * as markup: an image cannot run script, whatever the SVG contains.\n */\n@Component({\n selector: 'spark-two-factor-setup',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [ReactiveFormsModule, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsCheckboxComponent, BsFormComponent, BsFormControlDirective, BsSpinnerComponent, TranslateKeyPipe],\n template: `\n <div class=\"d-flex justify-content-center\">\n <bs-card style=\"width: 100%; max-width: 640px;\">\n <bs-card-header><h3 class=\"mb-0\">{{ 'auth.twoFactorSetupTitle' | t }}</h3></bs-card-header>\n <div class=\"p-4\">\n @for (message of messages(); track message) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3 d-block spark-account-error\">{{ message | t }}</bs-alert>\n }\n @if (loading()) {\n <div class=\"text-center\"><bs-spinner /></div>\n } @else if (state(); as s) {\n @if (recoveryCodes().length) {\n <bs-alert [type]=\"colors.warning\" class=\"mb-3 d-block spark-recovery-codes\">\n <p class=\"mb-2\">{{ 'auth.recoveryCodesShownOnce' | t }}</p>\n <div class=\"d-flex flex-wrap gap-3 font-monospace\">\n @for (code of recoveryCodes(); track code) { <span>{{ code }}</span> }\n </div>\n </bs-alert>\n }\n\n @if (s.isTwoFactorEnabled) {\n <p class=\"spark-2fa-enabled\">{{ 'auth.twoFactorEnabled' | t }}</p>\n <p class=\"text-muted\">{{ 'auth.recoveryCodesLeft' | t }}: {{ s.recoveryCodesLeft }}</p>\n <div class=\"d-flex flex-wrap gap-2\">\n <button type=\"button\" class=\"btn btn-outline-primary spark-2fa-new-codes\" [disabled]=\"busy()\" (click)=\"regenerateRecoveryCodes()\">\n {{ 'auth.newRecoveryCodes' | t }}\n </button>\n @if (s.isMachineRemembered) {\n <button type=\"button\" class=\"btn btn-outline-secondary\" [disabled]=\"busy()\" (click)=\"forgetMachine()\">\n {{ 'auth.forgetBrowser' | t }}\n </button>\n }\n <button type=\"button\" class=\"btn btn-outline-warning\" [disabled]=\"busy()\" (click)=\"resetKey()\">\n {{ 'auth.resetAuthenticator' | t }}\n </button>\n <button type=\"button\" class=\"btn btn-outline-danger spark-2fa-disable\" [disabled]=\"busy()\" (click)=\"disable()\">\n {{ 'auth.disableTwoFactor' | t }}\n </button>\n </div>\n\n @if (bypassOffered()) {\n <hr class=\"my-4\" />\n <div class=\"spark-2fa-external-bypass\">\n <bs-form>\n <bs-checkbox [type]=\"'checkbox'\" [name]=\"'externalTwoFactorBypass'\" [isToggled]=\"bypassChecked()\"\n (isToggledChange)=\"toggleBypass($event === true)\">\n {{ 'auth.externalTwoFactorBypassLabel' | t }}\n </bs-checkbox>\n </bs-form>\n <small class=\"text-muted d-block\">{{ 'auth.externalTwoFactorBypassHint' | t }}</small>\n @if (bypassPending()) {\n <bs-form>\n <form [formGroup]=\"bypassForm\" (ngSubmit)=\"confirmBypass()\" class=\"d-flex gap-2 align-items-end mt-2\">\n <div class=\"flex-grow-1\">\n <label for=\"bypassCode\" class=\"form-label\">{{ 'auth.code' | t }}</label>\n <input type=\"text\" id=\"bypassCode\" formControlName=\"code\" inputmode=\"numeric\" autocomplete=\"one-time-code\" maxlength=\"8\" />\n </div>\n <button type=\"submit\" class=\"btn btn-primary spark-2fa-bypass-confirm\" [disabled]=\"busy()\">{{ 'auth.verify' | t }}</button>\n <button type=\"button\" class=\"btn btn-outline-secondary spark-2fa-bypass-cancel\" [disabled]=\"busy()\" (click)=\"toggleBypass(false)\">{{ 'common.cancel' | t }}</button>\n </form>\n </bs-form>\n }\n </div>\n }\n } @else {\n <p>{{ 'auth.twoFactorSetupIntro' | t }}</p>\n @if (qrDataUrl(); as qr) {\n <div class=\"text-center my-3\">\n <img class=\"spark-2fa-qr\" [src]=\"qr\" width=\"200\" height=\"200\" [attr.alt]=\"'auth.qrCodeAlt' | t\" />\n </div>\n }\n @if (authenticator(); as a) {\n <p class=\"text-muted small mb-1\">{{ 'auth.manualKey' | t }}</p>\n <p class=\"font-monospace spark-2fa-key\">{{ formatKey(a.sharedKey) }}</p>\n }\n <bs-form>\n <form [formGroup]=\"form\" (ngSubmit)=\"enable()\" class=\"d-flex gap-2 align-items-end\">\n <div class=\"flex-grow-1\">\n <label for=\"twoFactorCode\" class=\"form-label\">{{ 'auth.code' | t }}</label>\n <input type=\"text\" id=\"twoFactorCode\" formControlName=\"code\" inputmode=\"numeric\" autocomplete=\"one-time-code\" maxlength=\"8\" />\n </div>\n <button type=\"submit\" class=\"btn btn-primary spark-2fa-enable\" [disabled]=\"busy()\">{{ 'auth.enableTwoFactor' | t }}</button>\n </form>\n </bs-form>\n }\n }\n </div>\n </bs-card>\n </div>\n `,\n})\nexport class SparkTwoFactorSetupComponent {\n private readonly auth = inject(SparkAuthService);\n private readonly translation = inject(SparkAuthTranslationService);\n protected readonly colors = Color;\n\n readonly form = new FormGroup({ code: new FormControl('', { nonNullable: true }) });\n\n readonly loading = signal(true);\n readonly busy = signal(false);\n readonly messages = signal<string[]>([]);\n readonly state = signal<SparkTwoFactorState | null>(null);\n readonly authenticator = signal<SparkAuthenticatorUri | null>(null);\n readonly recoveryCodes = signal<string[]>([]);\n\n /** The server's SVG as an image URL — displayed, never parsed into the DOM. */\n readonly qrDataUrl = computed(() => {\n const svg = this.authenticator()?.qrCodeSvg;\n return svg ? 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg) : null;\n });\n\n readonly bypassForm = new FormGroup({ code: new FormControl('', { nonNullable: true }) });\n /** The server offers the external-login bypass (`capabilities.externalLoginTwoFactorBypass`). */\n private readonly bypassCapability = signal(false);\n /** The account's bypass setting, or null until read (or when the read failed). */\n readonly bypass = signal<boolean | null>(null);\n /** Switched on but not yet confirmed with an authenticator code. */\n readonly bypassPending = signal(false);\n\n /** Shown only when the server offers it AND the account has two-factor on: there is nothing to skip otherwise. */\n readonly bypassOffered = computed(() =>\n this.bypassCapability() && this.bypass() !== null && this.state()?.isTwoFactorEnabled === true);\n readonly bypassChecked = computed(() => this.bypass() === true || this.bypassPending());\n\n constructor() {\n void this.refresh();\n void Promise.resolve()\n .then(() => this.auth.capabilities())\n .then(capabilities => {\n this.bypassCapability.set(capabilities?.externalLoginTwoFactorBypass === true);\n if (this.bypassCapability()) return this.loadBypass();\n return undefined;\n })\n .catch(() => { /* capability unknown: the toggle stays hidden */ });\n }\n\n private async loadBypass(): Promise<void> {\n const result = await this.auth.externalLoginTwoFactor();\n this.bypass.set(result.success && result.value ? result.value.bypass : null);\n }\n\n /** Off: saved at once. On: asks for an authenticator code first ({@link confirmBypass}). */\n async toggleBypass(on: boolean): Promise<void> {\n if (on) {\n if (this.bypass() !== true) this.bypassPending.set(true);\n return;\n }\n if (this.bypassPending()) {\n this.bypassPending.set(false);\n this.bypassForm.reset();\n return;\n }\n if (this.bypass() !== true) return;\n await this.saveBypass(false);\n }\n\n async confirmBypass(): Promise<void> {\n const code = this.bypassForm.controls.code.value.replace(/\\s|-/g, '');\n if (!code) {\n this.messages.set(['auth.enterCodeError']);\n return;\n }\n await this.saveBypass(true, code);\n }\n\n private async saveBypass(bypass: boolean, code?: string): Promise<void> {\n this.busy.set(true);\n this.messages.set([]);\n try {\n const result = await this.auth.setExternalLoginTwoFactor(bypass, code);\n if (result.success && result.value) {\n this.bypass.set(result.value.bypass);\n this.bypassPending.set(false);\n this.bypassForm.reset();\n } else {\n this.messages.set(result.status === 400 && !result.errors ? ['auth.invalidCode'] : accountMessages(result));\n }\n } finally {\n this.busy.set(false);\n }\n }\n\n /** Groups the base32 key in fours, the way authenticator apps show it. */\n protected formatKey(key: string): string {\n return key.replace(/(.{4})/g, '$1 ').trim();\n }\n\n async refresh(): Promise<void> {\n this.loading.set(true);\n const state = await this.auth.twoFactor();\n await this.applyState(state);\n this.loading.set(false);\n }\n\n async enable(): Promise<void> {\n const code = this.form.controls.code.value.replace(/\\s|-/g, '');\n if (!code) {\n this.messages.set(['auth.enterCodeError']);\n return;\n }\n await this.run({ enable: true, twoFactorCode: code });\n if (this.state()?.isTwoFactorEnabled) this.form.reset();\n }\n\n async disable(): Promise<void> {\n if (!confirm(this.translation.t('auth.confirmDisableTwoFactor'))) return;\n await this.run({ enable: false });\n }\n\n async regenerateRecoveryCodes(): Promise<void> {\n await this.run({ resetRecoveryCodes: true });\n }\n\n async forgetMachine(): Promise<void> {\n await this.run({ forgetMachine: true });\n // MapIdentityApi computes isMachineRemembered from the request's own remember-me cookie, which this\n // same response deletes, so it still answers true. The browser is forgotten once the call succeeded.\n const state = this.state();\n if (state?.isMachineRemembered && this.messages().length === 0)\n this.state.set({ ...state, isMachineRemembered: false });\n }\n\n async resetKey(): Promise<void> {\n if (!confirm(this.translation.t('auth.confirmResetAuthenticator'))) return;\n await this.run({ resetSharedKey: true });\n }\n\n private async run(request: SparkTwoFactorRequest): Promise<void> {\n this.busy.set(true);\n this.messages.set([]);\n try {\n await this.applyState(await this.auth.twoFactor(request));\n } finally {\n this.busy.set(false);\n }\n }\n\n private async applyState(result: SparkAccountResult<SparkTwoFactorState>): Promise<void> {\n if (!result.success || !result.value) {\n this.messages.set(result.status === 400 && !result.errors ? ['auth.invalidCode'] : accountMessages(result));\n return;\n }\n const wasEnabled = this.state()?.isTwoFactorEnabled;\n this.state.set(result.value);\n this.recoveryCodes.set(result.value.recoveryCodes ?? []);\n // Disabling two-factor may clear the bypass server-side; read it again when the state flips.\n if (this.bypassCapability() && wasEnabled !== undefined && wasEnabled !== result.value.isTwoFactorEnabled) {\n this.bypassPending.set(false);\n void this.loadBypass();\n }\n if (result.value.isTwoFactorEnabled) {\n this.authenticator.set(null);\n return;\n }\n const uri = await this.auth.authenticatorUri();\n this.authenticator.set(uri.success && uri.value ? uri.value : null);\n if (!uri.success) this.messages.set(accountMessages(uri));\n }\n}\n","import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { Color } from '@mintplayer/ng-bootstrap';\nimport { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';\nimport { BsCardComponent, BsCardHeaderComponent } from '@mintplayer/ng-bootstrap/card';\nimport { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';\nimport { SparkAuthService } from '@mintplayer/ng-spark/auth/core';\nimport {\n SPARK_EXTERNAL_PROVIDERS,\n SparkExternalLoginError,\n SparkExternalLogins,\n SparkExternalProviderPresentation,\n} from '@mintplayer/ng-spark/auth/models';\nimport { TranslateKeyPipe } from '@mintplayer/ng-spark/auth/pipes';\n\n/**\n * Connected logins (#460, D16): the providers attached to the account (remove, unless it is the last\n * way in — the server's `canUnlink`, and its refusal), and the configured providers that could be\n * attached (a popup handshake keyed on the signed-in user).\n *\n * Presentation (icon, display name) comes from the same `externalProvider()` declarations as the\n * sign-in page when they are in scope; the server stays authoritative over which providers exist.\n */\n@Component({\n selector: 'spark-external-logins',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsSpinnerComponent, TranslateKeyPipe],\n template: `\n <div class=\"d-flex justify-content-center\">\n <bs-card style=\"width: 100%; max-width: 640px;\">\n <bs-card-header><h3 class=\"mb-0\">{{ 'auth.externalLoginsTitle' | t }}</h3></bs-card-header>\n <div class=\"p-4\">\n @if (errorKey()) {\n <bs-alert [type]=\"errorIsNotice() ? colors.info : colors.danger\" class=\"mb-3 d-block spark-account-error\">{{ errorKey() | t }}</bs-alert>\n }\n @if (loading()) {\n <div class=\"text-center\"><bs-spinner /></div>\n } @else if (logins(); as l) {\n @for (login of l.linked; track login.provider + login.providerKey) {\n <div class=\"d-flex align-items-center justify-content-between border-bottom py-2 spark-linked-login\">\n <span>\n @if (iconFor(login.provider); as icon) { <i [class]=\"icon\" class=\"me-2\"></i> }\n {{ nameFor(login.provider, login.displayName) }}\n </span>\n <button type=\"button\" class=\"btn btn-outline-danger btn-sm\" [disabled]=\"busy() || !login.canUnlink\"\n [attr.title]=\"login.canUnlink ? null : ('auth.lastCredentialHint' | t)\"\n (click)=\"unlink(login.provider, login.providerKey)\">\n {{ 'auth.removeLogin' | t }}\n </button>\n </div>\n } @empty {\n <p class=\"text-muted\">{{ 'auth.noLinkedLogins' | t }}</p>\n }\n\n @if (l.available.length) {\n <h5 class=\"mt-4\">{{ 'auth.addLogin' | t }}</h5>\n <div class=\"d-flex flex-wrap gap-2\">\n @for (provider of l.available; track provider.provider) {\n <button type=\"button\" class=\"btn btn-outline-primary spark-available-login\" [disabled]=\"busy() || linkPending()\" (click)=\"link(provider.provider)\">\n @if (iconFor(provider.provider); as icon) { <i [class]=\"icon\" class=\"me-2\"></i> }\n {{ nameFor(provider.provider, provider.displayName) }}\n </button>\n }\n </div>\n @if (linkPending()) {\n <div class=\"text-center mt-3 spark-link-pending\"><bs-spinner /></div>\n }\n }\n }\n </div>\n </bs-card>\n </div>\n `,\n})\nexport class SparkExternalLoginsComponent {\n private readonly auth = inject(SparkAuthService);\n private readonly router = inject(Router);\n private readonly presentations: SparkExternalProviderPresentation[] = inject(SPARK_EXTERNAL_PROVIDERS, { optional: true }) ?? [];\n protected readonly colors = Color;\n\n readonly loading = signal(true);\n readonly busy = signal(false);\n readonly errorKey = signal('');\n readonly logins = signal<SparkExternalLogins | null>(null);\n\n /**\n * While a link popup is open. Taken from the service, not from the link() promise: under COOP the\n * popup reads closed long before the result arrives, and the attempt keeps listening (D2).\n */\n readonly linkPending = computed(() => this.auth.externalLoginPending());\n\n /** `link_confirmation_sent` travels as a failure but is news, not an error. */\n readonly errorIsNotice = computed(() => this.errorKey() === 'auth.externalLoginError.link_confirmation_sent');\n\n constructor() {\n // A redirect-mode link (inside an installed web app) comes back here with the outcome in the URL.\n const returned = this.auth.takeExternalLoginResult();\n if (returned) this.showLinkError(returned);\n void this.refresh();\n }\n\n private showLinkError(error: SparkExternalLoginError | undefined): void {\n if (error === 'popup_closed') return;\n this.errorKey.set(`auth.externalLoginError.${error ?? 'link_failed'}`);\n }\n\n private presentation(scheme: string): SparkExternalProviderPresentation | undefined {\n return this.presentations.find(p => p.scheme.toLowerCase() === scheme.toLowerCase());\n }\n\n protected iconFor(scheme: string): string | undefined {\n return this.presentation(scheme)?.iconClass;\n }\n\n protected nameFor(scheme: string, serverName: string): string {\n return this.presentation(scheme)?.displayName ?? serverName ?? scheme;\n }\n\n async refresh(): Promise<void> {\n this.loading.set(true);\n try {\n this.logins.set(await this.auth.externalLogins());\n } catch {\n // 404 = linking is not enabled on this deployment (SparkExternalLinking disabled).\n this.errorKey.set('auth.externalLoginsUnavailable');\n } finally {\n this.loading.set(false);\n }\n }\n\n /**\n * Links another provider. The buttons follow {@link linkPending} rather than this promise, which\n * settles only on the result, a newer attempt or the 10-minute timeout.\n */\n async link(provider: string): Promise<void> {\n this.errorKey.set('');\n const result = await this.auth.linkProvider(provider, { returnUrl: this.router.url });\n if (!result.success) this.showLinkError(result.error);\n await this.refresh();\n }\n\n async unlink(provider: string, providerKey: string): Promise<void> {\n this.errorKey.set('');\n this.busy.set(true);\n try {\n const result = await this.auth.unlinkProvider(provider, providerKey);\n if (!result.success) this.errorKey.set(`auth.unlinkError.${result.error ?? 'unlink_failed'}`);\n await this.refresh();\n } finally {\n this.busy.set(false);\n }\n }\n}\n","import { ChangeDetectionStrategy, Component, inject, OnInit, signal } from '@angular/core';\nimport { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';\nimport { Router } from '@angular/router';\nimport { Color } from '@mintplayer/ng-bootstrap';\nimport { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';\nimport { BsCardComponent, BsCardHeaderComponent } from '@mintplayer/ng-bootstrap/card';\nimport { BsCheckboxComponent } from '@mintplayer/ng-bootstrap/checkbox';\nimport { BsFormComponent, BsFormControlDirective } from '@mintplayer/ng-bootstrap/form';\nimport { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';\nimport { SparkAuthService } from '@mintplayer/ng-spark/auth/core';\nimport { SPARK_AUTH_CONFIG } from '@mintplayer/ng-spark/auth/models';\nimport { TranslateKeyPipe } from '@mintplayer/ng-spark/auth/pipes';\nimport { accountMessages } from './account-messages';\n\n/**\n * Personal data (#460, D8/D16): download everything the account and the application's\n * `ISparkPersonalDataContributor`s hold as JSON, and delete the account.\n *\n * Deletion needs re-authentication: the current password (the field shows only when the account has\n * one, from `account.hasPassword`), or — for an account without one — a sign-in\n * younger than the server's `ReauthenticationMaxAge` (5 minutes). A 403 `reauthentication_required`\n * says which is missing; the page then asks to sign in again. Handlers run first and the account is\n * removed last, so a failure (500 `deletion_failed`) leaves the account intact and retryable.\n */\n@Component({\n selector: 'spark-personal-data',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [ReactiveFormsModule, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsCheckboxComponent, BsFormComponent, BsFormControlDirective, BsSpinnerComponent, TranslateKeyPipe],\n template: `\n <div class=\"d-flex justify-content-center\">\n <bs-card style=\"width: 100%; max-width: 640px;\">\n <bs-card-header><h3 class=\"mb-0\">{{ 'auth.personalDataTitle' | t }}</h3></bs-card-header>\n <div class=\"p-4\">\n <p>{{ 'auth.personalDataDescription' | t }}</p>\n @for (message of downloadMessages(); track message) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3 d-block\">{{ message | t }}</bs-alert>\n }\n <button type=\"button\" class=\"btn btn-outline-primary spark-download-data\" [disabled]=\"busy()\" (click)=\"download()\">\n {{ 'auth.downloadPersonalData' | t }}\n </button>\n\n <hr class=\"my-4\" />\n\n <h5 class=\"text-danger\">{{ 'auth.deleteAccountTitle' | t }}</h5>\n <p>{{ 'auth.deleteAccountDescription' | t }}</p>\n @for (message of deleteMessages(); track message) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3 d-block spark-delete-error\">{{ message | t }}</bs-alert>\n }\n <bs-form>\n <form [formGroup]=\"form\" (ngSubmit)=\"deleteAccount()\">\n @if (hasPassword() !== false) {\n <div class=\"mb-3\">\n <label for=\"deletePassword\" class=\"form-label\">{{ 'auth.password' | t }}</label>\n <input type=\"password\" id=\"deletePassword\" formControlName=\"password\" autocomplete=\"current-password\" />\n <small class=\"text-muted\">{{ 'auth.deletePasswordHint' | t }}</small>\n </div>\n } @else {\n <p class=\"text-muted spark-delete-recent-sign-in\">{{ 'auth.deleteRecentSignIn' | t }}</p>\n }\n <div class=\"mb-3\">\n <bs-checkbox [type]=\"'checkbox'\" formControlName=\"confirm\" [name]=\"'confirmDelete'\">{{ 'auth.deleteAccountConfirm' | t }}</bs-checkbox>\n </div>\n <button type=\"submit\" class=\"btn btn-danger spark-delete-account\" [disabled]=\"busy() || !form.controls.confirm.value\">\n @if (busy()) { <bs-spinner class=\"me-1\" /> }\n {{ 'auth.deleteAccount' | t }}\n </button>\n </form>\n </bs-form>\n </div>\n </bs-card>\n </div>\n `,\n})\nexport class SparkPersonalDataComponent implements OnInit {\n private readonly auth = inject(SparkAuthService);\n private readonly router = inject(Router);\n private readonly config = inject(SPARK_AUTH_CONFIG);\n protected readonly colors = Color;\n\n readonly form = new FormGroup({\n password: new FormControl('', { nonNullable: true }),\n confirm: new FormControl(false, { nonNullable: true }),\n });\n\n readonly busy = signal(false);\n readonly downloadMessages = signal<string[]>([]);\n readonly deleteMessages = signal<string[]>([]);\n /**\n * Whether the account has a password (`account.hasPassword` of the personal data). Null while\n * unknown — an older server, or the read failed — which keeps the password field, as before.\n */\n readonly hasPassword = signal<boolean | null>(null);\n\n async ngOnInit(): Promise<void> {\n try {\n const result = await this.auth.personalData();\n const flag = result.success ? (result.value as { account?: { hasPassword?: unknown } } | null)?.account?.hasPassword : undefined;\n this.hasPassword.set(typeof flag === 'boolean' ? flag : null);\n } catch {\n this.hasPassword.set(null);\n }\n }\n\n async download(): Promise<void> {\n this.busy.set(true);\n this.downloadMessages.set([]);\n try {\n const result = await this.auth.personalData();\n if (!result.success) {\n this.downloadMessages.set(accountMessages(result));\n return;\n }\n const blob = new Blob([JSON.stringify(result.value, null, 2)], { type: 'application/json' });\n const url = URL.createObjectURL(blob);\n const anchor = document.createElement('a');\n anchor.href = url;\n anchor.download = 'personal-data.json';\n anchor.click();\n URL.revokeObjectURL(url);\n } finally {\n this.busy.set(false);\n }\n }\n\n async deleteAccount(): Promise<void> {\n if (!this.form.controls.confirm.value) return;\n this.busy.set(true);\n this.deleteMessages.set([]);\n try {\n const result = await this.auth.deleteAccount(this.form.controls.password.value || null);\n if (result.success) {\n await this.router.navigateByUrl(this.config.defaultRedirectUrl);\n return;\n }\n this.deleteMessages.set(accountMessages(result));\n } finally {\n this.busy.set(false);\n }\n }\n}\n","import { NgTemplateOutlet } from '@angular/common';\nimport { ChangeDetectionStrategy, Component, computed, inject, OnInit, signal } from '@angular/core';\nimport { Color } from '@mintplayer/ng-bootstrap';\nimport { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';\nimport { BsBadgeComponent } from '@mintplayer/ng-bootstrap/badge';\nimport { BsCardComponent, BsCardHeaderComponent } from '@mintplayer/ng-bootstrap/card';\nimport { BsCheckboxComponent } from '@mintplayer/ng-bootstrap/checkbox';\nimport { BsFormComponent } from '@mintplayer/ng-bootstrap/form';\nimport { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';\nimport { SparkAuthService, SparkAuthTranslationService } from '@mintplayer/ng-spark/auth/core';\nimport {\n SparkAgentExposedType,\n SparkAgentGrant,\n SparkAgentGrantsOverview,\n SparkAgentGrantType,\n SparkAgentPermission,\n SparkContributedApplication,\n sparkAgentCanWrite,\n} from '@mintplayer/ng-spark/auth/models';\nimport { TranslateKeyPipe } from '@mintplayer/ng-spark/auth/pipes';\nimport { accountMessages } from './account-messages';\n\n/** A choice of what an agent may do: read or read + changes, and every exposed type or a chosen list. */\ninterface GrantChoice {\n changes: boolean;\n /** `null`: everything the application makes available to agents (including types exposed later). */\n types: string[] | null;\n}\n\n/**\n * Connected applications (MCP PRD D14, mounted by `withConnectedAgents()`): the AI agents the user connected\n * and their personal access tokens, with what each may see and whether it may make changes; edit, disconnect,\n * create a token; then what other packages contribute (sign-ins at other websites).\n *\n * The page offers only what an agent could use: the application's limit for that agent and the types the user\n * may see. A change applies to the agent's next call, without signing in again. A new token is shown once.\n */\n@Component({\n selector: 'spark-connected-agents',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [\n BsAlertComponent, BsBadgeComponent, BsCardComponent, BsCardHeaderComponent, BsCheckboxComponent, BsFormComponent,\n BsSpinnerComponent, NgTemplateOutlet, TranslateKeyPipe,\n ],\n template: `\n <div class=\"d-flex justify-content-center\">\n <bs-card style=\"width: 100%; max-width: 760px;\">\n <bs-card-header><h3 class=\"mb-0\">{{ 'auth.agentsTitle' | t }}</h3></bs-card-header>\n <div class=\"p-4\">\n <p class=\"text-muted\">{{ 'auth.agentsDescription' | t }}</p>\n @for (message of messages(); track message) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3 d-block spark-agents-error\">{{ message | t }}</bs-alert>\n }\n @if (loading()) {\n <div class=\"text-center\"><bs-spinner /></div>\n } @else if (overview(); as data) {\n @for (grant of data.grants; track grant.key) {\n <section class=\"border rounded p-3 mb-3 spark-agent-grant\" [attr.data-key]=\"grant.key\">\n <div class=\"d-flex flex-wrap align-items-center gap-2\">\n <h5 class=\"mb-0 text-break spark-agent-name\">{{ grant.name || grant.clientId || grant.key }}</h5>\n <bs-badge [type]=\"colors.secondary\">{{ 'auth.agentKind.' + grant.kind | t }}</bs-badge>\n @if (grant.kind === 'ExternalAuthorizationServer') {\n <bs-badge [type]=\"colors.info\" class=\"spark-agent-issuer\" [title]=\"'auth.agentIssuer' | t\">{{ grant.issuer }}</bs-badge>\n } @else if (grant.kind !== 'PersonalAccessToken') {\n @if (grant.verifiedHost) {\n <bs-badge [type]=\"colors.success\" class=\"spark-agent-verified\">{{ grant.verifiedHost }}</bs-badge>\n } @else {\n <bs-badge [type]=\"colors.warning\" class=\"spark-agent-unverified\">{{ 'auth.agentUnverified' | t }}</bs-badge>\n }\n }\n @if (grant.status !== 'active') {\n <bs-badge [type]=\"colors.danger\" class=\"spark-agent-status\">{{ 'auth.agentStatus.' + grant.status | t }}</bs-badge>\n }\n </div>\n <small class=\"text-muted d-block mt-1 spark-agent-dates\">\n {{ 'auth.agentCreated' | t }} {{ date(grant.createdAt) }} ·\n @if (grant.lastUsedAt) { {{ 'auth.agentLastUsed' | t }} {{ date(grant.lastUsedAt) }} } @else { {{ 'auth.agentNeverUsed' | t }} }\n @if (grant.expiresAt) { · {{ 'auth.agentExpires' | t }} {{ date(grant.expiresAt) }} }\n </small>\n\n @if (editing() === grant.key) {\n <bs-form class=\"d-block mt-3\">\n @if (canWrite(grant.maxPermissions)) {\n <bs-checkbox [type]=\"'checkbox'\" [name]=\"'changes-' + grant.key\" [isToggled]=\"draft().changes\"\n (isToggledChange)=\"setChanges($event === true)\">{{ 'auth.agentAllowChanges' | t }}</bs-checkbox>\n }\n <ng-container *ngTemplateOutlet=\"typePicker\" />\n </bs-form>\n <div class=\"d-flex gap-2 mt-3\">\n <button type=\"button\" class=\"btn btn-primary btn-sm spark-agent-save\" [disabled]=\"busy() || !draftValid()\" (click)=\"save(grant)\">{{ 'auth.agentSave' | t }}</button>\n <button type=\"button\" class=\"btn btn-outline-secondary btn-sm\" [disabled]=\"busy()\" (click)=\"editing.set(null)\">{{ 'auth.agentCancel' | t }}</button>\n </div>\n } @else {\n <div class=\"mt-2 spark-agent-scope\">\n <span>{{ (canWrite(grant.permissions) ? 'auth.agentReadAndChanges' : 'auth.agentReadOnly') | t }}</span> ·\n @if (grant.types === null) {\n <span>{{ 'auth.agentEverything' | t }}</span>\n } @else {\n <span>{{ typeLabels(grant.types) }}</span>\n }\n </div>\n <div class=\"d-flex flex-wrap gap-2 mt-3\">\n <!-- A disconnected external agent (by the user or a security-stamp change) is allowed again by an edit. -->\n @if (grant.status === 'active' || grant.status === 'disconnected') {\n <button type=\"button\" class=\"btn btn-outline-primary btn-sm spark-agent-edit\" [disabled]=\"busy()\" (click)=\"edit(grant)\">{{ 'auth.agentEdit' | t }}</button>\n }\n @if (grant.status !== 'disconnected') {\n <button type=\"button\" class=\"btn btn-danger btn-sm spark-agent-revoke\" [disabled]=\"busy()\" (click)=\"revoke(grant)\">\n {{ (grant.kind === 'PersonalAccessToken' ? 'auth.agentRevokeToken' : 'auth.agentDisconnect') | t }}\n </button>\n }\n </div>\n }\n </section>\n } @empty {\n <p class=\"text-muted spark-agents-empty\">{{ 'auth.agentsNone' | t }}</p>\n }\n\n @if (data.personalAccessTokens) {\n <hr class=\"my-4\" />\n <h5>{{ 'auth.agentTokenTitle' | t }}</h5>\n <p class=\"text-muted\">{{ 'auth.agentTokenDescription' | t }}</p>\n @if (createdToken(); as token) {\n <bs-alert [type]=\"colors.warning\" class=\"mb-3 d-block spark-agent-token-created\">\n <strong>{{ 'auth.agentTokenShownOnce' | t }}</strong>\n <code class=\"d-block text-break my-2 spark-agent-token\">{{ token }}</code>\n <button type=\"button\" class=\"btn btn-sm btn-outline-dark\" (click)=\"copy(token)\">{{ 'auth.agentTokenCopy' | t }}</button>\n </bs-alert>\n }\n @if (creating()) {\n <bs-form class=\"d-block\">\n <div class=\"mb-3\">\n <label for=\"sparkAgentTokenName\" class=\"form-label\">{{ 'auth.agentTokenName' | t }}</label>\n <input id=\"sparkAgentTokenName\" type=\"text\" maxlength=\"100\" [value]=\"tokenName()\" (input)=\"tokenName.set(value($event))\" />\n </div>\n <div class=\"mb-3\">\n <label for=\"sparkAgentTokenDays\" class=\"form-label\">{{ 'auth.agentTokenDays' | t }}</label>\n <input id=\"sparkAgentTokenDays\" type=\"number\" min=\"1\" [max]=\"data.maxTokenLifetimeDays\" [value]=\"tokenDays()\" (input)=\"tokenDays.set(+value($event))\" />\n </div>\n @if (canWrite(data.tokenPermissions)) {\n <bs-checkbox [type]=\"'checkbox'\" [name]=\"'changes-new'\" [isToggled]=\"draft().changes\"\n (isToggledChange)=\"setChanges($event === true)\">{{ 'auth.agentAllowChanges' | t }}</bs-checkbox>\n }\n <ng-container *ngTemplateOutlet=\"typePicker\" />\n </bs-form>\n <div class=\"d-flex gap-2 mt-3\">\n <button type=\"button\" class=\"btn btn-primary spark-agent-token-create\" [disabled]=\"busy() || !tokenValid()\" (click)=\"createToken()\">{{ 'auth.agentTokenCreate' | t }}</button>\n <button type=\"button\" class=\"btn btn-outline-secondary\" [disabled]=\"busy()\" (click)=\"creating.set(false)\">{{ 'auth.agentCancel' | t }}</button>\n </div>\n } @else {\n <button type=\"button\" class=\"btn btn-outline-primary spark-agent-token-new\" [disabled]=\"busy()\" (click)=\"startToken()\">{{ 'auth.agentTokenNew' | t }}</button>\n }\n }\n\n @if (data.applications.length) {\n <hr class=\"my-4\" />\n <h5>{{ 'auth.agentApplicationsTitle' | t }}</h5>\n @for (app of data.applications; track app.source + '|' + app.id) {\n <section class=\"border rounded p-3 mb-2 d-flex justify-content-between align-items-start gap-2 spark-contributed-app\">\n <div class=\"text-break\">\n <strong>{{ app.name }}</strong>\n @if (app.description) { <small class=\"text-muted d-block\">{{ app.description }}</small> }\n @if (app.lastUsedAt) { <small class=\"text-muted d-block\">{{ 'auth.agentLastUsed' | t }} {{ date(app.lastUsedAt) }}</small> }\n </div>\n <button type=\"button\" class=\"btn btn-danger btn-sm\" [disabled]=\"busy()\" (click)=\"revokeApplication(app)\">{{ 'auth.agentDisconnect' | t }}</button>\n </section>\n }\n }\n }\n\n <ng-template #typePicker>\n <div class=\"mt-2\">\n <bs-checkbox [type]=\"'checkbox'\" [name]=\"'everything'\" [isToggled]=\"draft().types === null\"\n (isToggledChange)=\"setEverything($event === true)\">{{ 'auth.agentEverything' | t }}</bs-checkbox>\n @if (draft().types !== null) {\n @for (type of overview()?.types ?? []; track type.name) {\n <div class=\"ms-4 spark-agent-type\" [attr.data-type]=\"type.name\">\n <bs-checkbox [type]=\"'checkbox'\" [name]=\"'type-' + type.name\" [isToggled]=\"draft().types!.includes(type.name)\"\n (isToggledChange)=\"setType(type, $event === true)\">{{ type.label }}</bs-checkbox>\n @if (type.queries.length) { <small class=\"text-muted d-block ms-4\">{{ type.queries.join(', ') }}</small> }\n </div>\n } @empty {\n <small class=\"text-muted d-block ms-4\">{{ 'auth.agentNoTypes' | t }}</small>\n }\n }\n </div>\n </ng-template>\n </div>\n </bs-card>\n </div>\n `,\n})\nexport class SparkConnectedAgentsComponent implements OnInit {\n private readonly auth = inject(SparkAuthService);\n private readonly translation = inject(SparkAuthTranslationService);\n protected readonly colors = Color;\n\n readonly loading = signal(true);\n readonly busy = signal(false);\n readonly overview = signal<SparkAgentGrantsOverview | null>(null);\n readonly messages = signal<string[]>([]);\n /** The key of the grant being edited. */\n readonly editing = signal<string | null>(null);\n readonly creating = signal(false);\n readonly draft = signal<GrantChoice>({ changes: false, types: null });\n readonly tokenName = signal('');\n readonly tokenDays = signal(90);\n /** The token just created: shown until the next change on the page, and never again. */\n readonly createdToken = signal<string | null>(null);\n\n readonly draftValid = computed(() => this.draft().types === null || this.draft().types!.length > 0);\n readonly tokenValid = computed(() => {\n const max = this.overview()?.maxTokenLifetimeDays ?? 365;\n const days = this.tokenDays();\n return this.draftValid() && this.tokenName().trim().length > 0 && Number.isInteger(days) && days >= 1 && days <= max;\n });\n\n protected readonly canWrite = sparkAgentCanWrite;\n\n async ngOnInit(): Promise<void> {\n await this.load();\n }\n\n async load(): Promise<void> {\n this.loading.set(true);\n const result = await this.auth.agentGrants();\n if (result.success && result.value) {\n this.overview.set(result.value);\n } else {\n this.overview.set(null);\n this.messages.set(accountMessages(result));\n }\n this.loading.set(false);\n }\n\n edit(grant: SparkAgentGrant): void {\n this.creating.set(false);\n this.messages.set([]);\n this.draft.set({ changes: sparkAgentCanWrite(grant.permissions), types: grant.types?.map(t => t.name) ?? null });\n this.editing.set(grant.key);\n }\n\n startToken(): void {\n this.editing.set(null);\n this.messages.set([]);\n this.createdToken.set(null);\n this.tokenName.set('');\n this.tokenDays.set(this.overview()?.defaultTokenLifetimeDays ?? 90);\n this.draft.set({ changes: false, types: null });\n this.creating.set(true);\n }\n\n setChanges(changes: boolean): void {\n this.draft.update(d => ({ ...d, changes }));\n }\n\n setEverything(everything: boolean): void {\n this.draft.update(d => ({ ...d, types: everything ? null : [] }));\n }\n\n setType(type: SparkAgentExposedType, chosen: boolean): void {\n this.draft.update(d => {\n const types = (d.types ?? []).filter(name => name !== type.name);\n return { ...d, types: chosen ? [...types, type.name] : types };\n });\n }\n\n async save(grant: SparkAgentGrant): Promise<void> {\n const choice = this.draft();\n await this.run(() => this.auth.updateAgentGrant({ key: grant.key, permissions: this.permission(choice), types: this.types(choice) }),\n () => this.editing.set(null));\n }\n\n async createToken(): Promise<void> {\n const choice = this.draft();\n this.busy.set(true);\n this.messages.set([]);\n try {\n const result = await this.auth.createAgentToken({\n name: this.tokenName().trim(),\n expiresInDays: this.tokenDays(),\n permissions: this.permission(choice),\n types: this.types(choice),\n });\n if (!result.success || !result.value) {\n this.messages.set(accountMessages(result));\n return;\n }\n this.creating.set(false);\n await this.load();\n this.createdToken.set(result.value.token);\n } finally {\n this.busy.set(false);\n }\n }\n\n async revoke(grant: SparkAgentGrant): Promise<void> {\n if (!confirm(this.translation.t('auth.agentConfirmRevoke'))) return;\n await this.run(() => this.auth.revokeAgentGrant(grant.key));\n }\n\n async revokeApplication(app: SparkContributedApplication): Promise<void> {\n if (!confirm(this.translation.t('auth.agentConfirmRevoke'))) return;\n await this.run(() => this.auth.revokeConnectedApplication(app.source, app.id));\n }\n\n protected typeLabels(types: SparkAgentGrantType[]): string {\n const labels = new Map((this.overview()?.types ?? []).map(t => [t.name, t.label] as const));\n return types.map(t => labels.get(t.name) ?? t.name).join(', ');\n }\n\n protected date(iso: string | null): string {\n return iso ? new Date(iso).toLocaleDateString() : '';\n }\n\n protected value(event: Event): string {\n return (event.target as HTMLInputElement).value;\n }\n\n protected copy(token: string): void {\n void navigator.clipboard?.writeText(token);\n }\n\n private permission(choice: GrantChoice): SparkAgentPermission {\n return choice.changes ? 'Read, Write' : 'Read';\n }\n\n /** Each chosen type gets what the grant gets, within what the type itself allows. */\n private types(choice: GrantChoice): SparkAgentGrantType[] | null {\n if (choice.types === null) return null;\n const exposed = new Map((this.overview()?.types ?? []).map(t => [t.name, t] as const));\n return choice.types.map(name => ({\n name,\n permissions: choice.changes && sparkAgentCanWrite(exposed.get(name)?.permissions) ? 'Read, Write' : 'Read',\n }));\n }\n\n private async run(call: () => Promise<{ success: boolean }>, after?: () => void): Promise<void> {\n this.busy.set(true);\n this.messages.set([]);\n try {\n const result = await call();\n if (!result.success) {\n this.messages.set(accountMessages(result as never));\n return;\n }\n after?.();\n this.createdToken.set(null);\n await this.load();\n } finally {\n this.busy.set(false);\n }\n }\n}\n"],"mappings":"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"}
@@ -329,6 +329,30 @@ var SparkAuthService = class SparkAuthService {
329
329
  await this.csrfRefresh().catch(() => void 0);
330
330
  });
331
331
  }
332
+ agentGrants() {
333
+ return this.accountCall(() => firstValueFrom(this.http.get(`${this.config.apiBasePath}/manage/agents`)));
334
+ }
335
+ createAgentToken(request) {
336
+ return this.accountCall(() => firstValueFrom(this.http.post(`${this.config.apiBasePath}/manage/agents/tokens`, request)));
337
+ }
338
+ updateAgentGrant(update) {
339
+ return this.accountCall(async () => {
340
+ await firstValueFrom(this.http.post(`${this.config.apiBasePath}/manage/agents/update`, update));
341
+ });
342
+ }
343
+ revokeAgentGrant(key) {
344
+ return this.accountCall(async () => {
345
+ await firstValueFrom(this.http.post(`${this.config.apiBasePath}/manage/agents/revoke`, { key }));
346
+ });
347
+ }
348
+ revokeConnectedApplication(source, id) {
349
+ return this.accountCall(async () => {
350
+ await firstValueFrom(this.http.post(`${this.config.apiBasePath}/manage/agents/applications/revoke`, {
351
+ source,
352
+ id
353
+ }));
354
+ });
355
+ }
332
356
  async accountCall(call) {
333
357
  try {
334
358
  return {