@mintplayer/ng-spark-auth 22.1.0 → 22.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/mintplayer-ng-spark-auth-auth-bar.mjs +5 -3
- package/fesm2022/mintplayer-ng-spark-auth-auth-bar.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-auth-core.mjs +9 -0
- package/fesm2022/mintplayer-ng-spark-auth-core.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-auth-guards.mjs +2 -2
- package/fesm2022/mintplayer-ng-spark-auth-guards.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-auth-interceptors.mjs +2 -2
- package/fesm2022/mintplayer-ng-spark-auth-interceptors.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-auth-models.mjs +39 -2
- package/fesm2022/mintplayer-ng-spark-auth-models.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-auth-routes.mjs +48 -30
- package/fesm2022/mintplayer-ng-spark-auth-routes.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-auth-sign-in.mjs +80 -0
- package/fesm2022/mintplayer-ng-spark-auth-sign-in.mjs.map +1 -0
- package/package.json +5 -1
- package/types/mintplayer-ng-spark-auth-auth-bar.d.ts +2 -0
- package/types/mintplayer-ng-spark-auth-core.d.ts +8 -1
- package/types/mintplayer-ng-spark-auth-forgot-password.d.ts +1 -1
- package/types/mintplayer-ng-spark-auth-login.d.ts +1 -1
- package/types/mintplayer-ng-spark-auth-models.d.ts +64 -5
- package/types/mintplayer-ng-spark-auth-register.d.ts +1 -1
- package/types/mintplayer-ng-spark-auth-reset-password.d.ts +1 -1
- package/types/mintplayer-ng-spark-auth-routes.d.ts +15 -0
- package/types/mintplayer-ng-spark-auth-sign-in.d.ts +36 -0
- package/types/mintplayer-ng-spark-auth-two-factor.d.ts +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { inject, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
3
|
import { Router, RouterLink } from '@angular/router';
|
|
4
|
-
import { SPARK_AUTH_CONFIG } from '@mintplayer/ng-spark-auth/models';
|
|
4
|
+
import { SPARK_AUTH_CONFIG, resolveSignInUrl } from '@mintplayer/ng-spark-auth/models';
|
|
5
5
|
import { SparkAuthService } from '@mintplayer/ng-spark-auth/core';
|
|
6
6
|
import { TranslateKeyPipe } from '@mintplayer/ng-spark-auth/pipes';
|
|
7
7
|
|
|
@@ -9,6 +9,8 @@ class SparkAuthBarComponent {
|
|
|
9
9
|
authService = inject(SparkAuthService);
|
|
10
10
|
config = inject(SPARK_AUTH_CONFIG);
|
|
11
11
|
router = inject(Router);
|
|
12
|
+
/** Same target the guard and interceptor use, so the bar cannot link somewhere they would not send you. */
|
|
13
|
+
signInUrl = resolveSignInUrl(this.config, this.router);
|
|
12
14
|
async onLogout() {
|
|
13
15
|
try {
|
|
14
16
|
await this.authService.logout();
|
|
@@ -21,11 +23,11 @@ class SparkAuthBarComponent {
|
|
|
21
23
|
}
|
|
22
24
|
}
|
|
23
25
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkAuthBarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
24
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: SparkAuthBarComponent, isStandalone: true, selector: "spark-auth-bar", ngImport: i0, template: "@if (authService.isAuthenticated()) {\n <span class=\"me-2\">{{ authService.user()?.userName }}</span>\n <button class=\"btn btn-outline-light btn-sm\" (click)=\"onLogout()\">{{ 'auth.logout' | t }}</button>\n} @else {\n <a class=\"btn btn-outline-light btn-sm\" [routerLink]=\"
|
|
26
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: SparkAuthBarComponent, isStandalone: true, selector: "spark-auth-bar", ngImport: i0, template: "@if (authService.isAuthenticated()) {\n <span class=\"me-2\">{{ authService.user()?.userName }}</span>\n <button class=\"btn btn-outline-light btn-sm\" (click)=\"onLogout()\">{{ 'auth.logout' | t }}</button>\n} @else {\n <a class=\"btn btn-outline-light btn-sm\" [routerLink]=\"signInUrl\">{{ 'auth.login' | t }}</a>\n}\n", dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
25
27
|
}
|
|
26
28
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkAuthBarComponent, decorators: [{
|
|
27
29
|
type: Component,
|
|
28
|
-
args: [{ selector: 'spark-auth-bar', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [RouterLink, TranslateKeyPipe], template: "@if (authService.isAuthenticated()) {\n <span class=\"me-2\">{{ authService.user()?.userName }}</span>\n <button class=\"btn btn-outline-light btn-sm\" (click)=\"onLogout()\">{{ 'auth.logout' | t }}</button>\n} @else {\n <a class=\"btn btn-outline-light btn-sm\" [routerLink]=\"
|
|
30
|
+
args: [{ selector: 'spark-auth-bar', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [RouterLink, TranslateKeyPipe], template: "@if (authService.isAuthenticated()) {\n <span class=\"me-2\">{{ authService.user()?.userName }}</span>\n <button class=\"btn btn-outline-light btn-sm\" (click)=\"onLogout()\">{{ 'auth.logout' | t }}</button>\n} @else {\n <a class=\"btn btn-outline-light btn-sm\" [routerLink]=\"signInUrl\">{{ 'auth.login' | t }}</a>\n}\n" }]
|
|
29
31
|
}] });
|
|
30
32
|
|
|
31
33
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mintplayer-ng-spark-auth-auth-bar.mjs","sources":["../../auth-bar/src/spark-auth-bar.component.ts","../../auth-bar/src/spark-auth-bar.component.html","../../auth-bar/mintplayer-ng-spark-auth-auth-bar.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, inject } from '@angular/core';\nimport { RouterLink, Router } from '@angular/router';\nimport { SPARK_AUTH_CONFIG } from '@mintplayer/ng-spark-auth/models';\nimport { SparkAuthService } from '@mintplayer/ng-spark-auth/core';\nimport { TranslateKeyPipe } from '@mintplayer/ng-spark-auth/pipes';\n\n@Component({\n selector: 'spark-auth-bar',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [RouterLink, TranslateKeyPipe],\n templateUrl: './spark-auth-bar.component.html',\n})\nexport class SparkAuthBarComponent {\n readonly authService = inject(SparkAuthService);\n readonly config = inject(SPARK_AUTH_CONFIG);\n private readonly router = inject(Router);\n\n async onLogout(): Promise<void> {\n try {\n await this.authService.logout();\n } finally {\n // Always navigate away from the authenticated area, even if the server-side\n // logout call fails (network error, session already expired, etc.). The local\n // session state has been cleared by SparkAuthService regardless.\n this.router.navigateByUrl('/');\n }\n }\n}\n\nexport default SparkAuthBarComponent;\n","@if (authService.isAuthenticated()) {\n <span class=\"me-2\">{{ authService.user()?.userName }}</span>\n <button class=\"btn btn-outline-light btn-sm\" (click)=\"onLogout()\">{{ 'auth.logout' | t }}</button>\n} @else {\n <a class=\"btn btn-outline-light btn-sm\" [routerLink]=\"
|
|
1
|
+
{"version":3,"file":"mintplayer-ng-spark-auth-auth-bar.mjs","sources":["../../auth-bar/src/spark-auth-bar.component.ts","../../auth-bar/src/spark-auth-bar.component.html","../../auth-bar/mintplayer-ng-spark-auth-auth-bar.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, inject } from '@angular/core';\nimport { RouterLink, Router } from '@angular/router';\nimport { SPARK_AUTH_CONFIG, resolveSignInUrl } from '@mintplayer/ng-spark-auth/models';\nimport { SparkAuthService } from '@mintplayer/ng-spark-auth/core';\nimport { TranslateKeyPipe } from '@mintplayer/ng-spark-auth/pipes';\n\n@Component({\n selector: 'spark-auth-bar',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [RouterLink, TranslateKeyPipe],\n templateUrl: './spark-auth-bar.component.html',\n})\nexport class SparkAuthBarComponent {\n readonly authService = inject(SparkAuthService);\n readonly config = inject(SPARK_AUTH_CONFIG);\n private readonly router = inject(Router);\n\n /** Same target the guard and interceptor use, so the bar cannot link somewhere they would not send you. */\n readonly signInUrl = resolveSignInUrl(this.config, this.router);\n\n async onLogout(): Promise<void> {\n try {\n await this.authService.logout();\n } finally {\n // Always navigate away from the authenticated area, even if the server-side\n // logout call fails (network error, session already expired, etc.). The local\n // session state has been cleared by SparkAuthService regardless.\n this.router.navigateByUrl('/');\n }\n }\n}\n\nexport default SparkAuthBarComponent;\n","@if (authService.isAuthenticated()) {\n <span class=\"me-2\">{{ authService.user()?.userName }}</span>\n <button class=\"btn btn-outline-light btn-sm\" (click)=\"onLogout()\">{{ 'auth.logout' | t }}</button>\n} @else {\n <a class=\"btn btn-outline-light btn-sm\" [routerLink]=\"signInUrl\">{{ 'auth.login' | t }}</a>\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;;MAaa,qBAAqB,CAAA;AACvB,IAAA,WAAW,GAAG,MAAM,CAAC,gBAAgB,CAAC;AACtC,IAAA,MAAM,GAAG,MAAM,CAAC,iBAAiB,CAAC;AAC1B,IAAA,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;;IAG/B,SAAS,GAAG,gBAAgB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC;AAE/D,IAAA,MAAM,QAAQ,GAAA;AACZ,QAAA,IAAI;AACF,YAAA,MAAM,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE;QACjC;gBAAU;;;;AAIR,YAAA,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC;QAChC;IACF;uGAjBW,qBAAqB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAArB,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,qBAAqB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,gBAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,ECblC,sUAMA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EDIY,UAAU,6OAAE,gBAAgB,EAAA,IAAA,EAAA,GAAA,EAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;2FAG3B,qBAAqB,EAAA,UAAA,EAAA,CAAA;kBAPjC,SAAS;+BACE,gBAAgB,EAAA,UAAA,EACd,IAAI,EAAA,eAAA,EACC,uBAAuB,CAAC,MAAM,EAAA,OAAA,EACtC,CAAC,UAAU,EAAE,gBAAgB,CAAC,EAAA,QAAA,EAAA,sUAAA,EAAA;;;AEVzC;;AAEG;;;;"}
|
|
@@ -39,6 +39,15 @@ class SparkAuthService {
|
|
|
39
39
|
await this.csrfRefresh();
|
|
40
40
|
this.currentUser.set(null);
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* What sign-in methods this deployment actually offers.
|
|
44
|
+
*
|
|
45
|
+
* Read from the server rather than assumed from the client's own route configuration: the two are
|
|
46
|
+
* configured independently, and a mismatch is otherwise invisible until a user hits it.
|
|
47
|
+
*/
|
|
48
|
+
async capabilities() {
|
|
49
|
+
return await firstValueFrom(this.http.get(`${this.config.apiBasePath}/capabilities`));
|
|
50
|
+
}
|
|
42
51
|
async csrfRefresh() {
|
|
43
52
|
await firstValueFrom(this.http.post(`${this.config.apiBasePath}/csrf-refresh`, {}));
|
|
44
53
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mintplayer-ng-spark-auth-core.mjs","sources":["../../core/src/spark-auth.service.ts","../../core/src/spark-auth-translation.service.ts","../../core/mintplayer-ng-spark-auth-core.ts"],"sourcesContent":["import { computed, inject, Injectable, NgZone, signal } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { firstValueFrom } from 'rxjs';\nimport {\n AuthUser,\n SPARK_AUTH_CONFIG,\n SPARK_EXTERNAL_LOGIN_MESSAGE_TYPE,\n SparkExternalLoginError,\n SparkExternalLoginOptions,\n SparkExternalLoginResult,\n} from '@mintplayer/ng-spark-auth/models';\n\n/** How often the popup is checked for a manual close. */\nconst POPUP_CLOSE_POLL_MS = 400;\n\n@Injectable({ providedIn: 'root' })\nexport class SparkAuthService {\n private readonly http = inject(HttpClient);\n private readonly config = inject(SPARK_AUTH_CONFIG);\n private readonly zone = inject(NgZone);\n\n private readonly currentUser = signal<AuthUser | null>(null);\n\n readonly user = this.currentUser.asReadonly();\n readonly isAuthenticated = computed(() => this.currentUser()?.isAuthenticated === true);\n\n constructor() {\n this.checkAuth();\n }\n\n async login(email: string, password: string): Promise<void> {\n await firstValueFrom(this.http.post<void>(`${this.config.apiBasePath}/login?useCookies=true`, { email, password }));\n await this.csrfRefresh();\n await this.checkAuth();\n }\n\n async loginTwoFactor(twoFactorCode: string, twoFactorRecoveryCode?: string): Promise<void> {\n await firstValueFrom(this.http.post<void>(`${this.config.apiBasePath}/login?useCookies=true`, {\n twoFactorCode,\n twoFactorRecoveryCode,\n }));\n await this.csrfRefresh();\n await this.checkAuth();\n }\n\n async register(email: string, password: string): Promise<void> {\n await firstValueFrom(this.http.post<void>(`${this.config.apiBasePath}/register`, { email, password }));\n }\n\n async logout(): Promise<void> {\n await firstValueFrom(this.http.post<void>(`${this.config.apiBasePath}/logout`, {}));\n await this.csrfRefresh();\n this.currentUser.set(null);\n }\n\n async csrfRefresh(): Promise<void> {\n await firstValueFrom(this.http.post<void>(`${this.config.apiBasePath}/csrf-refresh`, {}));\n }\n\n async checkAuth(): Promise<AuthUser | null> {\n try {\n const user = await firstValueFrom(this.http.get<AuthUser>(`${this.config.apiBasePath}/me`));\n this.currentUser.set(user);\n return user;\n } catch {\n this.currentUser.set(null);\n return null;\n }\n }\n\n /**\n * Signs in through an external provider (GitHub, Google, …) and leaves this service's\n * `user()` signal up to date, exactly as `login()` does.\n *\n * The whole handshake lives here: opening the window, listening for the callback's\n * message, checking its origin, detecting a popup the user closed by hand, tearing the\n * listener and poll down on every exit path, and re-reading the session afterwards.\n * Callers get an outcome, not a protocol — which is the point, because the previous\n * hand-rolled version leaked its listener whenever the user simply closed the window.\n *\n * In `'redirect'` mode the returned promise never settles: this document is being\n * replaced, and the outcome arrives as the next page load rather than as a value.\n */\n loginWithProvider(provider: string, options: SparkExternalLoginOptions = {}): Promise<SparkExternalLoginResult> {\n const { returnUrl = this.config.defaultRedirectUrl, mode = 'popup' } = options;\n const url = `${this.config.apiBasePath}/external-login?provider=${encodeURIComponent(provider)}`\n + `&returnUrl=${encodeURIComponent(returnUrl)}`;\n\n if (mode === 'redirect') {\n window.location.assign(url);\n return new Promise<SparkExternalLoginResult>(() => { /* the page is going away */ });\n }\n\n return new Promise<SparkExternalLoginResult>((resolve) => {\n const popup = window.open(`${url}&popup=1`, 'spark-external-login', 'width=600,height=700');\n if (!popup) {\n resolve({ success: false, error: 'popup_blocked' });\n return;\n }\n\n // One settle path, so the listener and the poll cannot outlive the flow no matter\n // which of the four ways it ends.\n let settled = false;\n const settle = (error?: SparkExternalLoginError) => {\n if (settled) return;\n settled = true;\n window.removeEventListener('message', onMessage);\n clearInterval(poll);\n popup.close();\n\n this.zone.run(async () => {\n if (!error) await this.checkAuth();\n resolve(error ? { success: false, error } : { success: true });\n });\n };\n\n const onMessage = (event: MessageEvent) => {\n if (event.origin !== window.location.origin) return;\n if (event.data?.type !== SPARK_EXTERNAL_LOGIN_MESSAGE_TYPE) return;\n settle(event.data.success ? undefined : (event.data.error ?? 'no_login_info'));\n };\n\n // A user who closes the window never posts anything, so without this the promise\n // and its listener would both live forever.\n const poll = setInterval(() => {\n if (popup.closed) settle('popup_closed');\n }, POPUP_CLOSE_POLL_MS);\n\n window.addEventListener('message', onMessage);\n });\n }\n\n async forgotPassword(email: string): Promise<void> {\n await firstValueFrom(this.http.post<void>(`${this.config.apiBasePath}/forgotPassword`, { email }));\n }\n\n async resetPassword(email: string, resetCode: string, newPassword: string): Promise<void> {\n await firstValueFrom(this.http.post<void>(`${this.config.apiBasePath}/resetPassword`, {\n email,\n resetCode,\n newPassword,\n }));\n }\n}\n","import { Injectable, inject, signal, type Signal } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { firstValueFrom } from 'rxjs';\n\ntype TranslatedString = Record<string, string>;\n\n/** Read the global language signal set by @mintplayer/ng-spark */\nconst currentLanguage: Signal<string> =\n (globalThis as any).__sparkCurrentLanguage ?? signal('en');\n\n@Injectable({ providedIn: 'root' })\nexport class SparkAuthTranslationService {\n private readonly http = inject(HttpClient);\n private readonly translationsMap = signal<Record<string, TranslatedString>>({});\n\n constructor() {\n this.loadTranslations();\n }\n\n private async loadTranslations(): Promise<void> {\n try {\n const t = await firstValueFrom(this.http.get<Record<string, TranslatedString>>('/spark/translations'));\n this.translationsMap.set(t);\n } catch {\n // Translations failed to load; keys will be shown as-is\n }\n }\n\n t(key: string): string {\n const ts = this.translationsMap()[key];\n if (!ts) return key;\n const lang = currentLanguage();\n return ts[lang] ?? ts['en'] ?? Object.values(ts)[0] ?? key;\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mintplayer-ng-spark-auth-core.mjs","sources":["../../core/src/spark-auth.service.ts","../../core/src/spark-auth-translation.service.ts","../../core/mintplayer-ng-spark-auth-core.ts"],"sourcesContent":["import { computed, inject, Injectable, NgZone, signal } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { firstValueFrom } from 'rxjs';\nimport {\n AuthUser,\n SPARK_AUTH_CONFIG,\n SparkAuthCapabilities,\n SPARK_EXTERNAL_LOGIN_MESSAGE_TYPE,\n SparkExternalLoginError,\n SparkExternalLoginOptions,\n SparkExternalLoginResult,\n} from '@mintplayer/ng-spark-auth/models';\n\n/** How often the popup is checked for a manual close. */\nconst POPUP_CLOSE_POLL_MS = 400;\n\n@Injectable({ providedIn: 'root' })\nexport class SparkAuthService {\n private readonly http = inject(HttpClient);\n private readonly config = inject(SPARK_AUTH_CONFIG);\n private readonly zone = inject(NgZone);\n\n private readonly currentUser = signal<AuthUser | null>(null);\n\n readonly user = this.currentUser.asReadonly();\n readonly isAuthenticated = computed(() => this.currentUser()?.isAuthenticated === true);\n\n constructor() {\n this.checkAuth();\n }\n\n async login(email: string, password: string): Promise<void> {\n await firstValueFrom(this.http.post<void>(`${this.config.apiBasePath}/login?useCookies=true`, { email, password }));\n await this.csrfRefresh();\n await this.checkAuth();\n }\n\n async loginTwoFactor(twoFactorCode: string, twoFactorRecoveryCode?: string): Promise<void> {\n await firstValueFrom(this.http.post<void>(`${this.config.apiBasePath}/login?useCookies=true`, {\n twoFactorCode,\n twoFactorRecoveryCode,\n }));\n await this.csrfRefresh();\n await this.checkAuth();\n }\n\n async register(email: string, password: string): Promise<void> {\n await firstValueFrom(this.http.post<void>(`${this.config.apiBasePath}/register`, { email, password }));\n }\n\n async logout(): Promise<void> {\n await firstValueFrom(this.http.post<void>(`${this.config.apiBasePath}/logout`, {}));\n await this.csrfRefresh();\n this.currentUser.set(null);\n }\n\n /**\n * What sign-in methods this deployment actually offers.\n *\n * Read from the server rather than assumed from the client's own route configuration: the two are\n * configured independently, and a mismatch is otherwise invisible until a user hits it.\n */\n async capabilities(): Promise<SparkAuthCapabilities> {\n return await firstValueFrom(\n this.http.get<SparkAuthCapabilities>(`${this.config.apiBasePath}/capabilities`));\n }\n\n async csrfRefresh(): Promise<void> {\n await firstValueFrom(this.http.post<void>(`${this.config.apiBasePath}/csrf-refresh`, {}));\n }\n\n async checkAuth(): Promise<AuthUser | null> {\n try {\n const user = await firstValueFrom(this.http.get<AuthUser>(`${this.config.apiBasePath}/me`));\n this.currentUser.set(user);\n return user;\n } catch {\n this.currentUser.set(null);\n return null;\n }\n }\n\n /**\n * Signs in through an external provider (GitHub, Google, …) and leaves this service's\n * `user()` signal up to date, exactly as `login()` does.\n *\n * The whole handshake lives here: opening the window, listening for the callback's\n * message, checking its origin, detecting a popup the user closed by hand, tearing the\n * listener and poll down on every exit path, and re-reading the session afterwards.\n * Callers get an outcome, not a protocol — which is the point, because the previous\n * hand-rolled version leaked its listener whenever the user simply closed the window.\n *\n * In `'redirect'` mode the returned promise never settles: this document is being\n * replaced, and the outcome arrives as the next page load rather than as a value.\n */\n loginWithProvider(provider: string, options: SparkExternalLoginOptions = {}): Promise<SparkExternalLoginResult> {\n const { returnUrl = this.config.defaultRedirectUrl, mode = 'popup' } = options;\n const url = `${this.config.apiBasePath}/external-login?provider=${encodeURIComponent(provider)}`\n + `&returnUrl=${encodeURIComponent(returnUrl)}`;\n\n if (mode === 'redirect') {\n window.location.assign(url);\n return new Promise<SparkExternalLoginResult>(() => { /* the page is going away */ });\n }\n\n return new Promise<SparkExternalLoginResult>((resolve) => {\n const popup = window.open(`${url}&popup=1`, 'spark-external-login', 'width=600,height=700');\n if (!popup) {\n resolve({ success: false, error: 'popup_blocked' });\n return;\n }\n\n // One settle path, so the listener and the poll cannot outlive the flow no matter\n // which of the four ways it ends.\n let settled = false;\n const settle = (error?: SparkExternalLoginError) => {\n if (settled) return;\n settled = true;\n window.removeEventListener('message', onMessage);\n clearInterval(poll);\n popup.close();\n\n this.zone.run(async () => {\n if (!error) await this.checkAuth();\n resolve(error ? { success: false, error } : { success: true });\n });\n };\n\n const onMessage = (event: MessageEvent) => {\n if (event.origin !== window.location.origin) return;\n if (event.data?.type !== SPARK_EXTERNAL_LOGIN_MESSAGE_TYPE) return;\n settle(event.data.success ? undefined : (event.data.error ?? 'no_login_info'));\n };\n\n // A user who closes the window never posts anything, so without this the promise\n // and its listener would both live forever.\n const poll = setInterval(() => {\n if (popup.closed) settle('popup_closed');\n }, POPUP_CLOSE_POLL_MS);\n\n window.addEventListener('message', onMessage);\n });\n }\n\n async forgotPassword(email: string): Promise<void> {\n await firstValueFrom(this.http.post<void>(`${this.config.apiBasePath}/forgotPassword`, { email }));\n }\n\n async resetPassword(email: string, resetCode: string, newPassword: string): Promise<void> {\n await firstValueFrom(this.http.post<void>(`${this.config.apiBasePath}/resetPassword`, {\n email,\n resetCode,\n newPassword,\n }));\n }\n}\n","import { Injectable, inject, signal, type Signal } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { firstValueFrom } from 'rxjs';\n\ntype TranslatedString = Record<string, string>;\n\n/** Read the global language signal set by @mintplayer/ng-spark */\nconst currentLanguage: Signal<string> =\n (globalThis as any).__sparkCurrentLanguage ?? signal('en');\n\n@Injectable({ providedIn: 'root' })\nexport class SparkAuthTranslationService {\n private readonly http = inject(HttpClient);\n private readonly translationsMap = signal<Record<string, TranslatedString>>({});\n\n constructor() {\n this.loadTranslations();\n }\n\n private async loadTranslations(): Promise<void> {\n try {\n const t = await firstValueFrom(this.http.get<Record<string, TranslatedString>>('/spark/translations'));\n this.translationsMap.set(t);\n } catch {\n // Translations failed to load; keys will be shown as-is\n }\n }\n\n t(key: string): string {\n const ts = this.translationsMap()[key];\n if (!ts) return key;\n const lang = currentLanguage();\n return ts[lang] ?? ts['en'] ?? Object.values(ts)[0] ?? key;\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { inject } from '@angular/core';
|
|
2
2
|
import { Router } from '@angular/router';
|
|
3
|
-
import { SPARK_AUTH_CONFIG } from '@mintplayer/ng-spark-auth/models';
|
|
3
|
+
import { SPARK_AUTH_CONFIG, resolveSignInUrl } from '@mintplayer/ng-spark-auth/models';
|
|
4
4
|
import { SparkAuthService } from '@mintplayer/ng-spark-auth/core';
|
|
5
5
|
|
|
6
6
|
const sparkAuthGuard = (route, state) => {
|
|
@@ -10,7 +10,7 @@ const sparkAuthGuard = (route, state) => {
|
|
|
10
10
|
if (authService.isAuthenticated()) {
|
|
11
11
|
return true;
|
|
12
12
|
}
|
|
13
|
-
return router.createUrlTree([config
|
|
13
|
+
return router.createUrlTree([resolveSignInUrl(config, router)], {
|
|
14
14
|
queryParams: { returnUrl: state.url },
|
|
15
15
|
});
|
|
16
16
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mintplayer-ng-spark-auth-guards.mjs","sources":["../../guards/src/spark-auth.guard.ts","../../guards/mintplayer-ng-spark-auth-guards.ts"],"sourcesContent":["import { inject } from '@angular/core';\nimport { CanActivateFn, Router } from '@angular/router';\nimport { SPARK_AUTH_CONFIG } from '@mintplayer/ng-spark-auth/models';\nimport { SparkAuthService } from '@mintplayer/ng-spark-auth/core';\n\nexport const sparkAuthGuard: CanActivateFn = (route, state) => {\n const authService = inject(SparkAuthService);\n const router = inject(Router);\n const config = inject(SPARK_AUTH_CONFIG);\n\n if (authService.isAuthenticated()) {\n return true;\n }\n\n return router.createUrlTree([config
|
|
1
|
+
{"version":3,"file":"mintplayer-ng-spark-auth-guards.mjs","sources":["../../guards/src/spark-auth.guard.ts","../../guards/mintplayer-ng-spark-auth-guards.ts"],"sourcesContent":["import { inject } from '@angular/core';\nimport { CanActivateFn, Router } from '@angular/router';\nimport { SPARK_AUTH_CONFIG, resolveSignInUrl } from '@mintplayer/ng-spark-auth/models';\nimport { SparkAuthService } from '@mintplayer/ng-spark-auth/core';\n\nexport const sparkAuthGuard: CanActivateFn = (route, state) => {\n const authService = inject(SparkAuthService);\n const router = inject(Router);\n const config = inject(SPARK_AUTH_CONFIG);\n\n if (authService.isAuthenticated()) {\n return true;\n }\n\n return router.createUrlTree([resolveSignInUrl(config, router)], {\n queryParams: { returnUrl: state.url },\n });\n};\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;MAKa,cAAc,GAAkB,CAAC,KAAK,EAAE,KAAK,KAAI;AAC5D,IAAA,MAAM,WAAW,GAAG,MAAM,CAAC,gBAAgB,CAAC;AAC5C,IAAA,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;AAC7B,IAAA,MAAM,MAAM,GAAG,MAAM,CAAC,iBAAiB,CAAC;AAExC,IAAA,IAAI,WAAW,CAAC,eAAe,EAAE,EAAE;AACjC,QAAA,OAAO,IAAI;IACb;AAEA,IAAA,OAAO,MAAM,CAAC,aAAa,CAAC,CAAC,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE;AAC9D,QAAA,WAAW,EAAE,EAAE,SAAS,EAAE,KAAK,CAAC,GAAG,EAAE;AACtC,KAAA,CAAC;AACJ;;ACjBA;;AAEG;;;;"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { inject } from '@angular/core';
|
|
2
2
|
import { Router } from '@angular/router';
|
|
3
3
|
import { tap } from 'rxjs';
|
|
4
|
-
import { SPARK_AUTH_CONFIG, sanitizeReturnUrl } from '@mintplayer/ng-spark-auth/models';
|
|
4
|
+
import { SPARK_AUTH_CONFIG, sanitizeReturnUrl, resolveSignInUrl } from '@mintplayer/ng-spark-auth/models';
|
|
5
5
|
|
|
6
6
|
const sparkAuthInterceptor = (req, next) => {
|
|
7
7
|
const config = inject(SPARK_AUTH_CONFIG);
|
|
@@ -18,7 +18,7 @@ const sparkAuthInterceptor = (req, next) => {
|
|
|
18
18
|
// back as an open redirect after authentication.
|
|
19
19
|
const pathOnly = router.url.split('?')[0].split('#')[0];
|
|
20
20
|
const returnUrl = sanitizeReturnUrl(pathOnly, config.defaultRedirectUrl);
|
|
21
|
-
router.navigate([config
|
|
21
|
+
router.navigate([resolveSignInUrl(config, router)], {
|
|
22
22
|
queryParams: { returnUrl },
|
|
23
23
|
});
|
|
24
24
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mintplayer-ng-spark-auth-interceptors.mjs","sources":["../../interceptors/src/spark-auth.interceptor.ts","../../interceptors/mintplayer-ng-spark-auth-interceptors.ts"],"sourcesContent":["import { inject } from '@angular/core';\nimport { HttpInterceptorFn } from '@angular/common/http';\nimport { Router } from '@angular/router';\nimport { tap } from 'rxjs';\nimport { SPARK_AUTH_CONFIG, sanitizeReturnUrl } from '@mintplayer/ng-spark-auth/models';\n\nexport const sparkAuthInterceptor: HttpInterceptorFn = (req, next) => {\n const config = inject(SPARK_AUTH_CONFIG);\n const router = inject(Router);\n\n return next(req).pipe(\n tap({\n error: (error) => {\n if (\n error.status === 401 &&\n !req.url.startsWith(config.apiBasePath)\n ) {\n // R2-L4: drop query string and fragment from the captured URL before\n // putting it on the login URL. The unmodified router.url leaks\n // in-app draft text / document IDs / search queries into the next\n // access log entry for /login. Also pass through the same sanitizer\n // the login component uses so a path with embedded // can't reflect\n // back as an open redirect after authentication.\n const pathOnly = router.url.split('?')[0].split('#')[0];\n const returnUrl = sanitizeReturnUrl(pathOnly, config.defaultRedirectUrl);\n router.navigate([config
|
|
1
|
+
{"version":3,"file":"mintplayer-ng-spark-auth-interceptors.mjs","sources":["../../interceptors/src/spark-auth.interceptor.ts","../../interceptors/mintplayer-ng-spark-auth-interceptors.ts"],"sourcesContent":["import { inject } from '@angular/core';\nimport { HttpInterceptorFn } from '@angular/common/http';\nimport { Router } from '@angular/router';\nimport { tap } from 'rxjs';\nimport { SPARK_AUTH_CONFIG, resolveSignInUrl, sanitizeReturnUrl } from '@mintplayer/ng-spark-auth/models';\n\nexport const sparkAuthInterceptor: HttpInterceptorFn = (req, next) => {\n const config = inject(SPARK_AUTH_CONFIG);\n const router = inject(Router);\n\n return next(req).pipe(\n tap({\n error: (error) => {\n if (\n error.status === 401 &&\n !req.url.startsWith(config.apiBasePath)\n ) {\n // R2-L4: drop query string and fragment from the captured URL before\n // putting it on the login URL. The unmodified router.url leaks\n // in-app draft text / document IDs / search queries into the next\n // access log entry for /login. Also pass through the same sanitizer\n // the login component uses so a path with embedded // can't reflect\n // back as an open redirect after authentication.\n const pathOnly = router.url.split('?')[0].split('#')[0];\n const returnUrl = sanitizeReturnUrl(pathOnly, config.defaultRedirectUrl);\n router.navigate([resolveSignInUrl(config, router)], {\n queryParams: { returnUrl },\n });\n }\n },\n }),\n );\n};\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;MAMa,oBAAoB,GAAsB,CAAC,GAAG,EAAE,IAAI,KAAI;AACnE,IAAA,MAAM,MAAM,GAAG,MAAM,CAAC,iBAAiB,CAAC;AACxC,IAAA,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IAE7B,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CACnB,GAAG,CAAC;AACF,QAAA,KAAK,EAAE,CAAC,KAAK,KAAI;AACf,YAAA,IACE,KAAK,CAAC,MAAM,KAAK,GAAG;gBACpB,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,WAAW,CAAC,EACvC;;;;;;;gBAOA,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBACvD,MAAM,SAAS,GAAG,iBAAiB,CAAC,QAAQ,EAAE,MAAM,CAAC,kBAAkB,CAAC;gBACxE,MAAM,CAAC,QAAQ,CAAC,CAAC,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE;oBAClD,WAAW,EAAE,EAAE,SAAS,EAAE;AAC3B,iBAAA,CAAC;YACJ;QACF,CAAC;AACF,KAAA,CAAC,CACH;AACH;;AChCA;;AAEG;;;;"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { InjectionToken } from '@angular/core';
|
|
1
|
+
import { InjectionToken, isDevMode } from '@angular/core';
|
|
2
2
|
|
|
3
3
|
const SPARK_AUTH_CONFIG = new InjectionToken('SPARK_AUTH_CONFIG');
|
|
4
4
|
const defaultSparkAuthConfig = {
|
|
@@ -41,9 +41,46 @@ function sanitizeReturnUrl(value, defaultUrl) {
|
|
|
41
41
|
|
|
42
42
|
const SPARK_EXTERNAL_LOGIN_MESSAGE_TYPE = 'spark:external-login';
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Where to send someone who is not signed in.
|
|
46
|
+
*
|
|
47
|
+
* `loginUrl` is the single source of truth for this, and the only thing the guard, the interceptor
|
|
48
|
+
* and the auth bar should consult — an application that turns off local credentials points it at
|
|
49
|
+
* its own sign-in landing route.
|
|
50
|
+
*
|
|
51
|
+
* The reason this is a function rather than a property read is the failure mode it closes: nothing
|
|
52
|
+
* connects `loginUrl` to the routes that actually exist, so pointing it at a route that was never
|
|
53
|
+
* registered produces a redirect into a blank page with no type error, no build failure and no
|
|
54
|
+
* runtime error. In development that now warns, once, naming the value and the fix.
|
|
55
|
+
*/
|
|
56
|
+
function resolveSignInUrl(config, router) {
|
|
57
|
+
if (isDevMode() && !warned && !routeExists(router, config.loginUrl)) {
|
|
58
|
+
warned = true;
|
|
59
|
+
console.warn(`[ng-spark-auth] loginUrl is "${config.loginUrl}", but no route matches it, so redirecting an `
|
|
60
|
+
+ `unauthenticated user will land on a blank page. If this app runs with local credentials `
|
|
61
|
+
+ `disabled, point loginUrl at its sign-in route (for example provideSparkAuth({ loginUrl: '/sign-in' })).`);
|
|
62
|
+
}
|
|
63
|
+
return config.loginUrl;
|
|
64
|
+
}
|
|
65
|
+
let warned = false;
|
|
66
|
+
function routeExists(router, url) {
|
|
67
|
+
const target = url.replace(/^\/+/, '').split('?')[0].split('#')[0];
|
|
68
|
+
return matches(router.config, target);
|
|
69
|
+
}
|
|
70
|
+
function matches(routes, target, prefix = '') {
|
|
71
|
+
return routes.some(route => {
|
|
72
|
+
const path = [prefix, route.path ?? ''].filter(Boolean).join('/');
|
|
73
|
+
if (path === target)
|
|
74
|
+
return true;
|
|
75
|
+
// A wildcard route would match anything, including a URL nobody registered — treating it as a
|
|
76
|
+
// match would make this check always pass and warn about nothing.
|
|
77
|
+
return route.children ? matches(route.children, target, path) : false;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
44
81
|
/**
|
|
45
82
|
* Generated bundle index. Do not edit.
|
|
46
83
|
*/
|
|
47
84
|
|
|
48
|
-
export { SPARK_AUTH_CONFIG, SPARK_AUTH_ROUTE_PATHS, SPARK_EXTERNAL_LOGIN_MESSAGE_TYPE, defaultSparkAuthConfig, isSafeReturnUrl, sanitizeReturnUrl };
|
|
85
|
+
export { SPARK_AUTH_CONFIG, SPARK_AUTH_ROUTE_PATHS, SPARK_EXTERNAL_LOGIN_MESSAGE_TYPE, defaultSparkAuthConfig, isSafeReturnUrl, resolveSignInUrl, sanitizeReturnUrl };
|
|
49
86
|
//# sourceMappingURL=mintplayer-ng-spark-auth-models.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mintplayer-ng-spark-auth-models.mjs","sources":["../../models/src/auth-config.ts","../../models/src/auth-route-config.ts","../../models/src/return-url.ts","../../models/src/external-login.ts","../../models/mintplayer-ng-spark-auth-models.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\n\nexport interface SparkAuthConfig {\n apiBasePath: string;\n defaultRedirectUrl: string;\n loginUrl: string;\n}\n\nexport const SPARK_AUTH_CONFIG = new InjectionToken<SparkAuthConfig>('SPARK_AUTH_CONFIG');\n\nexport const defaultSparkAuthConfig: SparkAuthConfig = {\n apiBasePath: '/spark/auth',\n defaultRedirectUrl: '/',\n loginUrl: '/login',\n};\n","import { InjectionToken, Type } from '@angular/core';\n\nexport type SparkAuthRouteEntry = string | { path: string; component?: Type<unknown> };\n\nexport
|
|
1
|
+
{"version":3,"file":"mintplayer-ng-spark-auth-models.mjs","sources":["../../models/src/auth-config.ts","../../models/src/auth-route-config.ts","../../models/src/return-url.ts","../../models/src/external-login.ts","../../models/src/sign-in-redirect.ts","../../models/mintplayer-ng-spark-auth-models.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\n\nexport interface SparkAuthConfig {\n apiBasePath: string;\n defaultRedirectUrl: string;\n loginUrl: string;\n}\n\nexport const SPARK_AUTH_CONFIG = new InjectionToken<SparkAuthConfig>('SPARK_AUTH_CONFIG');\n\nexport const defaultSparkAuthConfig: SparkAuthConfig = {\n apiBasePath: '/spark/auth',\n defaultRedirectUrl: '/',\n loginUrl: '/login',\n};\n","import { InjectionToken, Type } from '@angular/core';\n\nexport type SparkAuthRouteEntry = string | { path: string; component?: Type<unknown> };\n\n/**\n * How much of the local-credential (email + password) page family to route.\n *\n * Mirrors the server's `SparkLocalCredentials`, and for the same reason it is a mode rather\n * than five independent switches: the pages form a star centred on the login page, and every\n * template dereferences its siblings' paths unconditionally, so removing any proper subset\n * leaves a dangling link. The family is the unit on both tiers.\n */\nexport type SparkLocalCredentialsMode = 'full' | 'sign-in-only' | 'disabled';\n\n/** The routable local-credential pages. Kept separate from {@link SparkAuthRouteConfig} so that\n * adding non-route options below cannot widen {@link SparkAuthRoutePaths}. */\nexport interface SparkAuthRouteEntries {\n /** The provider-button landing page. Routed whenever local credentials are limited. */\n signIn?: SparkAuthRouteEntry;\n login?: SparkAuthRouteEntry;\n twoFactor?: SparkAuthRouteEntry;\n register?: SparkAuthRouteEntry;\n forgotPassword?: SparkAuthRouteEntry;\n resetPassword?: SparkAuthRouteEntry;\n}\n\nexport interface SparkAuthRouteConfig extends SparkAuthRouteEntries {\n /**\n * Defaults to `'full'`, which routes every page — the behaviour of every version before this\n * option existed. Set it to match the server's `LocalCredentials` mode; `GET /spark/auth/capabilities`\n * reports what the server is actually running.\n */\n localCredentials?: SparkLocalCredentialsMode;\n}\n\n/**\n * Every path is present regardless of mode. The pages that are dropped are dropped together, so no\n * surviving template can dereference a missing sibling — which is what lets this stay `Required`\n * and keeps the change additive for consumers.\n */\nexport type SparkAuthRoutePaths = Required<Record<keyof SparkAuthRouteEntries, string>>;\n\nexport const SPARK_AUTH_ROUTE_PATHS = new InjectionToken<SparkAuthRoutePaths>('SPARK_AUTH_ROUTE_PATHS');\n","/**\n * Shared validator for `returnUrl` query parameters consumed by the auth\n * components and interceptor. The framework-side equivalent in\n * `SparkAuthenticationExtensions.SanitizeReturnUrl` enforces the same rule;\n * keep them in sync.\n *\n * Accepts only local in-app paths:\n * - Must start with a single `/`.\n * - Must not start with `//` or `/\\` (protocol-relative navigation that some\n * browsers and routers interpret as an external host).\n * - Must not contain CR/LF (defensive — the value flows into Router state and\n * eventually back into a `router.navigateByUrl` call).\n *\n * Returns the supplied default for anything else, never throws. Per R2-H9.\n */\nexport function isSafeReturnUrl(value: string | null | undefined): boolean {\n if (!value) return false;\n if (/[\\r\\n]/.test(value)) return false;\n if (!value.startsWith('/')) return false;\n if (value.length >= 2 && (value[1] === '/' || value[1] === '\\\\')) return false;\n return true;\n}\n\nexport function sanitizeReturnUrl(\n value: string | null | undefined,\n defaultUrl: string,\n): string {\n return isSafeReturnUrl(value) ? (value as string) : defaultUrl;\n}\n","/** How the user is sent to the external provider, and how the result comes back. */\nexport type SparkExternalLoginMode = 'popup' | 'redirect';\n\nexport interface SparkExternalLoginOptions {\n /** Where to land after a successful sign-in. Server-side sanitized to an in-app path. */\n returnUrl?: string;\n /** Defaults to `'popup'`. */\n mode?: SparkExternalLoginMode;\n}\n\n/**\n * Why an external login did not sign anyone in. The three `no_login_info` /\n * `email_not_verified` / `account_creation_failed` codes come from the server; the two\n * `popup_*` codes are raised here, because the browser is the only place that can observe\n * them. Deliberately coarse — never enough to tell \"no such account\" from anything else.\n */\nexport type SparkExternalLoginError =\n | 'no_login_info'\n | 'email_not_verified'\n | 'account_creation_failed'\n | 'popup_blocked'\n | 'popup_closed';\n\nexport interface SparkExternalLoginResult {\n success: boolean;\n error?: SparkExternalLoginError;\n}\n\n/** The message the callback page posts back to the window that opened it. */\nexport interface SparkExternalLoginMessage {\n type: 'spark:external-login';\n success: boolean;\n error?: SparkExternalLoginError;\n}\n\nexport const SPARK_EXTERNAL_LOGIN_MESSAGE_TYPE = 'spark:external-login';\n","import { isDevMode } from '@angular/core';\nimport { Route, Router } from '@angular/router';\nimport { SparkAuthConfig } from './auth-config';\n\n/**\n * Where to send someone who is not signed in.\n *\n * `loginUrl` is the single source of truth for this, and the only thing the guard, the interceptor\n * and the auth bar should consult — an application that turns off local credentials points it at\n * its own sign-in landing route.\n *\n * The reason this is a function rather than a property read is the failure mode it closes: nothing\n * connects `loginUrl` to the routes that actually exist, so pointing it at a route that was never\n * registered produces a redirect into a blank page with no type error, no build failure and no\n * runtime error. In development that now warns, once, naming the value and the fix.\n */\nexport function resolveSignInUrl(config: SparkAuthConfig, router: Router): string {\n if (isDevMode() && !warned && !routeExists(router, config.loginUrl)) {\n warned = true;\n console.warn(\n `[ng-spark-auth] loginUrl is \"${config.loginUrl}\", but no route matches it, so redirecting an `\n + `unauthenticated user will land on a blank page. If this app runs with local credentials `\n + `disabled, point loginUrl at its sign-in route (for example provideSparkAuth({ loginUrl: '/sign-in' })).`,\n );\n }\n\n return config.loginUrl;\n}\n\nlet warned = false;\n\nfunction routeExists(router: Router, url: string): boolean {\n const target = url.replace(/^\\/+/, '').split('?')[0].split('#')[0];\n return matches(router.config, target);\n}\n\nfunction matches(routes: Route[], target: string, prefix = ''): boolean {\n return routes.some(route => {\n const path = [prefix, route.path ?? ''].filter(Boolean).join('/');\n if (path === target) return true;\n // A wildcard route would match anything, including a URL nobody registered — treating it as a\n // match would make this check always pass and warn about nothing.\n return route.children ? matches(route.children, target, path) : false;\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;MAQa,iBAAiB,GAAG,IAAI,cAAc,CAAkB,mBAAmB;AAEjF,MAAM,sBAAsB,GAAoB;AACrD,IAAA,WAAW,EAAE,aAAa;AAC1B,IAAA,kBAAkB,EAAE,GAAG;AACvB,IAAA,QAAQ,EAAE,QAAQ;;;MC6BP,sBAAsB,GAAG,IAAI,cAAc,CAAsB,wBAAwB;;AC1CtG;;;;;;;;;;;;;;AAcG;AACG,SAAU,eAAe,CAAC,KAAgC,EAAA;AAC9D,IAAA,IAAI,CAAC,KAAK;AAAE,QAAA,OAAO,KAAK;AACxB,IAAA,IAAI,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC;AAAE,QAAA,OAAO,KAAK;AACtC,IAAA,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;AAAE,QAAA,OAAO,KAAK;IACxC,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;AAAE,QAAA,OAAO,KAAK;AAC9E,IAAA,OAAO,IAAI;AACb;AAEM,SAAU,iBAAiB,CAC/B,KAAgC,EAChC,UAAkB,EAAA;AAElB,IAAA,OAAO,eAAe,CAAC,KAAK,CAAC,GAAI,KAAgB,GAAG,UAAU;AAChE;;ACOO,MAAM,iCAAiC,GAAG;;AC/BjD;;;;;;;;;;;AAWG;AACG,SAAU,gBAAgB,CAAC,MAAuB,EAAE,MAAc,EAAA;AACtE,IAAA,IAAI,SAAS,EAAE,IAAI,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE;QACnE,MAAM,GAAG,IAAI;AACb,QAAA,OAAO,CAAC,IAAI,CACV,gCAAgC,MAAM,CAAC,QAAQ,CAAA,8CAAA;cAC7C,CAAA,wFAAA;AACA,cAAA,CAAA,uGAAA,CAAyG,CAC5G;IACH;IAEA,OAAO,MAAM,CAAC,QAAQ;AACxB;AAEA,IAAI,MAAM,GAAG,KAAK;AAElB,SAAS,WAAW,CAAC,MAAc,EAAE,GAAW,EAAA;IAC9C,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAClE,OAAO,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;AACvC;AAEA,SAAS,OAAO,CAAC,MAAe,EAAE,MAAc,EAAE,MAAM,GAAG,EAAE,EAAA;AAC3D,IAAA,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,IAAG;QACzB,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;QACjE,IAAI,IAAI,KAAK,MAAM;AAAE,YAAA,OAAO,IAAI;;;QAGhC,OAAO,KAAK,CAAC,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,KAAK;AACvE,IAAA,CAAC,CAAC;AACJ;;AC5CA;;AAEG;;;;"}
|
|
@@ -1,45 +1,63 @@
|
|
|
1
1
|
import { SPARK_AUTH_ROUTE_PATHS } from '@mintplayer/ng-spark-auth/models';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
? () => Promise.resolve(entry.component)
|
|
14
|
-
: defaultLoader,
|
|
15
|
-
};
|
|
3
|
+
/** The routed path for an entry, independent of how its component is loaded. */
|
|
4
|
+
function entryPath(entry, defaultPath) {
|
|
5
|
+
if (entry === undefined)
|
|
6
|
+
return defaultPath;
|
|
7
|
+
return typeof entry === 'string' ? entry : entry.path;
|
|
8
|
+
}
|
|
9
|
+
/** An explicitly supplied component wins over the library's lazy import. */
|
|
10
|
+
function child(entry, path, defaultLoader) {
|
|
11
|
+
const component = typeof entry === 'object' && entry.component ? entry.component : undefined;
|
|
12
|
+
return { path, loadComponent: component ? () => Promise.resolve(component) : defaultLoader };
|
|
16
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* The routes for Spark's authentication pages.
|
|
16
|
+
*
|
|
17
|
+
* `config.localCredentials` chooses how much of the email/password family to route, mirroring the
|
|
18
|
+
* server's `SparkLocalCredentials`. `GET /spark/auth/capabilities` reports what the server is
|
|
19
|
+
* actually running, so the two can be checked against each other.
|
|
20
|
+
*
|
|
21
|
+
* The `import()` expressions live *inside* the branches that need them. That placement is the point:
|
|
22
|
+
* a bundler decides whether to emit a lazy chunk from whether the `import()` call site is reachable,
|
|
23
|
+
* not from whether the route object referencing it survives — so filtering the children array after
|
|
24
|
+
* the fact would still ship every page. Excluded pages must have no reachable `import()` at all.
|
|
25
|
+
*
|
|
26
|
+
* `SPARK_AUTH_ROUTE_PATHS` is still provided in full. The excluded pages are excluded together, so
|
|
27
|
+
* nothing that survives can link to something that does not, and the token stays `Required`.
|
|
28
|
+
*/
|
|
17
29
|
function sparkAuthRoutes(config) {
|
|
18
|
-
const
|
|
19
|
-
const twoFactor = resolveEntry(config?.twoFactor, 'login/two-factor', () => import('@mintplayer/ng-spark-auth/two-factor').then(m => m.SparkTwoFactorComponent));
|
|
20
|
-
const register = resolveEntry(config?.register, 'register', () => import('@mintplayer/ng-spark-auth/register').then(m => m.SparkRegisterComponent));
|
|
21
|
-
const forgotPassword = resolveEntry(config?.forgotPassword, 'forgot-password', () => import('@mintplayer/ng-spark-auth/forgot-password').then(m => m.SparkForgotPasswordComponent));
|
|
22
|
-
const resetPassword = resolveEntry(config?.resetPassword, 'reset-password', () => import('@mintplayer/ng-spark-auth/reset-password').then(m => m.SparkResetPasswordComponent));
|
|
30
|
+
const mode = config?.localCredentials ?? 'full';
|
|
23
31
|
const paths = {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
signIn: '/' + entryPath(config?.signIn, 'sign-in'),
|
|
33
|
+
login: '/' + entryPath(config?.login, 'login'),
|
|
34
|
+
twoFactor: '/' + entryPath(config?.twoFactor, 'login/two-factor'),
|
|
35
|
+
register: '/' + entryPath(config?.register, 'register'),
|
|
36
|
+
forgotPassword: '/' + entryPath(config?.forgotPassword, 'forgot-password'),
|
|
37
|
+
resetPassword: '/' + entryPath(config?.resetPassword, 'reset-password'),
|
|
29
38
|
};
|
|
39
|
+
const children = [];
|
|
40
|
+
// Only when local credentials are limited. In 'full' mode the login page is already the landing
|
|
41
|
+
// page, and emitting a second one would change the routes of every app that never opted in.
|
|
42
|
+
if (mode !== 'full') {
|
|
43
|
+
children.push(child(config?.signIn, entryPath(config?.signIn, 'sign-in'), () => import('@mintplayer/ng-spark-auth/sign-in').then(m => m.SparkSignInComponent)));
|
|
44
|
+
}
|
|
45
|
+
if (mode !== 'disabled') {
|
|
46
|
+
children.push(child(config?.login, entryPath(config?.login, 'login'), () => import('@mintplayer/ng-spark-auth/login').then(m => m.SparkLoginComponent)),
|
|
47
|
+
// Reachable only from the login page's RequiresTwoFactor branch, and it posts to the same
|
|
48
|
+
// /login endpoint — so it belongs to password sign-in, not to authentication in general.
|
|
49
|
+
child(config?.twoFactor, entryPath(config?.twoFactor, 'login/two-factor'), () => import('@mintplayer/ng-spark-auth/two-factor').then(m => m.SparkTwoFactorComponent)), child(config?.forgotPassword, entryPath(config?.forgotPassword, 'forgot-password'), () => import('@mintplayer/ng-spark-auth/forgot-password').then(m => m.SparkForgotPasswordComponent)), child(config?.resetPassword, entryPath(config?.resetPassword, 'reset-password'), () => import('@mintplayer/ng-spark-auth/reset-password').then(m => m.SparkResetPasswordComponent)));
|
|
50
|
+
}
|
|
51
|
+
if (mode === 'full') {
|
|
52
|
+
children.push(child(config?.register, entryPath(config?.register, 'register'), () => import('@mintplayer/ng-spark-auth/register').then(m => m.SparkRegisterComponent)));
|
|
53
|
+
}
|
|
30
54
|
return [
|
|
31
55
|
{
|
|
32
56
|
path: '',
|
|
33
57
|
providers: [
|
|
34
58
|
{ provide: SPARK_AUTH_ROUTE_PATHS, useValue: paths },
|
|
35
59
|
],
|
|
36
|
-
children
|
|
37
|
-
{ path: login.path, loadComponent: login.loadComponent },
|
|
38
|
-
{ path: twoFactor.path, loadComponent: twoFactor.loadComponent },
|
|
39
|
-
{ path: register.path, loadComponent: register.loadComponent },
|
|
40
|
-
{ path: forgotPassword.path, loadComponent: forgotPassword.loadComponent },
|
|
41
|
-
{ path: resetPassword.path, loadComponent: resetPassword.loadComponent },
|
|
42
|
-
],
|
|
60
|
+
children,
|
|
43
61
|
},
|
|
44
62
|
];
|
|
45
63
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mintplayer-ng-spark-auth-routes.mjs","sources":["../../routes/src/spark-auth-routes.ts","../../routes/mintplayer-ng-spark-auth-routes.ts"],"sourcesContent":["import { SPARK_AUTH_ROUTE_PATHS, SparkAuthRouteConfig, SparkAuthRouteEntry, SparkAuthRoutePaths } from '@mintplayer/ng-spark-auth/models';\n\ninterface
|
|
1
|
+
{"version":3,"file":"mintplayer-ng-spark-auth-routes.mjs","sources":["../../routes/src/spark-auth-routes.ts","../../routes/mintplayer-ng-spark-auth-routes.ts"],"sourcesContent":["import { SPARK_AUTH_ROUTE_PATHS, SparkAuthRouteConfig, SparkAuthRouteEntry, SparkAuthRoutePaths } from '@mintplayer/ng-spark-auth/models';\n\ntype Loader = () => Promise<any>;\n\ninterface Child {\n path: string;\n loadComponent: Loader;\n}\n\n/** The routed path for an entry, independent of how its component is loaded. */\nfunction entryPath(entry: SparkAuthRouteEntry | undefined, defaultPath: string): string {\n if (entry === undefined) return defaultPath;\n return typeof entry === 'string' ? entry : entry.path;\n}\n\n/** An explicitly supplied component wins over the library's lazy import. */\nfunction child(entry: SparkAuthRouteEntry | undefined, path: string, defaultLoader: Loader): Child {\n const component = typeof entry === 'object' && entry.component ? entry.component : undefined;\n return { path, loadComponent: component ? () => Promise.resolve(component) : defaultLoader };\n}\n\n/**\n * The routes for Spark's authentication pages.\n *\n * `config.localCredentials` chooses how much of the email/password family to route, mirroring the\n * server's `SparkLocalCredentials`. `GET /spark/auth/capabilities` reports what the server is\n * actually running, so the two can be checked against each other.\n *\n * The `import()` expressions live *inside* the branches that need them. That placement is the point:\n * a bundler decides whether to emit a lazy chunk from whether the `import()` call site is reachable,\n * not from whether the route object referencing it survives — so filtering the children array after\n * the fact would still ship every page. Excluded pages must have no reachable `import()` at all.\n *\n * `SPARK_AUTH_ROUTE_PATHS` is still provided in full. The excluded pages are excluded together, so\n * nothing that survives can link to something that does not, and the token stays `Required`.\n */\nexport function sparkAuthRoutes(config?: SparkAuthRouteConfig): any[] {\n const mode = config?.localCredentials ?? 'full';\n\n const paths: SparkAuthRoutePaths = {\n signIn: '/' + entryPath(config?.signIn, 'sign-in'),\n login: '/' + entryPath(config?.login, 'login'),\n twoFactor: '/' + entryPath(config?.twoFactor, 'login/two-factor'),\n register: '/' + entryPath(config?.register, 'register'),\n forgotPassword: '/' + entryPath(config?.forgotPassword, 'forgot-password'),\n resetPassword: '/' + entryPath(config?.resetPassword, 'reset-password'),\n };\n\n const children: Child[] = [];\n\n // Only when local credentials are limited. In 'full' mode the login page is already the landing\n // page, and emitting a second one would change the routes of every app that never opted in.\n if (mode !== 'full') {\n children.push(\n child(config?.signIn, entryPath(config?.signIn, 'sign-in'),\n () => import('@mintplayer/ng-spark-auth/sign-in').then(m => m.SparkSignInComponent)),\n );\n }\n\n if (mode !== 'disabled') {\n children.push(\n child(config?.login, entryPath(config?.login, 'login'),\n () => import('@mintplayer/ng-spark-auth/login').then(m => m.SparkLoginComponent)),\n // Reachable only from the login page's RequiresTwoFactor branch, and it posts to the same\n // /login endpoint — so it belongs to password sign-in, not to authentication in general.\n child(config?.twoFactor, entryPath(config?.twoFactor, 'login/two-factor'),\n () => import('@mintplayer/ng-spark-auth/two-factor').then(m => m.SparkTwoFactorComponent)),\n child(config?.forgotPassword, entryPath(config?.forgotPassword, 'forgot-password'),\n () => import('@mintplayer/ng-spark-auth/forgot-password').then(m => m.SparkForgotPasswordComponent)),\n child(config?.resetPassword, entryPath(config?.resetPassword, 'reset-password'),\n () => import('@mintplayer/ng-spark-auth/reset-password').then(m => m.SparkResetPasswordComponent)),\n );\n }\n\n if (mode === 'full') {\n children.push(\n child(config?.register, entryPath(config?.register, 'register'),\n () => import('@mintplayer/ng-spark-auth/register').then(m => m.SparkRegisterComponent)),\n );\n }\n\n return [\n {\n path: '',\n providers: [\n { provide: SPARK_AUTH_ROUTE_PATHS, useValue: paths },\n ],\n children,\n },\n ];\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, signal, isDevMode, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { RouterLink } from '@angular/router';
|
|
4
|
+
import { Color } from '@mintplayer/ng-bootstrap';
|
|
5
|
+
import { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';
|
|
6
|
+
import { BsCardComponent, BsCardHeaderComponent } from '@mintplayer/ng-bootstrap/card';
|
|
7
|
+
import { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';
|
|
8
|
+
import { SPARK_AUTH_ROUTE_PATHS } from '@mintplayer/ng-spark-auth/models';
|
|
9
|
+
import { SparkAuthService } from '@mintplayer/ng-spark-auth/core';
|
|
10
|
+
import { TranslateKeyPipe } from '@mintplayer/ng-spark-auth/pipes';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The sign-in landing page for an application whose local credentials are turned off — a button per
|
|
14
|
+
* external provider, and a link to the password form when there still is one.
|
|
15
|
+
*
|
|
16
|
+
* The providers come from `GET /spark/auth/capabilities` rather than from a list the application
|
|
17
|
+
* hard-codes. That is the point of the component: every consumer that hand-rolled this before wrote
|
|
18
|
+
* the scheme name as a string literal, which is a silent mismatch waiting to happen the moment the
|
|
19
|
+
* server's provider registration changes.
|
|
20
|
+
*/
|
|
21
|
+
class SparkSignInComponent {
|
|
22
|
+
authService = inject(SparkAuthService);
|
|
23
|
+
routePaths = inject(SPARK_AUTH_ROUTE_PATHS);
|
|
24
|
+
colors = Color;
|
|
25
|
+
providers = signal([], /* @ts-ignore */
|
|
26
|
+
...(ngDevMode ? [{ debugName: "providers" }] : /* istanbul ignore next */ []));
|
|
27
|
+
localCredentialsAvailable = signal(false, /* @ts-ignore */
|
|
28
|
+
...(ngDevMode ? [{ debugName: "localCredentialsAvailable" }] : /* istanbul ignore next */ []));
|
|
29
|
+
loading = signal(true, /* @ts-ignore */
|
|
30
|
+
...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
31
|
+
failed = signal(false, /* @ts-ignore */
|
|
32
|
+
...(ngDevMode ? [{ debugName: "failed" }] : /* istanbul ignore next */ []));
|
|
33
|
+
constructor() {
|
|
34
|
+
void this.load();
|
|
35
|
+
}
|
|
36
|
+
async load() {
|
|
37
|
+
try {
|
|
38
|
+
const capabilities = await this.authService.capabilities();
|
|
39
|
+
this.providers.set(capabilities.externalProviders);
|
|
40
|
+
this.localCredentialsAvailable.set(capabilities.localCredentials !== 'Disabled');
|
|
41
|
+
this.warnOnMismatch(capabilities);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// A sign-in page that renders nothing looks identical to one whose providers all failed to
|
|
45
|
+
// load, so say which happened rather than showing an empty page.
|
|
46
|
+
this.failed.set(true);
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
this.loading.set(false);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The routes are a build-time decision and the server's mode a deployment-time one, so they can
|
|
54
|
+
* disagree without anything failing loudly. Reaching this page at all means the app routed it,
|
|
55
|
+
* which it only does when local credentials are meant to be limited.
|
|
56
|
+
*/
|
|
57
|
+
warnOnMismatch(capabilities) {
|
|
58
|
+
if (isDevMode() && capabilities.localCredentials === 'Full') {
|
|
59
|
+
console.warn('[ng-spark-auth] This app routes the sign-in landing page, but the server reports '
|
|
60
|
+
+ 'localCredentials = Full. The two are configured independently — check that '
|
|
61
|
+
+ "sparkAuthRoutes({ localCredentials: … }) matches AddAuthentication's LocalCredentials mode.");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
signInWith(provider) {
|
|
65
|
+
void this.authService.loginWithProvider(provider.scheme);
|
|
66
|
+
}
|
|
67
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkSignInComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
68
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: SparkSignInComponent, isStandalone: true, selector: "spark-sign-in", ngImport: i0, template: "<div class=\"d-flex justify-content-center\">\n <bs-card style=\"width: 100%; max-width: 400px;\">\n <bs-card-header>\n <h3 class=\"mb-0 text-center\">{{ 'auth.login' | t }}</h3>\n </bs-card-header>\n <div class=\"p-4\">\n @if (loading()) {\n <div class=\"text-center\"><bs-spinner /></div>\n } @else if (failed()) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-0\">{{ 'auth.signInUnavailable' | t }}</bs-alert>\n } @else {\n @for (provider of providers(); track provider.scheme) {\n <button type=\"button\" class=\"btn btn-outline-primary w-100 mb-2\" (click)=\"signInWith(provider)\">\n {{ provider.displayName }}\n </button>\n } @empty {\n <bs-alert [type]=\"colors.warning\" class=\"mb-0\">{{ 'auth.noSignInMethods' | t }}</bs-alert>\n }\n\n @if (localCredentialsAvailable()) {\n <div class=\"text-center mt-3\">\n <a [routerLink]=\"routePaths.login\">{{ 'auth.login' | t }}</a>\n </div>\n }\n }\n </div>\n </bs-card>\n</div>\n", dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "component", type: BsAlertComponent, selector: "bs-alert", inputs: ["type", "announce", "isVisible"], outputs: ["isVisibleChange", "afterOpenedOrClosed"] }, { kind: "component", type: BsCardComponent, selector: "bs-card", inputs: ["color", "outline"] }, { kind: "component", type: BsCardHeaderComponent, selector: "bs-card-header", inputs: ["color", "navStyle"] }, { kind: "component", type: BsSpinnerComponent, selector: "bs-spinner", inputs: ["type", "color"] }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
69
|
+
}
|
|
70
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkSignInComponent, decorators: [{
|
|
71
|
+
type: Component,
|
|
72
|
+
args: [{ selector: 'spark-sign-in', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [RouterLink, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsSpinnerComponent, TranslateKeyPipe], template: "<div class=\"d-flex justify-content-center\">\n <bs-card style=\"width: 100%; max-width: 400px;\">\n <bs-card-header>\n <h3 class=\"mb-0 text-center\">{{ 'auth.login' | t }}</h3>\n </bs-card-header>\n <div class=\"p-4\">\n @if (loading()) {\n <div class=\"text-center\"><bs-spinner /></div>\n } @else if (failed()) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-0\">{{ 'auth.signInUnavailable' | t }}</bs-alert>\n } @else {\n @for (provider of providers(); track provider.scheme) {\n <button type=\"button\" class=\"btn btn-outline-primary w-100 mb-2\" (click)=\"signInWith(provider)\">\n {{ provider.displayName }}\n </button>\n } @empty {\n <bs-alert [type]=\"colors.warning\" class=\"mb-0\">{{ 'auth.noSignInMethods' | t }}</bs-alert>\n }\n\n @if (localCredentialsAvailable()) {\n <div class=\"text-center mt-3\">\n <a [routerLink]=\"routePaths.login\">{{ 'auth.login' | t }}</a>\n </div>\n }\n }\n </div>\n </bs-card>\n</div>\n" }]
|
|
73
|
+
}], ctorParameters: () => [] });
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Generated bundle index. Do not edit.
|
|
77
|
+
*/
|
|
78
|
+
|
|
79
|
+
export { SparkSignInComponent };
|
|
80
|
+
//# sourceMappingURL=mintplayer-ng-spark-auth-sign-in.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mintplayer-ng-spark-auth-sign-in.mjs","sources":["../../sign-in/src/spark-sign-in.component.ts","../../sign-in/src/spark-sign-in.component.html","../../sign-in/mintplayer-ng-spark-auth-sign-in.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, inject, isDevMode, signal } from '@angular/core';\nimport { RouterLink } 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 { SPARK_AUTH_ROUTE_PATHS, SparkAuthCapabilities, SparkExternalProvider } from '@mintplayer/ng-spark-auth/models';\nimport { SparkAuthService } from '@mintplayer/ng-spark-auth/core';\nimport { TranslateKeyPipe } from '@mintplayer/ng-spark-auth/pipes';\n\n/**\n * The sign-in landing page for an application whose local credentials are turned off — a button per\n * external provider, and a link to the password form when there still is one.\n *\n * The providers come from `GET /spark/auth/capabilities` rather than from a list the application\n * hard-codes. That is the point of the component: every consumer that hand-rolled this before wrote\n * the scheme name as a string literal, which is a silent mismatch waiting to happen the moment the\n * server's provider registration changes.\n */\n@Component({\n selector: 'spark-sign-in',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [RouterLink, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsSpinnerComponent, TranslateKeyPipe],\n templateUrl: './spark-sign-in.component.html',\n})\nexport class SparkSignInComponent {\n private readonly authService = inject(SparkAuthService);\n readonly routePaths = inject(SPARK_AUTH_ROUTE_PATHS);\n readonly colors = Color;\n\n readonly providers = signal<SparkExternalProvider[]>([]);\n readonly localCredentialsAvailable = signal(false);\n readonly loading = signal(true);\n readonly failed = signal(false);\n\n constructor() {\n void this.load();\n }\n\n private async load(): Promise<void> {\n try {\n const capabilities = await this.authService.capabilities();\n this.providers.set(capabilities.externalProviders);\n this.localCredentialsAvailable.set(capabilities.localCredentials !== 'Disabled');\n this.warnOnMismatch(capabilities);\n } catch {\n // A sign-in page that renders nothing looks identical to one whose providers all failed to\n // load, so say which happened rather than showing an empty page.\n this.failed.set(true);\n } finally {\n this.loading.set(false);\n }\n }\n\n /**\n * The routes are a build-time decision and the server's mode a deployment-time one, so they can\n * disagree without anything failing loudly. Reaching this page at all means the app routed it,\n * which it only does when local credentials are meant to be limited.\n */\n private warnOnMismatch(capabilities: SparkAuthCapabilities): void {\n if (isDevMode() && capabilities.localCredentials === 'Full') {\n console.warn(\n '[ng-spark-auth] This app routes the sign-in landing page, but the server reports '\n + 'localCredentials = Full. The two are configured independently — check that '\n + \"sparkAuthRoutes({ localCredentials: … }) matches AddAuthentication's LocalCredentials mode.\",\n );\n }\n }\n\n signInWith(provider: SparkExternalProvider): void {\n void this.authService.loginWithProvider(provider.scheme);\n }\n}\n\nexport default SparkSignInComponent;\n","<div class=\"d-flex justify-content-center\">\n <bs-card style=\"width: 100%; max-width: 400px;\">\n <bs-card-header>\n <h3 class=\"mb-0 text-center\">{{ 'auth.login' | t }}</h3>\n </bs-card-header>\n <div class=\"p-4\">\n @if (loading()) {\n <div class=\"text-center\"><bs-spinner /></div>\n } @else if (failed()) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-0\">{{ 'auth.signInUnavailable' | t }}</bs-alert>\n } @else {\n @for (provider of providers(); track provider.scheme) {\n <button type=\"button\" class=\"btn btn-outline-primary w-100 mb-2\" (click)=\"signInWith(provider)\">\n {{ provider.displayName }}\n </button>\n } @empty {\n <bs-alert [type]=\"colors.warning\" class=\"mb-0\">{{ 'auth.noSignInMethods' | t }}</bs-alert>\n }\n\n @if (localCredentialsAvailable()) {\n <div class=\"text-center mt-3\">\n <a [routerLink]=\"routePaths.login\">{{ 'auth.login' | t }}</a>\n </div>\n }\n }\n </div>\n </bs-card>\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mintplayer/ng-spark-auth",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "22.
|
|
4
|
+
"version": "22.2.0",
|
|
5
5
|
"description": "Angular authentication library for MintPlayer.Spark",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -71,6 +71,10 @@
|
|
|
71
71
|
"types": "./types/mintplayer-ng-spark-auth-routes.d.ts",
|
|
72
72
|
"default": "./fesm2022/mintplayer-ng-spark-auth-routes.mjs"
|
|
73
73
|
},
|
|
74
|
+
"./sign-in": {
|
|
75
|
+
"types": "./types/mintplayer-ng-spark-auth-sign-in.d.ts",
|
|
76
|
+
"default": "./fesm2022/mintplayer-ng-spark-auth-sign-in.mjs"
|
|
77
|
+
},
|
|
74
78
|
"./two-factor": {
|
|
75
79
|
"types": "./types/mintplayer-ng-spark-auth-two-factor.d.ts",
|
|
76
80
|
"default": "./fesm2022/mintplayer-ng-spark-auth-two-factor.mjs"
|
|
@@ -6,6 +6,8 @@ declare class SparkAuthBarComponent {
|
|
|
6
6
|
readonly authService: SparkAuthService;
|
|
7
7
|
readonly config: _mintplayer_ng_spark_auth_models.SparkAuthConfig;
|
|
8
8
|
private readonly router;
|
|
9
|
+
/** Same target the guard and interceptor use, so the bar cannot link somewhere they would not send you. */
|
|
10
|
+
readonly signInUrl: string;
|
|
9
11
|
onLogout(): Promise<void>;
|
|
10
12
|
static ɵfac: i0.ɵɵFactoryDeclaration<SparkAuthBarComponent, never>;
|
|
11
13
|
static ɵcmp: i0.ɵɵComponentDeclaration<SparkAuthBarComponent, "spark-auth-bar", never, {}, {}, never, never, true, never>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { AuthUser, SparkExternalLoginOptions, SparkExternalLoginResult } from '@mintplayer/ng-spark-auth/models';
|
|
2
|
+
import { AuthUser, SparkAuthCapabilities, SparkExternalLoginOptions, SparkExternalLoginResult } from '@mintplayer/ng-spark-auth/models';
|
|
3
3
|
|
|
4
4
|
declare class SparkAuthService {
|
|
5
5
|
private readonly http;
|
|
@@ -13,6 +13,13 @@ declare class SparkAuthService {
|
|
|
13
13
|
loginTwoFactor(twoFactorCode: string, twoFactorRecoveryCode?: string): Promise<void>;
|
|
14
14
|
register(email: string, password: string): Promise<void>;
|
|
15
15
|
logout(): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* What sign-in methods this deployment actually offers.
|
|
18
|
+
*
|
|
19
|
+
* Read from the server rather than assumed from the client's own route configuration: the two are
|
|
20
|
+
* configured independently, and a mismatch is otherwise invisible until a user hits it.
|
|
21
|
+
*/
|
|
22
|
+
capabilities(): Promise<SparkAuthCapabilities>;
|
|
16
23
|
csrfRefresh(): Promise<void>;
|
|
17
24
|
checkAuth(): Promise<AuthUser | null>;
|
|
18
25
|
/**
|
|
@@ -7,7 +7,7 @@ declare class SparkForgotPasswordComponent {
|
|
|
7
7
|
private readonly fb;
|
|
8
8
|
private readonly authService;
|
|
9
9
|
private readonly translation;
|
|
10
|
-
readonly routePaths: Required<Record<keyof _mintplayer_ng_spark_auth_models.
|
|
10
|
+
readonly routePaths: Required<Record<keyof _mintplayer_ng_spark_auth_models.SparkAuthRouteEntries, string>>;
|
|
11
11
|
colors: typeof Color;
|
|
12
12
|
readonly loading: _angular_core.WritableSignal<boolean>;
|
|
13
13
|
readonly errorMessage: _angular_core.WritableSignal<string>;
|
|
@@ -10,7 +10,7 @@ declare class SparkLoginComponent {
|
|
|
10
10
|
private readonly route;
|
|
11
11
|
private readonly config;
|
|
12
12
|
private readonly translation;
|
|
13
|
-
readonly routePaths: Required<Record<keyof _mintplayer_ng_spark_auth_models.
|
|
13
|
+
readonly routePaths: Required<Record<keyof _mintplayer_ng_spark_auth_models.SparkAuthRouteEntries, string>>;
|
|
14
14
|
colors: typeof Color;
|
|
15
15
|
readonly loading: _angular_core.WritableSignal<boolean>;
|
|
16
16
|
readonly errorMessage: _angular_core.WritableSignal<string>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { InjectionToken, Type } from '@angular/core';
|
|
2
|
+
import { Router } from '@angular/router';
|
|
2
3
|
|
|
3
4
|
interface AuthUser {
|
|
4
5
|
isAuthenticated: boolean;
|
|
@@ -15,19 +16,63 @@ interface SparkAuthConfig {
|
|
|
15
16
|
declare const SPARK_AUTH_CONFIG: InjectionToken<SparkAuthConfig>;
|
|
16
17
|
declare const defaultSparkAuthConfig: SparkAuthConfig;
|
|
17
18
|
|
|
19
|
+
/** An external provider a user can actually click, as reported by the server. */
|
|
20
|
+
interface SparkExternalProvider {
|
|
21
|
+
scheme: string;
|
|
22
|
+
displayName: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* What `GET /spark/auth/capabilities` reports: how much of the local-credential surface this
|
|
26
|
+
* application mounts, and which external providers are registered.
|
|
27
|
+
*
|
|
28
|
+
* This exists so the two tiers cannot silently disagree. The route config is a build-time choice
|
|
29
|
+
* and the server's mode is a deployment-time one; without a channel between them, a mismatch shows
|
|
30
|
+
* up as a form that posts into a 404, or a sign-in page with no way to sign in.
|
|
31
|
+
*/
|
|
32
|
+
interface SparkAuthCapabilities {
|
|
33
|
+
localCredentials: 'Full' | 'SignInOnly' | 'Disabled';
|
|
34
|
+
externalProviders: SparkExternalProvider[];
|
|
35
|
+
}
|
|
36
|
+
|
|
18
37
|
type SparkAuthRouteEntry = string | {
|
|
19
38
|
path: string;
|
|
20
39
|
component?: Type<unknown>;
|
|
21
40
|
};
|
|
22
|
-
|
|
41
|
+
/**
|
|
42
|
+
* How much of the local-credential (email + password) page family to route.
|
|
43
|
+
*
|
|
44
|
+
* Mirrors the server's `SparkLocalCredentials`, and for the same reason it is a mode rather
|
|
45
|
+
* than five independent switches: the pages form a star centred on the login page, and every
|
|
46
|
+
* template dereferences its siblings' paths unconditionally, so removing any proper subset
|
|
47
|
+
* leaves a dangling link. The family is the unit on both tiers.
|
|
48
|
+
*/
|
|
49
|
+
type SparkLocalCredentialsMode = 'full' | 'sign-in-only' | 'disabled';
|
|
50
|
+
/** The routable local-credential pages. Kept separate from {@link SparkAuthRouteConfig} so that
|
|
51
|
+
* adding non-route options below cannot widen {@link SparkAuthRoutePaths}. */
|
|
52
|
+
interface SparkAuthRouteEntries {
|
|
53
|
+
/** The provider-button landing page. Routed whenever local credentials are limited. */
|
|
54
|
+
signIn?: SparkAuthRouteEntry;
|
|
23
55
|
login?: SparkAuthRouteEntry;
|
|
24
56
|
twoFactor?: SparkAuthRouteEntry;
|
|
25
57
|
register?: SparkAuthRouteEntry;
|
|
26
58
|
forgotPassword?: SparkAuthRouteEntry;
|
|
27
59
|
resetPassword?: SparkAuthRouteEntry;
|
|
28
60
|
}
|
|
29
|
-
|
|
30
|
-
|
|
61
|
+
interface SparkAuthRouteConfig extends SparkAuthRouteEntries {
|
|
62
|
+
/**
|
|
63
|
+
* Defaults to `'full'`, which routes every page — the behaviour of every version before this
|
|
64
|
+
* option existed. Set it to match the server's `LocalCredentials` mode; `GET /spark/auth/capabilities`
|
|
65
|
+
* reports what the server is actually running.
|
|
66
|
+
*/
|
|
67
|
+
localCredentials?: SparkLocalCredentialsMode;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Every path is present regardless of mode. The pages that are dropped are dropped together, so no
|
|
71
|
+
* surviving template can dereference a missing sibling — which is what lets this stay `Required`
|
|
72
|
+
* and keeps the change additive for consumers.
|
|
73
|
+
*/
|
|
74
|
+
type SparkAuthRoutePaths = Required<Record<keyof SparkAuthRouteEntries, string>>;
|
|
75
|
+
declare const SPARK_AUTH_ROUTE_PATHS: InjectionToken<Required<Record<keyof SparkAuthRouteEntries, string>>>;
|
|
31
76
|
|
|
32
77
|
/**
|
|
33
78
|
* Shared validator for `returnUrl` query parameters consumed by the auth
|
|
@@ -74,5 +119,19 @@ interface SparkExternalLoginMessage {
|
|
|
74
119
|
}
|
|
75
120
|
declare const SPARK_EXTERNAL_LOGIN_MESSAGE_TYPE = "spark:external-login";
|
|
76
121
|
|
|
77
|
-
|
|
78
|
-
|
|
122
|
+
/**
|
|
123
|
+
* Where to send someone who is not signed in.
|
|
124
|
+
*
|
|
125
|
+
* `loginUrl` is the single source of truth for this, and the only thing the guard, the interceptor
|
|
126
|
+
* and the auth bar should consult — an application that turns off local credentials points it at
|
|
127
|
+
* its own sign-in landing route.
|
|
128
|
+
*
|
|
129
|
+
* The reason this is a function rather than a property read is the failure mode it closes: nothing
|
|
130
|
+
* connects `loginUrl` to the routes that actually exist, so pointing it at a route that was never
|
|
131
|
+
* registered produces a redirect into a blank page with no type error, no build failure and no
|
|
132
|
+
* runtime error. In development that now warns, once, naming the value and the fix.
|
|
133
|
+
*/
|
|
134
|
+
declare function resolveSignInUrl(config: SparkAuthConfig, router: Router): string;
|
|
135
|
+
|
|
136
|
+
export { SPARK_AUTH_CONFIG, SPARK_AUTH_ROUTE_PATHS, SPARK_EXTERNAL_LOGIN_MESSAGE_TYPE, defaultSparkAuthConfig, isSafeReturnUrl, resolveSignInUrl, sanitizeReturnUrl };
|
|
137
|
+
export type { AuthUser, SparkAuthCapabilities, SparkAuthConfig, SparkAuthRouteConfig, SparkAuthRouteEntries, SparkAuthRouteEntry, SparkAuthRoutePaths, SparkExternalLoginError, SparkExternalLoginMessage, SparkExternalLoginMode, SparkExternalLoginOptions, SparkExternalLoginResult, SparkExternalProvider, SparkLocalCredentialsMode };
|
|
@@ -8,7 +8,7 @@ declare class SparkRegisterComponent {
|
|
|
8
8
|
private readonly authService;
|
|
9
9
|
private readonly router;
|
|
10
10
|
private readonly translation;
|
|
11
|
-
readonly routePaths: Required<Record<keyof _mintplayer_ng_spark_auth_models.
|
|
11
|
+
readonly routePaths: Required<Record<keyof _mintplayer_ng_spark_auth_models.SparkAuthRouteEntries, string>>;
|
|
12
12
|
colors: typeof Color;
|
|
13
13
|
readonly loading: _angular_core.WritableSignal<boolean>;
|
|
14
14
|
readonly errorMessage: _angular_core.WritableSignal<string>;
|
|
@@ -10,7 +10,7 @@ declare class SparkResetPasswordComponent implements OnInit {
|
|
|
10
10
|
private readonly router;
|
|
11
11
|
private readonly route;
|
|
12
12
|
private readonly translation;
|
|
13
|
-
readonly routePaths: Required<Record<keyof _mintplayer_ng_spark_auth_models.
|
|
13
|
+
readonly routePaths: Required<Record<keyof _mintplayer_ng_spark_auth_models.SparkAuthRouteEntries, string>>;
|
|
14
14
|
colors: typeof Color;
|
|
15
15
|
readonly loading: _angular_core.WritableSignal<boolean>;
|
|
16
16
|
readonly errorMessage: _angular_core.WritableSignal<string>;
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import { SparkAuthRouteConfig } from '@mintplayer/ng-spark-auth/models';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* The routes for Spark's authentication pages.
|
|
5
|
+
*
|
|
6
|
+
* `config.localCredentials` chooses how much of the email/password family to route, mirroring the
|
|
7
|
+
* server's `SparkLocalCredentials`. `GET /spark/auth/capabilities` reports what the server is
|
|
8
|
+
* actually running, so the two can be checked against each other.
|
|
9
|
+
*
|
|
10
|
+
* The `import()` expressions live *inside* the branches that need them. That placement is the point:
|
|
11
|
+
* a bundler decides whether to emit a lazy chunk from whether the `import()` call site is reachable,
|
|
12
|
+
* not from whether the route object referencing it survives — so filtering the children array after
|
|
13
|
+
* the fact would still ship every page. Excluded pages must have no reachable `import()` at all.
|
|
14
|
+
*
|
|
15
|
+
* `SPARK_AUTH_ROUTE_PATHS` is still provided in full. The excluded pages are excluded together, so
|
|
16
|
+
* nothing that survives can link to something that does not, and the token stays `Required`.
|
|
17
|
+
*/
|
|
3
18
|
declare function sparkAuthRoutes(config?: SparkAuthRouteConfig): any[];
|
|
4
19
|
|
|
5
20
|
export { sparkAuthRoutes };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as _angular_core from '@angular/core';
|
|
2
|
+
import * as _mintplayer_ng_spark_auth_models from '@mintplayer/ng-spark-auth/models';
|
|
3
|
+
import { SparkExternalProvider } from '@mintplayer/ng-spark-auth/models';
|
|
4
|
+
import { Color } from '@mintplayer/ng-bootstrap';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The sign-in landing page for an application whose local credentials are turned off — a button per
|
|
8
|
+
* external provider, and a link to the password form when there still is one.
|
|
9
|
+
*
|
|
10
|
+
* The providers come from `GET /spark/auth/capabilities` rather than from a list the application
|
|
11
|
+
* hard-codes. That is the point of the component: every consumer that hand-rolled this before wrote
|
|
12
|
+
* the scheme name as a string literal, which is a silent mismatch waiting to happen the moment the
|
|
13
|
+
* server's provider registration changes.
|
|
14
|
+
*/
|
|
15
|
+
declare class SparkSignInComponent {
|
|
16
|
+
private readonly authService;
|
|
17
|
+
readonly routePaths: Required<Record<keyof _mintplayer_ng_spark_auth_models.SparkAuthRouteEntries, string>>;
|
|
18
|
+
readonly colors: typeof Color;
|
|
19
|
+
readonly providers: _angular_core.WritableSignal<SparkExternalProvider[]>;
|
|
20
|
+
readonly localCredentialsAvailable: _angular_core.WritableSignal<boolean>;
|
|
21
|
+
readonly loading: _angular_core.WritableSignal<boolean>;
|
|
22
|
+
readonly failed: _angular_core.WritableSignal<boolean>;
|
|
23
|
+
constructor();
|
|
24
|
+
private load;
|
|
25
|
+
/**
|
|
26
|
+
* The routes are a build-time decision and the server's mode a deployment-time one, so they can
|
|
27
|
+
* disagree without anything failing loudly. Reaching this page at all means the app routed it,
|
|
28
|
+
* which it only does when local credentials are meant to be limited.
|
|
29
|
+
*/
|
|
30
|
+
private warnOnMismatch;
|
|
31
|
+
signInWith(provider: SparkExternalProvider): void;
|
|
32
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SparkSignInComponent, never>;
|
|
33
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<SparkSignInComponent, "spark-sign-in", never, {}, {}, never, never, true, never>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export { SparkSignInComponent };
|
|
@@ -10,7 +10,7 @@ declare class SparkTwoFactorComponent {
|
|
|
10
10
|
private readonly route;
|
|
11
11
|
private readonly config;
|
|
12
12
|
private readonly translation;
|
|
13
|
-
readonly routePaths: Required<Record<keyof _mintplayer_ng_spark_auth_models.
|
|
13
|
+
readonly routePaths: Required<Record<keyof _mintplayer_ng_spark_auth_models.SparkAuthRouteEntries, string>>;
|
|
14
14
|
colors: typeof Color;
|
|
15
15
|
readonly loading: _angular_core.WritableSignal<boolean>;
|
|
16
16
|
readonly errorMessage: _angular_core.WritableSignal<string>;
|