@mintplayer/ng-spark-auth 0.4.0 → 22.0.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 +3 -3
- package/fesm2022/mintplayer-ng-spark-auth-auth-bar.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-auth-core.mjs +12 -9
- package/fesm2022/mintplayer-ng-spark-auth-core.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-auth-forgot-password.mjs +9 -6
- package/fesm2022/mintplayer-ng-spark-auth-forgot-password.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-auth-interceptors.mjs +10 -2
- package/fesm2022/mintplayer-ng-spark-auth-interceptors.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-auth-login.mjs +19 -12
- package/fesm2022/mintplayer-ng-spark-auth-login.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-auth-models.mjs +31 -1
- package/fesm2022/mintplayer-ng-spark-auth-models.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-auth-pipes.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-auth-register.mjs +7 -5
- package/fesm2022/mintplayer-ng-spark-auth-register.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-auth-reset-password.mjs +9 -6
- package/fesm2022/mintplayer-ng-spark-auth-reset-password.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-auth-two-factor.mjs +16 -9
- package/fesm2022/mintplayer-ng-spark-auth-two-factor.mjs.map +1 -1
- package/package.json +8 -7
- package/types/mintplayer-ng-spark-auth-core.d.ts +1 -1
- package/types/mintplayer-ng-spark-auth-forgot-password.d.ts +1 -1
- package/types/mintplayer-ng-spark-auth-login.d.ts +3 -3
- package/types/mintplayer-ng-spark-auth-models.d.ts +19 -1
- package/types/mintplayer-ng-spark-auth-register.d.ts +3 -3
- package/types/mintplayer-ng-spark-auth-reset-password.d.ts +2 -2
- package/types/mintplayer-ng-spark-auth-two-factor.d.ts +2 -2
|
@@ -20,10 +20,10 @@ class SparkAuthBarComponent {
|
|
|
20
20
|
this.router.navigateByUrl('/');
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
24
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
23
|
+
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]=\"config.loginUrl\">{{ '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
25
|
}
|
|
26
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
26
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkAuthBarComponent, decorators: [{
|
|
27
27
|
type: Component,
|
|
28
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]=\"config.loginUrl\">{{ 'auth.login' | t }}</a>\n}\n" }]
|
|
29
29
|
}] });
|
|
@@ -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]=\"config.loginUrl\">{{ '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;AAExC,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;uGAdW,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,4UAMA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EDIY,UAAU
|
|
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]=\"config.loginUrl\">{{ '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;AAExC,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;uGAdW,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,4UAMA,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,4UAAA,EAAA;;;AEVzC;;AAEG;;;;"}
|
|
@@ -7,9 +7,11 @@ import { SPARK_AUTH_CONFIG } from '@mintplayer/ng-spark-auth/models';
|
|
|
7
7
|
class SparkAuthService {
|
|
8
8
|
http = inject(HttpClient);
|
|
9
9
|
config = inject(SPARK_AUTH_CONFIG);
|
|
10
|
-
currentUser = signal(null,
|
|
10
|
+
currentUser = signal(null, /* @ts-ignore */
|
|
11
|
+
...(ngDevMode ? [{ debugName: "currentUser" }] : /* istanbul ignore next */ []));
|
|
11
12
|
user = this.currentUser.asReadonly();
|
|
12
|
-
isAuthenticated = computed(() => this.currentUser()?.isAuthenticated === true,
|
|
13
|
+
isAuthenticated = computed(() => this.currentUser()?.isAuthenticated === true, /* @ts-ignore */
|
|
14
|
+
...(ngDevMode ? [{ debugName: "isAuthenticated" }] : /* istanbul ignore next */ []));
|
|
13
15
|
constructor() {
|
|
14
16
|
this.checkAuth();
|
|
15
17
|
}
|
|
@@ -58,10 +60,10 @@ class SparkAuthService {
|
|
|
58
60
|
newPassword,
|
|
59
61
|
}));
|
|
60
62
|
}
|
|
61
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
62
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
63
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkAuthService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
64
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkAuthService, providedIn: 'root' });
|
|
63
65
|
}
|
|
64
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
66
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkAuthService, decorators: [{
|
|
65
67
|
type: Injectable,
|
|
66
68
|
args: [{ providedIn: 'root' }]
|
|
67
69
|
}], ctorParameters: () => [] });
|
|
@@ -70,7 +72,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
|
|
|
70
72
|
const currentLanguage = globalThis.__sparkCurrentLanguage ?? signal('en');
|
|
71
73
|
class SparkAuthTranslationService {
|
|
72
74
|
http = inject(HttpClient);
|
|
73
|
-
translationsMap = signal({},
|
|
75
|
+
translationsMap = signal({}, /* @ts-ignore */
|
|
76
|
+
...(ngDevMode ? [{ debugName: "translationsMap" }] : /* istanbul ignore next */ []));
|
|
74
77
|
constructor() {
|
|
75
78
|
this.loadTranslations();
|
|
76
79
|
}
|
|
@@ -90,10 +93,10 @@ class SparkAuthTranslationService {
|
|
|
90
93
|
const lang = currentLanguage();
|
|
91
94
|
return ts[lang] ?? ts['en'] ?? Object.values(ts)[0] ?? key;
|
|
92
95
|
}
|
|
93
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
94
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
96
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkAuthTranslationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
97
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkAuthTranslationService, providedIn: 'root' });
|
|
95
98
|
}
|
|
96
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
99
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkAuthTranslationService, decorators: [{
|
|
97
100
|
type: Injectable,
|
|
98
101
|
args: [{ providedIn: 'root' }]
|
|
99
102
|
}], ctorParameters: () => [] });
|
|
@@ -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":";;;;;;MAMa,gBAAgB,CAAA;AACV,IAAA,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC;AACzB,IAAA,MAAM,GAAG,MAAM,CAAC,iBAAiB,CAAC;
|
|
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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"}
|
|
@@ -18,9 +18,12 @@ class SparkForgotPasswordComponent {
|
|
|
18
18
|
translation = inject(SparkAuthTranslationService);
|
|
19
19
|
routePaths = inject(SPARK_AUTH_ROUTE_PATHS);
|
|
20
20
|
colors = Color;
|
|
21
|
-
loading = signal(false,
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
loading = signal(false, /* @ts-ignore */
|
|
22
|
+
...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
23
|
+
errorMessage = signal('', /* @ts-ignore */
|
|
24
|
+
...(ngDevMode ? [{ debugName: "errorMessage" }] : /* istanbul ignore next */ []));
|
|
25
|
+
successMessage = signal('', /* @ts-ignore */
|
|
26
|
+
...(ngDevMode ? [{ debugName: "successMessage" }] : /* istanbul ignore next */ []));
|
|
24
27
|
form = this.fb.group({
|
|
25
28
|
email: ['', [Validators.required, Validators.email]],
|
|
26
29
|
});
|
|
@@ -42,10 +45,10 @@ class SparkForgotPasswordComponent {
|
|
|
42
45
|
this.successMessage.set(this.translation.t('auth.forgotPasswordSuccess'));
|
|
43
46
|
this.loading.set(false);
|
|
44
47
|
}
|
|
45
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
46
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
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 });
|
|
47
50
|
}
|
|
48
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
51
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkForgotPasswordComponent, decorators: [{
|
|
49
52
|
type: Component,
|
|
50
53
|
args: [{ selector: 'spark-forgot-password', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [ReactiveFormsModule, RouterLink, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsFormComponent, BsFormControlDirective, BsSpinnerComponent, TranslateKeyPipe], template: "<div class=\"d-flex justify-content-center\">\n <bs-card style=\"width: 100%; max-width: 400px;\">\n <bs-card-header>\n <h3 class=\"mb-0 text-center\">{{ 'auth.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" }]
|
|
51
54
|
}] });
|
|
@@ -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":";;;;;;;;;;;;;;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;
|
|
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,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 } from '@mintplayer/ng-spark-auth/models';
|
|
4
|
+
import { SPARK_AUTH_CONFIG, sanitizeReturnUrl } from '@mintplayer/ng-spark-auth/models';
|
|
5
5
|
|
|
6
6
|
const sparkAuthInterceptor = (req, next) => {
|
|
7
7
|
const config = inject(SPARK_AUTH_CONFIG);
|
|
@@ -10,8 +10,16 @@ const sparkAuthInterceptor = (req, next) => {
|
|
|
10
10
|
error: (error) => {
|
|
11
11
|
if (error.status === 401 &&
|
|
12
12
|
!req.url.startsWith(config.apiBasePath)) {
|
|
13
|
+
// R2-L4: drop query string and fragment from the captured URL before
|
|
14
|
+
// putting it on the login URL. The unmodified router.url leaks
|
|
15
|
+
// in-app draft text / document IDs / search queries into the next
|
|
16
|
+
// access log entry for /login. Also pass through the same sanitizer
|
|
17
|
+
// the login component uses so a path with embedded // can't reflect
|
|
18
|
+
// back as an open redirect after authentication.
|
|
19
|
+
const pathOnly = router.url.split('?')[0].split('#')[0];
|
|
20
|
+
const returnUrl = sanitizeReturnUrl(pathOnly, config.defaultRedirectUrl);
|
|
13
21
|
router.navigate([config.loginUrl], {
|
|
14
|
-
queryParams: { returnUrl
|
|
22
|
+
queryParams: { returnUrl },
|
|
15
23
|
});
|
|
16
24
|
}
|
|
17
25
|
},
|
|
@@ -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 } 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 router.navigate([config.loginUrl], {\n queryParams: { returnUrl
|
|
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.loginUrl], {\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,MAAM,CAAC,QAAQ,CAAC,EAAE;oBACjC,WAAW,EAAE,EAAE,SAAS,EAAE;AAC3B,iBAAA,CAAC;YACJ;QACF,CAAC;AACF,KAAA,CAAC,CACH;AACH;;AChCA;;AAEG;;;;"}
|
|
@@ -8,9 +8,9 @@ import { Color } from '@mintplayer/ng-bootstrap';
|
|
|
8
8
|
import { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';
|
|
9
9
|
import { BsCardComponent, BsCardHeaderComponent } from '@mintplayer/ng-bootstrap/card';
|
|
10
10
|
import { BsFormComponent, BsFormControlDirective } from '@mintplayer/ng-bootstrap/form';
|
|
11
|
-
import {
|
|
11
|
+
import { BsCheckboxComponent } from '@mintplayer/ng-bootstrap/checkbox';
|
|
12
12
|
import { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';
|
|
13
|
-
import { SPARK_AUTH_CONFIG, SPARK_AUTH_ROUTE_PATHS } from '@mintplayer/ng-spark-auth/models';
|
|
13
|
+
import { SPARK_AUTH_CONFIG, SPARK_AUTH_ROUTE_PATHS, sanitizeReturnUrl } from '@mintplayer/ng-spark-auth/models';
|
|
14
14
|
import { SparkAuthService, SparkAuthTranslationService } from '@mintplayer/ng-spark-auth/core';
|
|
15
15
|
import { TranslateKeyPipe } from '@mintplayer/ng-spark-auth/pipes';
|
|
16
16
|
|
|
@@ -23,8 +23,10 @@ class SparkLoginComponent {
|
|
|
23
23
|
translation = inject(SparkAuthTranslationService);
|
|
24
24
|
routePaths = inject(SPARK_AUTH_ROUTE_PATHS);
|
|
25
25
|
colors = Color;
|
|
26
|
-
loading = signal(false,
|
|
27
|
-
|
|
26
|
+
loading = signal(false, /* @ts-ignore */
|
|
27
|
+
...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
28
|
+
errorMessage = signal('', /* @ts-ignore */
|
|
29
|
+
...(ngDevMode ? [{ debugName: "errorMessage" }] : /* istanbul ignore next */ []));
|
|
28
30
|
form = this.fb.group({
|
|
29
31
|
email: ['', Validators.required],
|
|
30
32
|
password: ['', Validators.required],
|
|
@@ -38,14 +40,19 @@ class SparkLoginComponent {
|
|
|
38
40
|
const { email, password } = this.form.value;
|
|
39
41
|
try {
|
|
40
42
|
await this.authService.login(email, password);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
// R2-H9: validate returnUrl against the local-path rule. navigateByUrl
|
|
44
|
+
// accepts some external shapes (protocol-relative '//attacker'); reject
|
|
45
|
+
// and fall back to the default to close the open-redirect.
|
|
46
|
+
const returnUrl = sanitizeReturnUrl(this.route.snapshot.queryParamMap.get('returnUrl'), this.config.defaultRedirectUrl);
|
|
47
|
+
this.router.navigateByUrl(returnUrl);
|
|
43
48
|
}
|
|
44
49
|
catch (err) {
|
|
45
50
|
if (err instanceof HttpErrorResponse && err.status === 401 && err.error?.detail === 'RequiresTwoFactor') {
|
|
46
|
-
|
|
51
|
+
// Forward the same sanitized returnUrl to the 2FA step so the user
|
|
52
|
+
// lands on the intended in-app page after completing the challenge.
|
|
53
|
+
const returnUrl = sanitizeReturnUrl(this.route.snapshot.queryParamMap.get('returnUrl'), this.config.defaultRedirectUrl);
|
|
47
54
|
this.router.navigate([this.routePaths.twoFactor], {
|
|
48
|
-
queryParams:
|
|
55
|
+
queryParams: { returnUrl },
|
|
49
56
|
});
|
|
50
57
|
}
|
|
51
58
|
else {
|
|
@@ -56,12 +63,12 @@ class SparkLoginComponent {
|
|
|
56
63
|
this.loading.set(false);
|
|
57
64
|
}
|
|
58
65
|
}
|
|
59
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
60
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
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 });
|
|
61
68
|
}
|
|
62
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
69
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkLoginComponent, decorators: [{
|
|
63
70
|
type: Component,
|
|
64
|
-
args: [{ selector: 'spark-login', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [ReactiveFormsModule, RouterLink, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsFormComponent, BsFormControlDirective,
|
|
71
|
+
args: [{ selector: 'spark-login', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [ReactiveFormsModule, RouterLink, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsFormComponent, BsFormControlDirective, BsCheckboxComponent, BsSpinnerComponent, TranslateKeyPipe], template: "<div class=\"d-flex justify-content-center\">\n <bs-card style=\"width: 100%; max-width: 400px;\">\n <bs-card-header>\n <h3 class=\"mb-0 text-center\">{{ 'auth.login' | t }}</h3>\n </bs-card-header>\n <div class=\"p-4\">\n @if (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" }]
|
|
65
72
|
}] });
|
|
66
73
|
|
|
67
74
|
/**
|
|
@@ -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 {
|
|
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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"}
|
|
@@ -9,9 +9,39 @@ const defaultSparkAuthConfig = {
|
|
|
9
9
|
|
|
10
10
|
const SPARK_AUTH_ROUTE_PATHS = new InjectionToken('SPARK_AUTH_ROUTE_PATHS');
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Shared validator for `returnUrl` query parameters consumed by the auth
|
|
14
|
+
* components and interceptor. The framework-side equivalent in
|
|
15
|
+
* `SparkAuthenticationExtensions.SanitizeReturnUrl` enforces the same rule;
|
|
16
|
+
* keep them in sync.
|
|
17
|
+
*
|
|
18
|
+
* Accepts only local in-app paths:
|
|
19
|
+
* - Must start with a single `/`.
|
|
20
|
+
* - Must not start with `//` or `/\` (protocol-relative navigation that some
|
|
21
|
+
* browsers and routers interpret as an external host).
|
|
22
|
+
* - Must not contain CR/LF (defensive — the value flows into Router state and
|
|
23
|
+
* eventually back into a `router.navigateByUrl` call).
|
|
24
|
+
*
|
|
25
|
+
* Returns the supplied default for anything else, never throws. Per R2-H9.
|
|
26
|
+
*/
|
|
27
|
+
function isSafeReturnUrl(value) {
|
|
28
|
+
if (!value)
|
|
29
|
+
return false;
|
|
30
|
+
if (/[\r\n]/.test(value))
|
|
31
|
+
return false;
|
|
32
|
+
if (!value.startsWith('/'))
|
|
33
|
+
return false;
|
|
34
|
+
if (value.length >= 2 && (value[1] === '/' || value[1] === '\\'))
|
|
35
|
+
return false;
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
function sanitizeReturnUrl(value, defaultUrl) {
|
|
39
|
+
return isSafeReturnUrl(value) ? value : defaultUrl;
|
|
40
|
+
}
|
|
41
|
+
|
|
12
42
|
/**
|
|
13
43
|
* Generated bundle index. Do not edit.
|
|
14
44
|
*/
|
|
15
45
|
|
|
16
|
-
export { SPARK_AUTH_CONFIG, SPARK_AUTH_ROUTE_PATHS, defaultSparkAuthConfig };
|
|
46
|
+
export { SPARK_AUTH_CONFIG, SPARK_AUTH_ROUTE_PATHS, defaultSparkAuthConfig, isSafeReturnUrl, sanitizeReturnUrl };
|
|
17
47
|
//# 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/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 interface SparkAuthRouteConfig {\n login?: SparkAuthRouteEntry;\n twoFactor?: SparkAuthRouteEntry;\n register?: SparkAuthRouteEntry;\n forgotPassword?: SparkAuthRouteEntry;\n resetPassword?: SparkAuthRouteEntry;\n}\n\nexport type SparkAuthRoutePaths = Required<Record<keyof SparkAuthRouteConfig, string>>;\n\nexport const SPARK_AUTH_ROUTE_PATHS = new InjectionToken<SparkAuthRoutePaths>('SPARK_AUTH_ROUTE_PATHS');\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;;;MCCP,sBAAsB,GAAG,IAAI,cAAc,CAAsB,wBAAwB;;ACdtG;;AAEG;;;;"}
|
|
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 interface SparkAuthRouteConfig {\n login?: SparkAuthRouteEntry;\n twoFactor?: SparkAuthRouteEntry;\n register?: SparkAuthRouteEntry;\n forgotPassword?: SparkAuthRouteEntry;\n resetPassword?: SparkAuthRouteEntry;\n}\n\nexport type SparkAuthRoutePaths = Required<Record<keyof SparkAuthRouteConfig, 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","/**\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;;;MCCP,sBAAsB,GAAG,IAAI,cAAc,CAAsB,wBAAwB;;ACdtG;;;;;;;;;;;;;;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;;AC5BA;;AAEG;;;;"}
|
|
@@ -7,10 +7,10 @@ class TranslateKeyPipe {
|
|
|
7
7
|
transform(key) {
|
|
8
8
|
return this.translation.t(key);
|
|
9
9
|
}
|
|
10
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
11
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "
|
|
10
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: TranslateKeyPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
11
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.0.0", ngImport: i0, type: TranslateKeyPipe, isStandalone: true, name: "t", pure: false });
|
|
12
12
|
}
|
|
13
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
13
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: TranslateKeyPipe, decorators: [{
|
|
14
14
|
type: Pipe,
|
|
15
15
|
args: [{ name: 't', pure: false, standalone: true }]
|
|
16
16
|
}] });
|
|
@@ -28,8 +28,10 @@ class SparkRegisterComponent {
|
|
|
28
28
|
translation = inject(SparkAuthTranslationService);
|
|
29
29
|
routePaths = inject(SPARK_AUTH_ROUTE_PATHS);
|
|
30
30
|
colors = Color;
|
|
31
|
-
loading = signal(false,
|
|
32
|
-
|
|
31
|
+
loading = signal(false, /* @ts-ignore */
|
|
32
|
+
...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
33
|
+
errorMessage = signal('', /* @ts-ignore */
|
|
34
|
+
...(ngDevMode ? [{ debugName: "errorMessage" }] : /* istanbul ignore next */ []));
|
|
33
35
|
form = this.fb.group({
|
|
34
36
|
email: ['', [Validators.required, Validators.email]],
|
|
35
37
|
password: ['', Validators.required],
|
|
@@ -70,10 +72,10 @@ class SparkRegisterComponent {
|
|
|
70
72
|
this.loading.set(false);
|
|
71
73
|
}
|
|
72
74
|
}
|
|
73
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
74
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
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 });
|
|
75
77
|
}
|
|
76
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
78
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkRegisterComponent, decorators: [{
|
|
77
79
|
type: Component,
|
|
78
80
|
args: [{ selector: 'spark-register', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [ReactiveFormsModule, RouterLink, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsFormComponent, BsFormControlDirective, BsSpinnerComponent, TranslateKeyPipe], template: "<div class=\"d-flex justify-content-center\">\n <bs-card style=\"width: 100%; max-width: 400px;\">\n <bs-card-header>\n <h3 class=\"mb-0 text-center\">{{ 'auth.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" }]
|
|
79
81
|
}] });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mintplayer-ng-spark-auth-register.mjs","sources":["../../register/src/spark-register.component.ts","../../register/src/spark-register.component.html","../../register/mintplayer-ng-spark-auth-register.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';\nimport { ReactiveFormsModule, FormBuilder, Validators, AbstractControl, ValidationErrors } from '@angular/forms';\nimport { RouterLink, 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 { 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\nfunction passwordMatchValidator(control: AbstractControl): ValidationErrors | null {\n const password = control.get('password');\n const confirmPassword = control.get('confirmPassword');\n if (password && confirmPassword && password.value !== confirmPassword.value) {\n return { passwordMismatch: true };\n }\n return null;\n}\n\n@Component({\n selector: 'spark-register',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [ReactiveFormsModule, RouterLink, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsFormComponent, BsFormControlDirective, BsSpinnerComponent, TranslateKeyPipe],\n templateUrl: './spark-register.component.html',\n})\nexport class SparkRegisterComponent {\n private readonly fb = inject(FormBuilder);\n private readonly authService = inject(SparkAuthService);\n private readonly router = inject(Router);\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, Validators.email]],\n password: ['', Validators.required],\n confirmPassword: ['', Validators.required],\n }, { validators: passwordMatchValidator });\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\n const { email, password } = this.form.value;\n\n try {\n await this.authService.register(email!, password!);\n this.router.navigate([this.routePaths.login], {\n queryParams: { registered: 'true' },\n });\n } catch (err: any) {\n if (err instanceof HttpErrorResponse) {\n if (err.status === 400 && err.error?.errors) {\n const messages = ([] as string[]).concat(...Object.values(err.error.errors) as string[][]);\n this.errorMessage.set(messages.join(' '));\n } else if (err.error?.detail) {\n this.errorMessage.set(err.error.detail);\n } else {\n this.errorMessage.set(this.translation.t('auth.registrationFailed'));\n }\n } else {\n this.errorMessage.set(this.translation.t('auth.registrationFailed'));\n }\n } finally {\n this.loading.set(false);\n }\n }\n}\n\nexport default SparkRegisterComponent;\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.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","/**\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-register.mjs","sources":["../../register/src/spark-register.component.ts","../../register/src/spark-register.component.html","../../register/mintplayer-ng-spark-auth-register.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';\nimport { ReactiveFormsModule, FormBuilder, Validators, AbstractControl, ValidationErrors } from '@angular/forms';\nimport { RouterLink, 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 { 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\nfunction passwordMatchValidator(control: AbstractControl): ValidationErrors | null {\n const password = control.get('password');\n const confirmPassword = control.get('confirmPassword');\n if (password && confirmPassword && password.value !== confirmPassword.value) {\n return { passwordMismatch: true };\n }\n return null;\n}\n\n@Component({\n selector: 'spark-register',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [ReactiveFormsModule, RouterLink, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsFormComponent, BsFormControlDirective, BsSpinnerComponent, TranslateKeyPipe],\n templateUrl: './spark-register.component.html',\n})\nexport class SparkRegisterComponent {\n private readonly fb = inject(FormBuilder);\n private readonly authService = inject(SparkAuthService);\n private readonly router = inject(Router);\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, Validators.email]],\n password: ['', Validators.required],\n confirmPassword: ['', Validators.required],\n }, { validators: passwordMatchValidator });\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\n const { email, password } = this.form.value;\n\n try {\n await this.authService.register(email!, password!);\n this.router.navigate([this.routePaths.login], {\n queryParams: { registered: 'true' },\n });\n } catch (err: any) {\n if (err instanceof HttpErrorResponse) {\n if (err.status === 400 && err.error?.errors) {\n const messages = ([] as string[]).concat(...Object.values(err.error.errors) as string[][]);\n this.errorMessage.set(messages.join(' '));\n } else if (err.error?.detail) {\n this.errorMessage.set(err.error.detail);\n } else {\n this.errorMessage.set(this.translation.t('auth.registrationFailed'));\n }\n } else {\n this.errorMessage.set(this.translation.t('auth.registrationFailed'));\n }\n } finally {\n this.loading.set(false);\n }\n }\n}\n\nexport default SparkRegisterComponent;\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.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","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -29,9 +29,12 @@ class SparkResetPasswordComponent {
|
|
|
29
29
|
translation = inject(SparkAuthTranslationService);
|
|
30
30
|
routePaths = inject(SPARK_AUTH_ROUTE_PATHS);
|
|
31
31
|
colors = Color;
|
|
32
|
-
loading = signal(false,
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
loading = signal(false, /* @ts-ignore */
|
|
33
|
+
...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
34
|
+
errorMessage = signal('', /* @ts-ignore */
|
|
35
|
+
...(ngDevMode ? [{ debugName: "errorMessage" }] : /* istanbul ignore next */ []));
|
|
36
|
+
successMessage = signal('', /* @ts-ignore */
|
|
37
|
+
...(ngDevMode ? [{ debugName: "successMessage" }] : /* istanbul ignore next */ []));
|
|
35
38
|
email = '';
|
|
36
39
|
code = '';
|
|
37
40
|
form = this.fb.group({
|
|
@@ -74,10 +77,10 @@ class SparkResetPasswordComponent {
|
|
|
74
77
|
this.loading.set(false);
|
|
75
78
|
}
|
|
76
79
|
}
|
|
77
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
78
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
80
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkResetPasswordComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
81
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: SparkResetPasswordComponent, isStandalone: true, selector: "spark-reset-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.resetPassword' | t }}</h3>\n </bs-card-header>\n <div class=\"p-4\">\n @if (successMessage()) {\n <bs-alert [type]=\"colors.success\" class=\"mb-3\">\n {{ successMessage() }}\n <div class=\"mt-2\">\n <a [routerLink]=\"routePaths.login\">{{ 'auth.goToLogin' | t }}</a>\n </div>\n </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 <bs-form>\n <form [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n <div class=\"mb-3\">\n <label for=\"newPassword\" class=\"form-label\">{{ 'auth.newPassword' | t }}</label>\n <input\n type=\"password\"\n id=\"newPassword\"\n formControlName=\"newPassword\"\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.resetPassword' | t }}\n </button>\n </form>\n </bs-form>\n\n <div class=\"mt-3 text-center\">\n <a [routerLink]=\"routePaths.login\">{{ 'auth.backToLogin' | t }}</a>\n </div>\n }\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 });
|
|
79
82
|
}
|
|
80
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
83
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkResetPasswordComponent, decorators: [{
|
|
81
84
|
type: Component,
|
|
82
85
|
args: [{ selector: 'spark-reset-password', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [ReactiveFormsModule, RouterLink, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsFormComponent, BsFormControlDirective, BsSpinnerComponent, TranslateKeyPipe], template: "<div class=\"d-flex justify-content-center\">\n <bs-card style=\"width: 100%; max-width: 400px;\">\n <bs-card-header>\n <h3 class=\"mb-0 text-center\">{{ 'auth.resetPassword' | t }}</h3>\n </bs-card-header>\n <div class=\"p-4\">\n @if (successMessage()) {\n <bs-alert [type]=\"colors.success\" class=\"mb-3\">\n {{ successMessage() }}\n <div class=\"mt-2\">\n <a [routerLink]=\"routePaths.login\">{{ 'auth.goToLogin' | t }}</a>\n </div>\n </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 <bs-form>\n <form [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n <div class=\"mb-3\">\n <label for=\"newPassword\" class=\"form-label\">{{ 'auth.newPassword' | t }}</label>\n <input\n type=\"password\"\n id=\"newPassword\"\n formControlName=\"newPassword\"\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.resetPassword' | t }}\n </button>\n </form>\n </bs-form>\n\n <div class=\"mt-3 text-center\">\n <a [routerLink]=\"routePaths.login\">{{ 'auth.backToLogin' | t }}</a>\n </div>\n }\n </div>\n </bs-card>\n</div>\n" }]
|
|
83
86
|
}] });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mintplayer-ng-spark-auth-reset-password.mjs","sources":["../../reset-password/src/spark-reset-password.component.ts","../../reset-password/src/spark-reset-password.component.html","../../reset-password/mintplayer-ng-spark-auth-reset-password.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, inject, OnInit, signal } from '@angular/core';\nimport { ReactiveFormsModule, FormBuilder, Validators, AbstractControl, ValidationErrors } 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 { 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\nfunction passwordMatchValidator(control: AbstractControl): ValidationErrors | null {\n const password = control.get('newPassword');\n const confirmPassword = control.get('confirmPassword');\n if (password && confirmPassword && password.value !== confirmPassword.value) {\n return { passwordMismatch: true };\n }\n return null;\n}\n\n@Component({\n selector: 'spark-reset-password',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [ReactiveFormsModule, RouterLink, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsFormComponent, BsFormControlDirective, BsSpinnerComponent, TranslateKeyPipe],\n templateUrl: './spark-reset-password.component.html',\n})\nexport class SparkResetPasswordComponent implements OnInit {\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 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 private email = '';\n private code = '';\n\n readonly form = this.fb.group({\n newPassword: ['', Validators.required],\n confirmPassword: ['', Validators.required],\n }, { validators: passwordMatchValidator });\n\n ngOnInit(): void {\n this.email = this.route.snapshot.queryParamMap.get('email') ?? '';\n this.code = this.route.snapshot.queryParamMap.get('code') ?? '';\n\n if (!this.email || !this.code) {\n this.errorMessage.set(this.translation.t('auth.invalidResetLink'));\n }\n }\n\n async onSubmit(): Promise<void> {\n if (this.form.invalid) {\n this.form.markAllAsTouched();\n return;\n }\n\n if (!this.email || !this.code) {\n this.errorMessage.set(this.translation.t('auth.invalidResetLink'));\n return;\n }\n\n this.loading.set(true);\n this.errorMessage.set('');\n this.successMessage.set('');\n\n const { newPassword } = this.form.value;\n\n try {\n await this.authService.resetPassword(this.email, this.code, newPassword!);\n this.successMessage.set(this.translation.t('auth.resetSuccess'));\n } catch (err: any) {\n if (err instanceof HttpErrorResponse && err.error?.detail) {\n this.errorMessage.set(err.error.detail);\n } else {\n this.errorMessage.set(this.translation.t('auth.resetFailed'));\n }\n } finally {\n this.loading.set(false);\n }\n }\n}\n\nexport default SparkResetPasswordComponent;\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.resetPassword' | t }}</h3>\n </bs-card-header>\n <div class=\"p-4\">\n @if (successMessage()) {\n <bs-alert [type]=\"colors.success\" class=\"mb-3\">\n {{ successMessage() }}\n <div class=\"mt-2\">\n <a [routerLink]=\"routePaths.login\">{{ 'auth.goToLogin' | t }}</a>\n </div>\n </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 <bs-form>\n <form [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n <div class=\"mb-3\">\n <label for=\"newPassword\" class=\"form-label\">{{ 'auth.newPassword' | t }}</label>\n <input\n type=\"password\"\n id=\"newPassword\"\n formControlName=\"newPassword\"\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.resetPassword' | t }}\n </button>\n </form>\n </bs-form>\n\n <div class=\"mt-3 text-center\">\n <a [routerLink]=\"routePaths.login\">{{ 'auth.backToLogin' | t }}</a>\n </div>\n }\n </div>\n </bs-card>\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mintplayer-ng-spark-auth-reset-password.mjs","sources":["../../reset-password/src/spark-reset-password.component.ts","../../reset-password/src/spark-reset-password.component.html","../../reset-password/mintplayer-ng-spark-auth-reset-password.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, inject, OnInit, signal } from '@angular/core';\nimport { ReactiveFormsModule, FormBuilder, Validators, AbstractControl, ValidationErrors } 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 { 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\nfunction passwordMatchValidator(control: AbstractControl): ValidationErrors | null {\n const password = control.get('newPassword');\n const confirmPassword = control.get('confirmPassword');\n if (password && confirmPassword && password.value !== confirmPassword.value) {\n return { passwordMismatch: true };\n }\n return null;\n}\n\n@Component({\n selector: 'spark-reset-password',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [ReactiveFormsModule, RouterLink, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsFormComponent, BsFormControlDirective, BsSpinnerComponent, TranslateKeyPipe],\n templateUrl: './spark-reset-password.component.html',\n})\nexport class SparkResetPasswordComponent implements OnInit {\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 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 private email = '';\n private code = '';\n\n readonly form = this.fb.group({\n newPassword: ['', Validators.required],\n confirmPassword: ['', Validators.required],\n }, { validators: passwordMatchValidator });\n\n ngOnInit(): void {\n this.email = this.route.snapshot.queryParamMap.get('email') ?? '';\n this.code = this.route.snapshot.queryParamMap.get('code') ?? '';\n\n if (!this.email || !this.code) {\n this.errorMessage.set(this.translation.t('auth.invalidResetLink'));\n }\n }\n\n async onSubmit(): Promise<void> {\n if (this.form.invalid) {\n this.form.markAllAsTouched();\n return;\n }\n\n if (!this.email || !this.code) {\n this.errorMessage.set(this.translation.t('auth.invalidResetLink'));\n return;\n }\n\n this.loading.set(true);\n this.errorMessage.set('');\n this.successMessage.set('');\n\n const { newPassword } = this.form.value;\n\n try {\n await this.authService.resetPassword(this.email, this.code, newPassword!);\n this.successMessage.set(this.translation.t('auth.resetSuccess'));\n } catch (err: any) {\n if (err instanceof HttpErrorResponse && err.error?.detail) {\n this.errorMessage.set(err.error.detail);\n } else {\n this.errorMessage.set(this.translation.t('auth.resetFailed'));\n }\n } finally {\n this.loading.set(false);\n }\n }\n}\n\nexport default SparkResetPasswordComponent;\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.resetPassword' | t }}</h3>\n </bs-card-header>\n <div class=\"p-4\">\n @if (successMessage()) {\n <bs-alert [type]=\"colors.success\" class=\"mb-3\">\n {{ successMessage() }}\n <div class=\"mt-2\">\n <a [routerLink]=\"routePaths.login\">{{ 'auth.goToLogin' | t }}</a>\n </div>\n </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 <bs-form>\n <form [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n <div class=\"mb-3\">\n <label for=\"newPassword\" class=\"form-label\">{{ 'auth.newPassword' | t }}</label>\n <input\n type=\"password\"\n id=\"newPassword\"\n formControlName=\"newPassword\"\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.resetPassword' | t }}\n </button>\n </form>\n </bs-form>\n\n <div class=\"mt-3 text-center\">\n <a [routerLink]=\"routePaths.login\">{{ 'auth.backToLogin' | t }}</a>\n </div>\n }\n </div>\n </bs-card>\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -8,7 +8,7 @@ import { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';
|
|
|
8
8
|
import { BsCardComponent, BsCardHeaderComponent } from '@mintplayer/ng-bootstrap/card';
|
|
9
9
|
import { BsFormComponent, BsFormControlDirective } from '@mintplayer/ng-bootstrap/form';
|
|
10
10
|
import { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';
|
|
11
|
-
import { SPARK_AUTH_CONFIG, SPARK_AUTH_ROUTE_PATHS } from '@mintplayer/ng-spark-auth/models';
|
|
11
|
+
import { SPARK_AUTH_CONFIG, SPARK_AUTH_ROUTE_PATHS, sanitizeReturnUrl } from '@mintplayer/ng-spark-auth/models';
|
|
12
12
|
import { SparkAuthService, SparkAuthTranslationService } from '@mintplayer/ng-spark-auth/core';
|
|
13
13
|
import { TranslateKeyPipe } from '@mintplayer/ng-spark-auth/pipes';
|
|
14
14
|
|
|
@@ -21,9 +21,12 @@ class SparkTwoFactorComponent {
|
|
|
21
21
|
translation = inject(SparkAuthTranslationService);
|
|
22
22
|
routePaths = inject(SPARK_AUTH_ROUTE_PATHS);
|
|
23
23
|
colors = Color;
|
|
24
|
-
loading = signal(false,
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
loading = signal(false, /* @ts-ignore */
|
|
25
|
+
...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
26
|
+
errorMessage = signal('', /* @ts-ignore */
|
|
27
|
+
...(ngDevMode ? [{ debugName: "errorMessage" }] : /* istanbul ignore next */ []));
|
|
28
|
+
useRecoveryCode = signal(false, /* @ts-ignore */
|
|
29
|
+
...(ngDevMode ? [{ debugName: "useRecoveryCode" }] : /* istanbul ignore next */ []));
|
|
27
30
|
form = this.fb.group({
|
|
28
31
|
code: [''],
|
|
29
32
|
recoveryCode: [''],
|
|
@@ -47,8 +50,12 @@ class SparkTwoFactorComponent {
|
|
|
47
50
|
const twoFactorRecoveryCode = isRecovery ? code : undefined;
|
|
48
51
|
try {
|
|
49
52
|
await this.authService.loginTwoFactor(twoFactorCode ?? '', twoFactorRecoveryCode);
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
// R2-H9: same sanitization as login. The 2FA component receives returnUrl
|
|
54
|
+
// forwarded from the login step but must still validate — query params
|
|
55
|
+
// travel through router state and a deep-link to /two-factor?returnUrl=…
|
|
56
|
+
// is a viable attacker vector.
|
|
57
|
+
const returnUrl = sanitizeReturnUrl(this.route.snapshot.queryParamMap.get('returnUrl'), this.config.defaultRedirectUrl);
|
|
58
|
+
this.router.navigateByUrl(returnUrl);
|
|
52
59
|
}
|
|
53
60
|
catch {
|
|
54
61
|
this.errorMessage.set(this.translation.t('auth.invalidCode'));
|
|
@@ -57,10 +64,10 @@ class SparkTwoFactorComponent {
|
|
|
57
64
|
this.loading.set(false);
|
|
58
65
|
}
|
|
59
66
|
}
|
|
60
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
61
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
67
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkTwoFactorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
68
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: SparkTwoFactorComponent, isStandalone: true, selector: "spark-two-factor", 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.twoFactorTitle' | 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 @if (!useRecoveryCode()) {\n <div class=\"mb-3\">\n <label for=\"code\" class=\"form-label\">{{ 'auth.code' | t }}</label>\n <input\n type=\"text\"\n id=\"code\"\n formControlName=\"code\"\n autocomplete=\"one-time-code\"\n maxlength=\"6\"\n [placeholder]=\"'auth.enterCode' | t\"\n />\n </div>\n } @else {\n <div class=\"mb-3\">\n <label for=\"recoveryCode\" class=\"form-label\">{{ 'auth.recoveryCode' | t }}</label>\n <input\n type=\"text\"\n id=\"recoveryCode\"\n formControlName=\"recoveryCode\"\n autocomplete=\"off\"\n [placeholder]=\"'auth.enterRecoveryCode' | t\"\n />\n </div>\n }\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.verify' | t }}\n </button>\n </form>\n </bs-form>\n\n <div class=\"mt-3 text-center\">\n <button class=\"btn btn-link\" (click)=\"toggleRecoveryCode()\">\n @if (useRecoveryCode()) {\n {{ 'auth.useAuthCode' | t }}\n } @else {\n {{ 'auth.useRecoveryCode' | t }}\n }\n </button>\n </div>\n <div class=\"mt-2 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.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { 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 });
|
|
62
69
|
}
|
|
63
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
70
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkTwoFactorComponent, decorators: [{
|
|
64
71
|
type: Component,
|
|
65
72
|
args: [{ selector: 'spark-two-factor', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [ReactiveFormsModule, RouterLink, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsFormComponent, BsFormControlDirective, BsSpinnerComponent, TranslateKeyPipe], template: "<div class=\"d-flex justify-content-center\">\n <bs-card style=\"width: 100%; max-width: 400px;\">\n <bs-card-header>\n <h3 class=\"mb-0 text-center\">{{ 'auth.twoFactorTitle' | 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 @if (!useRecoveryCode()) {\n <div class=\"mb-3\">\n <label for=\"code\" class=\"form-label\">{{ 'auth.code' | t }}</label>\n <input\n type=\"text\"\n id=\"code\"\n formControlName=\"code\"\n autocomplete=\"one-time-code\"\n maxlength=\"6\"\n [placeholder]=\"'auth.enterCode' | t\"\n />\n </div>\n } @else {\n <div class=\"mb-3\">\n <label for=\"recoveryCode\" class=\"form-label\">{{ 'auth.recoveryCode' | t }}</label>\n <input\n type=\"text\"\n id=\"recoveryCode\"\n formControlName=\"recoveryCode\"\n autocomplete=\"off\"\n [placeholder]=\"'auth.enterRecoveryCode' | t\"\n />\n </div>\n }\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.verify' | t }}\n </button>\n </form>\n </bs-form>\n\n <div class=\"mt-3 text-center\">\n <button class=\"btn btn-link\" (click)=\"toggleRecoveryCode()\">\n @if (useRecoveryCode()) {\n {{ 'auth.useAuthCode' | t }}\n } @else {\n {{ 'auth.useRecoveryCode' | t }}\n }\n </button>\n </div>\n <div class=\"mt-2 text-center\">\n <a [routerLink]=\"routePaths.login\">{{ 'auth.backToLogin' | t }}</a>\n </div>\n </div>\n </bs-card>\n</div>\n" }]
|
|
66
73
|
}] });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mintplayer-ng-spark-auth-two-factor.mjs","sources":["../../two-factor/src/spark-two-factor.component.ts","../../two-factor/src/spark-two-factor.component.html","../../two-factor/mintplayer-ng-spark-auth-two-factor.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 { 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_CONFIG, 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-two-factor',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [ReactiveFormsModule, RouterLink, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsFormComponent, BsFormControlDirective, BsSpinnerComponent, TranslateKeyPipe],\n templateUrl: './spark-two-factor.component.html',\n})\nexport class SparkTwoFactorComponent {\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 readonly useRecoveryCode = signal(false);\n\n readonly form = this.fb.group({\n code: [''],\n recoveryCode: [''],\n });\n\n toggleRecoveryCode(): void {\n this.useRecoveryCode.update(v => !v);\n this.errorMessage.set('');\n }\n\n async onSubmit(): Promise<void> {\n const isRecovery = this.useRecoveryCode();\n const code = isRecovery ? this.form.value.recoveryCode : this.form.value.code;\n\n if (!code?.trim()) {\n this.errorMessage.set(isRecovery\n ? this.translation.t('auth.enterRecoveryCodeError')\n : this.translation.t('auth.enterCodeError'));\n return;\n }\n\n this.loading.set(true);\n this.errorMessage.set('');\n\n const twoFactorCode = isRecovery ? undefined : code;\n const twoFactorRecoveryCode = isRecovery ? code : undefined;\n\n try {\n await this.authService.loginTwoFactor(twoFactorCode ?? '', twoFactorRecoveryCode);\n const returnUrl = this.route.snapshot.queryParamMap.get('returnUrl');\n this.router.navigateByUrl(returnUrl
|
|
1
|
+
{"version":3,"file":"mintplayer-ng-spark-auth-two-factor.mjs","sources":["../../two-factor/src/spark-two-factor.component.ts","../../two-factor/src/spark-two-factor.component.html","../../two-factor/mintplayer-ng-spark-auth-two-factor.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 { 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_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-two-factor',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [ReactiveFormsModule, RouterLink, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsFormComponent, BsFormControlDirective, BsSpinnerComponent, TranslateKeyPipe],\n templateUrl: './spark-two-factor.component.html',\n})\nexport class SparkTwoFactorComponent {\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 readonly useRecoveryCode = signal(false);\n\n readonly form = this.fb.group({\n code: [''],\n recoveryCode: [''],\n });\n\n toggleRecoveryCode(): void {\n this.useRecoveryCode.update(v => !v);\n this.errorMessage.set('');\n }\n\n async onSubmit(): Promise<void> {\n const isRecovery = this.useRecoveryCode();\n const code = isRecovery ? this.form.value.recoveryCode : this.form.value.code;\n\n if (!code?.trim()) {\n this.errorMessage.set(isRecovery\n ? this.translation.t('auth.enterRecoveryCodeError')\n : this.translation.t('auth.enterCodeError'));\n return;\n }\n\n this.loading.set(true);\n this.errorMessage.set('');\n\n const twoFactorCode = isRecovery ? undefined : code;\n const twoFactorRecoveryCode = isRecovery ? code : undefined;\n\n try {\n await this.authService.loginTwoFactor(twoFactorCode ?? '', twoFactorRecoveryCode);\n // R2-H9: same sanitization as login. The 2FA component receives returnUrl\n // forwarded from the login step but must still validate — query params\n // travel through router state and a deep-link to /two-factor?returnUrl=…\n // is a viable attacker vector.\n const returnUrl = sanitizeReturnUrl(\n this.route.snapshot.queryParamMap.get('returnUrl'),\n this.config.defaultRedirectUrl);\n this.router.navigateByUrl(returnUrl);\n } catch {\n this.errorMessage.set(this.translation.t('auth.invalidCode'));\n } finally {\n this.loading.set(false);\n }\n }\n}\n\nexport default SparkTwoFactorComponent;\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.twoFactorTitle' | 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 @if (!useRecoveryCode()) {\n <div class=\"mb-3\">\n <label for=\"code\" class=\"form-label\">{{ 'auth.code' | t }}</label>\n <input\n type=\"text\"\n id=\"code\"\n formControlName=\"code\"\n autocomplete=\"one-time-code\"\n maxlength=\"6\"\n [placeholder]=\"'auth.enterCode' | t\"\n />\n </div>\n } @else {\n <div class=\"mb-3\">\n <label for=\"recoveryCode\" class=\"form-label\">{{ 'auth.recoveryCode' | t }}</label>\n <input\n type=\"text\"\n id=\"recoveryCode\"\n formControlName=\"recoveryCode\"\n autocomplete=\"off\"\n [placeholder]=\"'auth.enterRecoveryCode' | t\"\n />\n </div>\n }\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.verify' | t }}\n </button>\n </form>\n </bs-form>\n\n <div class=\"mt-3 text-center\">\n <button class=\"btn btn-link\" (click)=\"toggleRecoveryCode()\">\n @if (useRecoveryCode()) {\n {{ 'auth.useAuthCode' | t }}\n } @else {\n {{ 'auth.useRecoveryCode' | t }}\n }\n </button>\n </div>\n <div class=\"mt-2 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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"}
|
package/package.json
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mintplayer/ng-spark-auth",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "22.0.0",
|
|
5
5
|
"description": "Angular authentication library for MintPlayer.Spark",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "https://github.com/MintPlayer/MintPlayer.Spark",
|
|
9
|
-
"directory": "node_packages/ng-spark-auth"
|
|
9
|
+
"directory": "libs/node_packages/ng-spark-auth"
|
|
10
10
|
},
|
|
11
11
|
"peerDependencies": {
|
|
12
|
-
"@angular/core": "^
|
|
13
|
-
"@angular/common": "^
|
|
14
|
-
"@angular/router": "^
|
|
15
|
-
"@angular/forms": "^
|
|
16
|
-
"@mintplayer/ng-bootstrap": "^
|
|
12
|
+
"@angular/core": "^22.0.0",
|
|
13
|
+
"@angular/common": "^22.0.0",
|
|
14
|
+
"@angular/router": "^22.0.0",
|
|
15
|
+
"@angular/forms": "^22.0.0",
|
|
16
|
+
"@mintplayer/ng-bootstrap": "^22.2.0",
|
|
17
17
|
"rxjs": "~7.8.0"
|
|
18
18
|
},
|
|
19
19
|
"sideEffects": false,
|
|
@@ -76,6 +76,7 @@
|
|
|
76
76
|
"default": "./fesm2022/mintplayer-ng-spark-auth-two-factor.mjs"
|
|
77
77
|
}
|
|
78
78
|
},
|
|
79
|
+
"type": "module",
|
|
79
80
|
"dependencies": {
|
|
80
81
|
"tslib": "^2.3.0"
|
|
81
82
|
}
|
|
@@ -5,7 +5,7 @@ declare class SparkAuthService {
|
|
|
5
5
|
private readonly http;
|
|
6
6
|
private readonly config;
|
|
7
7
|
private readonly currentUser;
|
|
8
|
-
readonly user: i0.Signal<AuthUser>;
|
|
8
|
+
readonly user: i0.Signal<AuthUser | null>;
|
|
9
9
|
readonly isAuthenticated: i0.Signal<boolean>;
|
|
10
10
|
constructor();
|
|
11
11
|
login(email: string, password: string): Promise<void>;
|
|
@@ -13,7 +13,7 @@ declare class SparkForgotPasswordComponent {
|
|
|
13
13
|
readonly errorMessage: _angular_core.WritableSignal<string>;
|
|
14
14
|
readonly successMessage: _angular_core.WritableSignal<string>;
|
|
15
15
|
readonly form: _angular_forms.FormGroup<{
|
|
16
|
-
email: _angular_forms.FormControl<string>;
|
|
16
|
+
email: _angular_forms.FormControl<string | null>;
|
|
17
17
|
}>;
|
|
18
18
|
onSubmit(): Promise<void>;
|
|
19
19
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SparkForgotPasswordComponent, never>;
|
|
@@ -15,9 +15,9 @@ declare class SparkLoginComponent {
|
|
|
15
15
|
readonly loading: _angular_core.WritableSignal<boolean>;
|
|
16
16
|
readonly errorMessage: _angular_core.WritableSignal<string>;
|
|
17
17
|
readonly form: _angular_forms.FormGroup<{
|
|
18
|
-
email: _angular_forms.FormControl<string>;
|
|
19
|
-
password: _angular_forms.FormControl<string>;
|
|
20
|
-
rememberMe: _angular_forms.FormControl<boolean>;
|
|
18
|
+
email: _angular_forms.FormControl<string | null>;
|
|
19
|
+
password: _angular_forms.FormControl<string | null>;
|
|
20
|
+
rememberMe: _angular_forms.FormControl<boolean | null>;
|
|
21
21
|
}>;
|
|
22
22
|
onSubmit(): Promise<void>;
|
|
23
23
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SparkLoginComponent, never>;
|
|
@@ -29,5 +29,23 @@ interface SparkAuthRouteConfig {
|
|
|
29
29
|
type SparkAuthRoutePaths = Required<Record<keyof SparkAuthRouteConfig, string>>;
|
|
30
30
|
declare const SPARK_AUTH_ROUTE_PATHS: InjectionToken<Required<Record<keyof SparkAuthRouteConfig, string>>>;
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Shared validator for `returnUrl` query parameters consumed by the auth
|
|
34
|
+
* components and interceptor. The framework-side equivalent in
|
|
35
|
+
* `SparkAuthenticationExtensions.SanitizeReturnUrl` enforces the same rule;
|
|
36
|
+
* keep them in sync.
|
|
37
|
+
*
|
|
38
|
+
* Accepts only local in-app paths:
|
|
39
|
+
* - Must start with a single `/`.
|
|
40
|
+
* - Must not start with `//` or `/\` (protocol-relative navigation that some
|
|
41
|
+
* browsers and routers interpret as an external host).
|
|
42
|
+
* - Must not contain CR/LF (defensive — the value flows into Router state and
|
|
43
|
+
* eventually back into a `router.navigateByUrl` call).
|
|
44
|
+
*
|
|
45
|
+
* Returns the supplied default for anything else, never throws. Per R2-H9.
|
|
46
|
+
*/
|
|
47
|
+
declare function isSafeReturnUrl(value: string | null | undefined): boolean;
|
|
48
|
+
declare function sanitizeReturnUrl(value: string | null | undefined, defaultUrl: string): string;
|
|
49
|
+
|
|
50
|
+
export { SPARK_AUTH_CONFIG, SPARK_AUTH_ROUTE_PATHS, defaultSparkAuthConfig, isSafeReturnUrl, sanitizeReturnUrl };
|
|
33
51
|
export type { AuthUser, SparkAuthConfig, SparkAuthRouteConfig, SparkAuthRouteEntry, SparkAuthRoutePaths };
|
|
@@ -13,9 +13,9 @@ declare class SparkRegisterComponent {
|
|
|
13
13
|
readonly loading: _angular_core.WritableSignal<boolean>;
|
|
14
14
|
readonly errorMessage: _angular_core.WritableSignal<string>;
|
|
15
15
|
readonly form: _angular_forms.FormGroup<{
|
|
16
|
-
email: _angular_forms.FormControl<string>;
|
|
17
|
-
password: _angular_forms.FormControl<string>;
|
|
18
|
-
confirmPassword: _angular_forms.FormControl<string>;
|
|
16
|
+
email: _angular_forms.FormControl<string | null>;
|
|
17
|
+
password: _angular_forms.FormControl<string | null>;
|
|
18
|
+
confirmPassword: _angular_forms.FormControl<string | null>;
|
|
19
19
|
}>;
|
|
20
20
|
onSubmit(): Promise<void>;
|
|
21
21
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SparkRegisterComponent, never>;
|
|
@@ -18,8 +18,8 @@ declare class SparkResetPasswordComponent implements OnInit {
|
|
|
18
18
|
private email;
|
|
19
19
|
private code;
|
|
20
20
|
readonly form: _angular_forms.FormGroup<{
|
|
21
|
-
newPassword: _angular_forms.FormControl<string>;
|
|
22
|
-
confirmPassword: _angular_forms.FormControl<string>;
|
|
21
|
+
newPassword: _angular_forms.FormControl<string | null>;
|
|
22
|
+
confirmPassword: _angular_forms.FormControl<string | null>;
|
|
23
23
|
}>;
|
|
24
24
|
ngOnInit(): void;
|
|
25
25
|
onSubmit(): Promise<void>;
|
|
@@ -16,8 +16,8 @@ declare class SparkTwoFactorComponent {
|
|
|
16
16
|
readonly errorMessage: _angular_core.WritableSignal<string>;
|
|
17
17
|
readonly useRecoveryCode: _angular_core.WritableSignal<boolean>;
|
|
18
18
|
readonly form: _angular_forms.FormGroup<{
|
|
19
|
-
code: _angular_forms.FormControl<string>;
|
|
20
|
-
recoveryCode: _angular_forms.FormControl<string>;
|
|
19
|
+
code: _angular_forms.FormControl<string | null>;
|
|
20
|
+
recoveryCode: _angular_forms.FormControl<string | null>;
|
|
21
21
|
}>;
|
|
22
22
|
toggleRecoveryCode(): void;
|
|
23
23
|
onSubmit(): Promise<void>;
|