@mintplayer/ng-spark-auth 22.0.1 → 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 +73 -2
- package/fesm2022/mintplayer-ng-spark-auth-core.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-auth-forgot-password.mjs +1 -1
- package/fesm2022/mintplayer-ng-spark-auth-forgot-password.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-login.mjs +1 -1
- package/fesm2022/mintplayer-ng-spark-auth-login.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-auth-models.mjs +41 -2
- package/fesm2022/mintplayer-ng-spark-auth-models.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-auth-register.mjs +1 -1
- package/fesm2022/mintplayer-ng-spark-auth-register.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-auth-reset-password.mjs +1 -1
- package/fesm2022/mintplayer-ng-spark-auth-reset-password.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/fesm2022/mintplayer-ng-spark-auth-two-factor.mjs +1 -1
- package/fesm2022/mintplayer-ng-spark-auth-two-factor.mjs.map +1 -1
- 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 +23 -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 +91 -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;;;;"}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, signal, computed, Injectable } from '@angular/core';
|
|
2
|
+
import { inject, NgZone, signal, computed, Injectable } from '@angular/core';
|
|
3
3
|
import { HttpClient } from '@angular/common/http';
|
|
4
4
|
import { firstValueFrom } from 'rxjs';
|
|
5
|
-
import { SPARK_AUTH_CONFIG } from '@mintplayer/ng-spark-auth/models';
|
|
5
|
+
import { SPARK_AUTH_CONFIG, SPARK_EXTERNAL_LOGIN_MESSAGE_TYPE } from '@mintplayer/ng-spark-auth/models';
|
|
6
6
|
|
|
7
|
+
/** How often the popup is checked for a manual close. */
|
|
8
|
+
const POPUP_CLOSE_POLL_MS = 400;
|
|
7
9
|
class SparkAuthService {
|
|
8
10
|
http = inject(HttpClient);
|
|
9
11
|
config = inject(SPARK_AUTH_CONFIG);
|
|
12
|
+
zone = inject(NgZone);
|
|
10
13
|
currentUser = signal(null, /* @ts-ignore */
|
|
11
14
|
...(ngDevMode ? [{ debugName: "currentUser" }] : /* istanbul ignore next */ []));
|
|
12
15
|
user = this.currentUser.asReadonly();
|
|
@@ -36,6 +39,15 @@ class SparkAuthService {
|
|
|
36
39
|
await this.csrfRefresh();
|
|
37
40
|
this.currentUser.set(null);
|
|
38
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
|
+
}
|
|
39
51
|
async csrfRefresh() {
|
|
40
52
|
await firstValueFrom(this.http.post(`${this.config.apiBasePath}/csrf-refresh`, {}));
|
|
41
53
|
}
|
|
@@ -50,6 +62,65 @@ class SparkAuthService {
|
|
|
50
62
|
return null;
|
|
51
63
|
}
|
|
52
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Signs in through an external provider (GitHub, Google, …) and leaves this service's
|
|
67
|
+
* `user()` signal up to date, exactly as `login()` does.
|
|
68
|
+
*
|
|
69
|
+
* The whole handshake lives here: opening the window, listening for the callback's
|
|
70
|
+
* message, checking its origin, detecting a popup the user closed by hand, tearing the
|
|
71
|
+
* listener and poll down on every exit path, and re-reading the session afterwards.
|
|
72
|
+
* Callers get an outcome, not a protocol — which is the point, because the previous
|
|
73
|
+
* hand-rolled version leaked its listener whenever the user simply closed the window.
|
|
74
|
+
*
|
|
75
|
+
* In `'redirect'` mode the returned promise never settles: this document is being
|
|
76
|
+
* replaced, and the outcome arrives as the next page load rather than as a value.
|
|
77
|
+
*/
|
|
78
|
+
loginWithProvider(provider, options = {}) {
|
|
79
|
+
const { returnUrl = this.config.defaultRedirectUrl, mode = 'popup' } = options;
|
|
80
|
+
const url = `${this.config.apiBasePath}/external-login?provider=${encodeURIComponent(provider)}`
|
|
81
|
+
+ `&returnUrl=${encodeURIComponent(returnUrl)}`;
|
|
82
|
+
if (mode === 'redirect') {
|
|
83
|
+
window.location.assign(url);
|
|
84
|
+
return new Promise(() => { });
|
|
85
|
+
}
|
|
86
|
+
return new Promise((resolve) => {
|
|
87
|
+
const popup = window.open(`${url}&popup=1`, 'spark-external-login', 'width=600,height=700');
|
|
88
|
+
if (!popup) {
|
|
89
|
+
resolve({ success: false, error: 'popup_blocked' });
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
// One settle path, so the listener and the poll cannot outlive the flow no matter
|
|
93
|
+
// which of the four ways it ends.
|
|
94
|
+
let settled = false;
|
|
95
|
+
const settle = (error) => {
|
|
96
|
+
if (settled)
|
|
97
|
+
return;
|
|
98
|
+
settled = true;
|
|
99
|
+
window.removeEventListener('message', onMessage);
|
|
100
|
+
clearInterval(poll);
|
|
101
|
+
popup.close();
|
|
102
|
+
this.zone.run(async () => {
|
|
103
|
+
if (!error)
|
|
104
|
+
await this.checkAuth();
|
|
105
|
+
resolve(error ? { success: false, error } : { success: true });
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
const onMessage = (event) => {
|
|
109
|
+
if (event.origin !== window.location.origin)
|
|
110
|
+
return;
|
|
111
|
+
if (event.data?.type !== SPARK_EXTERNAL_LOGIN_MESSAGE_TYPE)
|
|
112
|
+
return;
|
|
113
|
+
settle(event.data.success ? undefined : (event.data.error ?? 'no_login_info'));
|
|
114
|
+
};
|
|
115
|
+
// A user who closes the window never posts anything, so without this the promise
|
|
116
|
+
// and its listener would both live forever.
|
|
117
|
+
const poll = setInterval(() => {
|
|
118
|
+
if (popup.closed)
|
|
119
|
+
settle('popup_closed');
|
|
120
|
+
}, POPUP_CLOSE_POLL_MS);
|
|
121
|
+
window.addEventListener('message', onMessage);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
53
124
|
async forgotPassword(email) {
|
|
54
125
|
await firstValueFrom(this.http.post(`${this.config.apiBasePath}/forgotPassword`, { email }));
|
|
55
126
|
}
|
|
@@ -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, signal } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { firstValueFrom } from 'rxjs';\nimport { AuthUser, SPARK_AUTH_CONFIG } from '@mintplayer/ng-spark-auth/models';\n\n@Injectable({ providedIn: 'root' })\nexport class SparkAuthService {\n private readonly http = inject(HttpClient);\n private readonly config = inject(SPARK_AUTH_CONFIG);\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 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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"}
|
|
@@ -46,7 +46,7 @@ class SparkForgotPasswordComponent {
|
|
|
46
46
|
this.loading.set(false);
|
|
47
47
|
}
|
|
48
48
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkForgotPasswordComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
49
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: SparkForgotPasswordComponent, isStandalone: true, selector: "spark-forgot-password", 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.forgotPasswordTitle' | t }}</h3>\n </bs-card-header>\n <div class=\"p-4\">\n @if (successMessage()) {\n <bs-alert [type]=\"colors.success\" class=\"mb-3\">{{ successMessage() }}</bs-alert>\n }\n\n @if (errorMessage()) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">{{ errorMessage() }}</bs-alert>\n }\n\n @if (!successMessage()) {\n <p class=\"text-muted mb-3\">\n {{ 'auth.forgotPasswordDescription' | t }}\n </p>\n\n <bs-form>\n <form [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n <div class=\"mb-3\">\n <label for=\"email\" class=\"form-label\">{{ 'auth.email' | t }}</label>\n <input\n type=\"email\"\n id=\"email\"\n formControlName=\"email\"\n autocomplete=\"email\"\n />\n </div>\n\n <button\n type=\"submit\"\n class=\"btn btn-primary w-100\"\n [disabled]=\"loading()\"\n >\n @if (loading()) {\n <bs-spinner class=\"me-1\" />\n }\n {{ 'auth.sendResetLink' | t }}\n </button>\n </form>\n </bs-form>\n }\n\n <div class=\"mt-3 text-center\">\n <a [routerLink]=\"routePaths.login\">{{ 'auth.backToLogin' | t }}</a>\n </div>\n </div>\n </bs-card>\n</div>\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { 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", "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: BsFormComponent, selector: "bs-form", inputs: ["action", "method"], outputs: ["submitted"] }, { kind: "directive", type: BsFormControlDirective, selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)" }, { kind: "component", type: BsSpinnerComponent, selector: "bs-spinner", inputs: ["type", "color"] }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
49
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: SparkForgotPasswordComponent, isStandalone: true, selector: "spark-forgot-password", 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.forgotPasswordTitle' | t }}</h3>\n </bs-card-header>\n <div class=\"p-4\">\n @if (successMessage()) {\n <bs-alert [type]=\"colors.success\" class=\"mb-3\">{{ successMessage() }}</bs-alert>\n }\n\n @if (errorMessage()) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">{{ errorMessage() }}</bs-alert>\n }\n\n @if (!successMessage()) {\n <p class=\"text-muted mb-3\">\n {{ 'auth.forgotPasswordDescription' | t }}\n </p>\n\n <bs-form>\n <form [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n <div class=\"mb-3\">\n <label for=\"email\" class=\"form-label\">{{ 'auth.email' | t }}</label>\n <input\n type=\"email\"\n id=\"email\"\n formControlName=\"email\"\n autocomplete=\"email\"\n />\n </div>\n\n <button\n type=\"submit\"\n class=\"btn btn-primary w-100\"\n [disabled]=\"loading()\"\n >\n @if (loading()) {\n <bs-spinner class=\"me-1\" />\n }\n {{ 'auth.sendResetLink' | t }}\n </button>\n </form>\n </bs-form>\n }\n\n <div class=\"mt-3 text-center\">\n <a [routerLink]=\"routePaths.login\">{{ 'auth.backToLogin' | t }}</a>\n </div>\n </div>\n </bs-card>\n</div>\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { 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: BsFormComponent, selector: "bs-form", inputs: ["action", "method"], outputs: ["submitted"] }, { kind: "directive", type: BsFormControlDirective, selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)" }, { kind: "component", type: BsSpinnerComponent, selector: "bs-spinner", inputs: ["type", "color"] }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
50
50
|
}
|
|
51
51
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkForgotPasswordComponent, decorators: [{
|
|
52
52
|
type: Component,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mintplayer-ng-spark-auth-forgot-password.mjs","sources":["../../forgot-password/src/spark-forgot-password.component.ts","../../forgot-password/src/spark-forgot-password.component.html","../../forgot-password/mintplayer-ng-spark-auth-forgot-password.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';\nimport { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';\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 { BsFormComponent, BsFormControlDirective } from '@mintplayer/ng-bootstrap/form';\nimport { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';\nimport { SPARK_AUTH_ROUTE_PATHS } from '@mintplayer/ng-spark-auth/models';\nimport { SparkAuthService, SparkAuthTranslationService } from '@mintplayer/ng-spark-auth/core';\nimport { TranslateKeyPipe } from '@mintplayer/ng-spark-auth/pipes';\n\n@Component({\n selector: 'spark-forgot-password',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [ReactiveFormsModule, RouterLink, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsFormComponent, BsFormControlDirective, BsSpinnerComponent, TranslateKeyPipe],\n templateUrl: './spark-forgot-password.component.html',\n})\nexport class SparkForgotPasswordComponent {\n private readonly fb = inject(FormBuilder);\n private readonly authService = inject(SparkAuthService);\n private readonly translation = inject(SparkAuthTranslationService);\n readonly routePaths = inject(SPARK_AUTH_ROUTE_PATHS);\n\n colors = Color;\n readonly loading = signal(false);\n readonly errorMessage = signal('');\n readonly successMessage = signal('');\n\n readonly form = this.fb.group({\n email: ['', [Validators.required, Validators.email]],\n });\n\n async onSubmit(): Promise<void> {\n if (this.form.invalid) {\n this.form.markAllAsTouched();\n return;\n }\n\n this.loading.set(true);\n this.errorMessage.set('');\n this.successMessage.set('');\n\n const { email } = this.form.value;\n\n try {\n await this.authService.forgotPassword(email!);\n } catch {\n // Don't reveal whether the email exists\n }\n this.successMessage.set(this.translation.t('auth.forgotPasswordSuccess'));\n this.loading.set(false);\n }\n}\n\nexport default SparkForgotPasswordComponent;\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.forgotPasswordTitle' | t }}</h3>\n </bs-card-header>\n <div class=\"p-4\">\n @if (successMessage()) {\n <bs-alert [type]=\"colors.success\" class=\"mb-3\">{{ successMessage() }}</bs-alert>\n }\n\n @if (errorMessage()) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">{{ errorMessage() }}</bs-alert>\n }\n\n @if (!successMessage()) {\n <p class=\"text-muted mb-3\">\n {{ 'auth.forgotPasswordDescription' | t }}\n </p>\n\n <bs-form>\n <form [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n <div class=\"mb-3\">\n <label for=\"email\" class=\"form-label\">{{ 'auth.email' | t }}</label>\n <input\n type=\"email\"\n id=\"email\"\n formControlName=\"email\"\n autocomplete=\"email\"\n />\n </div>\n\n <button\n type=\"submit\"\n class=\"btn btn-primary w-100\"\n [disabled]=\"loading()\"\n >\n @if (loading()) {\n <bs-spinner class=\"me-1\" />\n }\n {{ 'auth.sendResetLink' | t }}\n </button>\n </form>\n </bs-form>\n }\n\n <div class=\"mt-3 text-center\">\n <a [routerLink]=\"routePaths.login\">{{ 'auth.backToLogin' | t }}</a>\n </div>\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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"}
|
|
1
|
+
{"version":3,"file":"mintplayer-ng-spark-auth-forgot-password.mjs","sources":["../../forgot-password/src/spark-forgot-password.component.ts","../../forgot-password/src/spark-forgot-password.component.html","../../forgot-password/mintplayer-ng-spark-auth-forgot-password.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';\nimport { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';\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 { BsFormComponent, BsFormControlDirective } from '@mintplayer/ng-bootstrap/form';\nimport { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';\nimport { SPARK_AUTH_ROUTE_PATHS } from '@mintplayer/ng-spark-auth/models';\nimport { SparkAuthService, SparkAuthTranslationService } from '@mintplayer/ng-spark-auth/core';\nimport { TranslateKeyPipe } from '@mintplayer/ng-spark-auth/pipes';\n\n@Component({\n selector: 'spark-forgot-password',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [ReactiveFormsModule, RouterLink, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsFormComponent, BsFormControlDirective, BsSpinnerComponent, TranslateKeyPipe],\n templateUrl: './spark-forgot-password.component.html',\n})\nexport class SparkForgotPasswordComponent {\n private readonly fb = inject(FormBuilder);\n private readonly authService = inject(SparkAuthService);\n private readonly translation = inject(SparkAuthTranslationService);\n readonly routePaths = inject(SPARK_AUTH_ROUTE_PATHS);\n\n colors = Color;\n readonly loading = signal(false);\n readonly errorMessage = signal('');\n readonly successMessage = signal('');\n\n readonly form = this.fb.group({\n email: ['', [Validators.required, Validators.email]],\n });\n\n async onSubmit(): Promise<void> {\n if (this.form.invalid) {\n this.form.markAllAsTouched();\n return;\n }\n\n this.loading.set(true);\n this.errorMessage.set('');\n this.successMessage.set('');\n\n const { email } = this.form.value;\n\n try {\n await this.authService.forgotPassword(email!);\n } catch {\n // Don't reveal whether the email exists\n }\n this.successMessage.set(this.translation.t('auth.forgotPasswordSuccess'));\n this.loading.set(false);\n }\n}\n\nexport default SparkForgotPasswordComponent;\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.forgotPasswordTitle' | t }}</h3>\n </bs-card-header>\n <div class=\"p-4\">\n @if (successMessage()) {\n <bs-alert [type]=\"colors.success\" class=\"mb-3\">{{ successMessage() }}</bs-alert>\n }\n\n @if (errorMessage()) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">{{ errorMessage() }}</bs-alert>\n }\n\n @if (!successMessage()) {\n <p class=\"text-muted mb-3\">\n {{ 'auth.forgotPasswordDescription' | t }}\n </p>\n\n <bs-form>\n <form [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n <div class=\"mb-3\">\n <label for=\"email\" class=\"form-label\">{{ 'auth.email' | t }}</label>\n <input\n type=\"email\"\n id=\"email\"\n formControlName=\"email\"\n autocomplete=\"email\"\n />\n </div>\n\n <button\n type=\"submit\"\n class=\"btn btn-primary w-100\"\n [disabled]=\"loading()\"\n >\n @if (loading()) {\n <bs-spinner class=\"me-1\" />\n }\n {{ 'auth.sendResetLink' | t }}\n </button>\n </form>\n </bs-form>\n }\n\n <div class=\"mt-3 text-center\">\n <a [routerLink]=\"routePaths.login\">{{ 'auth.backToLogin' | t }}</a>\n </div>\n </div>\n </bs-card>\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;;;;;;;;;MAmBa,4BAA4B,CAAA;AACtB,IAAA,EAAE,GAAG,MAAM,CAAC,WAAW,CAAC;AACxB,IAAA,WAAW,GAAG,MAAM,CAAC,gBAAgB,CAAC;AACtC,IAAA,WAAW,GAAG,MAAM,CAAC,2BAA2B,CAAC;AACzD,IAAA,UAAU,GAAG,MAAM,CAAC,sBAAsB,CAAC;IAEpD,MAAM,GAAG,KAAK;IACL,OAAO,GAAG,MAAM,CAAC,KAAK;gFAAC;IACvB,YAAY,GAAG,MAAM,CAAC,EAAE;qFAAC;IACzB,cAAc,GAAG,MAAM,CAAC,EAAE;uFAAC;AAE3B,IAAA,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC;AAC5B,QAAA,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;AACrD,KAAA,CAAC;AAEF,IAAA,MAAM,QAAQ,GAAA;AACZ,QAAA,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE;AACrB,YAAA,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE;YAC5B;QACF;AAEA,QAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;AACtB,QAAA,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC;AACzB,QAAA,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;QAE3B,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK;AAEjC,QAAA,IAAI;YACF,MAAM,IAAI,CAAC,WAAW,CAAC,cAAc,CAAC,KAAM,CAAC;QAC/C;AAAE,QAAA,MAAM;;QAER;AACA,QAAA,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,4BAA4B,CAAC,CAAC;AACzE,QAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;IACzB;uGAlCW,4BAA4B,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAA5B,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,4BAA4B,iFCnBzC,8kDAmDA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EDnCY,mBAAmB,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,aAAA,EAAA,QAAA,EAAA,8CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,oBAAA,EAAA,QAAA,EAAA,wSAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,eAAA,EAAA,QAAA,EAAA,2CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,oBAAA,EAAA,QAAA,EAAA,sGAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,kBAAA,EAAA,QAAA,EAAA,aAAA,EAAA,MAAA,EAAA,CAAA,WAAA,CAAA,EAAA,OAAA,EAAA,CAAA,UAAA,CAAA,EAAA,QAAA,EAAA,CAAA,QAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,eAAA,EAAA,QAAA,EAAA,mBAAA,EAAA,MAAA,EAAA,CAAA,iBAAA,EAAA,UAAA,EAAA,SAAA,CAAA,EAAA,OAAA,EAAA,CAAA,eAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAE,UAAU,kPAAE,gBAAgB,EAAA,QAAA,EAAA,UAAA,EAAA,MAAA,EAAA,CAAA,MAAA,EAAA,UAAA,EAAA,WAAA,CAAA,EAAA,OAAA,EAAA,CAAA,iBAAA,EAAA,qBAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAE,eAAe,EAAA,QAAA,EAAA,SAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,SAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAE,qBAAqB,0FAAE,eAAe,EAAA,QAAA,EAAA,SAAA,EAAA,MAAA,EAAA,CAAA,QAAA,EAAA,QAAA,CAAA,EAAA,OAAA,EAAA,CAAA,WAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAE,sBAAsB,EAAA,QAAA,EAAA,6EAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAE,kBAAkB,6EAAE,gBAAgB,EAAA,IAAA,EAAA,GAAA,EAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;2FAGvK,4BAA4B,EAAA,UAAA,EAAA,CAAA;kBAPxC,SAAS;+BACE,uBAAuB,EAAA,UAAA,EACrB,IAAI,EAAA,eAAA,EACC,uBAAuB,CAAC,MAAM,EAAA,OAAA,EACtC,CAAC,mBAAmB,EAAE,UAAU,EAAE,gBAAgB,EAAE,eAAe,EAAE,qBAAqB,EAAE,eAAe,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,gBAAgB,CAAC,EAAA,QAAA,EAAA,8kDAAA,EAAA;;;AEhBrL;;AAEG;;;;"}
|
|
@@ -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;;;;"}
|
|
@@ -64,7 +64,7 @@ class SparkLoginComponent {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkLoginComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
67
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: SparkLoginComponent, isStandalone: true, selector: "spark-login", 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 (errorMessage()) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">{{ errorMessage() }}</bs-alert>\n }\n\n <bs-form>\n <form [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n <div class=\"mb-3\">\n <label for=\"email\" class=\"form-label\">{{ 'auth.email' | t }}</label>\n <input\n type=\"text\"\n id=\"email\"\n formControlName=\"email\"\n autocomplete=\"username\"\n />\n </div>\n\n <div class=\"mb-3\">\n <label for=\"password\" class=\"form-label\">{{ 'auth.password' | t }}</label>\n <input\n type=\"password\"\n id=\"password\"\n formControlName=\"password\"\n autocomplete=\"current-password\"\n />\n </div>\n\n <div class=\"mb-3\">\n <bs-checkbox [type]=\"'checkbox'\" formControlName=\"rememberMe\" [name]=\"'rememberMe'\">{{ 'auth.rememberMe' | t }}</bs-checkbox>\n </div>\n\n <button\n type=\"submit\"\n class=\"btn btn-primary w-100\"\n [disabled]=\"loading()\"\n >\n @if (loading()) {\n <bs-spinner class=\"me-1\" />\n }\n {{ 'auth.login' | t }}\n </button>\n </form>\n </bs-form>\n\n <div class=\"mt-3 text-center\">\n <a [routerLink]=\"routePaths.register\">{{ 'auth.createAccount' | t }}</a>\n </div>\n <div class=\"mt-2 text-center\">\n <a [routerLink]=\"routePaths.forgotPassword\">{{ 'auth.forgotPassword' | t }}</a>\n </div>\n </div>\n </bs-card>\n</div>\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { 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", "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: BsFormComponent, selector: "bs-form", inputs: ["action", "method"], outputs: ["submitted"] }, { kind: "directive", type: BsFormControlDirective, selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)" }, { kind: "component", type: BsCheckboxComponent, selector: "bs-checkbox", inputs: ["type", "isToggled", "indeterminate", "name", "value", "group"], outputs: ["isToggledChange", "indeterminateChange"] }, { kind: "component", type: BsSpinnerComponent, selector: "bs-spinner", inputs: ["type", "color"] }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
67
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: SparkLoginComponent, isStandalone: true, selector: "spark-login", 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 (errorMessage()) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">{{ errorMessage() }}</bs-alert>\n }\n\n <bs-form>\n <form [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n <div class=\"mb-3\">\n <label for=\"email\" class=\"form-label\">{{ 'auth.email' | t }}</label>\n <input\n type=\"text\"\n id=\"email\"\n formControlName=\"email\"\n autocomplete=\"username\"\n />\n </div>\n\n <div class=\"mb-3\">\n <label for=\"password\" class=\"form-label\">{{ 'auth.password' | t }}</label>\n <input\n type=\"password\"\n id=\"password\"\n formControlName=\"password\"\n autocomplete=\"current-password\"\n />\n </div>\n\n <div class=\"mb-3\">\n <bs-checkbox [type]=\"'checkbox'\" formControlName=\"rememberMe\" [name]=\"'rememberMe'\">{{ 'auth.rememberMe' | t }}</bs-checkbox>\n </div>\n\n <button\n type=\"submit\"\n class=\"btn btn-primary w-100\"\n [disabled]=\"loading()\"\n >\n @if (loading()) {\n <bs-spinner class=\"me-1\" />\n }\n {{ 'auth.login' | t }}\n </button>\n </form>\n </bs-form>\n\n <div class=\"mt-3 text-center\">\n <a [routerLink]=\"routePaths.register\">{{ 'auth.createAccount' | t }}</a>\n </div>\n <div class=\"mt-2 text-center\">\n <a [routerLink]=\"routePaths.forgotPassword\">{{ 'auth.forgotPassword' | t }}</a>\n </div>\n </div>\n </bs-card>\n</div>\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { 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: BsFormComponent, selector: "bs-form", inputs: ["action", "method"], outputs: ["submitted"] }, { kind: "directive", type: BsFormControlDirective, selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)" }, { kind: "component", type: BsCheckboxComponent, selector: "bs-checkbox", inputs: ["type", "isToggled", "indeterminate", "name", "value", "group"], outputs: ["isToggledChange", "indeterminateChange"] }, { kind: "component", type: BsSpinnerComponent, selector: "bs-spinner", inputs: ["type", "color"] }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
68
68
|
}
|
|
69
69
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkLoginComponent, decorators: [{
|
|
70
70
|
type: Component,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mintplayer-ng-spark-auth-login.mjs","sources":["../../login/src/spark-login.component.ts","../../login/src/spark-login.component.html","../../login/mintplayer-ng-spark-auth-login.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';\nimport { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';\nimport { RouterLink, ActivatedRoute, Router } from '@angular/router';\nimport { HttpErrorResponse } from '@angular/common/http';\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 { BsCheckboxComponent } from '@mintplayer/ng-bootstrap/checkbox';\nimport { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';\nimport { SPARK_AUTH_CONFIG, SPARK_AUTH_ROUTE_PATHS, sanitizeReturnUrl } from '@mintplayer/ng-spark-auth/models';\nimport { SparkAuthService, SparkAuthTranslationService } from '@mintplayer/ng-spark-auth/core';\nimport { TranslateKeyPipe } from '@mintplayer/ng-spark-auth/pipes';\n\n@Component({\n selector: 'spark-login',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [ReactiveFormsModule, RouterLink, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsFormComponent, BsFormControlDirective, BsCheckboxComponent, BsSpinnerComponent, TranslateKeyPipe],\n templateUrl: './spark-login.component.html',\n})\nexport class SparkLoginComponent {\n private readonly fb = inject(FormBuilder);\n private readonly authService = inject(SparkAuthService);\n private readonly router = inject(Router);\n private readonly route = inject(ActivatedRoute);\n private readonly config = inject(SPARK_AUTH_CONFIG);\n private readonly translation = inject(SparkAuthTranslationService);\n readonly routePaths = inject(SPARK_AUTH_ROUTE_PATHS);\n\n colors = Color;\n readonly loading = signal(false);\n readonly errorMessage = signal('');\n\n readonly form = this.fb.group({\n email: ['', Validators.required],\n password: ['', Validators.required],\n rememberMe: [false],\n });\n\n async onSubmit(): Promise<void> {\n if (this.form.invalid) return;\n\n this.loading.set(true);\n this.errorMessage.set('');\n\n const { email, password } = this.form.value;\n\n try {\n await this.authService.login(email!, password!);\n // R2-H9: validate returnUrl against the local-path rule. navigateByUrl\n // accepts some external shapes (protocol-relative '//attacker'); reject\n // and fall back to the default to close the open-redirect.\n const returnUrl = sanitizeReturnUrl(\n this.route.snapshot.queryParamMap.get('returnUrl'),\n this.config.defaultRedirectUrl);\n this.router.navigateByUrl(returnUrl);\n } catch (err: any) {\n if (err instanceof HttpErrorResponse && err.status === 401 && err.error?.detail === 'RequiresTwoFactor') {\n // Forward the same sanitized returnUrl to the 2FA step so the user\n // lands on the intended in-app page after completing the challenge.\n const returnUrl = sanitizeReturnUrl(\n this.route.snapshot.queryParamMap.get('returnUrl'),\n this.config.defaultRedirectUrl);\n this.router.navigate([this.routePaths.twoFactor], {\n queryParams: { returnUrl },\n });\n } else {\n this.errorMessage.set(this.translation.t('auth.invalidCredentials'));\n }\n } finally {\n this.loading.set(false);\n }\n }\n}\n\nexport default SparkLoginComponent;\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 (errorMessage()) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">{{ errorMessage() }}</bs-alert>\n }\n\n <bs-form>\n <form [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n <div class=\"mb-3\">\n <label for=\"email\" class=\"form-label\">{{ 'auth.email' | t }}</label>\n <input\n type=\"text\"\n id=\"email\"\n formControlName=\"email\"\n autocomplete=\"username\"\n />\n </div>\n\n <div class=\"mb-3\">\n <label for=\"password\" class=\"form-label\">{{ 'auth.password' | t }}</label>\n <input\n type=\"password\"\n id=\"password\"\n formControlName=\"password\"\n autocomplete=\"current-password\"\n />\n </div>\n\n <div class=\"mb-3\">\n <bs-checkbox [type]=\"'checkbox'\" formControlName=\"rememberMe\" [name]=\"'rememberMe'\">{{ 'auth.rememberMe' | t }}</bs-checkbox>\n </div>\n\n <button\n type=\"submit\"\n class=\"btn btn-primary w-100\"\n [disabled]=\"loading()\"\n >\n @if (loading()) {\n <bs-spinner class=\"me-1\" />\n }\n {{ 'auth.login' | t }}\n </button>\n </form>\n </bs-form>\n\n <div class=\"mt-3 text-center\">\n <a [routerLink]=\"routePaths.register\">{{ 'auth.createAccount' | t }}</a>\n </div>\n <div class=\"mt-2 text-center\">\n <a [routerLink]=\"routePaths.forgotPassword\">{{ 'auth.forgotPassword' | t }}</a>\n </div>\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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"}
|
|
1
|
+
{"version":3,"file":"mintplayer-ng-spark-auth-login.mjs","sources":["../../login/src/spark-login.component.ts","../../login/src/spark-login.component.html","../../login/mintplayer-ng-spark-auth-login.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';\nimport { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';\nimport { RouterLink, ActivatedRoute, Router } from '@angular/router';\nimport { HttpErrorResponse } from '@angular/common/http';\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 { BsCheckboxComponent } from '@mintplayer/ng-bootstrap/checkbox';\nimport { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';\nimport { SPARK_AUTH_CONFIG, SPARK_AUTH_ROUTE_PATHS, sanitizeReturnUrl } from '@mintplayer/ng-spark-auth/models';\nimport { SparkAuthService, SparkAuthTranslationService } from '@mintplayer/ng-spark-auth/core';\nimport { TranslateKeyPipe } from '@mintplayer/ng-spark-auth/pipes';\n\n@Component({\n selector: 'spark-login',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [ReactiveFormsModule, RouterLink, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsFormComponent, BsFormControlDirective, BsCheckboxComponent, BsSpinnerComponent, TranslateKeyPipe],\n templateUrl: './spark-login.component.html',\n})\nexport class SparkLoginComponent {\n private readonly fb = inject(FormBuilder);\n private readonly authService = inject(SparkAuthService);\n private readonly router = inject(Router);\n private readonly route = inject(ActivatedRoute);\n private readonly config = inject(SPARK_AUTH_CONFIG);\n private readonly translation = inject(SparkAuthTranslationService);\n readonly routePaths = inject(SPARK_AUTH_ROUTE_PATHS);\n\n colors = Color;\n readonly loading = signal(false);\n readonly errorMessage = signal('');\n\n readonly form = this.fb.group({\n email: ['', Validators.required],\n password: ['', Validators.required],\n rememberMe: [false],\n });\n\n async onSubmit(): Promise<void> {\n if (this.form.invalid) return;\n\n this.loading.set(true);\n this.errorMessage.set('');\n\n const { email, password } = this.form.value;\n\n try {\n await this.authService.login(email!, password!);\n // R2-H9: validate returnUrl against the local-path rule. navigateByUrl\n // accepts some external shapes (protocol-relative '//attacker'); reject\n // and fall back to the default to close the open-redirect.\n const returnUrl = sanitizeReturnUrl(\n this.route.snapshot.queryParamMap.get('returnUrl'),\n this.config.defaultRedirectUrl);\n this.router.navigateByUrl(returnUrl);\n } catch (err: any) {\n if (err instanceof HttpErrorResponse && err.status === 401 && err.error?.detail === 'RequiresTwoFactor') {\n // Forward the same sanitized returnUrl to the 2FA step so the user\n // lands on the intended in-app page after completing the challenge.\n const returnUrl = sanitizeReturnUrl(\n this.route.snapshot.queryParamMap.get('returnUrl'),\n this.config.defaultRedirectUrl);\n this.router.navigate([this.routePaths.twoFactor], {\n queryParams: { returnUrl },\n });\n } else {\n this.errorMessage.set(this.translation.t('auth.invalidCredentials'));\n }\n } finally {\n this.loading.set(false);\n }\n }\n}\n\nexport default SparkLoginComponent;\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 (errorMessage()) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">{{ errorMessage() }}</bs-alert>\n }\n\n <bs-form>\n <form [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n <div class=\"mb-3\">\n <label for=\"email\" class=\"form-label\">{{ 'auth.email' | t }}</label>\n <input\n type=\"text\"\n id=\"email\"\n formControlName=\"email\"\n autocomplete=\"username\"\n />\n </div>\n\n <div class=\"mb-3\">\n <label for=\"password\" class=\"form-label\">{{ 'auth.password' | t }}</label>\n <input\n type=\"password\"\n id=\"password\"\n formControlName=\"password\"\n autocomplete=\"current-password\"\n />\n </div>\n\n <div class=\"mb-3\">\n <bs-checkbox [type]=\"'checkbox'\" formControlName=\"rememberMe\" [name]=\"'rememberMe'\">{{ 'auth.rememberMe' | t }}</bs-checkbox>\n </div>\n\n <button\n type=\"submit\"\n class=\"btn btn-primary w-100\"\n [disabled]=\"loading()\"\n >\n @if (loading()) {\n <bs-spinner class=\"me-1\" />\n }\n {{ 'auth.login' | t }}\n </button>\n </form>\n </bs-form>\n\n <div class=\"mt-3 text-center\">\n <a [routerLink]=\"routePaths.register\">{{ 'auth.createAccount' | t }}</a>\n </div>\n <div class=\"mt-2 text-center\">\n <a [routerLink]=\"routePaths.forgotPassword\">{{ 'auth.forgotPassword' | t }}</a>\n </div>\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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"}
|
|
@@ -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 = {
|
|
@@ -39,9 +39,48 @@ function sanitizeReturnUrl(value, defaultUrl) {
|
|
|
39
39
|
return isSafeReturnUrl(value) ? value : defaultUrl;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
const SPARK_EXTERNAL_LOGIN_MESSAGE_TYPE = 'spark:external-login';
|
|
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
|
+
|
|
42
81
|
/**
|
|
43
82
|
* Generated bundle index. Do not edit.
|
|
44
83
|
*/
|
|
45
84
|
|
|
46
|
-
export { SPARK_AUTH_CONFIG, SPARK_AUTH_ROUTE_PATHS, defaultSparkAuthConfig, isSafeReturnUrl, sanitizeReturnUrl };
|
|
85
|
+
export { SPARK_AUTH_CONFIG, SPARK_AUTH_ROUTE_PATHS, SPARK_EXTERNAL_LOGIN_MESSAGE_TYPE, defaultSparkAuthConfig, isSafeReturnUrl, resolveSignInUrl, sanitizeReturnUrl };
|
|
47
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/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;;;;"}
|
|
@@ -73,7 +73,7 @@ class SparkRegisterComponent {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkRegisterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
76
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: SparkRegisterComponent, isStandalone: true, selector: "spark-register", 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.register' | t }}</h3>\n </bs-card-header>\n <div class=\"p-4\">\n @if (errorMessage()) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">{{ errorMessage() }}</bs-alert>\n }\n\n <bs-form>\n <form [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n <div class=\"mb-3\">\n <label for=\"email\" class=\"form-label\">{{ 'auth.email' | t }}</label>\n <input\n type=\"email\"\n id=\"email\"\n formControlName=\"email\"\n autocomplete=\"email\"\n />\n @if (form.get('email')?.touched && form.get('email')?.hasError('email')) {\n <div class=\"text-danger mt-1\">{{ 'auth.invalidEmail' | t }}</div>\n }\n </div>\n\n <div class=\"mb-3\">\n <label for=\"password\" class=\"form-label\">{{ 'auth.password' | t }}</label>\n <input\n type=\"password\"\n id=\"password\"\n formControlName=\"password\"\n autocomplete=\"new-password\"\n />\n </div>\n\n <div class=\"mb-3\">\n <label for=\"confirmPassword\" class=\"form-label\">{{ 'auth.confirmPassword' | t }}</label>\n <input\n type=\"password\"\n id=\"confirmPassword\"\n formControlName=\"confirmPassword\"\n autocomplete=\"new-password\"\n />\n @if (form.touched && form.hasError('passwordMismatch')) {\n <div class=\"text-danger mt-1\">{{ 'auth.passwordMismatch' | t }}</div>\n }\n </div>\n\n <button\n type=\"submit\"\n class=\"btn btn-primary w-100\"\n [disabled]=\"loading()\"\n >\n @if (loading()) {\n <bs-spinner class=\"me-1\" />\n }\n {{ 'auth.register' | t }}\n </button>\n </form>\n </bs-form>\n\n <div class=\"mt-3 text-center\">\n <span>{{ 'auth.alreadyHaveAccount' | t }} </span>\n <a [routerLink]=\"routePaths.login\">{{ 'auth.login' | t }}</a>\n </div>\n </div>\n </bs-card>\n</div>\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { 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", "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: BsFormComponent, selector: "bs-form", inputs: ["action", "method"], outputs: ["submitted"] }, { kind: "directive", type: BsFormControlDirective, selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)" }, { kind: "component", type: BsSpinnerComponent, selector: "bs-spinner", inputs: ["type", "color"] }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
76
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: SparkRegisterComponent, isStandalone: true, selector: "spark-register", 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.register' | t }}</h3>\n </bs-card-header>\n <div class=\"p-4\">\n @if (errorMessage()) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">{{ errorMessage() }}</bs-alert>\n }\n\n <bs-form>\n <form [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n <div class=\"mb-3\">\n <label for=\"email\" class=\"form-label\">{{ 'auth.email' | t }}</label>\n <input\n type=\"email\"\n id=\"email\"\n formControlName=\"email\"\n autocomplete=\"email\"\n />\n @if (form.get('email')?.touched && form.get('email')?.hasError('email')) {\n <div class=\"text-danger mt-1\">{{ 'auth.invalidEmail' | t }}</div>\n }\n </div>\n\n <div class=\"mb-3\">\n <label for=\"password\" class=\"form-label\">{{ 'auth.password' | t }}</label>\n <input\n type=\"password\"\n id=\"password\"\n formControlName=\"password\"\n autocomplete=\"new-password\"\n />\n </div>\n\n <div class=\"mb-3\">\n <label for=\"confirmPassword\" class=\"form-label\">{{ 'auth.confirmPassword' | t }}</label>\n <input\n type=\"password\"\n id=\"confirmPassword\"\n formControlName=\"confirmPassword\"\n autocomplete=\"new-password\"\n />\n @if (form.touched && form.hasError('passwordMismatch')) {\n <div class=\"text-danger mt-1\">{{ 'auth.passwordMismatch' | t }}</div>\n }\n </div>\n\n <button\n type=\"submit\"\n class=\"btn btn-primary w-100\"\n [disabled]=\"loading()\"\n >\n @if (loading()) {\n <bs-spinner class=\"me-1\" />\n }\n {{ 'auth.register' | t }}\n </button>\n </form>\n </bs-form>\n\n <div class=\"mt-3 text-center\">\n <span>{{ 'auth.alreadyHaveAccount' | t }} </span>\n <a [routerLink]=\"routePaths.login\">{{ 'auth.login' | t }}</a>\n </div>\n </div>\n </bs-card>\n</div>\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { 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: BsFormComponent, selector: "bs-form", inputs: ["action", "method"], outputs: ["submitted"] }, { kind: "directive", type: BsFormControlDirective, selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)" }, { kind: "component", type: BsSpinnerComponent, selector: "bs-spinner", inputs: ["type", "color"] }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
77
77
|
}
|
|
78
78
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkRegisterComponent, decorators: [{
|
|
79
79
|
type: Component,
|