@mintplayer/ng-spark-auth 0.0.1
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.
|
@@ -0,0 +1,1136 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, inject, signal, computed, Injectable, makeEnvironmentProviders, Component } from '@angular/core';
|
|
3
|
+
import { HttpClient, withInterceptors, withXsrfConfiguration } from '@angular/common/http';
|
|
4
|
+
import { switchMap, tap } from 'rxjs';
|
|
5
|
+
import { Router, RouterLink, ActivatedRoute } from '@angular/router';
|
|
6
|
+
import * as i1 from '@angular/forms';
|
|
7
|
+
import { FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
|
8
|
+
import * as i2 from '@mintplayer/ng-bootstrap/form';
|
|
9
|
+
import { BsFormModule } from '@mintplayer/ng-bootstrap/form';
|
|
10
|
+
import * as i3 from '@mintplayer/ng-bootstrap/toggle-button';
|
|
11
|
+
import { BsToggleButtonModule } from '@mintplayer/ng-bootstrap/toggle-button';
|
|
12
|
+
|
|
13
|
+
const SPARK_AUTH_CONFIG = new InjectionToken('SPARK_AUTH_CONFIG');
|
|
14
|
+
const defaultSparkAuthConfig = {
|
|
15
|
+
apiBasePath: '/spark/auth',
|
|
16
|
+
defaultRedirectUrl: '/',
|
|
17
|
+
loginUrl: '/login',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const SPARK_AUTH_ROUTE_PATHS = new InjectionToken('SPARK_AUTH_ROUTE_PATHS');
|
|
21
|
+
|
|
22
|
+
class SparkAuthService {
|
|
23
|
+
http = inject(HttpClient);
|
|
24
|
+
config = inject(SPARK_AUTH_CONFIG);
|
|
25
|
+
currentUser = signal(null, ...(ngDevMode ? [{ debugName: "currentUser" }] : []));
|
|
26
|
+
user = this.currentUser.asReadonly();
|
|
27
|
+
isAuthenticated = computed(() => this.currentUser()?.isAuthenticated === true, ...(ngDevMode ? [{ debugName: "isAuthenticated" }] : []));
|
|
28
|
+
constructor() {
|
|
29
|
+
this.checkAuth().subscribe();
|
|
30
|
+
}
|
|
31
|
+
login(email, password) {
|
|
32
|
+
return this.http
|
|
33
|
+
.post(`${this.config.apiBasePath}/login?useCookies=true`, { email, password })
|
|
34
|
+
.pipe(switchMap(() => this.csrfRefresh()), tap(() => { this.checkAuth().subscribe(); }));
|
|
35
|
+
}
|
|
36
|
+
loginTwoFactor(twoFactorCode, twoFactorRecoveryCode) {
|
|
37
|
+
return this.http
|
|
38
|
+
.post(`${this.config.apiBasePath}/login?useCookies=true`, {
|
|
39
|
+
twoFactorCode,
|
|
40
|
+
twoFactorRecoveryCode,
|
|
41
|
+
})
|
|
42
|
+
.pipe(switchMap(() => this.csrfRefresh()), tap(() => { this.checkAuth().subscribe(); }));
|
|
43
|
+
}
|
|
44
|
+
register(email, password) {
|
|
45
|
+
return this.http.post(`${this.config.apiBasePath}/register`, { email, password });
|
|
46
|
+
}
|
|
47
|
+
logout() {
|
|
48
|
+
return this.http
|
|
49
|
+
.post(`${this.config.apiBasePath}/logout`, {})
|
|
50
|
+
.pipe(switchMap(() => this.csrfRefresh()), tap(() => { this.currentUser.set(null); }));
|
|
51
|
+
}
|
|
52
|
+
csrfRefresh() {
|
|
53
|
+
return this.http.post(`${this.config.apiBasePath}/csrf-refresh`, {});
|
|
54
|
+
}
|
|
55
|
+
checkAuth() {
|
|
56
|
+
return this.http.get(`${this.config.apiBasePath}/me`).pipe(tap({
|
|
57
|
+
next: (user) => this.currentUser.set(user),
|
|
58
|
+
error: () => this.currentUser.set(null),
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
forgotPassword(email) {
|
|
62
|
+
return this.http.post(`${this.config.apiBasePath}/forgotPassword`, { email });
|
|
63
|
+
}
|
|
64
|
+
resetPassword(email, resetCode, newPassword) {
|
|
65
|
+
return this.http.post(`${this.config.apiBasePath}/resetPassword`, {
|
|
66
|
+
email,
|
|
67
|
+
resetCode,
|
|
68
|
+
newPassword,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SparkAuthService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
72
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SparkAuthService, providedIn: 'root' });
|
|
73
|
+
}
|
|
74
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SparkAuthService, decorators: [{
|
|
75
|
+
type: Injectable,
|
|
76
|
+
args: [{ providedIn: 'root' }]
|
|
77
|
+
}], ctorParameters: () => [] });
|
|
78
|
+
|
|
79
|
+
const sparkAuthInterceptor = (req, next) => {
|
|
80
|
+
const config = inject(SPARK_AUTH_CONFIG);
|
|
81
|
+
const router = inject(Router);
|
|
82
|
+
return next(req).pipe(tap({
|
|
83
|
+
error: (error) => {
|
|
84
|
+
if (error.status === 401 &&
|
|
85
|
+
!req.url.startsWith(config.apiBasePath)) {
|
|
86
|
+
router.navigate([config.loginUrl], {
|
|
87
|
+
queryParams: { returnUrl: router.url },
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
}));
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const sparkAuthGuard = (route, state) => {
|
|
95
|
+
const authService = inject(SparkAuthService);
|
|
96
|
+
const router = inject(Router);
|
|
97
|
+
const config = inject(SPARK_AUTH_CONFIG);
|
|
98
|
+
if (authService.isAuthenticated()) {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
return router.createUrlTree([config.loginUrl], {
|
|
102
|
+
queryParams: { returnUrl: state.url },
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
function provideSparkAuth(config) {
|
|
107
|
+
return makeEnvironmentProviders([
|
|
108
|
+
{
|
|
109
|
+
provide: SPARK_AUTH_CONFIG,
|
|
110
|
+
useValue: { ...defaultSparkAuthConfig, ...config },
|
|
111
|
+
},
|
|
112
|
+
]);
|
|
113
|
+
}
|
|
114
|
+
function withSparkAuth() {
|
|
115
|
+
return [
|
|
116
|
+
withInterceptors([sparkAuthInterceptor]),
|
|
117
|
+
withXsrfConfiguration({ cookieName: 'XSRF-TOKEN', headerName: 'X-XSRF-TOKEN' }),
|
|
118
|
+
];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function resolveEntry(entry, defaultPath, defaultLoader) {
|
|
122
|
+
if (entry === undefined || typeof entry === 'string') {
|
|
123
|
+
return {
|
|
124
|
+
path: typeof entry === 'string' ? entry : defaultPath,
|
|
125
|
+
loadComponent: defaultLoader,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
path: entry.path,
|
|
130
|
+
loadComponent: entry.component
|
|
131
|
+
? () => Promise.resolve(entry.component)
|
|
132
|
+
: defaultLoader,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function sparkAuthRoutes(config) {
|
|
136
|
+
const login = resolveEntry(config?.login, 'login', () => Promise.resolve().then(function () { return sparkLogin_component; }).then(m => m.SparkLoginComponent));
|
|
137
|
+
const twoFactor = resolveEntry(config?.twoFactor, 'login/two-factor', () => Promise.resolve().then(function () { return sparkTwoFactor_component; }).then(m => m.SparkTwoFactorComponent));
|
|
138
|
+
const register = resolveEntry(config?.register, 'register', () => Promise.resolve().then(function () { return sparkRegister_component; }).then(m => m.SparkRegisterComponent));
|
|
139
|
+
const forgotPassword = resolveEntry(config?.forgotPassword, 'forgot-password', () => Promise.resolve().then(function () { return sparkForgotPassword_component; }).then(m => m.SparkForgotPasswordComponent));
|
|
140
|
+
const resetPassword = resolveEntry(config?.resetPassword, 'reset-password', () => Promise.resolve().then(function () { return sparkResetPassword_component; }).then(m => m.SparkResetPasswordComponent));
|
|
141
|
+
const paths = {
|
|
142
|
+
login: '/' + login.path,
|
|
143
|
+
twoFactor: '/' + twoFactor.path,
|
|
144
|
+
register: '/' + register.path,
|
|
145
|
+
forgotPassword: '/' + forgotPassword.path,
|
|
146
|
+
resetPassword: '/' + resetPassword.path,
|
|
147
|
+
};
|
|
148
|
+
return [
|
|
149
|
+
{
|
|
150
|
+
path: '',
|
|
151
|
+
providers: [
|
|
152
|
+
{ provide: SPARK_AUTH_ROUTE_PATHS, useValue: paths },
|
|
153
|
+
],
|
|
154
|
+
children: [
|
|
155
|
+
{ path: login.path, loadComponent: login.loadComponent },
|
|
156
|
+
{ path: twoFactor.path, loadComponent: twoFactor.loadComponent },
|
|
157
|
+
{ path: register.path, loadComponent: register.loadComponent },
|
|
158
|
+
{ path: forgotPassword.path, loadComponent: forgotPassword.loadComponent },
|
|
159
|
+
{ path: resetPassword.path, loadComponent: resetPassword.loadComponent },
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
class SparkAuthBarComponent {
|
|
166
|
+
authService = inject(SparkAuthService);
|
|
167
|
+
config = inject(SPARK_AUTH_CONFIG);
|
|
168
|
+
router = inject(Router);
|
|
169
|
+
onLogout() {
|
|
170
|
+
this.authService.logout().subscribe(() => {
|
|
171
|
+
this.router.navigateByUrl('/');
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SparkAuthBarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
175
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: SparkAuthBarComponent, isStandalone: true, selector: "spark-auth-bar", ngImport: i0, template: `
|
|
176
|
+
@if (authService.isAuthenticated()) {
|
|
177
|
+
<span class="me-2">{{ authService.user()?.userName }}</span>
|
|
178
|
+
<button class="btn btn-outline-light btn-sm" (click)="onLogout()">Logout</button>
|
|
179
|
+
} @else {
|
|
180
|
+
<a class="btn btn-outline-light btn-sm" [routerLink]="config.loginUrl">Login</a>
|
|
181
|
+
}
|
|
182
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] });
|
|
183
|
+
}
|
|
184
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SparkAuthBarComponent, decorators: [{
|
|
185
|
+
type: Component,
|
|
186
|
+
args: [{
|
|
187
|
+
selector: 'spark-auth-bar',
|
|
188
|
+
standalone: true,
|
|
189
|
+
imports: [RouterLink],
|
|
190
|
+
template: `
|
|
191
|
+
@if (authService.isAuthenticated()) {
|
|
192
|
+
<span class="me-2">{{ authService.user()?.userName }}</span>
|
|
193
|
+
<button class="btn btn-outline-light btn-sm" (click)="onLogout()">Logout</button>
|
|
194
|
+
} @else {
|
|
195
|
+
<a class="btn btn-outline-light btn-sm" [routerLink]="config.loginUrl">Login</a>
|
|
196
|
+
}
|
|
197
|
+
`,
|
|
198
|
+
}]
|
|
199
|
+
}] });
|
|
200
|
+
|
|
201
|
+
class SparkLoginComponent {
|
|
202
|
+
fb = inject(FormBuilder);
|
|
203
|
+
authService = inject(SparkAuthService);
|
|
204
|
+
router = inject(Router);
|
|
205
|
+
route = inject(ActivatedRoute);
|
|
206
|
+
config = inject(SPARK_AUTH_CONFIG);
|
|
207
|
+
routePaths = inject(SPARK_AUTH_ROUTE_PATHS);
|
|
208
|
+
loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
|
|
209
|
+
errorMessage = signal('', ...(ngDevMode ? [{ debugName: "errorMessage" }] : []));
|
|
210
|
+
form = this.fb.group({
|
|
211
|
+
email: ['', Validators.required],
|
|
212
|
+
password: ['', Validators.required],
|
|
213
|
+
rememberMe: [false],
|
|
214
|
+
});
|
|
215
|
+
onSubmit() {
|
|
216
|
+
if (this.form.invalid)
|
|
217
|
+
return;
|
|
218
|
+
this.loading.set(true);
|
|
219
|
+
this.errorMessage.set('');
|
|
220
|
+
const { email, password } = this.form.value;
|
|
221
|
+
this.authService.login(email, password).subscribe({
|
|
222
|
+
next: () => {
|
|
223
|
+
this.loading.set(false);
|
|
224
|
+
const returnUrl = this.route.snapshot.queryParamMap.get('returnUrl');
|
|
225
|
+
this.router.navigateByUrl(returnUrl || this.config.defaultRedirectUrl);
|
|
226
|
+
},
|
|
227
|
+
error: (err) => {
|
|
228
|
+
this.loading.set(false);
|
|
229
|
+
if (err.status === 401 && err.error?.detail === 'RequiresTwoFactor') {
|
|
230
|
+
const returnUrl = this.route.snapshot.queryParamMap.get('returnUrl');
|
|
231
|
+
this.router.navigate([this.routePaths.twoFactor], {
|
|
232
|
+
queryParams: returnUrl ? { returnUrl } : undefined,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
this.errorMessage.set('Invalid email or password.');
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SparkLoginComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
242
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: SparkLoginComponent, isStandalone: true, selector: "spark-login", ngImport: i0, template: `
|
|
243
|
+
<div class="d-flex justify-content-center">
|
|
244
|
+
<div class="card" style="width: 100%; max-width: 400px;">
|
|
245
|
+
<div class="card-body">
|
|
246
|
+
<h3 class="card-title text-center mb-4">Login</h3>
|
|
247
|
+
|
|
248
|
+
@if (errorMessage()) {
|
|
249
|
+
<div class="alert alert-danger" role="alert">{{ errorMessage() }}</div>
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
<bs-form>
|
|
253
|
+
<form [formGroup]="form" (ngSubmit)="onSubmit()">
|
|
254
|
+
<div class="mb-3">
|
|
255
|
+
<label for="email" class="form-label">Email</label>
|
|
256
|
+
<input
|
|
257
|
+
type="text"
|
|
258
|
+
id="email"
|
|
259
|
+
formControlName="email"
|
|
260
|
+
autocomplete="username"
|
|
261
|
+
/>
|
|
262
|
+
</div>
|
|
263
|
+
|
|
264
|
+
<div class="mb-3">
|
|
265
|
+
<label for="password" class="form-label">Password</label>
|
|
266
|
+
<input
|
|
267
|
+
type="password"
|
|
268
|
+
id="password"
|
|
269
|
+
formControlName="password"
|
|
270
|
+
autocomplete="current-password"
|
|
271
|
+
/>
|
|
272
|
+
</div>
|
|
273
|
+
|
|
274
|
+
<div class="mb-3">
|
|
275
|
+
<bs-toggle-button [type]="'checkbox'" formControlName="rememberMe" [name]="'rememberMe'">Remember me</bs-toggle-button>
|
|
276
|
+
</div>
|
|
277
|
+
|
|
278
|
+
<button
|
|
279
|
+
type="submit"
|
|
280
|
+
class="btn btn-primary w-100"
|
|
281
|
+
[disabled]="loading()"
|
|
282
|
+
>
|
|
283
|
+
@if (loading()) {
|
|
284
|
+
<span class="spinner-border spinner-border-sm me-1" role="status"></span>
|
|
285
|
+
}
|
|
286
|
+
Login
|
|
287
|
+
</button>
|
|
288
|
+
</form>
|
|
289
|
+
</bs-form>
|
|
290
|
+
|
|
291
|
+
<div class="mt-3 text-center">
|
|
292
|
+
<a [routerLink]="routePaths.register">Create an account</a>
|
|
293
|
+
</div>
|
|
294
|
+
<div class="mt-2 text-center">
|
|
295
|
+
<a [routerLink]="routePaths.forgotPassword">Forgot password?</a>
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
`, isInline: true, 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])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[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", "routerLink"] }, { kind: "ngmodule", type: BsFormModule }, { kind: "component", type: i2.BsFormComponent, selector: "bs-form", inputs: ["action", "method"], outputs: ["submitted"] }, { kind: "directive", type: i2.BsFormControlDirective, selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)" }, { kind: "ngmodule", type: BsToggleButtonModule }, { kind: "component", type: i3.BsToggleButtonComponent, selector: "bs-toggle-button", inputs: ["type", "isToggled", "name", "value", "group"], outputs: ["isToggledChange"] }, { kind: "directive", type: i3.BsToggleButtonValueAccessor, selector: "bs-toggle-button" }] });
|
|
301
|
+
}
|
|
302
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SparkLoginComponent, decorators: [{
|
|
303
|
+
type: Component,
|
|
304
|
+
args: [{
|
|
305
|
+
selector: 'spark-login',
|
|
306
|
+
standalone: true,
|
|
307
|
+
imports: [ReactiveFormsModule, RouterLink, BsFormModule, BsToggleButtonModule],
|
|
308
|
+
template: `
|
|
309
|
+
<div class="d-flex justify-content-center">
|
|
310
|
+
<div class="card" style="width: 100%; max-width: 400px;">
|
|
311
|
+
<div class="card-body">
|
|
312
|
+
<h3 class="card-title text-center mb-4">Login</h3>
|
|
313
|
+
|
|
314
|
+
@if (errorMessage()) {
|
|
315
|
+
<div class="alert alert-danger" role="alert">{{ errorMessage() }}</div>
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
<bs-form>
|
|
319
|
+
<form [formGroup]="form" (ngSubmit)="onSubmit()">
|
|
320
|
+
<div class="mb-3">
|
|
321
|
+
<label for="email" class="form-label">Email</label>
|
|
322
|
+
<input
|
|
323
|
+
type="text"
|
|
324
|
+
id="email"
|
|
325
|
+
formControlName="email"
|
|
326
|
+
autocomplete="username"
|
|
327
|
+
/>
|
|
328
|
+
</div>
|
|
329
|
+
|
|
330
|
+
<div class="mb-3">
|
|
331
|
+
<label for="password" class="form-label">Password</label>
|
|
332
|
+
<input
|
|
333
|
+
type="password"
|
|
334
|
+
id="password"
|
|
335
|
+
formControlName="password"
|
|
336
|
+
autocomplete="current-password"
|
|
337
|
+
/>
|
|
338
|
+
</div>
|
|
339
|
+
|
|
340
|
+
<div class="mb-3">
|
|
341
|
+
<bs-toggle-button [type]="'checkbox'" formControlName="rememberMe" [name]="'rememberMe'">Remember me</bs-toggle-button>
|
|
342
|
+
</div>
|
|
343
|
+
|
|
344
|
+
<button
|
|
345
|
+
type="submit"
|
|
346
|
+
class="btn btn-primary w-100"
|
|
347
|
+
[disabled]="loading()"
|
|
348
|
+
>
|
|
349
|
+
@if (loading()) {
|
|
350
|
+
<span class="spinner-border spinner-border-sm me-1" role="status"></span>
|
|
351
|
+
}
|
|
352
|
+
Login
|
|
353
|
+
</button>
|
|
354
|
+
</form>
|
|
355
|
+
</bs-form>
|
|
356
|
+
|
|
357
|
+
<div class="mt-3 text-center">
|
|
358
|
+
<a [routerLink]="routePaths.register">Create an account</a>
|
|
359
|
+
</div>
|
|
360
|
+
<div class="mt-2 text-center">
|
|
361
|
+
<a [routerLink]="routePaths.forgotPassword">Forgot password?</a>
|
|
362
|
+
</div>
|
|
363
|
+
</div>
|
|
364
|
+
</div>
|
|
365
|
+
</div>
|
|
366
|
+
`,
|
|
367
|
+
}]
|
|
368
|
+
}] });
|
|
369
|
+
|
|
370
|
+
var sparkLogin_component = /*#__PURE__*/Object.freeze({
|
|
371
|
+
__proto__: null,
|
|
372
|
+
SparkLoginComponent: SparkLoginComponent,
|
|
373
|
+
default: SparkLoginComponent
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
class SparkTwoFactorComponent {
|
|
377
|
+
fb = inject(FormBuilder);
|
|
378
|
+
authService = inject(SparkAuthService);
|
|
379
|
+
router = inject(Router);
|
|
380
|
+
route = inject(ActivatedRoute);
|
|
381
|
+
config = inject(SPARK_AUTH_CONFIG);
|
|
382
|
+
routePaths = inject(SPARK_AUTH_ROUTE_PATHS);
|
|
383
|
+
loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
|
|
384
|
+
errorMessage = signal('', ...(ngDevMode ? [{ debugName: "errorMessage" }] : []));
|
|
385
|
+
useRecoveryCode = signal(false, ...(ngDevMode ? [{ debugName: "useRecoveryCode" }] : []));
|
|
386
|
+
form = this.fb.group({
|
|
387
|
+
code: [''],
|
|
388
|
+
recoveryCode: [''],
|
|
389
|
+
});
|
|
390
|
+
toggleRecoveryCode() {
|
|
391
|
+
this.useRecoveryCode.update(v => !v);
|
|
392
|
+
this.errorMessage.set('');
|
|
393
|
+
}
|
|
394
|
+
onSubmit() {
|
|
395
|
+
const isRecovery = this.useRecoveryCode();
|
|
396
|
+
const code = isRecovery ? this.form.value.recoveryCode : this.form.value.code;
|
|
397
|
+
if (!code?.trim()) {
|
|
398
|
+
this.errorMessage.set(isRecovery ? 'Please enter a recovery code.' : 'Please enter the 6-digit code.');
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
this.loading.set(true);
|
|
402
|
+
this.errorMessage.set('');
|
|
403
|
+
const twoFactorCode = isRecovery ? undefined : code;
|
|
404
|
+
const twoFactorRecoveryCode = isRecovery ? code : undefined;
|
|
405
|
+
this.authService.loginTwoFactor(twoFactorCode ?? '', twoFactorRecoveryCode).subscribe({
|
|
406
|
+
next: () => {
|
|
407
|
+
this.loading.set(false);
|
|
408
|
+
const returnUrl = this.route.snapshot.queryParamMap.get('returnUrl');
|
|
409
|
+
this.router.navigateByUrl(returnUrl || this.config.defaultRedirectUrl);
|
|
410
|
+
},
|
|
411
|
+
error: () => {
|
|
412
|
+
this.loading.set(false);
|
|
413
|
+
this.errorMessage.set('Invalid code. Please try again.');
|
|
414
|
+
},
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SparkTwoFactorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
418
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: SparkTwoFactorComponent, isStandalone: true, selector: "spark-two-factor", ngImport: i0, template: `
|
|
419
|
+
<div class="d-flex justify-content-center">
|
|
420
|
+
<div class="card" style="width: 100%; max-width: 400px;">
|
|
421
|
+
<div class="card-body">
|
|
422
|
+
<h3 class="card-title text-center mb-4">Two-Factor Authentication</h3>
|
|
423
|
+
|
|
424
|
+
@if (errorMessage()) {
|
|
425
|
+
<div class="alert alert-danger" role="alert">{{ errorMessage() }}</div>
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
<bs-form>
|
|
429
|
+
<form [formGroup]="form" (ngSubmit)="onSubmit()">
|
|
430
|
+
@if (!useRecoveryCode()) {
|
|
431
|
+
<div class="mb-3">
|
|
432
|
+
<label for="code" class="form-label">Authentication Code</label>
|
|
433
|
+
<input
|
|
434
|
+
type="text"
|
|
435
|
+
id="code"
|
|
436
|
+
formControlName="code"
|
|
437
|
+
autocomplete="one-time-code"
|
|
438
|
+
maxlength="6"
|
|
439
|
+
placeholder="Enter 6-digit code"
|
|
440
|
+
/>
|
|
441
|
+
</div>
|
|
442
|
+
} @else {
|
|
443
|
+
<div class="mb-3">
|
|
444
|
+
<label for="recoveryCode" class="form-label">Recovery Code</label>
|
|
445
|
+
<input
|
|
446
|
+
type="text"
|
|
447
|
+
id="recoveryCode"
|
|
448
|
+
formControlName="recoveryCode"
|
|
449
|
+
autocomplete="off"
|
|
450
|
+
placeholder="Enter recovery code"
|
|
451
|
+
/>
|
|
452
|
+
</div>
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
<button
|
|
456
|
+
type="submit"
|
|
457
|
+
class="btn btn-primary w-100"
|
|
458
|
+
[disabled]="loading()"
|
|
459
|
+
>
|
|
460
|
+
@if (loading()) {
|
|
461
|
+
<span class="spinner-border spinner-border-sm me-1" role="status"></span>
|
|
462
|
+
}
|
|
463
|
+
Verify
|
|
464
|
+
</button>
|
|
465
|
+
</form>
|
|
466
|
+
</bs-form>
|
|
467
|
+
|
|
468
|
+
<div class="mt-3 text-center">
|
|
469
|
+
<button class="btn btn-link" (click)="toggleRecoveryCode()">
|
|
470
|
+
@if (useRecoveryCode()) {
|
|
471
|
+
Use authentication code instead
|
|
472
|
+
} @else {
|
|
473
|
+
Use a recovery code instead
|
|
474
|
+
}
|
|
475
|
+
</button>
|
|
476
|
+
</div>
|
|
477
|
+
<div class="mt-2 text-center">
|
|
478
|
+
<a [routerLink]="routePaths.login">Back to login</a>
|
|
479
|
+
</div>
|
|
480
|
+
</div>
|
|
481
|
+
</div>
|
|
482
|
+
</div>
|
|
483
|
+
`, isInline: true, 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])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: BsFormModule }, { kind: "component", type: i2.BsFormComponent, selector: "bs-form", inputs: ["action", "method"], outputs: ["submitted"] }, { kind: "directive", type: i2.BsFormControlDirective, selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)" }] });
|
|
484
|
+
}
|
|
485
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SparkTwoFactorComponent, decorators: [{
|
|
486
|
+
type: Component,
|
|
487
|
+
args: [{
|
|
488
|
+
selector: 'spark-two-factor',
|
|
489
|
+
standalone: true,
|
|
490
|
+
imports: [ReactiveFormsModule, RouterLink, BsFormModule],
|
|
491
|
+
template: `
|
|
492
|
+
<div class="d-flex justify-content-center">
|
|
493
|
+
<div class="card" style="width: 100%; max-width: 400px;">
|
|
494
|
+
<div class="card-body">
|
|
495
|
+
<h3 class="card-title text-center mb-4">Two-Factor Authentication</h3>
|
|
496
|
+
|
|
497
|
+
@if (errorMessage()) {
|
|
498
|
+
<div class="alert alert-danger" role="alert">{{ errorMessage() }}</div>
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
<bs-form>
|
|
502
|
+
<form [formGroup]="form" (ngSubmit)="onSubmit()">
|
|
503
|
+
@if (!useRecoveryCode()) {
|
|
504
|
+
<div class="mb-3">
|
|
505
|
+
<label for="code" class="form-label">Authentication Code</label>
|
|
506
|
+
<input
|
|
507
|
+
type="text"
|
|
508
|
+
id="code"
|
|
509
|
+
formControlName="code"
|
|
510
|
+
autocomplete="one-time-code"
|
|
511
|
+
maxlength="6"
|
|
512
|
+
placeholder="Enter 6-digit code"
|
|
513
|
+
/>
|
|
514
|
+
</div>
|
|
515
|
+
} @else {
|
|
516
|
+
<div class="mb-3">
|
|
517
|
+
<label for="recoveryCode" class="form-label">Recovery Code</label>
|
|
518
|
+
<input
|
|
519
|
+
type="text"
|
|
520
|
+
id="recoveryCode"
|
|
521
|
+
formControlName="recoveryCode"
|
|
522
|
+
autocomplete="off"
|
|
523
|
+
placeholder="Enter recovery code"
|
|
524
|
+
/>
|
|
525
|
+
</div>
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
<button
|
|
529
|
+
type="submit"
|
|
530
|
+
class="btn btn-primary w-100"
|
|
531
|
+
[disabled]="loading()"
|
|
532
|
+
>
|
|
533
|
+
@if (loading()) {
|
|
534
|
+
<span class="spinner-border spinner-border-sm me-1" role="status"></span>
|
|
535
|
+
}
|
|
536
|
+
Verify
|
|
537
|
+
</button>
|
|
538
|
+
</form>
|
|
539
|
+
</bs-form>
|
|
540
|
+
|
|
541
|
+
<div class="mt-3 text-center">
|
|
542
|
+
<button class="btn btn-link" (click)="toggleRecoveryCode()">
|
|
543
|
+
@if (useRecoveryCode()) {
|
|
544
|
+
Use authentication code instead
|
|
545
|
+
} @else {
|
|
546
|
+
Use a recovery code instead
|
|
547
|
+
}
|
|
548
|
+
</button>
|
|
549
|
+
</div>
|
|
550
|
+
<div class="mt-2 text-center">
|
|
551
|
+
<a [routerLink]="routePaths.login">Back to login</a>
|
|
552
|
+
</div>
|
|
553
|
+
</div>
|
|
554
|
+
</div>
|
|
555
|
+
</div>
|
|
556
|
+
`,
|
|
557
|
+
}]
|
|
558
|
+
}] });
|
|
559
|
+
|
|
560
|
+
var sparkTwoFactor_component = /*#__PURE__*/Object.freeze({
|
|
561
|
+
__proto__: null,
|
|
562
|
+
SparkTwoFactorComponent: SparkTwoFactorComponent,
|
|
563
|
+
default: SparkTwoFactorComponent
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
function passwordMatchValidator$1(control) {
|
|
567
|
+
const password = control.get('password');
|
|
568
|
+
const confirmPassword = control.get('confirmPassword');
|
|
569
|
+
if (password && confirmPassword && password.value !== confirmPassword.value) {
|
|
570
|
+
return { passwordMismatch: true };
|
|
571
|
+
}
|
|
572
|
+
return null;
|
|
573
|
+
}
|
|
574
|
+
class SparkRegisterComponent {
|
|
575
|
+
fb = inject(FormBuilder);
|
|
576
|
+
authService = inject(SparkAuthService);
|
|
577
|
+
router = inject(Router);
|
|
578
|
+
routePaths = inject(SPARK_AUTH_ROUTE_PATHS);
|
|
579
|
+
loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
|
|
580
|
+
errorMessage = signal('', ...(ngDevMode ? [{ debugName: "errorMessage" }] : []));
|
|
581
|
+
form = this.fb.group({
|
|
582
|
+
email: ['', [Validators.required, Validators.email]],
|
|
583
|
+
password: ['', Validators.required],
|
|
584
|
+
confirmPassword: ['', Validators.required],
|
|
585
|
+
}, { validators: passwordMatchValidator$1 });
|
|
586
|
+
onSubmit() {
|
|
587
|
+
if (this.form.invalid) {
|
|
588
|
+
this.form.markAllAsTouched();
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
this.loading.set(true);
|
|
592
|
+
this.errorMessage.set('');
|
|
593
|
+
const { email, password } = this.form.value;
|
|
594
|
+
this.authService.register(email, password).subscribe({
|
|
595
|
+
next: () => {
|
|
596
|
+
this.loading.set(false);
|
|
597
|
+
this.router.navigate([this.routePaths.login], {
|
|
598
|
+
queryParams: { registered: 'true' },
|
|
599
|
+
});
|
|
600
|
+
},
|
|
601
|
+
error: (err) => {
|
|
602
|
+
this.loading.set(false);
|
|
603
|
+
if (err.status === 400 && err.error?.errors) {
|
|
604
|
+
const messages = [].concat(...Object.values(err.error.errors));
|
|
605
|
+
this.errorMessage.set(messages.join(' '));
|
|
606
|
+
}
|
|
607
|
+
else if (err.error?.detail) {
|
|
608
|
+
this.errorMessage.set(err.error.detail);
|
|
609
|
+
}
|
|
610
|
+
else {
|
|
611
|
+
this.errorMessage.set('Registration failed. Please try again.');
|
|
612
|
+
}
|
|
613
|
+
},
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SparkRegisterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
617
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: SparkRegisterComponent, isStandalone: true, selector: "spark-register", ngImport: i0, template: `
|
|
618
|
+
<div class="d-flex justify-content-center">
|
|
619
|
+
<div class="card" style="width: 100%; max-width: 400px;">
|
|
620
|
+
<div class="card-body">
|
|
621
|
+
<h3 class="card-title text-center mb-4">Register</h3>
|
|
622
|
+
|
|
623
|
+
@if (errorMessage()) {
|
|
624
|
+
<div class="alert alert-danger" role="alert">{{ errorMessage() }}</div>
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
<bs-form>
|
|
628
|
+
<form [formGroup]="form" (ngSubmit)="onSubmit()">
|
|
629
|
+
<div class="mb-3">
|
|
630
|
+
<label for="email" class="form-label">Email</label>
|
|
631
|
+
<input
|
|
632
|
+
type="email"
|
|
633
|
+
id="email"
|
|
634
|
+
formControlName="email"
|
|
635
|
+
autocomplete="email"
|
|
636
|
+
/>
|
|
637
|
+
@if (form.get('email')?.touched && form.get('email')?.hasError('email')) {
|
|
638
|
+
<div class="text-danger mt-1">Please enter a valid email address.</div>
|
|
639
|
+
}
|
|
640
|
+
</div>
|
|
641
|
+
|
|
642
|
+
<div class="mb-3">
|
|
643
|
+
<label for="password" class="form-label">Password</label>
|
|
644
|
+
<input
|
|
645
|
+
type="password"
|
|
646
|
+
id="password"
|
|
647
|
+
formControlName="password"
|
|
648
|
+
autocomplete="new-password"
|
|
649
|
+
/>
|
|
650
|
+
</div>
|
|
651
|
+
|
|
652
|
+
<div class="mb-3">
|
|
653
|
+
<label for="confirmPassword" class="form-label">Confirm Password</label>
|
|
654
|
+
<input
|
|
655
|
+
type="password"
|
|
656
|
+
id="confirmPassword"
|
|
657
|
+
formControlName="confirmPassword"
|
|
658
|
+
autocomplete="new-password"
|
|
659
|
+
/>
|
|
660
|
+
@if (form.touched && form.hasError('passwordMismatch')) {
|
|
661
|
+
<div class="text-danger mt-1">Passwords do not match.</div>
|
|
662
|
+
}
|
|
663
|
+
</div>
|
|
664
|
+
|
|
665
|
+
<button
|
|
666
|
+
type="submit"
|
|
667
|
+
class="btn btn-primary w-100"
|
|
668
|
+
[disabled]="loading()"
|
|
669
|
+
>
|
|
670
|
+
@if (loading()) {
|
|
671
|
+
<span class="spinner-border spinner-border-sm me-1" role="status"></span>
|
|
672
|
+
}
|
|
673
|
+
Register
|
|
674
|
+
</button>
|
|
675
|
+
</form>
|
|
676
|
+
</bs-form>
|
|
677
|
+
|
|
678
|
+
<div class="mt-3 text-center">
|
|
679
|
+
<span>Already have an account? </span>
|
|
680
|
+
<a [routerLink]="routePaths.login">Login</a>
|
|
681
|
+
</div>
|
|
682
|
+
</div>
|
|
683
|
+
</div>
|
|
684
|
+
</div>
|
|
685
|
+
`, isInline: true, 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])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[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", "routerLink"] }, { kind: "ngmodule", type: BsFormModule }, { kind: "component", type: i2.BsFormComponent, selector: "bs-form", inputs: ["action", "method"], outputs: ["submitted"] }, { kind: "directive", type: i2.BsFormControlDirective, selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)" }] });
|
|
686
|
+
}
|
|
687
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SparkRegisterComponent, decorators: [{
|
|
688
|
+
type: Component,
|
|
689
|
+
args: [{
|
|
690
|
+
selector: 'spark-register',
|
|
691
|
+
standalone: true,
|
|
692
|
+
imports: [ReactiveFormsModule, RouterLink, BsFormModule],
|
|
693
|
+
template: `
|
|
694
|
+
<div class="d-flex justify-content-center">
|
|
695
|
+
<div class="card" style="width: 100%; max-width: 400px;">
|
|
696
|
+
<div class="card-body">
|
|
697
|
+
<h3 class="card-title text-center mb-4">Register</h3>
|
|
698
|
+
|
|
699
|
+
@if (errorMessage()) {
|
|
700
|
+
<div class="alert alert-danger" role="alert">{{ errorMessage() }}</div>
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
<bs-form>
|
|
704
|
+
<form [formGroup]="form" (ngSubmit)="onSubmit()">
|
|
705
|
+
<div class="mb-3">
|
|
706
|
+
<label for="email" class="form-label">Email</label>
|
|
707
|
+
<input
|
|
708
|
+
type="email"
|
|
709
|
+
id="email"
|
|
710
|
+
formControlName="email"
|
|
711
|
+
autocomplete="email"
|
|
712
|
+
/>
|
|
713
|
+
@if (form.get('email')?.touched && form.get('email')?.hasError('email')) {
|
|
714
|
+
<div class="text-danger mt-1">Please enter a valid email address.</div>
|
|
715
|
+
}
|
|
716
|
+
</div>
|
|
717
|
+
|
|
718
|
+
<div class="mb-3">
|
|
719
|
+
<label for="password" class="form-label">Password</label>
|
|
720
|
+
<input
|
|
721
|
+
type="password"
|
|
722
|
+
id="password"
|
|
723
|
+
formControlName="password"
|
|
724
|
+
autocomplete="new-password"
|
|
725
|
+
/>
|
|
726
|
+
</div>
|
|
727
|
+
|
|
728
|
+
<div class="mb-3">
|
|
729
|
+
<label for="confirmPassword" class="form-label">Confirm Password</label>
|
|
730
|
+
<input
|
|
731
|
+
type="password"
|
|
732
|
+
id="confirmPassword"
|
|
733
|
+
formControlName="confirmPassword"
|
|
734
|
+
autocomplete="new-password"
|
|
735
|
+
/>
|
|
736
|
+
@if (form.touched && form.hasError('passwordMismatch')) {
|
|
737
|
+
<div class="text-danger mt-1">Passwords do not match.</div>
|
|
738
|
+
}
|
|
739
|
+
</div>
|
|
740
|
+
|
|
741
|
+
<button
|
|
742
|
+
type="submit"
|
|
743
|
+
class="btn btn-primary w-100"
|
|
744
|
+
[disabled]="loading()"
|
|
745
|
+
>
|
|
746
|
+
@if (loading()) {
|
|
747
|
+
<span class="spinner-border spinner-border-sm me-1" role="status"></span>
|
|
748
|
+
}
|
|
749
|
+
Register
|
|
750
|
+
</button>
|
|
751
|
+
</form>
|
|
752
|
+
</bs-form>
|
|
753
|
+
|
|
754
|
+
<div class="mt-3 text-center">
|
|
755
|
+
<span>Already have an account? </span>
|
|
756
|
+
<a [routerLink]="routePaths.login">Login</a>
|
|
757
|
+
</div>
|
|
758
|
+
</div>
|
|
759
|
+
</div>
|
|
760
|
+
</div>
|
|
761
|
+
`,
|
|
762
|
+
}]
|
|
763
|
+
}] });
|
|
764
|
+
|
|
765
|
+
var sparkRegister_component = /*#__PURE__*/Object.freeze({
|
|
766
|
+
__proto__: null,
|
|
767
|
+
SparkRegisterComponent: SparkRegisterComponent,
|
|
768
|
+
default: SparkRegisterComponent
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
class SparkForgotPasswordComponent {
|
|
772
|
+
fb = inject(FormBuilder);
|
|
773
|
+
authService = inject(SparkAuthService);
|
|
774
|
+
routePaths = inject(SPARK_AUTH_ROUTE_PATHS);
|
|
775
|
+
loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
|
|
776
|
+
errorMessage = signal('', ...(ngDevMode ? [{ debugName: "errorMessage" }] : []));
|
|
777
|
+
successMessage = signal('', ...(ngDevMode ? [{ debugName: "successMessage" }] : []));
|
|
778
|
+
form = this.fb.group({
|
|
779
|
+
email: ['', [Validators.required, Validators.email]],
|
|
780
|
+
});
|
|
781
|
+
onSubmit() {
|
|
782
|
+
if (this.form.invalid) {
|
|
783
|
+
this.form.markAllAsTouched();
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
this.loading.set(true);
|
|
787
|
+
this.errorMessage.set('');
|
|
788
|
+
this.successMessage.set('');
|
|
789
|
+
const { email } = this.form.value;
|
|
790
|
+
this.authService.forgotPassword(email).subscribe({
|
|
791
|
+
next: () => {
|
|
792
|
+
this.loading.set(false);
|
|
793
|
+
this.successMessage.set('If an account with that email exists, we\'ve sent a password reset link.');
|
|
794
|
+
},
|
|
795
|
+
error: () => {
|
|
796
|
+
this.loading.set(false);
|
|
797
|
+
// Don't reveal whether the email exists
|
|
798
|
+
this.successMessage.set('If an account with that email exists, we\'ve sent a password reset link.');
|
|
799
|
+
},
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SparkForgotPasswordComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
803
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: SparkForgotPasswordComponent, isStandalone: true, selector: "spark-forgot-password", ngImport: i0, template: `
|
|
804
|
+
<div class="d-flex justify-content-center">
|
|
805
|
+
<div class="card" style="width: 100%; max-width: 400px;">
|
|
806
|
+
<div class="card-body">
|
|
807
|
+
<h3 class="card-title text-center mb-4">Forgot Password</h3>
|
|
808
|
+
|
|
809
|
+
@if (successMessage()) {
|
|
810
|
+
<div class="alert alert-success" role="alert">{{ successMessage() }}</div>
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
@if (errorMessage()) {
|
|
814
|
+
<div class="alert alert-danger" role="alert">{{ errorMessage() }}</div>
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
@if (!successMessage()) {
|
|
818
|
+
<p class="text-muted mb-3">
|
|
819
|
+
Enter your email address and we will send you a link to reset your password.
|
|
820
|
+
</p>
|
|
821
|
+
|
|
822
|
+
<bs-form>
|
|
823
|
+
<form [formGroup]="form" (ngSubmit)="onSubmit()">
|
|
824
|
+
<div class="mb-3">
|
|
825
|
+
<label for="email" class="form-label">Email</label>
|
|
826
|
+
<input
|
|
827
|
+
type="email"
|
|
828
|
+
id="email"
|
|
829
|
+
formControlName="email"
|
|
830
|
+
autocomplete="email"
|
|
831
|
+
/>
|
|
832
|
+
</div>
|
|
833
|
+
|
|
834
|
+
<button
|
|
835
|
+
type="submit"
|
|
836
|
+
class="btn btn-primary w-100"
|
|
837
|
+
[disabled]="loading()"
|
|
838
|
+
>
|
|
839
|
+
@if (loading()) {
|
|
840
|
+
<span class="spinner-border spinner-border-sm me-1" role="status"></span>
|
|
841
|
+
}
|
|
842
|
+
Send Reset Link
|
|
843
|
+
</button>
|
|
844
|
+
</form>
|
|
845
|
+
</bs-form>
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
<div class="mt-3 text-center">
|
|
849
|
+
<a [routerLink]="routePaths.login">Back to login</a>
|
|
850
|
+
</div>
|
|
851
|
+
</div>
|
|
852
|
+
</div>
|
|
853
|
+
</div>
|
|
854
|
+
`, isInline: true, 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])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[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", "routerLink"] }, { kind: "ngmodule", type: BsFormModule }, { kind: "component", type: i2.BsFormComponent, selector: "bs-form", inputs: ["action", "method"], outputs: ["submitted"] }, { kind: "directive", type: i2.BsFormControlDirective, selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)" }] });
|
|
855
|
+
}
|
|
856
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SparkForgotPasswordComponent, decorators: [{
|
|
857
|
+
type: Component,
|
|
858
|
+
args: [{
|
|
859
|
+
selector: 'spark-forgot-password',
|
|
860
|
+
standalone: true,
|
|
861
|
+
imports: [ReactiveFormsModule, RouterLink, BsFormModule],
|
|
862
|
+
template: `
|
|
863
|
+
<div class="d-flex justify-content-center">
|
|
864
|
+
<div class="card" style="width: 100%; max-width: 400px;">
|
|
865
|
+
<div class="card-body">
|
|
866
|
+
<h3 class="card-title text-center mb-4">Forgot Password</h3>
|
|
867
|
+
|
|
868
|
+
@if (successMessage()) {
|
|
869
|
+
<div class="alert alert-success" role="alert">{{ successMessage() }}</div>
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
@if (errorMessage()) {
|
|
873
|
+
<div class="alert alert-danger" role="alert">{{ errorMessage() }}</div>
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
@if (!successMessage()) {
|
|
877
|
+
<p class="text-muted mb-3">
|
|
878
|
+
Enter your email address and we will send you a link to reset your password.
|
|
879
|
+
</p>
|
|
880
|
+
|
|
881
|
+
<bs-form>
|
|
882
|
+
<form [formGroup]="form" (ngSubmit)="onSubmit()">
|
|
883
|
+
<div class="mb-3">
|
|
884
|
+
<label for="email" class="form-label">Email</label>
|
|
885
|
+
<input
|
|
886
|
+
type="email"
|
|
887
|
+
id="email"
|
|
888
|
+
formControlName="email"
|
|
889
|
+
autocomplete="email"
|
|
890
|
+
/>
|
|
891
|
+
</div>
|
|
892
|
+
|
|
893
|
+
<button
|
|
894
|
+
type="submit"
|
|
895
|
+
class="btn btn-primary w-100"
|
|
896
|
+
[disabled]="loading()"
|
|
897
|
+
>
|
|
898
|
+
@if (loading()) {
|
|
899
|
+
<span class="spinner-border spinner-border-sm me-1" role="status"></span>
|
|
900
|
+
}
|
|
901
|
+
Send Reset Link
|
|
902
|
+
</button>
|
|
903
|
+
</form>
|
|
904
|
+
</bs-form>
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
<div class="mt-3 text-center">
|
|
908
|
+
<a [routerLink]="routePaths.login">Back to login</a>
|
|
909
|
+
</div>
|
|
910
|
+
</div>
|
|
911
|
+
</div>
|
|
912
|
+
</div>
|
|
913
|
+
`,
|
|
914
|
+
}]
|
|
915
|
+
}] });
|
|
916
|
+
|
|
917
|
+
var sparkForgotPassword_component = /*#__PURE__*/Object.freeze({
|
|
918
|
+
__proto__: null,
|
|
919
|
+
SparkForgotPasswordComponent: SparkForgotPasswordComponent,
|
|
920
|
+
default: SparkForgotPasswordComponent
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
function passwordMatchValidator(control) {
|
|
924
|
+
const password = control.get('newPassword');
|
|
925
|
+
const confirmPassword = control.get('confirmPassword');
|
|
926
|
+
if (password && confirmPassword && password.value !== confirmPassword.value) {
|
|
927
|
+
return { passwordMismatch: true };
|
|
928
|
+
}
|
|
929
|
+
return null;
|
|
930
|
+
}
|
|
931
|
+
class SparkResetPasswordComponent {
|
|
932
|
+
fb = inject(FormBuilder);
|
|
933
|
+
authService = inject(SparkAuthService);
|
|
934
|
+
router = inject(Router);
|
|
935
|
+
route = inject(ActivatedRoute);
|
|
936
|
+
routePaths = inject(SPARK_AUTH_ROUTE_PATHS);
|
|
937
|
+
loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
|
|
938
|
+
errorMessage = signal('', ...(ngDevMode ? [{ debugName: "errorMessage" }] : []));
|
|
939
|
+
successMessage = signal('', ...(ngDevMode ? [{ debugName: "successMessage" }] : []));
|
|
940
|
+
email = '';
|
|
941
|
+
code = '';
|
|
942
|
+
form = this.fb.group({
|
|
943
|
+
newPassword: ['', Validators.required],
|
|
944
|
+
confirmPassword: ['', Validators.required],
|
|
945
|
+
}, { validators: passwordMatchValidator });
|
|
946
|
+
ngOnInit() {
|
|
947
|
+
this.email = this.route.snapshot.queryParamMap.get('email') ?? '';
|
|
948
|
+
this.code = this.route.snapshot.queryParamMap.get('code') ?? '';
|
|
949
|
+
if (!this.email || !this.code) {
|
|
950
|
+
this.errorMessage.set('Invalid password reset link. Please request a new one.');
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
onSubmit() {
|
|
954
|
+
if (this.form.invalid) {
|
|
955
|
+
this.form.markAllAsTouched();
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
if (!this.email || !this.code) {
|
|
959
|
+
this.errorMessage.set('Invalid password reset link. Please request a new one.');
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
this.loading.set(true);
|
|
963
|
+
this.errorMessage.set('');
|
|
964
|
+
this.successMessage.set('');
|
|
965
|
+
const { newPassword } = this.form.value;
|
|
966
|
+
this.authService.resetPassword(this.email, this.code, newPassword).subscribe({
|
|
967
|
+
next: () => {
|
|
968
|
+
this.loading.set(false);
|
|
969
|
+
this.successMessage.set('Your password has been reset successfully.');
|
|
970
|
+
},
|
|
971
|
+
error: (err) => {
|
|
972
|
+
this.loading.set(false);
|
|
973
|
+
if (err.error?.detail) {
|
|
974
|
+
this.errorMessage.set(err.error.detail);
|
|
975
|
+
}
|
|
976
|
+
else {
|
|
977
|
+
this.errorMessage.set('Failed to reset password. The link may have expired.');
|
|
978
|
+
}
|
|
979
|
+
},
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SparkResetPasswordComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
983
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: SparkResetPasswordComponent, isStandalone: true, selector: "spark-reset-password", ngImport: i0, template: `
|
|
984
|
+
<div class="d-flex justify-content-center">
|
|
985
|
+
<div class="card" style="width: 100%; max-width: 400px;">
|
|
986
|
+
<div class="card-body">
|
|
987
|
+
<h3 class="card-title text-center mb-4">Reset Password</h3>
|
|
988
|
+
|
|
989
|
+
@if (successMessage()) {
|
|
990
|
+
<div class="alert alert-success" role="alert">
|
|
991
|
+
{{ successMessage() }}
|
|
992
|
+
<div class="mt-2">
|
|
993
|
+
<a [routerLink]="routePaths.login">Go to login</a>
|
|
994
|
+
</div>
|
|
995
|
+
</div>
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
@if (errorMessage()) {
|
|
999
|
+
<div class="alert alert-danger" role="alert">{{ errorMessage() }}</div>
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
@if (!successMessage()) {
|
|
1003
|
+
<bs-form>
|
|
1004
|
+
<form [formGroup]="form" (ngSubmit)="onSubmit()">
|
|
1005
|
+
<div class="mb-3">
|
|
1006
|
+
<label for="newPassword" class="form-label">New Password</label>
|
|
1007
|
+
<input
|
|
1008
|
+
type="password"
|
|
1009
|
+
id="newPassword"
|
|
1010
|
+
formControlName="newPassword"
|
|
1011
|
+
autocomplete="new-password"
|
|
1012
|
+
/>
|
|
1013
|
+
</div>
|
|
1014
|
+
|
|
1015
|
+
<div class="mb-3">
|
|
1016
|
+
<label for="confirmPassword" class="form-label">Confirm Password</label>
|
|
1017
|
+
<input
|
|
1018
|
+
type="password"
|
|
1019
|
+
id="confirmPassword"
|
|
1020
|
+
formControlName="confirmPassword"
|
|
1021
|
+
autocomplete="new-password"
|
|
1022
|
+
/>
|
|
1023
|
+
@if (form.touched && form.hasError('passwordMismatch')) {
|
|
1024
|
+
<div class="text-danger mt-1">Passwords do not match.</div>
|
|
1025
|
+
}
|
|
1026
|
+
</div>
|
|
1027
|
+
|
|
1028
|
+
<button
|
|
1029
|
+
type="submit"
|
|
1030
|
+
class="btn btn-primary w-100"
|
|
1031
|
+
[disabled]="loading()"
|
|
1032
|
+
>
|
|
1033
|
+
@if (loading()) {
|
|
1034
|
+
<span class="spinner-border spinner-border-sm me-1" role="status"></span>
|
|
1035
|
+
}
|
|
1036
|
+
Reset Password
|
|
1037
|
+
</button>
|
|
1038
|
+
</form>
|
|
1039
|
+
</bs-form>
|
|
1040
|
+
|
|
1041
|
+
<div class="mt-3 text-center">
|
|
1042
|
+
<a [routerLink]="routePaths.login">Back to login</a>
|
|
1043
|
+
</div>
|
|
1044
|
+
}
|
|
1045
|
+
</div>
|
|
1046
|
+
</div>
|
|
1047
|
+
</div>
|
|
1048
|
+
`, isInline: true, 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])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[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", "routerLink"] }, { kind: "ngmodule", type: BsFormModule }, { kind: "component", type: i2.BsFormComponent, selector: "bs-form", inputs: ["action", "method"], outputs: ["submitted"] }, { kind: "directive", type: i2.BsFormControlDirective, selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)" }] });
|
|
1049
|
+
}
|
|
1050
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SparkResetPasswordComponent, decorators: [{
|
|
1051
|
+
type: Component,
|
|
1052
|
+
args: [{
|
|
1053
|
+
selector: 'spark-reset-password',
|
|
1054
|
+
standalone: true,
|
|
1055
|
+
imports: [ReactiveFormsModule, RouterLink, BsFormModule],
|
|
1056
|
+
template: `
|
|
1057
|
+
<div class="d-flex justify-content-center">
|
|
1058
|
+
<div class="card" style="width: 100%; max-width: 400px;">
|
|
1059
|
+
<div class="card-body">
|
|
1060
|
+
<h3 class="card-title text-center mb-4">Reset Password</h3>
|
|
1061
|
+
|
|
1062
|
+
@if (successMessage()) {
|
|
1063
|
+
<div class="alert alert-success" role="alert">
|
|
1064
|
+
{{ successMessage() }}
|
|
1065
|
+
<div class="mt-2">
|
|
1066
|
+
<a [routerLink]="routePaths.login">Go to login</a>
|
|
1067
|
+
</div>
|
|
1068
|
+
</div>
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
@if (errorMessage()) {
|
|
1072
|
+
<div class="alert alert-danger" role="alert">{{ errorMessage() }}</div>
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
@if (!successMessage()) {
|
|
1076
|
+
<bs-form>
|
|
1077
|
+
<form [formGroup]="form" (ngSubmit)="onSubmit()">
|
|
1078
|
+
<div class="mb-3">
|
|
1079
|
+
<label for="newPassword" class="form-label">New Password</label>
|
|
1080
|
+
<input
|
|
1081
|
+
type="password"
|
|
1082
|
+
id="newPassword"
|
|
1083
|
+
formControlName="newPassword"
|
|
1084
|
+
autocomplete="new-password"
|
|
1085
|
+
/>
|
|
1086
|
+
</div>
|
|
1087
|
+
|
|
1088
|
+
<div class="mb-3">
|
|
1089
|
+
<label for="confirmPassword" class="form-label">Confirm Password</label>
|
|
1090
|
+
<input
|
|
1091
|
+
type="password"
|
|
1092
|
+
id="confirmPassword"
|
|
1093
|
+
formControlName="confirmPassword"
|
|
1094
|
+
autocomplete="new-password"
|
|
1095
|
+
/>
|
|
1096
|
+
@if (form.touched && form.hasError('passwordMismatch')) {
|
|
1097
|
+
<div class="text-danger mt-1">Passwords do not match.</div>
|
|
1098
|
+
}
|
|
1099
|
+
</div>
|
|
1100
|
+
|
|
1101
|
+
<button
|
|
1102
|
+
type="submit"
|
|
1103
|
+
class="btn btn-primary w-100"
|
|
1104
|
+
[disabled]="loading()"
|
|
1105
|
+
>
|
|
1106
|
+
@if (loading()) {
|
|
1107
|
+
<span class="spinner-border spinner-border-sm me-1" role="status"></span>
|
|
1108
|
+
}
|
|
1109
|
+
Reset Password
|
|
1110
|
+
</button>
|
|
1111
|
+
</form>
|
|
1112
|
+
</bs-form>
|
|
1113
|
+
|
|
1114
|
+
<div class="mt-3 text-center">
|
|
1115
|
+
<a [routerLink]="routePaths.login">Back to login</a>
|
|
1116
|
+
</div>
|
|
1117
|
+
}
|
|
1118
|
+
</div>
|
|
1119
|
+
</div>
|
|
1120
|
+
</div>
|
|
1121
|
+
`,
|
|
1122
|
+
}]
|
|
1123
|
+
}] });
|
|
1124
|
+
|
|
1125
|
+
var sparkResetPassword_component = /*#__PURE__*/Object.freeze({
|
|
1126
|
+
__proto__: null,
|
|
1127
|
+
SparkResetPasswordComponent: SparkResetPasswordComponent,
|
|
1128
|
+
default: SparkResetPasswordComponent
|
|
1129
|
+
});
|
|
1130
|
+
|
|
1131
|
+
/**
|
|
1132
|
+
* Generated bundle index. Do not edit.
|
|
1133
|
+
*/
|
|
1134
|
+
|
|
1135
|
+
export { SPARK_AUTH_CONFIG, SPARK_AUTH_ROUTE_PATHS, SparkAuthBarComponent, SparkAuthService, SparkForgotPasswordComponent, SparkLoginComponent, SparkRegisterComponent, SparkResetPasswordComponent, SparkTwoFactorComponent, defaultSparkAuthConfig, provideSparkAuth, sparkAuthGuard, sparkAuthInterceptor, sparkAuthRoutes, withSparkAuth };
|
|
1136
|
+
//# sourceMappingURL=mintplayer-ng-spark-auth.mjs.map
|