@mintplayer/ng-spark-auth 22.2.0 → 22.3.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-login.mjs +2 -2
- package/fesm2022/mintplayer-ng-spark-auth-login.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-auth-models.mjs +2 -1
- package/fesm2022/mintplayer-ng-spark-auth-models.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-auth-register.mjs +7 -3
- package/fesm2022/mintplayer-ng-spark-auth-register.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-auth-routes.mjs +122 -34
- package/fesm2022/mintplayer-ng-spark-auth-routes.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-auth-sign-in.mjs +80 -15
- package/fesm2022/mintplayer-ng-spark-auth-sign-in.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-auth.mjs +2 -2
- package/fesm2022/mintplayer-ng-spark-auth.mjs.map +1 -1
- package/package.json +1 -1
- package/types/mintplayer-ng-spark-auth-forgot-password.d.ts +1 -1
- package/types/mintplayer-ng-spark-auth-login.d.ts +1 -1
- package/types/mintplayer-ng-spark-auth-models.d.ts +32 -27
- package/types/mintplayer-ng-spark-auth-register.d.ts +1 -1
- package/types/mintplayer-ng-spark-auth-reset-password.d.ts +1 -1
- package/types/mintplayer-ng-spark-auth-routes.d.ts +82 -12
- package/types/mintplayer-ng-spark-auth-sign-in.d.ts +58 -7
- package/types/mintplayer-ng-spark-auth-two-factor.d.ts +1 -1
|
@@ -64,11 +64,11 @@ class SparkLoginComponent {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkLoginComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
67
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: SparkLoginComponent, isStandalone: true, selector: "spark-login", ngImport: i0, template: "<div class=\"d-flex justify-content-center\">\n <bs-card style=\"width: 100%; max-width: 400px;\">\n <bs-card-header>\n <h3 class=\"mb-0 text-center\">{{ 'auth.login' | t }}</h3>\n </bs-card-header>\n <div class=\"p-4\">\n @if (errorMessage()) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">{{ errorMessage() }}</bs-alert>\n }\n\n <bs-form>\n <form [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n <div class=\"mb-3\">\n <label for=\"email\" class=\"form-label\">{{ 'auth.email' | t }}</label>\n <input\n type=\"text\"\n id=\"email\"\n formControlName=\"email\"\n autocomplete=\"username\"\n />\n </div>\n\n <div class=\"mb-3\">\n <label for=\"password\" class=\"form-label\">{{ 'auth.password' | t }}</label>\n <input\n type=\"password\"\n id=\"password\"\n formControlName=\"password\"\n autocomplete=\"current-password\"\n />\n </div>\n\n <div class=\"mb-3\">\n <bs-checkbox [type]=\"'checkbox'\" formControlName=\"rememberMe\" [name]=\"'rememberMe'\">{{ 'auth.rememberMe' | t }}</bs-checkbox>\n </div>\n\n <button\n type=\"submit\"\n class=\"btn btn-primary w-100\"\n [disabled]=\"loading()\"\n >\n @if (loading()) {\n <bs-spinner class=\"me-1\" />\n }\n {{ 'auth.login' | t }}\n </button>\n </form>\n </bs-form>\n\n <div class=\"mt-3 text-center\">\n
|
|
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 <!-- Registration is a separate opt-in feature (withRegistration()), so it may not be mounted\n even though this page is. A [routerLink] bound to undefined navigates to the current\n route instead of failing, which reads as a dead link. -->\n @if (routePaths.register) {\n <div class=\"mt-3 text-center\">\n <a [routerLink]=\"routePaths.register\">{{ 'auth.createAccount' | t }}</a>\n </div>\n }\n <div class=\"mt-2 text-center\">\n <a [routerLink]=\"routePaths.forgotPassword\">{{ 'auth.forgotPassword' | t }}</a>\n </div>\n </div>\n </bs-card>\n</div>\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "component", type: BsAlertComponent, selector: "bs-alert", inputs: ["type", "announce", "isVisible"], outputs: ["isVisibleChange", "afterOpenedOrClosed"] }, { kind: "component", type: BsCardComponent, selector: "bs-card", inputs: ["color", "outline"] }, { kind: "component", type: BsCardHeaderComponent, selector: "bs-card-header", inputs: ["color", "navStyle"] }, { kind: "component", type: BsFormComponent, selector: "bs-form", inputs: ["action", "method"], outputs: ["submitted"] }, { kind: "directive", type: BsFormControlDirective, selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)" }, { kind: "component", type: BsCheckboxComponent, selector: "bs-checkbox", inputs: ["type", "isToggled", "indeterminate", "name", "value", "group"], outputs: ["isToggledChange", "indeterminateChange"] }, { kind: "component", type: BsSpinnerComponent, selector: "bs-spinner", inputs: ["type", "color"] }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
68
68
|
}
|
|
69
69
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkLoginComponent, decorators: [{
|
|
70
70
|
type: Component,
|
|
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
|
|
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 <!-- Registration is a separate opt-in feature (withRegistration()), so it may not be mounted\n even though this page is. A [routerLink] bound to undefined navigates to the current\n route instead of failing, which reads as a dead link. -->\n @if (routePaths.register) {\n <div class=\"mt-3 text-center\">\n <a [routerLink]=\"routePaths.register\">{{ 'auth.createAccount' | t }}</a>\n </div>\n }\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" }]
|
|
72
72
|
}] });
|
|
73
73
|
|
|
74
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 { 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
|
|
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 <!-- Registration is a separate opt-in feature (withRegistration()), so it may not be mounted\n even though this page is. A [routerLink] bound to undefined navigates to the current\n route instead of failing, which reads as a dead link. -->\n @if (routePaths.register) {\n <div class=\"mt-3 text-center\">\n <a [routerLink]=\"routePaths.register\">{{ 'auth.createAccount' | t }}</a>\n </div>\n }\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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"}
|
|
@@ -8,6 +8,7 @@ const defaultSparkAuthConfig = {
|
|
|
8
8
|
};
|
|
9
9
|
|
|
10
10
|
const SPARK_AUTH_ROUTE_PATHS = new InjectionToken('SPARK_AUTH_ROUTE_PATHS');
|
|
11
|
+
const SPARK_EXTERNAL_PROVIDERS = new InjectionToken('SPARK_EXTERNAL_PROVIDERS');
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Shared validator for `returnUrl` query parameters consumed by the auth
|
|
@@ -82,5 +83,5 @@ function matches(routes, target, prefix = '') {
|
|
|
82
83
|
* Generated bundle index. Do not edit.
|
|
83
84
|
*/
|
|
84
85
|
|
|
85
|
-
export { SPARK_AUTH_CONFIG, SPARK_AUTH_ROUTE_PATHS, SPARK_EXTERNAL_LOGIN_MESSAGE_TYPE, defaultSparkAuthConfig, isSafeReturnUrl, resolveSignInUrl, sanitizeReturnUrl };
|
|
86
|
+
export { SPARK_AUTH_CONFIG, SPARK_AUTH_ROUTE_PATHS, SPARK_EXTERNAL_LOGIN_MESSAGE_TYPE, SPARK_EXTERNAL_PROVIDERS, defaultSparkAuthConfig, isSafeReturnUrl, resolveSignInUrl, sanitizeReturnUrl };
|
|
86
87
|
//# sourceMappingURL=mintplayer-ng-spark-auth-models.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mintplayer-ng-spark-auth-models.mjs","sources":["../../models/src/auth-config.ts","../../models/src/auth-route-config.ts","../../models/src/return-url.ts","../../models/src/external-login.ts","../../models/src/sign-in-redirect.ts","../../models/mintplayer-ng-spark-auth-models.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\n\nexport interface SparkAuthConfig {\n apiBasePath: string;\n defaultRedirectUrl: string;\n loginUrl: string;\n}\n\nexport const SPARK_AUTH_CONFIG = new InjectionToken<SparkAuthConfig>('SPARK_AUTH_CONFIG');\n\nexport const defaultSparkAuthConfig: SparkAuthConfig = {\n apiBasePath: '/spark/auth',\n defaultRedirectUrl: '/',\n loginUrl: '/login',\n};\n","import { InjectionToken, Type } from '@angular/core';\n\nexport type SparkAuthRouteEntry = string | { path: string; component?: Type<unknown> };\n\n
|
|
1
|
+
{"version":3,"file":"mintplayer-ng-spark-auth-models.mjs","sources":["../../models/src/auth-config.ts","../../models/src/auth-route-config.ts","../../models/src/return-url.ts","../../models/src/external-login.ts","../../models/src/sign-in-redirect.ts","../../models/mintplayer-ng-spark-auth-models.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\n\nexport interface SparkAuthConfig {\n apiBasePath: string;\n defaultRedirectUrl: string;\n loginUrl: string;\n}\n\nexport const SPARK_AUTH_CONFIG = new InjectionToken<SparkAuthConfig>('SPARK_AUTH_CONFIG');\n\nexport const defaultSparkAuthConfig: SparkAuthConfig = {\n apiBasePath: '/spark/auth',\n defaultRedirectUrl: '/',\n loginUrl: '/login',\n};\n","import { InjectionToken, Type } from '@angular/core';\n\nexport type SparkAuthRouteEntry = string | { path: string; component?: Type<unknown> };\n\n/** The routable authentication pages. */\nexport interface SparkAuthRouteEntries {\n /** The provider-button landing page, mounted by `withExternalLogin()`. */\n signIn?: SparkAuthRouteEntry;\n login?: SparkAuthRouteEntry;\n twoFactor?: SparkAuthRouteEntry;\n register?: SparkAuthRouteEntry;\n forgotPassword?: SparkAuthRouteEntry;\n resetPassword?: SparkAuthRouteEntry;\n}\n\n/**\n * The paths of the pages that were actually mounted.\n *\n * Partial, and that is the change: pages are opted into individually now, so a template cannot\n * assume its siblings exist. `login` links to `register` only when registration was opted into, and\n * the sign-in landing page links to `login` only when local login was. A `[routerLink]` bound to\n * `undefined` silently navigates to the current route, so every cross-feature link is guarded.\n */\nexport type SparkAuthRoutePaths = Partial<Record<keyof SparkAuthRouteEntries, string>>;\n\nexport const SPARK_AUTH_ROUTE_PATHS = new InjectionToken<SparkAuthRoutePaths>('SPARK_AUTH_ROUTE_PATHS');\n\n/**\n * Presentation for one external provider's button — an icon, a label, an ordering.\n *\n * **It decorates; it does not declare.** The server stays authoritative over which providers exist\n * (`GET /spark/auth/capabilities`), because letting the client declare them would reintroduce exactly\n * the string-literal mismatch that endpoint exists to prevent. A scheme the server reports with no\n * matching declaration falls back to the default button, so adding a provider server-side never\n * yields a blank page; a declaration matching no reported scheme is simply unused.\n */\nexport interface SparkExternalProviderPresentation {\n /** The ASP.NET Core authentication scheme, as the server reports it. Matched case-insensitively. */\n scheme: string;\n /** Overrides the server's display name. */\n displayName?: string;\n /** A CSS class for an icon element rendered before the label. */\n iconClass?: string;\n /** Lower sorts first. Providers with no declared order keep the server's order, after declared ones. */\n order?: number;\n}\n\nexport const SPARK_EXTERNAL_PROVIDERS = new InjectionToken<SparkExternalProviderPresentation[]>(\n 'SPARK_EXTERNAL_PROVIDERS',\n);\n","/**\n * Shared validator for `returnUrl` query parameters consumed by the auth\n * components and interceptor. The framework-side equivalent in\n * `SparkAuthenticationExtensions.SanitizeReturnUrl` enforces the same rule;\n * keep them in sync.\n *\n * Accepts only local in-app paths:\n * - Must start with a single `/`.\n * - Must not start with `//` or `/\\` (protocol-relative navigation that some\n * browsers and routers interpret as an external host).\n * - Must not contain CR/LF (defensive — the value flows into Router state and\n * eventually back into a `router.navigateByUrl` call).\n *\n * Returns the supplied default for anything else, never throws. Per R2-H9.\n */\nexport function isSafeReturnUrl(value: string | null | undefined): boolean {\n if (!value) return false;\n if (/[\\r\\n]/.test(value)) return false;\n if (!value.startsWith('/')) return false;\n if (value.length >= 2 && (value[1] === '/' || value[1] === '\\\\')) return false;\n return true;\n}\n\nexport function sanitizeReturnUrl(\n value: string | null | undefined,\n defaultUrl: string,\n): string {\n return isSafeReturnUrl(value) ? (value as string) : defaultUrl;\n}\n","/** How the user is sent to the external provider, and how the result comes back. */\nexport type SparkExternalLoginMode = 'popup' | 'redirect';\n\nexport interface SparkExternalLoginOptions {\n /** Where to land after a successful sign-in. Server-side sanitized to an in-app path. */\n returnUrl?: string;\n /** Defaults to `'popup'`. */\n mode?: SparkExternalLoginMode;\n}\n\n/**\n * Why an external login did not sign anyone in. The three `no_login_info` /\n * `email_not_verified` / `account_creation_failed` codes come from the server; the two\n * `popup_*` codes are raised here, because the browser is the only place that can observe\n * them. Deliberately coarse — never enough to tell \"no such account\" from anything else.\n */\nexport type SparkExternalLoginError =\n | 'no_login_info'\n | 'email_not_verified'\n | 'account_creation_failed'\n | 'popup_blocked'\n | 'popup_closed';\n\nexport interface SparkExternalLoginResult {\n success: boolean;\n error?: SparkExternalLoginError;\n}\n\n/** The message the callback page posts back to the window that opened it. */\nexport interface SparkExternalLoginMessage {\n type: 'spark:external-login';\n success: boolean;\n error?: SparkExternalLoginError;\n}\n\nexport const SPARK_EXTERNAL_LOGIN_MESSAGE_TYPE = 'spark:external-login';\n","import { isDevMode } from '@angular/core';\nimport { Route, Router } from '@angular/router';\nimport { SparkAuthConfig } from './auth-config';\n\n/**\n * Where to send someone who is not signed in.\n *\n * `loginUrl` is the single source of truth for this, and the only thing the guard, the interceptor\n * and the auth bar should consult — an application that turns off local credentials points it at\n * its own sign-in landing route.\n *\n * The reason this is a function rather than a property read is the failure mode it closes: nothing\n * connects `loginUrl` to the routes that actually exist, so pointing it at a route that was never\n * registered produces a redirect into a blank page with no type error, no build failure and no\n * runtime error. In development that now warns, once, naming the value and the fix.\n */\nexport function resolveSignInUrl(config: SparkAuthConfig, router: Router): string {\n if (isDevMode() && !warned && !routeExists(router, config.loginUrl)) {\n warned = true;\n console.warn(\n `[ng-spark-auth] loginUrl is \"${config.loginUrl}\", but no route matches it, so redirecting an `\n + `unauthenticated user will land on a blank page. If this app runs with local credentials `\n + `disabled, point loginUrl at its sign-in route (for example provideSparkAuth({ loginUrl: '/sign-in' })).`,\n );\n }\n\n return config.loginUrl;\n}\n\nlet warned = false;\n\nfunction routeExists(router: Router, url: string): boolean {\n const target = url.replace(/^\\/+/, '').split('?')[0].split('#')[0];\n return matches(router.config, target);\n}\n\nfunction matches(routes: Route[], target: string, prefix = ''): boolean {\n return routes.some(route => {\n const path = [prefix, route.path ?? ''].filter(Boolean).join('/');\n if (path === target) return true;\n // A wildcard route would match anything, including a URL nobody registered — treating it as a\n // match would make this check always pass and warn about nothing.\n return route.children ? matches(route.children, target, path) : false;\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;MAQa,iBAAiB,GAAG,IAAI,cAAc,CAAkB,mBAAmB;AAEjF,MAAM,sBAAsB,GAAoB;AACrD,IAAA,WAAW,EAAE,aAAa;AAC1B,IAAA,kBAAkB,EAAE,GAAG;AACvB,IAAA,QAAQ,EAAE,QAAQ;;;MCYP,sBAAsB,GAAG,IAAI,cAAc,CAAsB,wBAAwB;MAsBzF,wBAAwB,GAAG,IAAI,cAAc,CACxD,0BAA0B;;AChD5B;;;;;;;;;;;;;;AAcG;AACG,SAAU,eAAe,CAAC,KAAgC,EAAA;AAC9D,IAAA,IAAI,CAAC,KAAK;AAAE,QAAA,OAAO,KAAK;AACxB,IAAA,IAAI,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC;AAAE,QAAA,OAAO,KAAK;AACtC,IAAA,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;AAAE,QAAA,OAAO,KAAK;IACxC,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;AAAE,QAAA,OAAO,KAAK;AAC9E,IAAA,OAAO,IAAI;AACb;AAEM,SAAU,iBAAiB,CAC/B,KAAgC,EAChC,UAAkB,EAAA;AAElB,IAAA,OAAO,eAAe,CAAC,KAAK,CAAC,GAAI,KAAgB,GAAG,UAAU;AAChE;;ACOO,MAAM,iCAAiC,GAAG;;AC/BjD;;;;;;;;;;;AAWG;AACG,SAAU,gBAAgB,CAAC,MAAuB,EAAE,MAAc,EAAA;AACtE,IAAA,IAAI,SAAS,EAAE,IAAI,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE;QACnE,MAAM,GAAG,IAAI;AACb,QAAA,OAAO,CAAC,IAAI,CACV,gCAAgC,MAAM,CAAC,QAAQ,CAAA,8CAAA;cAC7C,CAAA,wFAAA;AACA,cAAA,CAAA,uGAAA,CAAyG,CAC5G;IACH;IAEA,OAAO,MAAM,CAAC,QAAQ;AACxB;AAEA,IAAI,MAAM,GAAG,KAAK;AAElB,SAAS,WAAW,CAAC,MAAc,EAAE,GAAW,EAAA;IAC9C,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAClE,OAAO,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;AACvC;AAEA,SAAS,OAAO,CAAC,MAAe,EAAE,MAAc,EAAE,MAAM,GAAG,EAAE,EAAA;AAC3D,IAAA,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,IAAG;QACzB,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;QACjE,IAAI,IAAI,KAAK,MAAM;AAAE,YAAA,OAAO,IAAI;;;QAGhC,OAAO,KAAK,CAAC,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,KAAK;AACvE,IAAA,CAAC,CAAC;AACJ;;AC5CA;;AAEG;;;;"}
|
|
@@ -47,7 +47,11 @@ class SparkRegisterComponent {
|
|
|
47
47
|
const { email, password } = this.form.value;
|
|
48
48
|
try {
|
|
49
49
|
await this.authService.register(email, password);
|
|
50
|
-
|
|
50
|
+
// An app can mount registration without the password form (external-only sign-in with
|
|
51
|
+
// self-service account creation). Landing on the site root beats navigating to `undefined`,
|
|
52
|
+
// which the router resolves to the current route — leaving the user on a form they just
|
|
53
|
+
// submitted successfully.
|
|
54
|
+
this.router.navigate([this.routePaths.login ?? '/'], {
|
|
51
55
|
queryParams: { registered: 'true' },
|
|
52
56
|
});
|
|
53
57
|
}
|
|
@@ -73,11 +77,11 @@ class SparkRegisterComponent {
|
|
|
73
77
|
}
|
|
74
78
|
}
|
|
75
79
|
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
|
|
80
|
+
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 <!-- withRegistration() is opt-in independently of withLocalLogin(), so the password form may\n not be mounted. An unguarded [routerLink]=\"undefined\" navigates to the current route. -->\n @if (routePaths.login) {\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 }\n </div>\n </bs-card>\n</div>\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "component", type: BsAlertComponent, selector: "bs-alert", inputs: ["type", "announce", "isVisible"], outputs: ["isVisibleChange", "afterOpenedOrClosed"] }, { kind: "component", type: BsCardComponent, selector: "bs-card", inputs: ["color", "outline"] }, { kind: "component", type: BsCardHeaderComponent, selector: "bs-card-header", inputs: ["color", "navStyle"] }, { kind: "component", type: BsFormComponent, selector: "bs-form", inputs: ["action", "method"], outputs: ["submitted"] }, { kind: "directive", type: BsFormControlDirective, selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)" }, { kind: "component", type: BsSpinnerComponent, selector: "bs-spinner", inputs: ["type", "color"] }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
77
81
|
}
|
|
78
82
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkRegisterComponent, decorators: [{
|
|
79
83
|
type: Component,
|
|
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
|
|
84
|
+
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 <!-- withRegistration() is opt-in independently of withLocalLogin(), so the password form may\n not be mounted. An unguarded [routerLink]=\"undefined\" navigates to the current route. -->\n @if (routePaths.login) {\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 }\n </div>\n </bs-card>\n</div>\n" }]
|
|
81
85
|
}] });
|
|
82
86
|
|
|
83
87
|
/**
|
|
@@ -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 // An app can mount registration without the password form (external-only sign-in with\n // self-service account creation). Landing on the site root beats navigating to `undefined`,\n // which the router resolves to the current route — leaving the user on a form they just\n // submitted successfully.\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 <!-- withRegistration() is opt-in independently of withLocalLogin(), so the password form may\n not be mounted. An unguarded [routerLink]=\"undefined\" navigates to the current route. -->\n @if (routePaths.login) {\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 }\n </div>\n </bs-card>\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SPARK_AUTH_ROUTE_PATHS } from '@mintplayer/ng-spark-auth/models';
|
|
1
|
+
import { SPARK_AUTH_ROUTE_PATHS, SPARK_EXTERNAL_PROVIDERS } from '@mintplayer/ng-spark-auth/models';
|
|
2
2
|
|
|
3
3
|
/** The routed path for an entry, independent of how its component is loaded. */
|
|
4
4
|
function entryPath(entry, defaultPath) {
|
|
@@ -14,57 +14,145 @@ function child(entry, path, defaultLoader) {
|
|
|
14
14
|
/**
|
|
15
15
|
* The routes for Spark's authentication pages.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* **Nothing is mounted unless a feature asks for it.** Passing no features emits no pages at all —
|
|
18
|
+
* opt-in enforced by construction rather than by a flag, matching `provideHttpClient(withFetch())`
|
|
19
|
+
* and `provideRouter(withComponentInputBinding())`. Before this the pages mounted by default and an
|
|
20
|
+
* application had to opt *out*, so every app shipped a registration form whether or not it wanted
|
|
21
|
+
* one.
|
|
20
22
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
23
|
+
* ```ts
|
|
24
|
+
* sparkAuthRoutes(
|
|
25
|
+
* withLocalLogin(),
|
|
26
|
+
* withRegistration(),
|
|
27
|
+
* withExternalLogin(githubProvider(), facebookProvider()),
|
|
28
|
+
* )
|
|
29
|
+
* ```
|
|
25
30
|
*
|
|
26
|
-
* `
|
|
27
|
-
*
|
|
31
|
+
* The `import()` expressions live *inside* each feature. That placement is the point: a bundler
|
|
32
|
+
* decides whether to emit a lazy chunk from whether the `import()` call site is reachable, not from
|
|
33
|
+
* whether the route object referencing it survives — so filtering a children array after the fact
|
|
34
|
+
* would still ship every page. A page nobody opted into has no reachable `import()` at all.
|
|
28
35
|
*/
|
|
29
|
-
function sparkAuthRoutes(
|
|
30
|
-
const mode = config?.localCredentials ?? 'full';
|
|
31
|
-
const paths = {
|
|
32
|
-
signIn: '/' + entryPath(config?.signIn, 'sign-in'),
|
|
33
|
-
login: '/' + entryPath(config?.login, 'login'),
|
|
34
|
-
twoFactor: '/' + entryPath(config?.twoFactor, 'login/two-factor'),
|
|
35
|
-
register: '/' + entryPath(config?.register, 'register'),
|
|
36
|
-
forgotPassword: '/' + entryPath(config?.forgotPassword, 'forgot-password'),
|
|
37
|
-
resetPassword: '/' + entryPath(config?.resetPassword, 'reset-password'),
|
|
38
|
-
};
|
|
36
|
+
function sparkAuthRoutes(...features) {
|
|
39
37
|
const children = [];
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
children.push(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
// Reachable only from the login page's RequiresTwoFactor branch, and it posts to the same
|
|
48
|
-
// /login endpoint — so it belongs to password sign-in, not to authentication in general.
|
|
49
|
-
child(config?.twoFactor, entryPath(config?.twoFactor, 'login/two-factor'), () => import('@mintplayer/ng-spark-auth/two-factor').then(m => m.SparkTwoFactorComponent)), child(config?.forgotPassword, entryPath(config?.forgotPassword, 'forgot-password'), () => import('@mintplayer/ng-spark-auth/forgot-password').then(m => m.SparkForgotPasswordComponent)), child(config?.resetPassword, entryPath(config?.resetPassword, 'reset-password'), () => import('@mintplayer/ng-spark-auth/reset-password').then(m => m.SparkResetPasswordComponent)));
|
|
50
|
-
}
|
|
51
|
-
if (mode === 'full') {
|
|
52
|
-
children.push(child(config?.register, entryPath(config?.register, 'register'), () => import('@mintplayer/ng-spark-auth/register').then(m => m.SparkRegisterComponent)));
|
|
38
|
+
const paths = {};
|
|
39
|
+
const externalProviders = [];
|
|
40
|
+
for (const feature of features) {
|
|
41
|
+
children.push(...feature.children);
|
|
42
|
+
Object.assign(paths, feature.paths);
|
|
43
|
+
if (feature.providers)
|
|
44
|
+
externalProviders.push(...feature.providers);
|
|
53
45
|
}
|
|
54
46
|
return [
|
|
55
47
|
{
|
|
56
48
|
path: '',
|
|
57
49
|
providers: [
|
|
58
50
|
{ provide: SPARK_AUTH_ROUTE_PATHS, useValue: paths },
|
|
51
|
+
{ provide: SPARK_EXTERNAL_PROVIDERS, useValue: externalProviders },
|
|
59
52
|
],
|
|
60
53
|
children,
|
|
61
54
|
},
|
|
62
55
|
];
|
|
63
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Mounts the email/password family: login, two-factor, forgot-password, reset-password.
|
|
59
|
+
*
|
|
60
|
+
* These four are one feature rather than four because they form a star centred on the login page and
|
|
61
|
+
* every template dereferences its siblings unconditionally — removing any proper subset leaves a
|
|
62
|
+
* dangling link. Registration is genuinely separable, and is its own feature.
|
|
63
|
+
*
|
|
64
|
+
* Two-factor belongs here because it is reachable only from the login page's `RequiresTwoFactor`
|
|
65
|
+
* branch and posts to the same `/login` endpoint: it is part of password sign-in, not of
|
|
66
|
+
* authentication in general.
|
|
67
|
+
*/
|
|
68
|
+
function withLocalLogin(entries) {
|
|
69
|
+
const login = entryPath(entries?.login, 'login');
|
|
70
|
+
const twoFactor = entryPath(entries?.twoFactor, 'login/two-factor');
|
|
71
|
+
const forgotPassword = entryPath(entries?.forgotPassword, 'forgot-password');
|
|
72
|
+
const resetPassword = entryPath(entries?.resetPassword, 'reset-password');
|
|
73
|
+
return {
|
|
74
|
+
paths: {
|
|
75
|
+
login: '/' + login,
|
|
76
|
+
twoFactor: '/' + twoFactor,
|
|
77
|
+
forgotPassword: '/' + forgotPassword,
|
|
78
|
+
resetPassword: '/' + resetPassword,
|
|
79
|
+
},
|
|
80
|
+
children: [
|
|
81
|
+
child(entries?.login, login, () => import('@mintplayer/ng-spark-auth/login').then(m => m.SparkLoginComponent)),
|
|
82
|
+
child(entries?.twoFactor, twoFactor, () => import('@mintplayer/ng-spark-auth/two-factor').then(m => m.SparkTwoFactorComponent)),
|
|
83
|
+
child(entries?.forgotPassword, forgotPassword, () => import('@mintplayer/ng-spark-auth/forgot-password').then(m => m.SparkForgotPasswordComponent)),
|
|
84
|
+
child(entries?.resetPassword, resetPassword, () => import('@mintplayer/ng-spark-auth/reset-password').then(m => m.SparkResetPasswordComponent)),
|
|
85
|
+
],
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Mounts the self-service registration page.
|
|
90
|
+
*
|
|
91
|
+
* Separate from {@link withLocalLogin} because the two decisions genuinely are separate: an
|
|
92
|
+
* application whose accounts are provisioned by an administrator still needs password sign-in and
|
|
93
|
+
* password reset. Mount it only alongside a server whose `SparkLocalCredentials` is `Full` — with
|
|
94
|
+
* `SignInOnly` the endpoint it posts to is not mapped.
|
|
95
|
+
*/
|
|
96
|
+
function withRegistration(entry) {
|
|
97
|
+
const register = entryPath(entry, 'register');
|
|
98
|
+
return {
|
|
99
|
+
paths: { register: '/' + register },
|
|
100
|
+
children: [
|
|
101
|
+
child(entry, register, () => import('@mintplayer/ng-spark-auth/register').then(m => m.SparkRegisterComponent)),
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Mounts the sign-in landing page — a button per external provider the *server* reports.
|
|
107
|
+
*
|
|
108
|
+
* Accepts provider declarations and, in any position, one options object for the page's own path;
|
|
109
|
+
* the two shapes are disjoint, so `withExternalLogin(githubProvider(), facebookProvider())` and
|
|
110
|
+
* `withExternalLogin({ signIn: 'landing' }, githubProvider())` both read naturally.
|
|
111
|
+
*
|
|
112
|
+
* Declaring no providers is valid and useful: the page still renders whatever
|
|
113
|
+
* `GET /spark/auth/capabilities` reports, using default buttons.
|
|
114
|
+
*/
|
|
115
|
+
function withExternalLogin(...providersOrOptions) {
|
|
116
|
+
const providers = providersOrOptions.filter(isProvider);
|
|
117
|
+
const options = providersOrOptions.find((p) => !isProvider(p));
|
|
118
|
+
const signIn = entryPath(options?.signIn, 'sign-in');
|
|
119
|
+
return {
|
|
120
|
+
paths: { signIn: '/' + signIn },
|
|
121
|
+
providers,
|
|
122
|
+
children: [
|
|
123
|
+
child(options?.signIn, signIn, () => import('@mintplayer/ng-spark-auth/sign-in').then(m => m.SparkSignInComponent)),
|
|
124
|
+
],
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function isProvider(value) {
|
|
128
|
+
return typeof value.scheme === 'string';
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Presentation for one provider's button, keyed by the scheme the server reports.
|
|
132
|
+
*
|
|
133
|
+
* The generic form. `githubProvider()` and friends are this with a scheme and an icon filled in —
|
|
134
|
+
* they exist so the common case does not require knowing the scheme string, not because the library
|
|
135
|
+
* has an opinion about which providers exist.
|
|
136
|
+
*/
|
|
137
|
+
function externalProvider(scheme, presentation) {
|
|
138
|
+
return { scheme, ...presentation };
|
|
139
|
+
}
|
|
140
|
+
function githubProvider(presentation) {
|
|
141
|
+
return externalProvider('GitHub', { iconClass: 'bi bi-github', ...presentation });
|
|
142
|
+
}
|
|
143
|
+
function googleProvider(presentation) {
|
|
144
|
+
return externalProvider('Google', { iconClass: 'bi bi-google', ...presentation });
|
|
145
|
+
}
|
|
146
|
+
function facebookProvider(presentation) {
|
|
147
|
+
return externalProvider('Facebook', { iconClass: 'bi bi-facebook', ...presentation });
|
|
148
|
+
}
|
|
149
|
+
function microsoftProvider(presentation) {
|
|
150
|
+
return externalProvider('Microsoft', { iconClass: 'bi bi-microsoft', ...presentation });
|
|
151
|
+
}
|
|
64
152
|
|
|
65
153
|
/**
|
|
66
154
|
* Generated bundle index. Do not edit.
|
|
67
155
|
*/
|
|
68
156
|
|
|
69
|
-
export { sparkAuthRoutes };
|
|
157
|
+
export { externalProvider, facebookProvider, githubProvider, googleProvider, microsoftProvider, sparkAuthRoutes, withExternalLogin, withLocalLogin, withRegistration };
|
|
70
158
|
//# sourceMappingURL=mintplayer-ng-spark-auth-routes.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mintplayer-ng-spark-auth-routes.mjs","sources":["../../routes/src/spark-auth-routes.ts","../../routes/mintplayer-ng-spark-auth-routes.ts"],"sourcesContent":["import { SPARK_AUTH_ROUTE_PATHS, SparkAuthRouteConfig, SparkAuthRouteEntry, SparkAuthRoutePaths } from '@mintplayer/ng-spark-auth/models';\n\ntype Loader = () => Promise<any>;\n\ninterface Child {\n path: string;\n loadComponent: Loader;\n}\n\n/** The routed path for an entry, independent of how its component is loaded. */\nfunction entryPath(entry: SparkAuthRouteEntry | undefined, defaultPath: string): string {\n if (entry === undefined) return defaultPath;\n return typeof entry === 'string' ? entry : entry.path;\n}\n\n/** An explicitly supplied component wins over the library's lazy import. */\nfunction child(entry: SparkAuthRouteEntry | undefined, path: string, defaultLoader: Loader): Child {\n const component = typeof entry === 'object' && entry.component ? entry.component : undefined;\n return { path, loadComponent: component ? () => Promise.resolve(component) : defaultLoader };\n}\n\n/**\n * The routes for Spark's authentication pages.\n *\n * `config.localCredentials` chooses how much of the email/password family to route, mirroring the\n * server's `SparkLocalCredentials`. `GET /spark/auth/capabilities` reports what the server is\n * actually running, so the two can be checked against each other.\n *\n * The `import()` expressions live *inside* the branches that need them. That placement is the point:\n * a bundler decides whether to emit a lazy chunk from whether the `import()` call site is reachable,\n * not from whether the route object referencing it survives — so filtering the children array after\n * the fact would still ship every page. Excluded pages must have no reachable `import()` at all.\n *\n * `SPARK_AUTH_ROUTE_PATHS` is still provided in full. The excluded pages are excluded together, so\n * nothing that survives can link to something that does not, and the token stays `Required`.\n */\nexport function sparkAuthRoutes(config?: SparkAuthRouteConfig): any[] {\n const mode = config?.localCredentials ?? 'full';\n\n const paths: SparkAuthRoutePaths = {\n signIn: '/' + entryPath(config?.signIn, 'sign-in'),\n login: '/' + entryPath(config?.login, 'login'),\n twoFactor: '/' + entryPath(config?.twoFactor, 'login/two-factor'),\n register: '/' + entryPath(config?.register, 'register'),\n forgotPassword: '/' + entryPath(config?.forgotPassword, 'forgot-password'),\n resetPassword: '/' + entryPath(config?.resetPassword, 'reset-password'),\n };\n\n const children: Child[] = [];\n\n // Only when local credentials are limited. In 'full' mode the login page is already the landing\n // page, and emitting a second one would change the routes of every app that never opted in.\n if (mode !== 'full') {\n children.push(\n child(config?.signIn, entryPath(config?.signIn, 'sign-in'),\n () => import('@mintplayer/ng-spark-auth/sign-in').then(m => m.SparkSignInComponent)),\n );\n }\n\n if (mode !== 'disabled') {\n children.push(\n child(config?.login, entryPath(config?.login, 'login'),\n () => import('@mintplayer/ng-spark-auth/login').then(m => m.SparkLoginComponent)),\n // Reachable only from the login page's RequiresTwoFactor branch, and it posts to the same\n // /login endpoint — so it belongs to password sign-in, not to authentication in general.\n child(config?.twoFactor, entryPath(config?.twoFactor, 'login/two-factor'),\n () => import('@mintplayer/ng-spark-auth/two-factor').then(m => m.SparkTwoFactorComponent)),\n child(config?.forgotPassword, entryPath(config?.forgotPassword, 'forgot-password'),\n () => import('@mintplayer/ng-spark-auth/forgot-password').then(m => m.SparkForgotPasswordComponent)),\n child(config?.resetPassword, entryPath(config?.resetPassword, 'reset-password'),\n () => import('@mintplayer/ng-spark-auth/reset-password').then(m => m.SparkResetPasswordComponent)),\n );\n }\n\n if (mode === 'full') {\n children.push(\n child(config?.register, entryPath(config?.register, 'register'),\n () => import('@mintplayer/ng-spark-auth/register').then(m => m.SparkRegisterComponent)),\n );\n }\n\n return [\n {\n path: '',\n providers: [\n { provide: SPARK_AUTH_ROUTE_PATHS, useValue: paths },\n ],\n children,\n },\n ];\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mintplayer-ng-spark-auth-routes.mjs","sources":["../../routes/src/spark-auth-routes.ts","../../routes/mintplayer-ng-spark-auth-routes.ts"],"sourcesContent":["import {\n SPARK_AUTH_ROUTE_PATHS,\n SPARK_EXTERNAL_PROVIDERS,\n SparkAuthRouteEntries,\n SparkAuthRouteEntry,\n SparkAuthRoutePaths,\n SparkExternalProviderPresentation,\n} from '@mintplayer/ng-spark-auth/models';\n\ntype Loader = () => Promise<any>;\n\ninterface Child {\n path: string;\n loadComponent: Loader;\n}\n\n/** The routed path for an entry, independent of how its component is loaded. */\nfunction entryPath(entry: SparkAuthRouteEntry | undefined, defaultPath: string): string {\n if (entry === undefined) return defaultPath;\n return typeof entry === 'string' ? entry : entry.path;\n}\n\n/** An explicitly supplied component wins over the library's lazy import. */\nfunction child(entry: SparkAuthRouteEntry | undefined, path: string, defaultLoader: Loader): Child {\n const component = typeof entry === 'object' && entry.component ? entry.component : undefined;\n return { path, loadComponent: component ? () => Promise.resolve(component) : defaultLoader };\n}\n\n/**\n * One opt-in group of authentication pages. Produced by `withLocalLogin()`, `withRegistration()` and\n * `withExternalLogin()`; not constructible by consumers, which is what keeps the set of mountable\n * pages a decision this library makes rather than a shape an application can invent.\n */\nexport interface SparkAuthRoutesFeature {\n readonly children: Child[];\n readonly paths: SparkAuthRoutePaths;\n readonly providers?: SparkExternalProviderPresentation[];\n}\n\n/** Path override for the sign-in landing page. Named to stay clear of the core package's\n * `SparkExternalLoginOptions`, which is about the sign-in *call* rather than the route. */\nexport type SparkExternalLoginRouteOptions = Pick<SparkAuthRouteEntries, 'signIn'>;\n\n/**\n * The routes for Spark's authentication pages.\n *\n * **Nothing is mounted unless a feature asks for it.** Passing no features emits no pages at all —\n * opt-in enforced by construction rather than by a flag, matching `provideHttpClient(withFetch())`\n * and `provideRouter(withComponentInputBinding())`. Before this the pages mounted by default and an\n * application had to opt *out*, so every app shipped a registration form whether or not it wanted\n * one.\n *\n * ```ts\n * sparkAuthRoutes(\n * withLocalLogin(),\n * withRegistration(),\n * withExternalLogin(githubProvider(), facebookProvider()),\n * )\n * ```\n *\n * The `import()` expressions live *inside* each feature. That placement is the point: a bundler\n * decides whether to emit a lazy chunk from whether the `import()` call site is reachable, not from\n * whether the route object referencing it survives — so filtering a children array after the fact\n * would still ship every page. A page nobody opted into has no reachable `import()` at all.\n */\nexport function sparkAuthRoutes(...features: SparkAuthRoutesFeature[]): any[] {\n const children: Child[] = [];\n const paths: SparkAuthRoutePaths = {};\n const externalProviders: SparkExternalProviderPresentation[] = [];\n\n for (const feature of features) {\n children.push(...feature.children);\n Object.assign(paths, feature.paths);\n if (feature.providers) externalProviders.push(...feature.providers);\n }\n\n return [\n {\n path: '',\n providers: [\n { provide: SPARK_AUTH_ROUTE_PATHS, useValue: paths },\n { provide: SPARK_EXTERNAL_PROVIDERS, useValue: externalProviders },\n ],\n children,\n },\n ];\n}\n\n/**\n * Mounts the email/password family: login, two-factor, forgot-password, reset-password.\n *\n * These four are one feature rather than four because they form a star centred on the login page and\n * every template dereferences its siblings unconditionally — removing any proper subset leaves a\n * dangling link. Registration is genuinely separable, and is its own feature.\n *\n * Two-factor belongs here because it is reachable only from the login page's `RequiresTwoFactor`\n * branch and posts to the same `/login` endpoint: it is part of password sign-in, not of\n * authentication in general.\n */\nexport function withLocalLogin(\n entries?: Pick<SparkAuthRouteEntries, 'login' | 'twoFactor' | 'forgotPassword' | 'resetPassword'>,\n): SparkAuthRoutesFeature {\n const login = entryPath(entries?.login, 'login');\n const twoFactor = entryPath(entries?.twoFactor, 'login/two-factor');\n const forgotPassword = entryPath(entries?.forgotPassword, 'forgot-password');\n const resetPassword = entryPath(entries?.resetPassword, 'reset-password');\n\n return {\n paths: {\n login: '/' + login,\n twoFactor: '/' + twoFactor,\n forgotPassword: '/' + forgotPassword,\n resetPassword: '/' + resetPassword,\n },\n children: [\n child(entries?.login, login,\n () => import('@mintplayer/ng-spark-auth/login').then(m => m.SparkLoginComponent)),\n child(entries?.twoFactor, twoFactor,\n () => import('@mintplayer/ng-spark-auth/two-factor').then(m => m.SparkTwoFactorComponent)),\n child(entries?.forgotPassword, forgotPassword,\n () => import('@mintplayer/ng-spark-auth/forgot-password').then(m => m.SparkForgotPasswordComponent)),\n child(entries?.resetPassword, resetPassword,\n () => import('@mintplayer/ng-spark-auth/reset-password').then(m => m.SparkResetPasswordComponent)),\n ],\n };\n}\n\n/**\n * Mounts the self-service registration page.\n *\n * Separate from {@link withLocalLogin} because the two decisions genuinely are separate: an\n * application whose accounts are provisioned by an administrator still needs password sign-in and\n * password reset. Mount it only alongside a server whose `SparkLocalCredentials` is `Full` — with\n * `SignInOnly` the endpoint it posts to is not mapped.\n */\nexport function withRegistration(entry?: SparkAuthRouteEntry): SparkAuthRoutesFeature {\n const register = entryPath(entry, 'register');\n\n return {\n paths: { register: '/' + register },\n children: [\n child(entry, register,\n () => import('@mintplayer/ng-spark-auth/register').then(m => m.SparkRegisterComponent)),\n ],\n };\n}\n\n/**\n * Mounts the sign-in landing page — a button per external provider the *server* reports.\n *\n * Accepts provider declarations and, in any position, one options object for the page's own path;\n * the two shapes are disjoint, so `withExternalLogin(githubProvider(), facebookProvider())` and\n * `withExternalLogin({ signIn: 'landing' }, githubProvider())` both read naturally.\n *\n * Declaring no providers is valid and useful: the page still renders whatever\n * `GET /spark/auth/capabilities` reports, using default buttons.\n */\nexport function withExternalLogin(\n ...providersOrOptions: (SparkExternalProviderPresentation | SparkExternalLoginRouteOptions)[]\n): SparkAuthRoutesFeature {\n const providers = providersOrOptions.filter(isProvider);\n const options = providersOrOptions.find((p): p is SparkExternalLoginRouteOptions => !isProvider(p));\n const signIn = entryPath(options?.signIn, 'sign-in');\n\n return {\n paths: { signIn: '/' + signIn },\n providers,\n children: [\n child(options?.signIn, signIn,\n () => import('@mintplayer/ng-spark-auth/sign-in').then(m => m.SparkSignInComponent)),\n ],\n };\n}\n\nfunction isProvider(\n value: SparkExternalProviderPresentation | SparkExternalLoginRouteOptions,\n): value is SparkExternalProviderPresentation {\n return typeof (value as SparkExternalProviderPresentation).scheme === 'string';\n}\n\n/**\n * Presentation for one provider's button, keyed by the scheme the server reports.\n *\n * The generic form. `githubProvider()` and friends are this with a scheme and an icon filled in —\n * they exist so the common case does not require knowing the scheme string, not because the library\n * has an opinion about which providers exist.\n */\nexport function externalProvider(\n scheme: string,\n presentation?: Omit<SparkExternalProviderPresentation, 'scheme'>,\n): SparkExternalProviderPresentation {\n return { scheme, ...presentation };\n}\n\nexport function githubProvider(\n presentation?: Omit<SparkExternalProviderPresentation, 'scheme'>,\n): SparkExternalProviderPresentation {\n return externalProvider('GitHub', { iconClass: 'bi bi-github', ...presentation });\n}\n\nexport function googleProvider(\n presentation?: Omit<SparkExternalProviderPresentation, 'scheme'>,\n): SparkExternalProviderPresentation {\n return externalProvider('Google', { iconClass: 'bi bi-google', ...presentation });\n}\n\nexport function facebookProvider(\n presentation?: Omit<SparkExternalProviderPresentation, 'scheme'>,\n): SparkExternalProviderPresentation {\n return externalProvider('Facebook', { iconClass: 'bi bi-facebook', ...presentation });\n}\n\nexport function microsoftProvider(\n presentation?: Omit<SparkExternalProviderPresentation, 'scheme'>,\n): SparkExternalProviderPresentation {\n return externalProvider('Microsoft', { iconClass: 'bi bi-microsoft', ...presentation });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;AAgBA;AACA,SAAS,SAAS,CAAC,KAAsC,EAAE,WAAmB,EAAA;IAC5E,IAAI,KAAK,KAAK,SAAS;AAAE,QAAA,OAAO,WAAW;AAC3C,IAAA,OAAO,OAAO,KAAK,KAAK,QAAQ,GAAG,KAAK,GAAG,KAAK,CAAC,IAAI;AACvD;AAEA;AACA,SAAS,KAAK,CAAC,KAAsC,EAAE,IAAY,EAAE,aAAqB,EAAA;IACxF,MAAM,SAAS,GAAG,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS,GAAG,SAAS;IAC5F,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,SAAS,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,aAAa,EAAE;AAC9F;AAiBA;;;;;;;;;;;;;;;;;;;;;AAqBG;AACG,SAAU,eAAe,CAAC,GAAG,QAAkC,EAAA;IACnE,MAAM,QAAQ,GAAY,EAAE;IAC5B,MAAM,KAAK,GAAwB,EAAE;IACrC,MAAM,iBAAiB,GAAwC,EAAE;AAEjE,IAAA,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE;QAC9B,QAAQ,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC;QAClC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC;QACnC,IAAI,OAAO,CAAC,SAAS;YAAE,iBAAiB,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC;IACrE;IAEA,OAAO;AACL,QAAA;AACE,YAAA,IAAI,EAAE,EAAE;AACR,YAAA,SAAS,EAAE;AACT,gBAAA,EAAE,OAAO,EAAE,sBAAsB,EAAE,QAAQ,EAAE,KAAK,EAAE;AACpD,gBAAA,EAAE,OAAO,EAAE,wBAAwB,EAAE,QAAQ,EAAE,iBAAiB,EAAE;AACnE,aAAA;YACD,QAAQ;AACT,SAAA;KACF;AACH;AAEA;;;;;;;;;;AAUG;AACG,SAAU,cAAc,CAC5B,OAAiG,EAAA;IAEjG,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC;IAChD,MAAM,SAAS,GAAG,SAAS,CAAC,OAAO,EAAE,SAAS,EAAE,kBAAkB,CAAC;IACnE,MAAM,cAAc,GAAG,SAAS,CAAC,OAAO,EAAE,cAAc,EAAE,iBAAiB,CAAC;IAC5E,MAAM,aAAa,GAAG,SAAS,CAAC,OAAO,EAAE,aAAa,EAAE,gBAAgB,CAAC;IAEzE,OAAO;AACL,QAAA,KAAK,EAAE;YACL,KAAK,EAAE,GAAG,GAAG,KAAK;YAClB,SAAS,EAAE,GAAG,GAAG,SAAS;YAC1B,cAAc,EAAE,GAAG,GAAG,cAAc;YACpC,aAAa,EAAE,GAAG,GAAG,aAAa;AACnC,SAAA;AACD,QAAA,QAAQ,EAAE;YACR,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,EACzB,MAAM,OAAO,iCAAiC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,mBAAmB,CAAC,CAAC;YACnF,KAAK,CAAC,OAAO,EAAE,SAAS,EAAE,SAAS,EACjC,MAAM,OAAO,sCAAsC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,uBAAuB,CAAC,CAAC;YAC5F,KAAK,CAAC,OAAO,EAAE,cAAc,EAAE,cAAc,EAC3C,MAAM,OAAO,2CAA2C,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,4BAA4B,CAAC,CAAC;YACtG,KAAK,CAAC,OAAO,EAAE,aAAa,EAAE,aAAa,EACzC,MAAM,OAAO,0CAA0C,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,2BAA2B,CAAC,CAAC;AACrG,SAAA;KACF;AACH;AAEA;;;;;;;AAOG;AACG,SAAU,gBAAgB,CAAC,KAA2B,EAAA;IAC1D,MAAM,QAAQ,GAAG,SAAS,CAAC,KAAK,EAAE,UAAU,CAAC;IAE7C,OAAO;AACL,QAAA,KAAK,EAAE,EAAE,QAAQ,EAAE,GAAG,GAAG,QAAQ,EAAE;AACnC,QAAA,QAAQ,EAAE;YACR,KAAK,CAAC,KAAK,EAAE,QAAQ,EACnB,MAAM,OAAO,oCAAoC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,sBAAsB,CAAC,CAAC;AAC1F,SAAA;KACF;AACH;AAEA;;;;;;;;;AASG;AACG,SAAU,iBAAiB,CAC/B,GAAG,kBAA0F,EAAA;IAE7F,MAAM,SAAS,GAAG,kBAAkB,CAAC,MAAM,CAAC,UAAU,CAAC;AACvD,IAAA,MAAM,OAAO,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,KAA0C,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IACnG,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC;IAEpD,OAAO;AACL,QAAA,KAAK,EAAE,EAAE,MAAM,EAAE,GAAG,GAAG,MAAM,EAAE;QAC/B,SAAS;AACT,QAAA,QAAQ,EAAE;YACR,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAC3B,MAAM,OAAO,mCAAmC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,oBAAoB,CAAC,CAAC;AACvF,SAAA;KACF;AACH;AAEA,SAAS,UAAU,CACjB,KAAyE,EAAA;AAEzE,IAAA,OAAO,OAAQ,KAA2C,CAAC,MAAM,KAAK,QAAQ;AAChF;AAEA;;;;;;AAMG;AACG,SAAU,gBAAgB,CAC9B,MAAc,EACd,YAAgE,EAAA;AAEhE,IAAA,OAAO,EAAE,MAAM,EAAE,GAAG,YAAY,EAAE;AACpC;AAEM,SAAU,cAAc,CAC5B,YAAgE,EAAA;AAEhE,IAAA,OAAO,gBAAgB,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,cAAc,EAAE,GAAG,YAAY,EAAE,CAAC;AACnF;AAEM,SAAU,cAAc,CAC5B,YAAgE,EAAA;AAEhE,IAAA,OAAO,gBAAgB,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,cAAc,EAAE,GAAG,YAAY,EAAE,CAAC;AACnF;AAEM,SAAU,gBAAgB,CAC9B,YAAgE,EAAA;AAEhE,IAAA,OAAO,gBAAgB,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,gBAAgB,EAAE,GAAG,YAAY,EAAE,CAAC;AACvF;AAEM,SAAU,iBAAiB,CAC/B,YAAgE,EAAA;AAEhE,IAAA,OAAO,gBAAgB,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,iBAAiB,EAAE,GAAG,YAAY,EAAE,CAAC;AACzF;;ACxNA;;AAEG;;;;"}
|
|
@@ -1,42 +1,100 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, signal, isDevMode, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
-
import {
|
|
2
|
+
import { inject, input, signal, computed, isDevMode, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { NgTemplateOutlet } from '@angular/common';
|
|
4
|
+
import { ActivatedRoute, RouterLink } from '@angular/router';
|
|
4
5
|
import { Color } from '@mintplayer/ng-bootstrap';
|
|
5
6
|
import { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';
|
|
6
7
|
import { BsCardComponent, BsCardHeaderComponent } from '@mintplayer/ng-bootstrap/card';
|
|
7
8
|
import { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';
|
|
8
|
-
import { SPARK_AUTH_ROUTE_PATHS } from '@mintplayer/ng-spark-auth/models';
|
|
9
|
+
import { SPARK_EXTERNAL_PROVIDERS, SPARK_AUTH_ROUTE_PATHS, isSafeReturnUrl } from '@mintplayer/ng-spark-auth/models';
|
|
9
10
|
import { SparkAuthService } from '@mintplayer/ng-spark-auth/core';
|
|
10
11
|
import { TranslateKeyPipe } from '@mintplayer/ng-spark-auth/pipes';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
|
-
* The sign-in landing page
|
|
14
|
-
*
|
|
14
|
+
* The sign-in landing page: a button per external provider, and a link to the password form when the
|
|
15
|
+
* application mounted one.
|
|
15
16
|
*
|
|
16
17
|
* The providers come from `GET /spark/auth/capabilities` rather than from a list the application
|
|
17
18
|
* hard-codes. That is the point of the component: every consumer that hand-rolled this before wrote
|
|
18
19
|
* the scheme name as a string literal, which is a silent mismatch waiting to happen the moment the
|
|
19
|
-
* server's provider registration changes.
|
|
20
|
+
* server's provider registration changes. A `withExternalLogin(githubProvider())` declaration only
|
|
21
|
+
* *decorates* what the server reports — it cannot conjure a provider the server does not have.
|
|
20
22
|
*/
|
|
21
23
|
class SparkSignInComponent {
|
|
22
24
|
authService = inject(SparkAuthService);
|
|
25
|
+
declaredProviders = inject(SPARK_EXTERNAL_PROVIDERS, { optional: true }) ?? [];
|
|
26
|
+
route = inject(ActivatedRoute, { optional: true });
|
|
23
27
|
routePaths = inject(SPARK_AUTH_ROUTE_PATHS);
|
|
24
28
|
colors = Color;
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Where to land after a successful external sign-in.
|
|
31
|
+
*
|
|
32
|
+
* Falls back to a `?returnUrl=` query parameter, matching what the login page already does — so a
|
|
33
|
+
* plain `<a routerLink="/sign-in" [queryParams]="{ returnUrl: '/somewhere' }">` works with no
|
|
34
|
+
* wiring, which is what a routed page needs since the router passes it no inputs. The query value
|
|
35
|
+
* is validated as a local path before use; an off-site one is dropped rather than followed.
|
|
36
|
+
*/
|
|
37
|
+
returnUrl = input(undefined, /* @ts-ignore */
|
|
38
|
+
...(ngDevMode ? [{ debugName: "returnUrl" }] : /* istanbul ignore next */ []));
|
|
39
|
+
effectiveReturnUrl() {
|
|
40
|
+
const explicit = this.returnUrl();
|
|
41
|
+
if (explicit)
|
|
42
|
+
return explicit;
|
|
43
|
+
const fromQuery = this.route?.snapshot.queryParamMap.get('returnUrl');
|
|
44
|
+
return isSafeReturnUrl(fromQuery) ? fromQuery : undefined;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Replaces the default provider button. Rendered once per provider with a
|
|
48
|
+
* {@link SparkProviderButtonContext}.
|
|
49
|
+
*
|
|
50
|
+
* Reachable only when the consumer *hosts* this component — the router instantiates it with no
|
|
51
|
+
* projected content, so on a default `withExternalLogin()` route there is nothing to project into.
|
|
52
|
+
* Host it via `withExternalLogin({ signIn: { path: 'sign-in', component: MySignIn } })` and pass
|
|
53
|
+
* the template from there.
|
|
54
|
+
*/
|
|
55
|
+
providerTemplate = input(null, /* @ts-ignore */
|
|
56
|
+
...(ngDevMode ? [{ debugName: "providerTemplate" }] : /* istanbul ignore next */ []));
|
|
57
|
+
reported = signal([], /* @ts-ignore */
|
|
58
|
+
...(ngDevMode ? [{ debugName: "reported" }] : /* istanbul ignore next */ []));
|
|
27
59
|
localCredentialsAvailable = signal(false, /* @ts-ignore */
|
|
28
60
|
...(ngDevMode ? [{ debugName: "localCredentialsAvailable" }] : /* istanbul ignore next */ []));
|
|
29
61
|
loading = signal(true, /* @ts-ignore */
|
|
30
62
|
...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
31
63
|
failed = signal(false, /* @ts-ignore */
|
|
32
64
|
...(ngDevMode ? [{ debugName: "failed" }] : /* istanbul ignore next */ []));
|
|
65
|
+
/**
|
|
66
|
+
* The server's list, decorated and ordered by whatever the application declared. Declared-but-not-
|
|
67
|
+
* reported schemes are dropped and reported-but-not-declared ones keep a default button, so
|
|
68
|
+
* neither side can produce a provider the other does not have.
|
|
69
|
+
*/
|
|
70
|
+
providers = computed(() => {
|
|
71
|
+
const declarations = new Map(this.declaredProviders.map(p => [p.scheme.toLowerCase(), p]));
|
|
72
|
+
return this.reported()
|
|
73
|
+
.map((provider, index) => {
|
|
74
|
+
const declared = declarations.get(provider.scheme.toLowerCase());
|
|
75
|
+
return {
|
|
76
|
+
provider: {
|
|
77
|
+
...provider,
|
|
78
|
+
displayName: declared?.displayName ?? provider.displayName,
|
|
79
|
+
iconClass: declared?.iconClass,
|
|
80
|
+
},
|
|
81
|
+
// Undeclared providers sort after declared ones, keeping the server's relative order among
|
|
82
|
+
// themselves — so adding a provider server-side appends a button rather than reshuffling.
|
|
83
|
+
order: declared?.order ?? Number.MAX_SAFE_INTEGER,
|
|
84
|
+
index,
|
|
85
|
+
};
|
|
86
|
+
})
|
|
87
|
+
.sort((a, b) => a.order - b.order || a.index - b.index)
|
|
88
|
+
.map(entry => entry.provider);
|
|
89
|
+
}, /* @ts-ignore */
|
|
90
|
+
...(ngDevMode ? [{ debugName: "providers" }] : /* istanbul ignore next */ []));
|
|
33
91
|
constructor() {
|
|
34
92
|
void this.load();
|
|
35
93
|
}
|
|
36
94
|
async load() {
|
|
37
95
|
try {
|
|
38
96
|
const capabilities = await this.authService.capabilities();
|
|
39
|
-
this.
|
|
97
|
+
this.reported.set(capabilities.externalProviders);
|
|
40
98
|
this.localCredentialsAvailable.set(capabilities.localCredentials !== 'Disabled');
|
|
41
99
|
this.warnOnMismatch(capabilities);
|
|
42
100
|
}
|
|
@@ -57,20 +115,27 @@ class SparkSignInComponent {
|
|
|
57
115
|
warnOnMismatch(capabilities) {
|
|
58
116
|
if (isDevMode() && capabilities.localCredentials === 'Full') {
|
|
59
117
|
console.warn('[ng-spark-auth] This app routes the sign-in landing page, but the server reports '
|
|
60
|
-
+ 'localCredentials = Full. The two are configured independently — check that '
|
|
61
|
-
+ "sparkAuthRoutes(
|
|
118
|
+
+ 'localCredentials = Full. The two are configured independently — check that the features '
|
|
119
|
+
+ "passed to sparkAuthRoutes() match AddAuthentication's LocalCredentials mode.");
|
|
62
120
|
}
|
|
63
121
|
}
|
|
122
|
+
/** The closure handed to a projected template, so a consumer never names a scheme itself. */
|
|
123
|
+
contextFor(provider) {
|
|
124
|
+
return { $implicit: provider, signIn: () => this.signInWith(provider) };
|
|
125
|
+
}
|
|
64
126
|
signInWith(provider) {
|
|
65
|
-
void this.authService.loginWithProvider(provider.scheme);
|
|
127
|
+
void this.authService.loginWithProvider(provider.scheme, { returnUrl: this.effectiveReturnUrl() });
|
|
66
128
|
}
|
|
67
129
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkSignInComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
68
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: SparkSignInComponent, isStandalone: true, selector: "spark-sign-in", ngImport: i0, template: "<div class=\"d-flex justify-content-center\">\n <bs-card style=\"width: 100%; max-width: 400px;\">\n <bs-card-header>\n <h3 class=\"mb-0 text-center\">{{ 'auth.login' | t }}</h3>\n </bs-card-header>\n <div class=\"p-4\">\n @if (loading()) {\n <div class=\"text-center\"><bs-spinner /></div>\n } @else if (failed()) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-0\">{{ 'auth.signInUnavailable' | t }}</bs-alert>\n } @else {\n @for (provider of providers(); track provider.scheme) {\n <button type=\"button\" class=\"btn btn-outline-primary w-100 mb-2\" (click)=\"signInWith(provider)\">\n
|
|
130
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: SparkSignInComponent, isStandalone: true, selector: "spark-sign-in", inputs: { returnUrl: { classPropertyName: "returnUrl", publicName: "returnUrl", isSignal: true, isRequired: false, transformFunction: null }, providerTemplate: { classPropertyName: "providerTemplate", publicName: "providerTemplate", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"d-flex justify-content-center\">\n <bs-card style=\"width: 100%; max-width: 400px;\">\n <bs-card-header>\n <h3 class=\"mb-0 text-center\">{{ 'auth.login' | t }}</h3>\n </bs-card-header>\n <div class=\"p-4\">\n @if (loading()) {\n <div class=\"text-center\"><bs-spinner /></div>\n } @else if (failed()) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-0\">{{ 'auth.signInUnavailable' | t }}</bs-alert>\n } @else {\n @for (provider of providers(); track provider.scheme) {\n @if (providerTemplate(); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl; context: contextFor(provider)\" />\n } @else {\n <button type=\"button\" class=\"btn btn-outline-primary w-100 mb-2\" (click)=\"signInWith(provider)\">\n @if (provider.iconClass) {\n <i [class]=\"provider.iconClass\" class=\"me-2\"></i>\n }\n {{ provider.displayName }}\n </button>\n }\n } @empty {\n <bs-alert [type]=\"colors.warning\" class=\"mb-0\">{{ 'auth.noSignInMethods' | t }}</bs-alert>\n }\n\n <!-- Two conditions, not one. The server may still accept passwords while this app chose not\n to mount the page \u2014 sparkAuthRoutes is opt-in per feature now \u2014 and a [routerLink] bound\n to undefined silently navigates to the current route rather than failing. -->\n @if (localCredentialsAvailable() && routePaths.login) {\n <div class=\"text-center mt-3\">\n <a [routerLink]=\"routePaths.login\">{{ 'auth.login' | t }}</a>\n </div>\n }\n }\n </div>\n </bs-card>\n</div>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "component", type: BsAlertComponent, selector: "bs-alert", inputs: ["type", "announce", "isVisible"], outputs: ["isVisibleChange", "afterOpenedOrClosed"] }, { kind: "component", type: BsCardComponent, selector: "bs-card", inputs: ["color", "outline"] }, { kind: "component", type: BsCardHeaderComponent, selector: "bs-card-header", inputs: ["color", "navStyle"] }, { kind: "component", type: BsSpinnerComponent, selector: "bs-spinner", inputs: ["type", "color"] }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
69
131
|
}
|
|
70
132
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkSignInComponent, decorators: [{
|
|
71
133
|
type: Component,
|
|
72
|
-
args: [{ selector: 'spark-sign-in', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
73
|
-
|
|
134
|
+
args: [{ selector: 'spark-sign-in', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
135
|
+
NgTemplateOutlet, RouterLink, BsAlertComponent, BsCardComponent, BsCardHeaderComponent,
|
|
136
|
+
BsSpinnerComponent, TranslateKeyPipe,
|
|
137
|
+
], template: "<div class=\"d-flex justify-content-center\">\n <bs-card style=\"width: 100%; max-width: 400px;\">\n <bs-card-header>\n <h3 class=\"mb-0 text-center\">{{ 'auth.login' | t }}</h3>\n </bs-card-header>\n <div class=\"p-4\">\n @if (loading()) {\n <div class=\"text-center\"><bs-spinner /></div>\n } @else if (failed()) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-0\">{{ 'auth.signInUnavailable' | t }}</bs-alert>\n } @else {\n @for (provider of providers(); track provider.scheme) {\n @if (providerTemplate(); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl; context: contextFor(provider)\" />\n } @else {\n <button type=\"button\" class=\"btn btn-outline-primary w-100 mb-2\" (click)=\"signInWith(provider)\">\n @if (provider.iconClass) {\n <i [class]=\"provider.iconClass\" class=\"me-2\"></i>\n }\n {{ provider.displayName }}\n </button>\n }\n } @empty {\n <bs-alert [type]=\"colors.warning\" class=\"mb-0\">{{ 'auth.noSignInMethods' | t }}</bs-alert>\n }\n\n <!-- Two conditions, not one. The server may still accept passwords while this app chose not\n to mount the page \u2014 sparkAuthRoutes is opt-in per feature now \u2014 and a [routerLink] bound\n to undefined silently navigates to the current route rather than failing. -->\n @if (localCredentialsAvailable() && routePaths.login) {\n <div class=\"text-center mt-3\">\n <a [routerLink]=\"routePaths.login\">{{ 'auth.login' | t }}</a>\n </div>\n }\n }\n </div>\n </bs-card>\n</div>\n" }]
|
|
138
|
+
}], ctorParameters: () => [], propDecorators: { returnUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "returnUrl", required: false }] }], providerTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "providerTemplate", required: false }] }] } });
|
|
74
139
|
|
|
75
140
|
/**
|
|
76
141
|
* Generated bundle index. Do not edit.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mintplayer-ng-spark-auth-sign-in.mjs","sources":["../../sign-in/src/spark-sign-in.component.ts","../../sign-in/src/spark-sign-in.component.html","../../sign-in/mintplayer-ng-spark-auth-sign-in.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, inject, isDevMode, signal } from '@angular/core';\nimport { RouterLink } from '@angular/router';\nimport { Color } from '@mintplayer/ng-bootstrap';\nimport { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';\nimport { BsCardComponent, BsCardHeaderComponent } from '@mintplayer/ng-bootstrap/card';\nimport { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';\nimport { SPARK_AUTH_ROUTE_PATHS, SparkAuthCapabilities, SparkExternalProvider } from '@mintplayer/ng-spark-auth/models';\nimport { SparkAuthService } from '@mintplayer/ng-spark-auth/core';\nimport { TranslateKeyPipe } from '@mintplayer/ng-spark-auth/pipes';\n\n/**\n * The sign-in landing page for an application whose local credentials are turned off — a button per\n * external provider, and a link to the password form when there still is one.\n *\n * The providers come from `GET /spark/auth/capabilities` rather than from a list the application\n * hard-codes. That is the point of the component: every consumer that hand-rolled this before wrote\n * the scheme name as a string literal, which is a silent mismatch waiting to happen the moment the\n * server's provider registration changes.\n */\n@Component({\n selector: 'spark-sign-in',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [RouterLink, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsSpinnerComponent, TranslateKeyPipe],\n templateUrl: './spark-sign-in.component.html',\n})\nexport class SparkSignInComponent {\n private readonly authService = inject(SparkAuthService);\n readonly routePaths = inject(SPARK_AUTH_ROUTE_PATHS);\n readonly colors = Color;\n\n readonly providers = signal<SparkExternalProvider[]>([]);\n readonly localCredentialsAvailable = signal(false);\n readonly loading = signal(true);\n readonly failed = signal(false);\n\n constructor() {\n void this.load();\n }\n\n private async load(): Promise<void> {\n try {\n const capabilities = await this.authService.capabilities();\n this.providers.set(capabilities.externalProviders);\n this.localCredentialsAvailable.set(capabilities.localCredentials !== 'Disabled');\n this.warnOnMismatch(capabilities);\n } catch {\n // A sign-in page that renders nothing looks identical to one whose providers all failed to\n // load, so say which happened rather than showing an empty page.\n this.failed.set(true);\n } finally {\n this.loading.set(false);\n }\n }\n\n /**\n * The routes are a build-time decision and the server's mode a deployment-time one, so they can\n * disagree without anything failing loudly. Reaching this page at all means the app routed it,\n * which it only does when local credentials are meant to be limited.\n */\n private warnOnMismatch(capabilities: SparkAuthCapabilities): void {\n if (isDevMode() && capabilities.localCredentials === 'Full') {\n console.warn(\n '[ng-spark-auth] This app routes the sign-in landing page, but the server reports '\n + 'localCredentials = Full. The two are configured independently — check that '\n + \"sparkAuthRoutes({ localCredentials: … }) matches AddAuthentication's LocalCredentials mode.\",\n );\n }\n }\n\n signInWith(provider: SparkExternalProvider): void {\n void this.authService.loginWithProvider(provider.scheme);\n }\n}\n\nexport default SparkSignInComponent;\n","<div class=\"d-flex justify-content-center\">\n <bs-card style=\"width: 100%; max-width: 400px;\">\n <bs-card-header>\n <h3 class=\"mb-0 text-center\">{{ 'auth.login' | t }}</h3>\n </bs-card-header>\n <div class=\"p-4\">\n @if (loading()) {\n <div class=\"text-center\"><bs-spinner /></div>\n } @else if (failed()) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-0\">{{ 'auth.signInUnavailable' | t }}</bs-alert>\n } @else {\n @for (provider of providers(); track provider.scheme) {\n <button type=\"button\" class=\"btn btn-outline-primary w-100 mb-2\" (click)=\"signInWith(provider)\">\n {{ provider.displayName }}\n </button>\n } @empty {\n <bs-alert [type]=\"colors.warning\" class=\"mb-0\">{{ 'auth.noSignInMethods' | t }}</bs-alert>\n }\n\n @if (localCredentialsAvailable()) {\n <div class=\"text-center mt-3\">\n <a [routerLink]=\"routePaths.login\">{{ 'auth.login' | t }}</a>\n </div>\n }\n }\n </div>\n </bs-card>\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mintplayer-ng-spark-auth-sign-in.mjs","sources":["../../sign-in/src/spark-sign-in.component.ts","../../sign-in/src/spark-sign-in.component.html","../../sign-in/mintplayer-ng-spark-auth-sign-in.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, TemplateRef, computed, inject, input, isDevMode, signal } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { ActivatedRoute, RouterLink } from '@angular/router';\nimport { Color } from '@mintplayer/ng-bootstrap';\nimport { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';\nimport { BsCardComponent, BsCardHeaderComponent } from '@mintplayer/ng-bootstrap/card';\nimport { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';\nimport {\n SPARK_AUTH_ROUTE_PATHS,\n SPARK_EXTERNAL_PROVIDERS,\n SparkAuthCapabilities,\n SparkExternalProvider,\n SparkExternalProviderPresentation,\n isSafeReturnUrl,\n} from '@mintplayer/ng-spark-auth/models';\nimport { SparkAuthService } from '@mintplayer/ng-spark-auth/core';\nimport { TranslateKeyPipe } from '@mintplayer/ng-spark-auth/pipes';\n\n/**\n * One provider's button, as the template sees it: the provider itself, and the call that signs in\n * with it.\n *\n * **Passing the closure is the point.** A consumer never touches `provider.scheme`, so the failure\n * this component exists to prevent — a hard-coded scheme string that silently stops matching when\n * the server's registration changes — is unreachable by construction. It also means busy state,\n * `returnUrl` handling and popup-versus-redirect can move behind `signIn` later without a\n * consumer-facing change.\n */\nexport interface SparkProviderButtonContext {\n $implicit: SparkExternalProviderView;\n signIn: () => void;\n}\n\n/** A provider the server reported, merged with whatever presentation the app declared for it. */\nexport interface SparkExternalProviderView extends SparkExternalProvider {\n iconClass?: string;\n}\n\n/**\n * The sign-in landing page: a button per external provider, and a link to the password form when the\n * application mounted one.\n *\n * The providers come from `GET /spark/auth/capabilities` rather than from a list the application\n * hard-codes. That is the point of the component: every consumer that hand-rolled this before wrote\n * the scheme name as a string literal, which is a silent mismatch waiting to happen the moment the\n * server's provider registration changes. A `withExternalLogin(githubProvider())` declaration only\n * *decorates* what the server reports — it cannot conjure a provider the server does not have.\n */\n@Component({\n selector: 'spark-sign-in',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [\n NgTemplateOutlet, RouterLink, BsAlertComponent, BsCardComponent, BsCardHeaderComponent,\n BsSpinnerComponent, TranslateKeyPipe,\n ],\n templateUrl: './spark-sign-in.component.html',\n})\nexport class SparkSignInComponent {\n private readonly authService = inject(SparkAuthService);\n private readonly declaredProviders = inject(SPARK_EXTERNAL_PROVIDERS, { optional: true }) ?? [];\n private readonly route = inject(ActivatedRoute, { optional: true });\n readonly routePaths = inject(SPARK_AUTH_ROUTE_PATHS);\n readonly colors = Color;\n\n /**\n * Where to land after a successful external sign-in.\n *\n * Falls back to a `?returnUrl=` query parameter, matching what the login page already does — so a\n * plain `<a routerLink=\"/sign-in\" [queryParams]=\"{ returnUrl: '/somewhere' }\">` works with no\n * wiring, which is what a routed page needs since the router passes it no inputs. The query value\n * is validated as a local path before use; an off-site one is dropped rather than followed.\n */\n readonly returnUrl = input<string | undefined>(undefined);\n\n private effectiveReturnUrl(): string | undefined {\n const explicit = this.returnUrl();\n if (explicit) return explicit;\n\n const fromQuery = this.route?.snapshot.queryParamMap.get('returnUrl');\n return isSafeReturnUrl(fromQuery) ? fromQuery! : undefined;\n }\n\n /**\n * Replaces the default provider button. Rendered once per provider with a\n * {@link SparkProviderButtonContext}.\n *\n * Reachable only when the consumer *hosts* this component — the router instantiates it with no\n * projected content, so on a default `withExternalLogin()` route there is nothing to project into.\n * Host it via `withExternalLogin({ signIn: { path: 'sign-in', component: MySignIn } })` and pass\n * the template from there.\n */\n readonly providerTemplate = input<TemplateRef<SparkProviderButtonContext> | null>(null);\n\n private readonly reported = signal<SparkExternalProvider[]>([]);\n readonly localCredentialsAvailable = signal(false);\n readonly loading = signal(true);\n readonly failed = signal(false);\n\n /**\n * The server's list, decorated and ordered by whatever the application declared. Declared-but-not-\n * reported schemes are dropped and reported-but-not-declared ones keep a default button, so\n * neither side can produce a provider the other does not have.\n */\n readonly providers = computed<SparkExternalProviderView[]>(() => {\n const declarations = new Map<string, SparkExternalProviderPresentation>(\n this.declaredProviders.map(p => [p.scheme.toLowerCase(), p]),\n );\n\n return this.reported()\n .map((provider, index) => {\n const declared = declarations.get(provider.scheme.toLowerCase());\n return {\n provider: {\n ...provider,\n displayName: declared?.displayName ?? provider.displayName,\n iconClass: declared?.iconClass,\n } satisfies SparkExternalProviderView,\n // Undeclared providers sort after declared ones, keeping the server's relative order among\n // themselves — so adding a provider server-side appends a button rather than reshuffling.\n order: declared?.order ?? Number.MAX_SAFE_INTEGER,\n index,\n };\n })\n .sort((a, b) => a.order - b.order || a.index - b.index)\n .map(entry => entry.provider);\n });\n\n constructor() {\n void this.load();\n }\n\n private async load(): Promise<void> {\n try {\n const capabilities = await this.authService.capabilities();\n this.reported.set(capabilities.externalProviders);\n this.localCredentialsAvailable.set(capabilities.localCredentials !== 'Disabled');\n this.warnOnMismatch(capabilities);\n } catch {\n // A sign-in page that renders nothing looks identical to one whose providers all failed to\n // load, so say which happened rather than showing an empty page.\n this.failed.set(true);\n } finally {\n this.loading.set(false);\n }\n }\n\n /**\n * The routes are a build-time decision and the server's mode a deployment-time one, so they can\n * disagree without anything failing loudly. Reaching this page at all means the app routed it,\n * which it only does when local credentials are meant to be limited.\n */\n private warnOnMismatch(capabilities: SparkAuthCapabilities): void {\n if (isDevMode() && capabilities.localCredentials === 'Full') {\n console.warn(\n '[ng-spark-auth] This app routes the sign-in landing page, but the server reports '\n + 'localCredentials = Full. The two are configured independently — check that the features '\n + \"passed to sparkAuthRoutes() match AddAuthentication's LocalCredentials mode.\",\n );\n }\n }\n\n /** The closure handed to a projected template, so a consumer never names a scheme itself. */\n contextFor(provider: SparkExternalProviderView): SparkProviderButtonContext {\n return { $implicit: provider, signIn: () => this.signInWith(provider) };\n }\n\n signInWith(provider: SparkExternalProviderView): void {\n void this.authService.loginWithProvider(provider.scheme, { returnUrl: this.effectiveReturnUrl() });\n }\n}\n\nexport default SparkSignInComponent;\n","<div class=\"d-flex justify-content-center\">\n <bs-card style=\"width: 100%; max-width: 400px;\">\n <bs-card-header>\n <h3 class=\"mb-0 text-center\">{{ 'auth.login' | t }}</h3>\n </bs-card-header>\n <div class=\"p-4\">\n @if (loading()) {\n <div class=\"text-center\"><bs-spinner /></div>\n } @else if (failed()) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-0\">{{ 'auth.signInUnavailable' | t }}</bs-alert>\n } @else {\n @for (provider of providers(); track provider.scheme) {\n @if (providerTemplate(); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl; context: contextFor(provider)\" />\n } @else {\n <button type=\"button\" class=\"btn btn-outline-primary w-100 mb-2\" (click)=\"signInWith(provider)\">\n @if (provider.iconClass) {\n <i [class]=\"provider.iconClass\" class=\"me-2\"></i>\n }\n {{ provider.displayName }}\n </button>\n }\n } @empty {\n <bs-alert [type]=\"colors.warning\" class=\"mb-0\">{{ 'auth.noSignInMethods' | t }}</bs-alert>\n }\n\n <!-- Two conditions, not one. The server may still accept passwords while this app chose not\n to mount the page — sparkAuthRoutes is opt-in per feature now — and a [routerLink] bound\n to undefined silently navigates to the current route rather than failing. -->\n @if (localCredentialsAvailable() && routePaths.login) {\n <div class=\"text-center mt-3\">\n <a [routerLink]=\"routePaths.login\">{{ 'auth.login' | t }}</a>\n </div>\n }\n }\n </div>\n </bs-card>\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -26,9 +26,9 @@ function withSparkAuth() {
|
|
|
26
26
|
// @mintplayer/ng-spark-auth/guards (sparkAuthGuard)
|
|
27
27
|
// @mintplayer/ng-spark-auth/interceptors (sparkAuthInterceptor)
|
|
28
28
|
// @mintplayer/ng-spark-auth/pipes (TranslateKeyPipe)
|
|
29
|
-
// @mintplayer/ng-spark-auth/routes (sparkAuthRoutes)
|
|
29
|
+
// @mintplayer/ng-spark-auth/routes (sparkAuthRoutes + its with* features)
|
|
30
30
|
// @mintplayer/ng-spark-auth/auth-bar (SparkAuthBarComponent)
|
|
31
|
-
// @mintplayer/ng-spark-auth/{login,two-factor,register,forgot-password,reset-password}
|
|
31
|
+
// @mintplayer/ng-spark-auth/{login,two-factor,register,forgot-password,reset-password,sign-in}
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* Generated bundle index. Do not edit.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mintplayer-ng-spark-auth.mjs","sources":["../../src/lib/provide-spark-auth.ts","../../src/public-api.ts","../../src/mintplayer-ng-spark-auth.ts"],"sourcesContent":["import { EnvironmentProviders, makeEnvironmentProviders } from '@angular/core';\nimport { HttpFeature, HttpFeatureKind, withInterceptors, withXsrfConfiguration } from '@angular/common/http';\nimport {\n defaultSparkAuthConfig,\n SPARK_AUTH_CONFIG,\n SparkAuthConfig,\n} from '@mintplayer/ng-spark-auth/models';\nimport { sparkAuthInterceptor } from '@mintplayer/ng-spark-auth/interceptors';\n\nexport function provideSparkAuth(\n config?: Partial<SparkAuthConfig>,\n): EnvironmentProviders {\n return makeEnvironmentProviders([\n {\n provide: SPARK_AUTH_CONFIG,\n useValue: { ...defaultSparkAuthConfig, ...config },\n },\n ]);\n}\n\nexport function withSparkAuth(): HttpFeature<HttpFeatureKind>[] {\n return [\n withInterceptors([sparkAuthInterceptor]),\n withXsrfConfiguration({ cookieName: 'XSRF-TOKEN', headerName: 'X-XSRF-TOKEN' }),\n ];\n}\n","// Root entry point — bootstrap API only.\n// For other members import from sub-paths:\n// @mintplayer/ng-spark-auth/core (SparkAuthService, SparkAuthTranslationService)\n// @mintplayer/ng-spark-auth/models (types + SPARK_AUTH_ROUTE_PATHS)\n// @mintplayer/ng-spark-auth/guards (sparkAuthGuard)\n// @mintplayer/ng-spark-auth/interceptors (sparkAuthInterceptor)\n// @mintplayer/ng-spark-auth/pipes (TranslateKeyPipe)\n// @mintplayer/ng-spark-auth/routes (sparkAuthRoutes)\n// @mintplayer/ng-spark-auth/auth-bar (SparkAuthBarComponent)\n// @mintplayer/ng-spark-auth/{login,two-factor,register,forgot-password,reset-password}\n\nexport type { SparkAuthConfig } from '@mintplayer/ng-spark-auth/models';\nexport { SPARK_AUTH_CONFIG, defaultSparkAuthConfig } from '@mintplayer/ng-spark-auth/models';\nexport { provideSparkAuth, withSparkAuth } from './lib/provide-spark-auth';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;;;AASM,SAAU,gBAAgB,CAC9B,MAAiC,EAAA;AAEjC,IAAA,OAAO,wBAAwB,CAAC;AAC9B,QAAA;AACE,YAAA,OAAO,EAAE,iBAAiB;AAC1B,YAAA,QAAQ,EAAE,EAAE,GAAG,sBAAsB,EAAE,GAAG,MAAM,EAAE;AACnD,SAAA;AACF,KAAA,CAAC;AACJ;SAEgB,aAAa,GAAA;IAC3B,OAAO;AACL,QAAA,gBAAgB,CAAC,CAAC,oBAAoB,CAAC,CAAC;QACxC,qBAAqB,CAAC,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,CAAC;KAChF;AACH;;ACzBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACTA;;AAEG;;;;"}
|
|
1
|
+
{"version":3,"file":"mintplayer-ng-spark-auth.mjs","sources":["../../src/lib/provide-spark-auth.ts","../../src/public-api.ts","../../src/mintplayer-ng-spark-auth.ts"],"sourcesContent":["import { EnvironmentProviders, makeEnvironmentProviders } from '@angular/core';\nimport { HttpFeature, HttpFeatureKind, withInterceptors, withXsrfConfiguration } from '@angular/common/http';\nimport {\n defaultSparkAuthConfig,\n SPARK_AUTH_CONFIG,\n SparkAuthConfig,\n} from '@mintplayer/ng-spark-auth/models';\nimport { sparkAuthInterceptor } from '@mintplayer/ng-spark-auth/interceptors';\n\nexport function provideSparkAuth(\n config?: Partial<SparkAuthConfig>,\n): EnvironmentProviders {\n return makeEnvironmentProviders([\n {\n provide: SPARK_AUTH_CONFIG,\n useValue: { ...defaultSparkAuthConfig, ...config },\n },\n ]);\n}\n\nexport function withSparkAuth(): HttpFeature<HttpFeatureKind>[] {\n return [\n withInterceptors([sparkAuthInterceptor]),\n withXsrfConfiguration({ cookieName: 'XSRF-TOKEN', headerName: 'X-XSRF-TOKEN' }),\n ];\n}\n","// Root entry point — bootstrap API only.\n// For other members import from sub-paths:\n// @mintplayer/ng-spark-auth/core (SparkAuthService, SparkAuthTranslationService)\n// @mintplayer/ng-spark-auth/models (types + SPARK_AUTH_ROUTE_PATHS)\n// @mintplayer/ng-spark-auth/guards (sparkAuthGuard)\n// @mintplayer/ng-spark-auth/interceptors (sparkAuthInterceptor)\n// @mintplayer/ng-spark-auth/pipes (TranslateKeyPipe)\n// @mintplayer/ng-spark-auth/routes (sparkAuthRoutes + its with* features)\n// @mintplayer/ng-spark-auth/auth-bar (SparkAuthBarComponent)\n// @mintplayer/ng-spark-auth/{login,two-factor,register,forgot-password,reset-password,sign-in}\n\nexport type { SparkAuthConfig } from '@mintplayer/ng-spark-auth/models';\nexport { SPARK_AUTH_CONFIG, defaultSparkAuthConfig } from '@mintplayer/ng-spark-auth/models';\nexport { provideSparkAuth, withSparkAuth } from './lib/provide-spark-auth';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;;;AASM,SAAU,gBAAgB,CAC9B,MAAiC,EAAA;AAEjC,IAAA,OAAO,wBAAwB,CAAC;AAC9B,QAAA;AACE,YAAA,OAAO,EAAE,iBAAiB;AAC1B,YAAA,QAAQ,EAAE,EAAE,GAAG,sBAAsB,EAAE,GAAG,MAAM,EAAE;AACnD,SAAA;AACF,KAAA,CAAC;AACJ;SAEgB,aAAa,GAAA;IAC3B,OAAO;AACL,QAAA,gBAAgB,CAAC,CAAC,oBAAoB,CAAC,CAAC;QACxC,qBAAqB,CAAC,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,CAAC;KAChF;AACH;;ACzBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACTA;;AAEG;;;;"}
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@ declare class SparkForgotPasswordComponent {
|
|
|
7
7
|
private readonly fb;
|
|
8
8
|
private readonly authService;
|
|
9
9
|
private readonly translation;
|
|
10
|
-
readonly routePaths:
|
|
10
|
+
readonly routePaths: Partial<Record<keyof _mintplayer_ng_spark_auth_models.SparkAuthRouteEntries, string>>;
|
|
11
11
|
colors: typeof Color;
|
|
12
12
|
readonly loading: _angular_core.WritableSignal<boolean>;
|
|
13
13
|
readonly errorMessage: _angular_core.WritableSignal<string>;
|
|
@@ -10,7 +10,7 @@ declare class SparkLoginComponent {
|
|
|
10
10
|
private readonly route;
|
|
11
11
|
private readonly config;
|
|
12
12
|
private readonly translation;
|
|
13
|
-
readonly routePaths:
|
|
13
|
+
readonly routePaths: Partial<Record<keyof _mintplayer_ng_spark_auth_models.SparkAuthRouteEntries, string>>;
|
|
14
14
|
colors: typeof Color;
|
|
15
15
|
readonly loading: _angular_core.WritableSignal<boolean>;
|
|
16
16
|
readonly errorMessage: _angular_core.WritableSignal<string>;
|
|
@@ -38,19 +38,9 @@ type SparkAuthRouteEntry = string | {
|
|
|
38
38
|
path: string;
|
|
39
39
|
component?: Type<unknown>;
|
|
40
40
|
};
|
|
41
|
-
/**
|
|
42
|
-
* How much of the local-credential (email + password) page family to route.
|
|
43
|
-
*
|
|
44
|
-
* Mirrors the server's `SparkLocalCredentials`, and for the same reason it is a mode rather
|
|
45
|
-
* than five independent switches: the pages form a star centred on the login page, and every
|
|
46
|
-
* template dereferences its siblings' paths unconditionally, so removing any proper subset
|
|
47
|
-
* leaves a dangling link. The family is the unit on both tiers.
|
|
48
|
-
*/
|
|
49
|
-
type SparkLocalCredentialsMode = 'full' | 'sign-in-only' | 'disabled';
|
|
50
|
-
/** The routable local-credential pages. Kept separate from {@link SparkAuthRouteConfig} so that
|
|
51
|
-
* adding non-route options below cannot widen {@link SparkAuthRoutePaths}. */
|
|
41
|
+
/** The routable authentication pages. */
|
|
52
42
|
interface SparkAuthRouteEntries {
|
|
53
|
-
/** The provider-button landing page
|
|
43
|
+
/** The provider-button landing page, mounted by `withExternalLogin()`. */
|
|
54
44
|
signIn?: SparkAuthRouteEntry;
|
|
55
45
|
login?: SparkAuthRouteEntry;
|
|
56
46
|
twoFactor?: SparkAuthRouteEntry;
|
|
@@ -58,21 +48,36 @@ interface SparkAuthRouteEntries {
|
|
|
58
48
|
forgotPassword?: SparkAuthRouteEntry;
|
|
59
49
|
resetPassword?: SparkAuthRouteEntry;
|
|
60
50
|
}
|
|
61
|
-
interface SparkAuthRouteConfig extends SparkAuthRouteEntries {
|
|
62
|
-
/**
|
|
63
|
-
* Defaults to `'full'`, which routes every page — the behaviour of every version before this
|
|
64
|
-
* option existed. Set it to match the server's `LocalCredentials` mode; `GET /spark/auth/capabilities`
|
|
65
|
-
* reports what the server is actually running.
|
|
66
|
-
*/
|
|
67
|
-
localCredentials?: SparkLocalCredentialsMode;
|
|
68
|
-
}
|
|
69
51
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* and
|
|
52
|
+
* The paths of the pages that were actually mounted.
|
|
53
|
+
*
|
|
54
|
+
* Partial, and that is the change: pages are opted into individually now, so a template cannot
|
|
55
|
+
* assume its siblings exist. `login` links to `register` only when registration was opted into, and
|
|
56
|
+
* the sign-in landing page links to `login` only when local login was. A `[routerLink]` bound to
|
|
57
|
+
* `undefined` silently navigates to the current route, so every cross-feature link is guarded.
|
|
58
|
+
*/
|
|
59
|
+
type SparkAuthRoutePaths = Partial<Record<keyof SparkAuthRouteEntries, string>>;
|
|
60
|
+
declare const SPARK_AUTH_ROUTE_PATHS: InjectionToken<Partial<Record<keyof SparkAuthRouteEntries, string>>>;
|
|
61
|
+
/**
|
|
62
|
+
* Presentation for one external provider's button — an icon, a label, an ordering.
|
|
63
|
+
*
|
|
64
|
+
* **It decorates; it does not declare.** The server stays authoritative over which providers exist
|
|
65
|
+
* (`GET /spark/auth/capabilities`), because letting the client declare them would reintroduce exactly
|
|
66
|
+
* the string-literal mismatch that endpoint exists to prevent. A scheme the server reports with no
|
|
67
|
+
* matching declaration falls back to the default button, so adding a provider server-side never
|
|
68
|
+
* yields a blank page; a declaration matching no reported scheme is simply unused.
|
|
73
69
|
*/
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
interface SparkExternalProviderPresentation {
|
|
71
|
+
/** The ASP.NET Core authentication scheme, as the server reports it. Matched case-insensitively. */
|
|
72
|
+
scheme: string;
|
|
73
|
+
/** Overrides the server's display name. */
|
|
74
|
+
displayName?: string;
|
|
75
|
+
/** A CSS class for an icon element rendered before the label. */
|
|
76
|
+
iconClass?: string;
|
|
77
|
+
/** Lower sorts first. Providers with no declared order keep the server's order, after declared ones. */
|
|
78
|
+
order?: number;
|
|
79
|
+
}
|
|
80
|
+
declare const SPARK_EXTERNAL_PROVIDERS: InjectionToken<SparkExternalProviderPresentation[]>;
|
|
76
81
|
|
|
77
82
|
/**
|
|
78
83
|
* Shared validator for `returnUrl` query parameters consumed by the auth
|
|
@@ -133,5 +138,5 @@ declare const SPARK_EXTERNAL_LOGIN_MESSAGE_TYPE = "spark:external-login";
|
|
|
133
138
|
*/
|
|
134
139
|
declare function resolveSignInUrl(config: SparkAuthConfig, router: Router): string;
|
|
135
140
|
|
|
136
|
-
export { SPARK_AUTH_CONFIG, SPARK_AUTH_ROUTE_PATHS, SPARK_EXTERNAL_LOGIN_MESSAGE_TYPE, defaultSparkAuthConfig, isSafeReturnUrl, resolveSignInUrl, sanitizeReturnUrl };
|
|
137
|
-
export type { AuthUser, SparkAuthCapabilities, SparkAuthConfig,
|
|
141
|
+
export { SPARK_AUTH_CONFIG, SPARK_AUTH_ROUTE_PATHS, SPARK_EXTERNAL_LOGIN_MESSAGE_TYPE, SPARK_EXTERNAL_PROVIDERS, defaultSparkAuthConfig, isSafeReturnUrl, resolveSignInUrl, sanitizeReturnUrl };
|
|
142
|
+
export type { AuthUser, SparkAuthCapabilities, SparkAuthConfig, SparkAuthRouteEntries, SparkAuthRouteEntry, SparkAuthRoutePaths, SparkExternalLoginError, SparkExternalLoginMessage, SparkExternalLoginMode, SparkExternalLoginOptions, SparkExternalLoginResult, SparkExternalProvider, SparkExternalProviderPresentation };
|
|
@@ -8,7 +8,7 @@ declare class SparkRegisterComponent {
|
|
|
8
8
|
private readonly authService;
|
|
9
9
|
private readonly router;
|
|
10
10
|
private readonly translation;
|
|
11
|
-
readonly routePaths:
|
|
11
|
+
readonly routePaths: Partial<Record<keyof _mintplayer_ng_spark_auth_models.SparkAuthRouteEntries, string>>;
|
|
12
12
|
colors: typeof Color;
|
|
13
13
|
readonly loading: _angular_core.WritableSignal<boolean>;
|
|
14
14
|
readonly errorMessage: _angular_core.WritableSignal<string>;
|
|
@@ -10,7 +10,7 @@ declare class SparkResetPasswordComponent implements OnInit {
|
|
|
10
10
|
private readonly router;
|
|
11
11
|
private readonly route;
|
|
12
12
|
private readonly translation;
|
|
13
|
-
readonly routePaths:
|
|
13
|
+
readonly routePaths: Partial<Record<keyof _mintplayer_ng_spark_auth_models.SparkAuthRouteEntries, string>>;
|
|
14
14
|
colors: typeof Color;
|
|
15
15
|
readonly loading: _angular_core.WritableSignal<boolean>;
|
|
16
16
|
readonly errorMessage: _angular_core.WritableSignal<string>;
|
|
@@ -1,20 +1,90 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SparkAuthRoutePaths, SparkExternalProviderPresentation, SparkAuthRouteEntries, SparkAuthRouteEntry } from '@mintplayer/ng-spark-auth/models';
|
|
2
2
|
|
|
3
|
+
type Loader = () => Promise<any>;
|
|
4
|
+
interface Child {
|
|
5
|
+
path: string;
|
|
6
|
+
loadComponent: Loader;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* One opt-in group of authentication pages. Produced by `withLocalLogin()`, `withRegistration()` and
|
|
10
|
+
* `withExternalLogin()`; not constructible by consumers, which is what keeps the set of mountable
|
|
11
|
+
* pages a decision this library makes rather than a shape an application can invent.
|
|
12
|
+
*/
|
|
13
|
+
interface SparkAuthRoutesFeature {
|
|
14
|
+
readonly children: Child[];
|
|
15
|
+
readonly paths: SparkAuthRoutePaths;
|
|
16
|
+
readonly providers?: SparkExternalProviderPresentation[];
|
|
17
|
+
}
|
|
18
|
+
/** Path override for the sign-in landing page. Named to stay clear of the core package's
|
|
19
|
+
* `SparkExternalLoginOptions`, which is about the sign-in *call* rather than the route. */
|
|
20
|
+
type SparkExternalLoginRouteOptions = Pick<SparkAuthRouteEntries, 'signIn'>;
|
|
3
21
|
/**
|
|
4
22
|
* The routes for Spark's authentication pages.
|
|
5
23
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
24
|
+
* **Nothing is mounted unless a feature asks for it.** Passing no features emits no pages at all —
|
|
25
|
+
* opt-in enforced by construction rather than by a flag, matching `provideHttpClient(withFetch())`
|
|
26
|
+
* and `provideRouter(withComponentInputBinding())`. Before this the pages mounted by default and an
|
|
27
|
+
* application had to opt *out*, so every app shipped a registration form whether or not it wanted
|
|
28
|
+
* one.
|
|
29
|
+
*
|
|
30
|
+
* ```ts
|
|
31
|
+
* sparkAuthRoutes(
|
|
32
|
+
* withLocalLogin(),
|
|
33
|
+
* withRegistration(),
|
|
34
|
+
* withExternalLogin(githubProvider(), facebookProvider()),
|
|
35
|
+
* )
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* The `import()` expressions live *inside* each feature. That placement is the point: a bundler
|
|
39
|
+
* decides whether to emit a lazy chunk from whether the `import()` call site is reachable, not from
|
|
40
|
+
* whether the route object referencing it survives — so filtering a children array after the fact
|
|
41
|
+
* would still ship every page. A page nobody opted into has no reachable `import()` at all.
|
|
42
|
+
*/
|
|
43
|
+
declare function sparkAuthRoutes(...features: SparkAuthRoutesFeature[]): any[];
|
|
44
|
+
/**
|
|
45
|
+
* Mounts the email/password family: login, two-factor, forgot-password, reset-password.
|
|
46
|
+
*
|
|
47
|
+
* These four are one feature rather than four because they form a star centred on the login page and
|
|
48
|
+
* every template dereferences its siblings unconditionally — removing any proper subset leaves a
|
|
49
|
+
* dangling link. Registration is genuinely separable, and is its own feature.
|
|
50
|
+
*
|
|
51
|
+
* Two-factor belongs here because it is reachable only from the login page's `RequiresTwoFactor`
|
|
52
|
+
* branch and posts to the same `/login` endpoint: it is part of password sign-in, not of
|
|
53
|
+
* authentication in general.
|
|
54
|
+
*/
|
|
55
|
+
declare function withLocalLogin(entries?: Pick<SparkAuthRouteEntries, 'login' | 'twoFactor' | 'forgotPassword' | 'resetPassword'>): SparkAuthRoutesFeature;
|
|
56
|
+
/**
|
|
57
|
+
* Mounts the self-service registration page.
|
|
58
|
+
*
|
|
59
|
+
* Separate from {@link withLocalLogin} because the two decisions genuinely are separate: an
|
|
60
|
+
* application whose accounts are provisioned by an administrator still needs password sign-in and
|
|
61
|
+
* password reset. Mount it only alongside a server whose `SparkLocalCredentials` is `Full` — with
|
|
62
|
+
* `SignInOnly` the endpoint it posts to is not mapped.
|
|
63
|
+
*/
|
|
64
|
+
declare function withRegistration(entry?: SparkAuthRouteEntry): SparkAuthRoutesFeature;
|
|
65
|
+
/**
|
|
66
|
+
* Mounts the sign-in landing page — a button per external provider the *server* reports.
|
|
67
|
+
*
|
|
68
|
+
* Accepts provider declarations and, in any position, one options object for the page's own path;
|
|
69
|
+
* the two shapes are disjoint, so `withExternalLogin(githubProvider(), facebookProvider())` and
|
|
70
|
+
* `withExternalLogin({ signIn: 'landing' }, githubProvider())` both read naturally.
|
|
9
71
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
|
|
13
|
-
|
|
72
|
+
* Declaring no providers is valid and useful: the page still renders whatever
|
|
73
|
+
* `GET /spark/auth/capabilities` reports, using default buttons.
|
|
74
|
+
*/
|
|
75
|
+
declare function withExternalLogin(...providersOrOptions: (SparkExternalProviderPresentation | SparkExternalLoginRouteOptions)[]): SparkAuthRoutesFeature;
|
|
76
|
+
/**
|
|
77
|
+
* Presentation for one provider's button, keyed by the scheme the server reports.
|
|
14
78
|
*
|
|
15
|
-
* `
|
|
16
|
-
*
|
|
79
|
+
* The generic form. `githubProvider()` and friends are this with a scheme and an icon filled in —
|
|
80
|
+
* they exist so the common case does not require knowing the scheme string, not because the library
|
|
81
|
+
* has an opinion about which providers exist.
|
|
17
82
|
*/
|
|
18
|
-
declare function
|
|
83
|
+
declare function externalProvider(scheme: string, presentation?: Omit<SparkExternalProviderPresentation, 'scheme'>): SparkExternalProviderPresentation;
|
|
84
|
+
declare function githubProvider(presentation?: Omit<SparkExternalProviderPresentation, 'scheme'>): SparkExternalProviderPresentation;
|
|
85
|
+
declare function googleProvider(presentation?: Omit<SparkExternalProviderPresentation, 'scheme'>): SparkExternalProviderPresentation;
|
|
86
|
+
declare function facebookProvider(presentation?: Omit<SparkExternalProviderPresentation, 'scheme'>): SparkExternalProviderPresentation;
|
|
87
|
+
declare function microsoftProvider(presentation?: Omit<SparkExternalProviderPresentation, 'scheme'>): SparkExternalProviderPresentation;
|
|
19
88
|
|
|
20
|
-
export { sparkAuthRoutes };
|
|
89
|
+
export { externalProvider, facebookProvider, githubProvider, googleProvider, microsoftProvider, sparkAuthRoutes, withExternalLogin, withLocalLogin, withRegistration };
|
|
90
|
+
export type { SparkAuthRoutesFeature, SparkExternalLoginRouteOptions };
|
|
@@ -1,25 +1,73 @@
|
|
|
1
1
|
import * as _angular_core from '@angular/core';
|
|
2
|
+
import { TemplateRef } from '@angular/core';
|
|
2
3
|
import * as _mintplayer_ng_spark_auth_models from '@mintplayer/ng-spark-auth/models';
|
|
3
4
|
import { SparkExternalProvider } from '@mintplayer/ng-spark-auth/models';
|
|
4
5
|
import { Color } from '@mintplayer/ng-bootstrap';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
8
|
+
* One provider's button, as the template sees it: the provider itself, and the call that signs in
|
|
9
|
+
* with it.
|
|
10
|
+
*
|
|
11
|
+
* **Passing the closure is the point.** A consumer never touches `provider.scheme`, so the failure
|
|
12
|
+
* this component exists to prevent — a hard-coded scheme string that silently stops matching when
|
|
13
|
+
* the server's registration changes — is unreachable by construction. It also means busy state,
|
|
14
|
+
* `returnUrl` handling and popup-versus-redirect can move behind `signIn` later without a
|
|
15
|
+
* consumer-facing change.
|
|
16
|
+
*/
|
|
17
|
+
interface SparkProviderButtonContext {
|
|
18
|
+
$implicit: SparkExternalProviderView;
|
|
19
|
+
signIn: () => void;
|
|
20
|
+
}
|
|
21
|
+
/** A provider the server reported, merged with whatever presentation the app declared for it. */
|
|
22
|
+
interface SparkExternalProviderView extends SparkExternalProvider {
|
|
23
|
+
iconClass?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The sign-in landing page: a button per external provider, and a link to the password form when the
|
|
27
|
+
* application mounted one.
|
|
9
28
|
*
|
|
10
29
|
* The providers come from `GET /spark/auth/capabilities` rather than from a list the application
|
|
11
30
|
* hard-codes. That is the point of the component: every consumer that hand-rolled this before wrote
|
|
12
31
|
* the scheme name as a string literal, which is a silent mismatch waiting to happen the moment the
|
|
13
|
-
* server's provider registration changes.
|
|
32
|
+
* server's provider registration changes. A `withExternalLogin(githubProvider())` declaration only
|
|
33
|
+
* *decorates* what the server reports — it cannot conjure a provider the server does not have.
|
|
14
34
|
*/
|
|
15
35
|
declare class SparkSignInComponent {
|
|
16
36
|
private readonly authService;
|
|
17
|
-
readonly
|
|
37
|
+
private readonly declaredProviders;
|
|
38
|
+
private readonly route;
|
|
39
|
+
readonly routePaths: Partial<Record<keyof _mintplayer_ng_spark_auth_models.SparkAuthRouteEntries, string>>;
|
|
18
40
|
readonly colors: typeof Color;
|
|
19
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Where to land after a successful external sign-in.
|
|
43
|
+
*
|
|
44
|
+
* Falls back to a `?returnUrl=` query parameter, matching what the login page already does — so a
|
|
45
|
+
* plain `<a routerLink="/sign-in" [queryParams]="{ returnUrl: '/somewhere' }">` works with no
|
|
46
|
+
* wiring, which is what a routed page needs since the router passes it no inputs. The query value
|
|
47
|
+
* is validated as a local path before use; an off-site one is dropped rather than followed.
|
|
48
|
+
*/
|
|
49
|
+
readonly returnUrl: _angular_core.InputSignal<string | undefined>;
|
|
50
|
+
private effectiveReturnUrl;
|
|
51
|
+
/**
|
|
52
|
+
* Replaces the default provider button. Rendered once per provider with a
|
|
53
|
+
* {@link SparkProviderButtonContext}.
|
|
54
|
+
*
|
|
55
|
+
* Reachable only when the consumer *hosts* this component — the router instantiates it with no
|
|
56
|
+
* projected content, so on a default `withExternalLogin()` route there is nothing to project into.
|
|
57
|
+
* Host it via `withExternalLogin({ signIn: { path: 'sign-in', component: MySignIn } })` and pass
|
|
58
|
+
* the template from there.
|
|
59
|
+
*/
|
|
60
|
+
readonly providerTemplate: _angular_core.InputSignal<TemplateRef<SparkProviderButtonContext> | null>;
|
|
61
|
+
private readonly reported;
|
|
20
62
|
readonly localCredentialsAvailable: _angular_core.WritableSignal<boolean>;
|
|
21
63
|
readonly loading: _angular_core.WritableSignal<boolean>;
|
|
22
64
|
readonly failed: _angular_core.WritableSignal<boolean>;
|
|
65
|
+
/**
|
|
66
|
+
* The server's list, decorated and ordered by whatever the application declared. Declared-but-not-
|
|
67
|
+
* reported schemes are dropped and reported-but-not-declared ones keep a default button, so
|
|
68
|
+
* neither side can produce a provider the other does not have.
|
|
69
|
+
*/
|
|
70
|
+
readonly providers: _angular_core.Signal<SparkExternalProviderView[]>;
|
|
23
71
|
constructor();
|
|
24
72
|
private load;
|
|
25
73
|
/**
|
|
@@ -28,9 +76,12 @@ declare class SparkSignInComponent {
|
|
|
28
76
|
* which it only does when local credentials are meant to be limited.
|
|
29
77
|
*/
|
|
30
78
|
private warnOnMismatch;
|
|
31
|
-
|
|
79
|
+
/** The closure handed to a projected template, so a consumer never names a scheme itself. */
|
|
80
|
+
contextFor(provider: SparkExternalProviderView): SparkProviderButtonContext;
|
|
81
|
+
signInWith(provider: SparkExternalProviderView): void;
|
|
32
82
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SparkSignInComponent, never>;
|
|
33
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<SparkSignInComponent, "spark-sign-in", never, {}, {}, never, never, true, never>;
|
|
83
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<SparkSignInComponent, "spark-sign-in", never, { "returnUrl": { "alias": "returnUrl"; "required": false; "isSignal": true; }; "providerTemplate": { "alias": "providerTemplate"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
34
84
|
}
|
|
35
85
|
|
|
36
86
|
export { SparkSignInComponent };
|
|
87
|
+
export type { SparkExternalProviderView, SparkProviderButtonContext };
|
|
@@ -10,7 +10,7 @@ declare class SparkTwoFactorComponent {
|
|
|
10
10
|
private readonly route;
|
|
11
11
|
private readonly config;
|
|
12
12
|
private readonly translation;
|
|
13
|
-
readonly routePaths:
|
|
13
|
+
readonly routePaths: Partial<Record<keyof _mintplayer_ng_spark_auth_models.SparkAuthRouteEntries, string>>;
|
|
14
14
|
colors: typeof Color;
|
|
15
15
|
readonly loading: _angular_core.WritableSignal<boolean>;
|
|
16
16
|
readonly errorMessage: _angular_core.WritableSignal<string>;
|