@pheme-ai/auth 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +67 -0
- package/fesm2022/pheme-ai-auth-pages-login.mjs +129 -0
- package/fesm2022/pheme-ai-auth-pages-login.mjs.map +1 -0
- package/fesm2022/pheme-ai-auth.mjs +156 -0
- package/fesm2022/pheme-ai-auth.mjs.map +1 -0
- package/package.json +53 -0
- package/types/pheme-ai-auth-pages-login.d.ts +23 -0
- package/types/pheme-ai-auth.d.ts +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pheme contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# @pheme-ai/auth
|
|
2
|
+
|
|
3
|
+
Session authentication utilities for Pheme prototypes.
|
|
4
|
+
|
|
5
|
+
The package provides a small Angular auth layer for demo and prototype chat applications that use cookie-backed sessions and XSRF-protected state-changing requests.
|
|
6
|
+
|
|
7
|
+
## Exports
|
|
8
|
+
|
|
9
|
+
Primary entrypoint `@pheme-ai/auth`:
|
|
10
|
+
|
|
11
|
+
- `provideSessionAuth` and `SESSION_AUTH_CONFIG` for configuring auth endpoints and routes.
|
|
12
|
+
- `SessionAuthService` for login, logout, and local authenticated state.
|
|
13
|
+
- `sessionAuthGuard` for protecting routes.
|
|
14
|
+
- `sessionAuthInterceptor` for adding XSRF headers to state-changing requests.
|
|
15
|
+
|
|
16
|
+
Secondary entrypoint `@pheme-ai/auth/pages/login`:
|
|
17
|
+
|
|
18
|
+
- `LoginPage` for a ready-to-use Material login screen.
|
|
19
|
+
|
|
20
|
+
## API Expectations
|
|
21
|
+
|
|
22
|
+
The default configuration uses `/api` as the base URL and expects these endpoints:
|
|
23
|
+
|
|
24
|
+
- `GET /csrf` to issue or refresh the `XSRF-TOKEN` cookie.
|
|
25
|
+
- `POST /login` to authenticate and create a session cookie.
|
|
26
|
+
- `POST /logout` to end the current session.
|
|
27
|
+
|
|
28
|
+
The interceptor reads the `XSRF-TOKEN` cookie and sends it as `X-XSRF-TOKEN` on mutating API requests.
|
|
29
|
+
|
|
30
|
+
## Configuration
|
|
31
|
+
|
|
32
|
+
Register the package in the consuming application configuration:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { provideHttpClient, withInterceptors } from '@angular/common/http';
|
|
36
|
+
import { provideSessionAuth, sessionAuthInterceptor } from '@pheme-ai/auth';
|
|
37
|
+
|
|
38
|
+
export const appConfig = {
|
|
39
|
+
providers: [
|
|
40
|
+
provideSessionAuth({ apiBaseUrl: '/api' }),
|
|
41
|
+
provideHttpClient(withInterceptors([sessionAuthInterceptor])),
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Available configuration fields:
|
|
47
|
+
|
|
48
|
+
- `apiBaseUrl` - base URL for auth API requests.
|
|
49
|
+
- `brandLogoUrl` - optional logo URL displayed on the login page.
|
|
50
|
+
- `brandName` - optional brand name displayed on the login page.
|
|
51
|
+
- `csrfPath` - path used to request an XSRF cookie.
|
|
52
|
+
- `loginPath` - path used to submit credentials.
|
|
53
|
+
- `loginRoute` - router commands used when redirecting unauthenticated users.
|
|
54
|
+
- `storageKey` - local storage key used for the lightweight authenticated flag.
|
|
55
|
+
|
|
56
|
+
## Build And Test
|
|
57
|
+
|
|
58
|
+
From the workspace root:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
ng build @pheme-ai/auth
|
|
62
|
+
ng test @pheme-ai/auth
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Status
|
|
66
|
+
|
|
67
|
+
This package is intentionally minimal. It is useful for chat prototypes that need realistic route protection and request behavior without bringing in a full identity provider integration.
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { HttpClient, HttpParams, HttpErrorResponse } from '@angular/common/http';
|
|
2
|
+
import { NgOptimizedImage } from '@angular/common';
|
|
3
|
+
import * as i0 from '@angular/core';
|
|
4
|
+
import { InjectionToken, makeEnvironmentProviders, inject, signal, Injectable, Component } from '@angular/core';
|
|
5
|
+
import * as i5 from '@angular/forms';
|
|
6
|
+
import { FormGroup, FormControl, Validators, ReactiveFormsModule } from '@angular/forms';
|
|
7
|
+
import * as i1 from '@angular/material/button';
|
|
8
|
+
import { MatButtonModule } from '@angular/material/button';
|
|
9
|
+
import * as i2 from '@angular/material/card';
|
|
10
|
+
import { MatCardModule } from '@angular/material/card';
|
|
11
|
+
import * as i3 from '@angular/material/form-field';
|
|
12
|
+
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
13
|
+
import * as i4 from '@angular/material/input';
|
|
14
|
+
import { MatInputModule } from '@angular/material/input';
|
|
15
|
+
import { ActivatedRoute, Router } from '@angular/router';
|
|
16
|
+
import { switchMap, tap, map, finalize } from 'rxjs';
|
|
17
|
+
|
|
18
|
+
const defaultSessionAuthConfig = {
|
|
19
|
+
apiBaseUrl: '/api',
|
|
20
|
+
brandLogoUrl: '',
|
|
21
|
+
brandName: '',
|
|
22
|
+
csrfPath: '/csrf',
|
|
23
|
+
loginPath: '/login',
|
|
24
|
+
loginRoute: ['/login'],
|
|
25
|
+
storageKey: 'pheme.authenticated',
|
|
26
|
+
};
|
|
27
|
+
const SESSION_AUTH_CONFIG = new InjectionToken('SESSION_AUTH_CONFIG', {
|
|
28
|
+
providedIn: 'root',
|
|
29
|
+
factory: () => defaultSessionAuthConfig,
|
|
30
|
+
});
|
|
31
|
+
function provideSessionAuth(config = {}) {
|
|
32
|
+
return makeEnvironmentProviders([
|
|
33
|
+
{
|
|
34
|
+
provide: SESSION_AUTH_CONFIG,
|
|
35
|
+
useValue: { ...defaultSessionAuthConfig, ...config },
|
|
36
|
+
},
|
|
37
|
+
]);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
class SessionAuthService {
|
|
41
|
+
http = inject(HttpClient);
|
|
42
|
+
config = inject(SESSION_AUTH_CONFIG);
|
|
43
|
+
isAuthenticated = signal(localStorage.getItem(this.config.storageKey) === 'true', /* @ts-ignore */
|
|
44
|
+
...(ngDevMode ? [{ debugName: "isAuthenticated" }] : /* istanbul ignore next */ []));
|
|
45
|
+
login(credentials) {
|
|
46
|
+
const body = new HttpParams()
|
|
47
|
+
.set('username', credentials.username)
|
|
48
|
+
.set('password', credentials.password);
|
|
49
|
+
return this.http.get(`${this.config.apiBaseUrl}${this.config.csrfPath}`).pipe(switchMap(() => this.http.post(`${this.config.apiBaseUrl}${this.config.loginPath}`, body)), tap(() => this.setAuthenticated(true)), map(() => undefined));
|
|
50
|
+
}
|
|
51
|
+
clearSession() {
|
|
52
|
+
this.setAuthenticated(false);
|
|
53
|
+
}
|
|
54
|
+
setAuthenticated(authenticated) {
|
|
55
|
+
this.isAuthenticated.set(authenticated);
|
|
56
|
+
if (authenticated) {
|
|
57
|
+
localStorage.setItem(this.config.storageKey, 'true');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
localStorage.removeItem(this.config.storageKey);
|
|
61
|
+
}
|
|
62
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SessionAuthService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
63
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SessionAuthService, providedIn: 'root' });
|
|
64
|
+
}
|
|
65
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SessionAuthService, decorators: [{
|
|
66
|
+
type: Injectable,
|
|
67
|
+
args: [{ providedIn: 'root' }]
|
|
68
|
+
}] });
|
|
69
|
+
|
|
70
|
+
class LoginPage {
|
|
71
|
+
authConfig = inject(SESSION_AUTH_CONFIG);
|
|
72
|
+
authService = inject(SessionAuthService);
|
|
73
|
+
route = inject(ActivatedRoute);
|
|
74
|
+
router = inject(Router);
|
|
75
|
+
brandLogoUrl = this.authConfig.brandLogoUrl;
|
|
76
|
+
brandName = this.authConfig.brandName;
|
|
77
|
+
form = new FormGroup({
|
|
78
|
+
username: new FormControl('', { nonNullable: true, validators: [Validators.required] }),
|
|
79
|
+
password: new FormControl('', { nonNullable: true, validators: [Validators.required] }),
|
|
80
|
+
});
|
|
81
|
+
isSubmitting = signal(false, /* @ts-ignore */
|
|
82
|
+
...(ngDevMode ? [{ debugName: "isSubmitting" }] : /* istanbul ignore next */ []));
|
|
83
|
+
errorMessage = signal('', /* @ts-ignore */
|
|
84
|
+
...(ngDevMode ? [{ debugName: "errorMessage" }] : /* istanbul ignore next */ []));
|
|
85
|
+
submit() {
|
|
86
|
+
if (this.form.invalid || this.isSubmitting()) {
|
|
87
|
+
this.form.markAllAsTouched();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
this.errorMessage.set('');
|
|
91
|
+
this.isSubmitting.set(true);
|
|
92
|
+
this.authService
|
|
93
|
+
.login(this.form.getRawValue())
|
|
94
|
+
.pipe(finalize(() => this.isSubmitting.set(false)))
|
|
95
|
+
.subscribe({
|
|
96
|
+
next: () => {
|
|
97
|
+
const returnUrl = this.route.snapshot.queryParamMap.get('returnUrl') ?? '/';
|
|
98
|
+
void this.router.navigateByUrl(returnUrl);
|
|
99
|
+
},
|
|
100
|
+
error: (error) => this.errorMessage.set(this.describeError(error)),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
describeError(error) {
|
|
104
|
+
if (error instanceof HttpErrorResponse && (error.status === 401 || error.status === 403)) {
|
|
105
|
+
return 'Invalid username or password.';
|
|
106
|
+
}
|
|
107
|
+
return 'Could not sign in. Check your connection and try again.';
|
|
108
|
+
}
|
|
109
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LoginPage, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
110
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: LoginPage, isStandalone: true, selector: "ph-login-page", ngImport: i0, template: "<main class=\"login-page\" aria-labelledby=\"login-title\">\n <mat-card appearance=\"outlined\" class=\"login-card\">\n @if (brandLogoUrl || brandName) {\n <div class=\"brand\" aria-label=\"Application\">\n @if (brandLogoUrl) {\n <img [ngSrc]=\"brandLogoUrl\" width=\"40\" height=\"48\" alt=\"\" priority />\n }\n @if (brandName) {\n <span>{{ brandName }}</span>\n }\n </div>\n }\n <mat-card-header>\n <mat-card-title id=\"login-title\">Sign in</mat-card-title>\n <mat-card-subtitle>Sign in to open your chats.</mat-card-subtitle>\n </mat-card-header>\n\n <mat-card-content>\n <form class=\"login-form\" [formGroup]=\"form\" (ngSubmit)=\"submit()\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Username</mat-label>\n <input matInput formControlName=\"username\" autocomplete=\"username\" required />\n @if (form.controls.username.hasError('required') && form.controls.username.touched) {\n <mat-error>Enter your username</mat-error>\n }\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\">\n <mat-label>Password</mat-label>\n <input\n matInput\n type=\"password\"\n formControlName=\"password\"\n autocomplete=\"current-password\"\n required\n />\n @if (form.controls.password.hasError('required') && form.controls.password.touched) {\n <mat-error>Enter your password</mat-error>\n }\n </mat-form-field>\n\n @if (errorMessage()) {\n <p class=\"login-error\" role=\"alert\">{{ errorMessage() }}</p>\n }\n\n <button mat-flat-button type=\"submit\" [disabled]=\"form.invalid || isSubmitting()\">\n {{ isSubmitting() ? 'Signing in...' : 'Sign in' }}\n </button>\n </form>\n </mat-card-content>\n </mat-card>\n</main>\n", styles: [".login-page{align-items:center;background:radial-gradient(circle at top left,rgba(103,80,164,.18),transparent 32rem),var(--mat-sys-surface);display:grid;min-height:100dvh;padding:24px}.login-card{justify-self:center;max-width:420px;padding-top:8px;width:100%}.brand{align-items:center;display:flex;font:var(--mat-sys-headline-small);gap:12px;padding:16px 16px 8px}.brand img{display:block}.login-form{display:grid;gap:16px;padding-top:16px}.login-error{color:var(--mat-sys-error);margin:0}button{justify-self:start}@media(max-width:520px){.login-page{align-items:start;padding:16px}button{width:100%}}\n"], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i2.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i2.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: i2.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i2.MatCardSubtitle, selector: "mat-card-subtitle, [mat-card-subtitle], [matCardSubtitle]" }, { kind: "directive", type: i2.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "directive", type: NgOptimizedImage, selector: "img[ngSrc]", inputs: ["ngSrc", "ngSrcset", "sizes", "width", "height", "decoding", "loading", "priority", "loaderParams", "disableOptimizedSrcset", "fill", "placeholder", "placeholderConfig", "src", "srcset"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i5.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i5.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: i5.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i5.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i5.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i5.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i5.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }] });
|
|
111
|
+
}
|
|
112
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LoginPage, decorators: [{
|
|
113
|
+
type: Component,
|
|
114
|
+
args: [{ selector: 'ph-login-page', imports: [
|
|
115
|
+
MatButtonModule,
|
|
116
|
+
MatCardModule,
|
|
117
|
+
MatFormFieldModule,
|
|
118
|
+
MatInputModule,
|
|
119
|
+
NgOptimizedImage,
|
|
120
|
+
ReactiveFormsModule,
|
|
121
|
+
], template: "<main class=\"login-page\" aria-labelledby=\"login-title\">\n <mat-card appearance=\"outlined\" class=\"login-card\">\n @if (brandLogoUrl || brandName) {\n <div class=\"brand\" aria-label=\"Application\">\n @if (brandLogoUrl) {\n <img [ngSrc]=\"brandLogoUrl\" width=\"40\" height=\"48\" alt=\"\" priority />\n }\n @if (brandName) {\n <span>{{ brandName }}</span>\n }\n </div>\n }\n <mat-card-header>\n <mat-card-title id=\"login-title\">Sign in</mat-card-title>\n <mat-card-subtitle>Sign in to open your chats.</mat-card-subtitle>\n </mat-card-header>\n\n <mat-card-content>\n <form class=\"login-form\" [formGroup]=\"form\" (ngSubmit)=\"submit()\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Username</mat-label>\n <input matInput formControlName=\"username\" autocomplete=\"username\" required />\n @if (form.controls.username.hasError('required') && form.controls.username.touched) {\n <mat-error>Enter your username</mat-error>\n }\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\">\n <mat-label>Password</mat-label>\n <input\n matInput\n type=\"password\"\n formControlName=\"password\"\n autocomplete=\"current-password\"\n required\n />\n @if (form.controls.password.hasError('required') && form.controls.password.touched) {\n <mat-error>Enter your password</mat-error>\n }\n </mat-form-field>\n\n @if (errorMessage()) {\n <p class=\"login-error\" role=\"alert\">{{ errorMessage() }}</p>\n }\n\n <button mat-flat-button type=\"submit\" [disabled]=\"form.invalid || isSubmitting()\">\n {{ isSubmitting() ? 'Signing in...' : 'Sign in' }}\n </button>\n </form>\n </mat-card-content>\n </mat-card>\n</main>\n", styles: [".login-page{align-items:center;background:radial-gradient(circle at top left,rgba(103,80,164,.18),transparent 32rem),var(--mat-sys-surface);display:grid;min-height:100dvh;padding:24px}.login-card{justify-self:center;max-width:420px;padding-top:8px;width:100%}.brand{align-items:center;display:flex;font:var(--mat-sys-headline-small);gap:12px;padding:16px 16px 8px}.brand img{display:block}.login-form{display:grid;gap:16px;padding-top:16px}.login-error{color:var(--mat-sys-error);margin:0}button{justify-self:start}@media(max-width:520px){.login-page{align-items:start;padding:16px}button{width:100%}}\n"] }]
|
|
122
|
+
}] });
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Generated bundle index. Do not edit.
|
|
126
|
+
*/
|
|
127
|
+
|
|
128
|
+
export { LoginPage };
|
|
129
|
+
//# sourceMappingURL=pheme-ai-auth-pages-login.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pheme-ai-auth-pages-login.mjs","sources":["../../../../projects/pheme/auth/src/lib/auth-config.ts","../../../../projects/pheme/auth/src/lib/services/session-auth-service.ts","../../../../projects/pheme/auth/src/lib/pages/login-page/login-page.ts","../../../../projects/pheme/auth/src/lib/pages/login-page/login-page.html","../../../../projects/pheme/auth/src/pheme-ai-auth-pages-login.ts"],"sourcesContent":["import { makeEnvironmentProviders, InjectionToken, type EnvironmentProviders } from '@angular/core';\n\nexport interface SessionAuthConfig {\n apiBaseUrl: string;\n brandLogoUrl: string;\n brandName: string;\n csrfPath: string;\n loginPath: string;\n loginRoute: readonly string[];\n storageKey: string;\n}\n\nconst defaultSessionAuthConfig: SessionAuthConfig = {\n apiBaseUrl: '/api',\n brandLogoUrl: '',\n brandName: '',\n csrfPath: '/csrf',\n loginPath: '/login',\n loginRoute: ['/login'],\n storageKey: 'pheme.authenticated',\n};\n\nexport const SESSION_AUTH_CONFIG = new InjectionToken<SessionAuthConfig>('SESSION_AUTH_CONFIG', {\n providedIn: 'root',\n factory: () => defaultSessionAuthConfig,\n});\n\nexport function provideSessionAuth(config: Partial<SessionAuthConfig> = {}): EnvironmentProviders {\n return makeEnvironmentProviders([\n {\n provide: SESSION_AUTH_CONFIG,\n useValue: { ...defaultSessionAuthConfig, ...config },\n },\n ]);\n}\n","import { HttpClient, HttpParams } from '@angular/common/http';\nimport { inject, Injectable, signal } from '@angular/core';\nimport { map, Observable, switchMap, tap } from 'rxjs';\nimport { SESSION_AUTH_CONFIG } from '../auth-config';\n\nexport interface LoginCredentials {\n username: string;\n password: string;\n}\n\ninterface StatusResponse {\n code: string;\n message: string;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class SessionAuthService {\n private readonly http = inject(HttpClient);\n private readonly config = inject(SESSION_AUTH_CONFIG);\n\n readonly isAuthenticated = signal(localStorage.getItem(this.config.storageKey) === 'true');\n\n login(credentials: LoginCredentials): Observable<void> {\n const body = new HttpParams()\n .set('username', credentials.username)\n .set('password', credentials.password);\n\n return this.http.get<void>(`${this.config.apiBaseUrl}${this.config.csrfPath}`).pipe(\n switchMap(() =>\n this.http.post<StatusResponse>(`${this.config.apiBaseUrl}${this.config.loginPath}`, body),\n ),\n tap(() => this.setAuthenticated(true)),\n map(() => undefined),\n );\n }\n\n clearSession(): void {\n this.setAuthenticated(false);\n }\n\n private setAuthenticated(authenticated: boolean): void {\n this.isAuthenticated.set(authenticated);\n\n if (authenticated) {\n localStorage.setItem(this.config.storageKey, 'true');\n return;\n }\n\n localStorage.removeItem(this.config.storageKey);\n }\n}\n","import { HttpErrorResponse } from '@angular/common/http';\nimport { NgOptimizedImage } from '@angular/common';\nimport { Component, inject, signal } from '@angular/core';\nimport { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatInputModule } from '@angular/material/input';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { finalize } from 'rxjs';\nimport { SESSION_AUTH_CONFIG } from '../../auth-config';\nimport { SessionAuthService } from '../../services/session-auth-service';\n\n@Component({\n selector: 'ph-login-page',\n imports: [\n MatButtonModule,\n MatCardModule,\n MatFormFieldModule,\n MatInputModule,\n NgOptimizedImage,\n ReactiveFormsModule,\n ],\n templateUrl: './login-page.html',\n styleUrl: './login-page.scss',\n})\nexport class LoginPage {\n private readonly authConfig = inject(SESSION_AUTH_CONFIG);\n private readonly authService = inject(SessionAuthService);\n private readonly route = inject(ActivatedRoute);\n private readonly router = inject(Router);\n\n protected readonly brandLogoUrl = this.authConfig.brandLogoUrl;\n protected readonly brandName = this.authConfig.brandName;\n\n protected readonly form = new FormGroup({\n username: new FormControl('', { nonNullable: true, validators: [Validators.required] }),\n password: new FormControl('', { nonNullable: true, validators: [Validators.required] }),\n });\n protected readonly isSubmitting = signal(false);\n protected readonly errorMessage = signal('');\n\n protected submit(): void {\n if (this.form.invalid || this.isSubmitting()) {\n this.form.markAllAsTouched();\n return;\n }\n\n this.errorMessage.set('');\n this.isSubmitting.set(true);\n\n this.authService\n .login(this.form.getRawValue())\n .pipe(finalize(() => this.isSubmitting.set(false)))\n .subscribe({\n next: () => {\n const returnUrl = this.route.snapshot.queryParamMap.get('returnUrl') ?? '/';\n void this.router.navigateByUrl(returnUrl);\n },\n error: (error: unknown) => this.errorMessage.set(this.describeError(error)),\n });\n }\n\n private describeError(error: unknown): string {\n if (error instanceof HttpErrorResponse && (error.status === 401 || error.status === 403)) {\n return 'Invalid username or password.';\n }\n\n return 'Could not sign in. Check your connection and try again.';\n }\n}\n","<main class=\"login-page\" aria-labelledby=\"login-title\">\n <mat-card appearance=\"outlined\" class=\"login-card\">\n @if (brandLogoUrl || brandName) {\n <div class=\"brand\" aria-label=\"Application\">\n @if (brandLogoUrl) {\n <img [ngSrc]=\"brandLogoUrl\" width=\"40\" height=\"48\" alt=\"\" priority />\n }\n @if (brandName) {\n <span>{{ brandName }}</span>\n }\n </div>\n }\n <mat-card-header>\n <mat-card-title id=\"login-title\">Sign in</mat-card-title>\n <mat-card-subtitle>Sign in to open your chats.</mat-card-subtitle>\n </mat-card-header>\n\n <mat-card-content>\n <form class=\"login-form\" [formGroup]=\"form\" (ngSubmit)=\"submit()\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Username</mat-label>\n <input matInput formControlName=\"username\" autocomplete=\"username\" required />\n @if (form.controls.username.hasError('required') && form.controls.username.touched) {\n <mat-error>Enter your username</mat-error>\n }\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\">\n <mat-label>Password</mat-label>\n <input\n matInput\n type=\"password\"\n formControlName=\"password\"\n autocomplete=\"current-password\"\n required\n />\n @if (form.controls.password.hasError('required') && form.controls.password.touched) {\n <mat-error>Enter your password</mat-error>\n }\n </mat-form-field>\n\n @if (errorMessage()) {\n <p class=\"login-error\" role=\"alert\">{{ errorMessage() }}</p>\n }\n\n <button mat-flat-button type=\"submit\" [disabled]=\"form.invalid || isSubmitting()\">\n {{ isSubmitting() ? 'Signing in...' : 'Sign in' }}\n </button>\n </form>\n </mat-card-content>\n </mat-card>\n</main>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './login-page-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, makeEnvironmentProviders, inject, signal, Injectable, Component } from '@angular/core';
|
|
3
|
+
import { HttpClient, HttpParams, HttpErrorResponse } from '@angular/common/http';
|
|
4
|
+
import { NgOptimizedImage } from '@angular/common';
|
|
5
|
+
import * as i5 from '@angular/forms';
|
|
6
|
+
import { FormGroup, FormControl, Validators, ReactiveFormsModule } from '@angular/forms';
|
|
7
|
+
import * as i1 from '@angular/material/button';
|
|
8
|
+
import { MatButtonModule } from '@angular/material/button';
|
|
9
|
+
import * as i2 from '@angular/material/card';
|
|
10
|
+
import { MatCardModule } from '@angular/material/card';
|
|
11
|
+
import * as i3 from '@angular/material/form-field';
|
|
12
|
+
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
13
|
+
import * as i4 from '@angular/material/input';
|
|
14
|
+
import { MatInputModule } from '@angular/material/input';
|
|
15
|
+
import { ActivatedRoute, Router } from '@angular/router';
|
|
16
|
+
import { switchMap, tap, map, finalize, catchError, throwError } from 'rxjs';
|
|
17
|
+
|
|
18
|
+
const defaultSessionAuthConfig = {
|
|
19
|
+
apiBaseUrl: '/api',
|
|
20
|
+
brandLogoUrl: '',
|
|
21
|
+
brandName: '',
|
|
22
|
+
csrfPath: '/csrf',
|
|
23
|
+
loginPath: '/login',
|
|
24
|
+
loginRoute: ['/login'],
|
|
25
|
+
storageKey: 'pheme.authenticated',
|
|
26
|
+
};
|
|
27
|
+
const SESSION_AUTH_CONFIG = new InjectionToken('SESSION_AUTH_CONFIG', {
|
|
28
|
+
providedIn: 'root',
|
|
29
|
+
factory: () => defaultSessionAuthConfig,
|
|
30
|
+
});
|
|
31
|
+
function provideSessionAuth(config = {}) {
|
|
32
|
+
return makeEnvironmentProviders([
|
|
33
|
+
{
|
|
34
|
+
provide: SESSION_AUTH_CONFIG,
|
|
35
|
+
useValue: { ...defaultSessionAuthConfig, ...config },
|
|
36
|
+
},
|
|
37
|
+
]);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
class SessionAuthService {
|
|
41
|
+
http = inject(HttpClient);
|
|
42
|
+
config = inject(SESSION_AUTH_CONFIG);
|
|
43
|
+
isAuthenticated = signal(localStorage.getItem(this.config.storageKey) === 'true', /* @ts-ignore */
|
|
44
|
+
...(ngDevMode ? [{ debugName: "isAuthenticated" }] : /* istanbul ignore next */ []));
|
|
45
|
+
login(credentials) {
|
|
46
|
+
const body = new HttpParams()
|
|
47
|
+
.set('username', credentials.username)
|
|
48
|
+
.set('password', credentials.password);
|
|
49
|
+
return this.http.get(`${this.config.apiBaseUrl}${this.config.csrfPath}`).pipe(switchMap(() => this.http.post(`${this.config.apiBaseUrl}${this.config.loginPath}`, body)), tap(() => this.setAuthenticated(true)), map(() => undefined));
|
|
50
|
+
}
|
|
51
|
+
clearSession() {
|
|
52
|
+
this.setAuthenticated(false);
|
|
53
|
+
}
|
|
54
|
+
setAuthenticated(authenticated) {
|
|
55
|
+
this.isAuthenticated.set(authenticated);
|
|
56
|
+
if (authenticated) {
|
|
57
|
+
localStorage.setItem(this.config.storageKey, 'true');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
localStorage.removeItem(this.config.storageKey);
|
|
61
|
+
}
|
|
62
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SessionAuthService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
63
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SessionAuthService, providedIn: 'root' });
|
|
64
|
+
}
|
|
65
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SessionAuthService, decorators: [{
|
|
66
|
+
type: Injectable,
|
|
67
|
+
args: [{ providedIn: 'root' }]
|
|
68
|
+
}] });
|
|
69
|
+
|
|
70
|
+
class LoginPage {
|
|
71
|
+
authConfig = inject(SESSION_AUTH_CONFIG);
|
|
72
|
+
authService = inject(SessionAuthService);
|
|
73
|
+
route = inject(ActivatedRoute);
|
|
74
|
+
router = inject(Router);
|
|
75
|
+
brandLogoUrl = this.authConfig.brandLogoUrl;
|
|
76
|
+
brandName = this.authConfig.brandName;
|
|
77
|
+
form = new FormGroup({
|
|
78
|
+
username: new FormControl('', { nonNullable: true, validators: [Validators.required] }),
|
|
79
|
+
password: new FormControl('', { nonNullable: true, validators: [Validators.required] }),
|
|
80
|
+
});
|
|
81
|
+
isSubmitting = signal(false, /* @ts-ignore */
|
|
82
|
+
...(ngDevMode ? [{ debugName: "isSubmitting" }] : /* istanbul ignore next */ []));
|
|
83
|
+
errorMessage = signal('', /* @ts-ignore */
|
|
84
|
+
...(ngDevMode ? [{ debugName: "errorMessage" }] : /* istanbul ignore next */ []));
|
|
85
|
+
submit() {
|
|
86
|
+
if (this.form.invalid || this.isSubmitting()) {
|
|
87
|
+
this.form.markAllAsTouched();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
this.errorMessage.set('');
|
|
91
|
+
this.isSubmitting.set(true);
|
|
92
|
+
this.authService
|
|
93
|
+
.login(this.form.getRawValue())
|
|
94
|
+
.pipe(finalize(() => this.isSubmitting.set(false)))
|
|
95
|
+
.subscribe({
|
|
96
|
+
next: () => {
|
|
97
|
+
const returnUrl = this.route.snapshot.queryParamMap.get('returnUrl') ?? '/';
|
|
98
|
+
void this.router.navigateByUrl(returnUrl);
|
|
99
|
+
},
|
|
100
|
+
error: (error) => this.errorMessage.set(this.describeError(error)),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
describeError(error) {
|
|
104
|
+
if (error instanceof HttpErrorResponse && (error.status === 401 || error.status === 403)) {
|
|
105
|
+
return 'Invalid username or password.';
|
|
106
|
+
}
|
|
107
|
+
return 'Could not sign in. Check your connection and try again.';
|
|
108
|
+
}
|
|
109
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LoginPage, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
110
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: LoginPage, isStandalone: true, selector: "ph-login-page", ngImport: i0, template: "<main class=\"login-page\" aria-labelledby=\"login-title\">\n <mat-card appearance=\"outlined\" class=\"login-card\">\n @if (brandLogoUrl || brandName) {\n <div class=\"brand\" aria-label=\"Application\">\n @if (brandLogoUrl) {\n <img [ngSrc]=\"brandLogoUrl\" width=\"40\" height=\"48\" alt=\"\" priority />\n }\n @if (brandName) {\n <span>{{ brandName }}</span>\n }\n </div>\n }\n <mat-card-header>\n <mat-card-title id=\"login-title\">Sign in</mat-card-title>\n <mat-card-subtitle>Sign in to open your chats.</mat-card-subtitle>\n </mat-card-header>\n\n <mat-card-content>\n <form class=\"login-form\" [formGroup]=\"form\" (ngSubmit)=\"submit()\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Username</mat-label>\n <input matInput formControlName=\"username\" autocomplete=\"username\" required />\n @if (form.controls.username.hasError('required') && form.controls.username.touched) {\n <mat-error>Enter your username</mat-error>\n }\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\">\n <mat-label>Password</mat-label>\n <input\n matInput\n type=\"password\"\n formControlName=\"password\"\n autocomplete=\"current-password\"\n required\n />\n @if (form.controls.password.hasError('required') && form.controls.password.touched) {\n <mat-error>Enter your password</mat-error>\n }\n </mat-form-field>\n\n @if (errorMessage()) {\n <p class=\"login-error\" role=\"alert\">{{ errorMessage() }}</p>\n }\n\n <button mat-flat-button type=\"submit\" [disabled]=\"form.invalid || isSubmitting()\">\n {{ isSubmitting() ? 'Signing in...' : 'Sign in' }}\n </button>\n </form>\n </mat-card-content>\n </mat-card>\n</main>\n", styles: [".login-page{align-items:center;background:radial-gradient(circle at top left,rgba(103,80,164,.18),transparent 32rem),var(--mat-sys-surface);display:grid;min-height:100dvh;padding:24px}.login-card{justify-self:center;max-width:420px;padding-top:8px;width:100%}.brand{align-items:center;display:flex;font:var(--mat-sys-headline-small);gap:12px;padding:16px 16px 8px}.brand img{display:block}.login-form{display:grid;gap:16px;padding-top:16px}.login-error{color:var(--mat-sys-error);margin:0}button{justify-self:start}@media(max-width:520px){.login-page{align-items:start;padding:16px}button{width:100%}}\n"], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i2.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i2.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: i2.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i2.MatCardSubtitle, selector: "mat-card-subtitle, [mat-card-subtitle], [matCardSubtitle]" }, { kind: "directive", type: i2.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "directive", type: NgOptimizedImage, selector: "img[ngSrc]", inputs: ["ngSrc", "ngSrcset", "sizes", "width", "height", "decoding", "loading", "priority", "loaderParams", "disableOptimizedSrcset", "fill", "placeholder", "placeholderConfig", "src", "srcset"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i5.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i5.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: i5.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i5.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i5.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i5.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i5.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }] });
|
|
111
|
+
}
|
|
112
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LoginPage, decorators: [{
|
|
113
|
+
type: Component,
|
|
114
|
+
args: [{ selector: 'ph-login-page', imports: [
|
|
115
|
+
MatButtonModule,
|
|
116
|
+
MatCardModule,
|
|
117
|
+
MatFormFieldModule,
|
|
118
|
+
MatInputModule,
|
|
119
|
+
NgOptimizedImage,
|
|
120
|
+
ReactiveFormsModule,
|
|
121
|
+
], template: "<main class=\"login-page\" aria-labelledby=\"login-title\">\n <mat-card appearance=\"outlined\" class=\"login-card\">\n @if (brandLogoUrl || brandName) {\n <div class=\"brand\" aria-label=\"Application\">\n @if (brandLogoUrl) {\n <img [ngSrc]=\"brandLogoUrl\" width=\"40\" height=\"48\" alt=\"\" priority />\n }\n @if (brandName) {\n <span>{{ brandName }}</span>\n }\n </div>\n }\n <mat-card-header>\n <mat-card-title id=\"login-title\">Sign in</mat-card-title>\n <mat-card-subtitle>Sign in to open your chats.</mat-card-subtitle>\n </mat-card-header>\n\n <mat-card-content>\n <form class=\"login-form\" [formGroup]=\"form\" (ngSubmit)=\"submit()\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Username</mat-label>\n <input matInput formControlName=\"username\" autocomplete=\"username\" required />\n @if (form.controls.username.hasError('required') && form.controls.username.touched) {\n <mat-error>Enter your username</mat-error>\n }\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\">\n <mat-label>Password</mat-label>\n <input\n matInput\n type=\"password\"\n formControlName=\"password\"\n autocomplete=\"current-password\"\n required\n />\n @if (form.controls.password.hasError('required') && form.controls.password.touched) {\n <mat-error>Enter your password</mat-error>\n }\n </mat-form-field>\n\n @if (errorMessage()) {\n <p class=\"login-error\" role=\"alert\">{{ errorMessage() }}</p>\n }\n\n <button mat-flat-button type=\"submit\" [disabled]=\"form.invalid || isSubmitting()\">\n {{ isSubmitting() ? 'Signing in...' : 'Sign in' }}\n </button>\n </form>\n </mat-card-content>\n </mat-card>\n</main>\n", styles: [".login-page{align-items:center;background:radial-gradient(circle at top left,rgba(103,80,164,.18),transparent 32rem),var(--mat-sys-surface);display:grid;min-height:100dvh;padding:24px}.login-card{justify-self:center;max-width:420px;padding-top:8px;width:100%}.brand{align-items:center;display:flex;font:var(--mat-sys-headline-small);gap:12px;padding:16px 16px 8px}.brand img{display:block}.login-form{display:grid;gap:16px;padding-top:16px}.login-error{color:var(--mat-sys-error);margin:0}button{justify-self:start}@media(max-width:520px){.login-page{align-items:start;padding:16px}button{width:100%}}\n"] }]
|
|
122
|
+
}] });
|
|
123
|
+
|
|
124
|
+
const sessionAuthGuard = (_route, state) => {
|
|
125
|
+
const authService = inject(SessionAuthService);
|
|
126
|
+
if (authService.isAuthenticated()) {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
const config = inject(SESSION_AUTH_CONFIG);
|
|
130
|
+
return inject(Router).createUrlTree(config.loginRoute, {
|
|
131
|
+
queryParams: { returnUrl: state.url },
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const sessionAuthInterceptor = (request, next) => {
|
|
136
|
+
const authService = inject(SessionAuthService);
|
|
137
|
+
const config = inject(SESSION_AUTH_CONFIG);
|
|
138
|
+
const router = inject(Router);
|
|
139
|
+
return next(request).pipe(catchError((error) => {
|
|
140
|
+
if (error instanceof HttpErrorResponse &&
|
|
141
|
+
error.status === 401 &&
|
|
142
|
+
request.url.startsWith(`${config.apiBaseUrl}/`) &&
|
|
143
|
+
!request.url.endsWith(config.loginPath)) {
|
|
144
|
+
authService.clearSession();
|
|
145
|
+
void router.navigate(config.loginRoute);
|
|
146
|
+
}
|
|
147
|
+
return throwError(() => error);
|
|
148
|
+
}));
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Generated bundle index. Do not edit.
|
|
153
|
+
*/
|
|
154
|
+
|
|
155
|
+
export { LoginPage, SESSION_AUTH_CONFIG, SessionAuthService, provideSessionAuth, sessionAuthGuard, sessionAuthInterceptor };
|
|
156
|
+
//# sourceMappingURL=pheme-ai-auth.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pheme-ai-auth.mjs","sources":["../../../../projects/pheme/auth/src/lib/auth-config.ts","../../../../projects/pheme/auth/src/lib/services/session-auth-service.ts","../../../../projects/pheme/auth/src/lib/pages/login-page/login-page.ts","../../../../projects/pheme/auth/src/lib/pages/login-page/login-page.html","../../../../projects/pheme/auth/src/lib/services/session-auth-guard.ts","../../../../projects/pheme/auth/src/lib/services/session-auth-interceptor.ts","../../../../projects/pheme/auth/src/pheme-ai-auth.ts"],"sourcesContent":["import { makeEnvironmentProviders, InjectionToken, type EnvironmentProviders } from '@angular/core';\n\nexport interface SessionAuthConfig {\n apiBaseUrl: string;\n brandLogoUrl: string;\n brandName: string;\n csrfPath: string;\n loginPath: string;\n loginRoute: readonly string[];\n storageKey: string;\n}\n\nconst defaultSessionAuthConfig: SessionAuthConfig = {\n apiBaseUrl: '/api',\n brandLogoUrl: '',\n brandName: '',\n csrfPath: '/csrf',\n loginPath: '/login',\n loginRoute: ['/login'],\n storageKey: 'pheme.authenticated',\n};\n\nexport const SESSION_AUTH_CONFIG = new InjectionToken<SessionAuthConfig>('SESSION_AUTH_CONFIG', {\n providedIn: 'root',\n factory: () => defaultSessionAuthConfig,\n});\n\nexport function provideSessionAuth(config: Partial<SessionAuthConfig> = {}): EnvironmentProviders {\n return makeEnvironmentProviders([\n {\n provide: SESSION_AUTH_CONFIG,\n useValue: { ...defaultSessionAuthConfig, ...config },\n },\n ]);\n}\n","import { HttpClient, HttpParams } from '@angular/common/http';\nimport { inject, Injectable, signal } from '@angular/core';\nimport { map, Observable, switchMap, tap } from 'rxjs';\nimport { SESSION_AUTH_CONFIG } from '../auth-config';\n\nexport interface LoginCredentials {\n username: string;\n password: string;\n}\n\ninterface StatusResponse {\n code: string;\n message: string;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class SessionAuthService {\n private readonly http = inject(HttpClient);\n private readonly config = inject(SESSION_AUTH_CONFIG);\n\n readonly isAuthenticated = signal(localStorage.getItem(this.config.storageKey) === 'true');\n\n login(credentials: LoginCredentials): Observable<void> {\n const body = new HttpParams()\n .set('username', credentials.username)\n .set('password', credentials.password);\n\n return this.http.get<void>(`${this.config.apiBaseUrl}${this.config.csrfPath}`).pipe(\n switchMap(() =>\n this.http.post<StatusResponse>(`${this.config.apiBaseUrl}${this.config.loginPath}`, body),\n ),\n tap(() => this.setAuthenticated(true)),\n map(() => undefined),\n );\n }\n\n clearSession(): void {\n this.setAuthenticated(false);\n }\n\n private setAuthenticated(authenticated: boolean): void {\n this.isAuthenticated.set(authenticated);\n\n if (authenticated) {\n localStorage.setItem(this.config.storageKey, 'true');\n return;\n }\n\n localStorage.removeItem(this.config.storageKey);\n }\n}\n","import { HttpErrorResponse } from '@angular/common/http';\nimport { NgOptimizedImage } from '@angular/common';\nimport { Component, inject, signal } from '@angular/core';\nimport { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatInputModule } from '@angular/material/input';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { finalize } from 'rxjs';\nimport { SESSION_AUTH_CONFIG } from '../../auth-config';\nimport { SessionAuthService } from '../../services/session-auth-service';\n\n@Component({\n selector: 'ph-login-page',\n imports: [\n MatButtonModule,\n MatCardModule,\n MatFormFieldModule,\n MatInputModule,\n NgOptimizedImage,\n ReactiveFormsModule,\n ],\n templateUrl: './login-page.html',\n styleUrl: './login-page.scss',\n})\nexport class LoginPage {\n private readonly authConfig = inject(SESSION_AUTH_CONFIG);\n private readonly authService = inject(SessionAuthService);\n private readonly route = inject(ActivatedRoute);\n private readonly router = inject(Router);\n\n protected readonly brandLogoUrl = this.authConfig.brandLogoUrl;\n protected readonly brandName = this.authConfig.brandName;\n\n protected readonly form = new FormGroup({\n username: new FormControl('', { nonNullable: true, validators: [Validators.required] }),\n password: new FormControl('', { nonNullable: true, validators: [Validators.required] }),\n });\n protected readonly isSubmitting = signal(false);\n protected readonly errorMessage = signal('');\n\n protected submit(): void {\n if (this.form.invalid || this.isSubmitting()) {\n this.form.markAllAsTouched();\n return;\n }\n\n this.errorMessage.set('');\n this.isSubmitting.set(true);\n\n this.authService\n .login(this.form.getRawValue())\n .pipe(finalize(() => this.isSubmitting.set(false)))\n .subscribe({\n next: () => {\n const returnUrl = this.route.snapshot.queryParamMap.get('returnUrl') ?? '/';\n void this.router.navigateByUrl(returnUrl);\n },\n error: (error: unknown) => this.errorMessage.set(this.describeError(error)),\n });\n }\n\n private describeError(error: unknown): string {\n if (error instanceof HttpErrorResponse && (error.status === 401 || error.status === 403)) {\n return 'Invalid username or password.';\n }\n\n return 'Could not sign in. Check your connection and try again.';\n }\n}\n","<main class=\"login-page\" aria-labelledby=\"login-title\">\n <mat-card appearance=\"outlined\" class=\"login-card\">\n @if (brandLogoUrl || brandName) {\n <div class=\"brand\" aria-label=\"Application\">\n @if (brandLogoUrl) {\n <img [ngSrc]=\"brandLogoUrl\" width=\"40\" height=\"48\" alt=\"\" priority />\n }\n @if (brandName) {\n <span>{{ brandName }}</span>\n }\n </div>\n }\n <mat-card-header>\n <mat-card-title id=\"login-title\">Sign in</mat-card-title>\n <mat-card-subtitle>Sign in to open your chats.</mat-card-subtitle>\n </mat-card-header>\n\n <mat-card-content>\n <form class=\"login-form\" [formGroup]=\"form\" (ngSubmit)=\"submit()\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Username</mat-label>\n <input matInput formControlName=\"username\" autocomplete=\"username\" required />\n @if (form.controls.username.hasError('required') && form.controls.username.touched) {\n <mat-error>Enter your username</mat-error>\n }\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\">\n <mat-label>Password</mat-label>\n <input\n matInput\n type=\"password\"\n formControlName=\"password\"\n autocomplete=\"current-password\"\n required\n />\n @if (form.controls.password.hasError('required') && form.controls.password.touched) {\n <mat-error>Enter your password</mat-error>\n }\n </mat-form-field>\n\n @if (errorMessage()) {\n <p class=\"login-error\" role=\"alert\">{{ errorMessage() }}</p>\n }\n\n <button mat-flat-button type=\"submit\" [disabled]=\"form.invalid || isSubmitting()\">\n {{ isSubmitting() ? 'Signing in...' : 'Sign in' }}\n </button>\n </form>\n </mat-card-content>\n </mat-card>\n</main>\n","import { inject } from '@angular/core';\nimport { CanActivateFn, Router } from '@angular/router';\nimport { SESSION_AUTH_CONFIG } from '../auth-config';\nimport { SessionAuthService } from './session-auth-service';\n\nexport const sessionAuthGuard: CanActivateFn = (_route, state) => {\n const authService = inject(SessionAuthService);\n\n if (authService.isAuthenticated()) {\n return true;\n }\n\n const config = inject(SESSION_AUTH_CONFIG);\n\n return inject(Router).createUrlTree(config.loginRoute, {\n queryParams: { returnUrl: state.url },\n });\n};\n","import { HttpErrorResponse, HttpInterceptorFn } from '@angular/common/http';\nimport { inject } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { catchError, throwError } from 'rxjs';\nimport { SESSION_AUTH_CONFIG } from '../auth-config';\nimport { SessionAuthService } from './session-auth-service';\n\nexport const sessionAuthInterceptor: HttpInterceptorFn = (request, next) => {\n const authService = inject(SessionAuthService);\n const config = inject(SESSION_AUTH_CONFIG);\n const router = inject(Router);\n\n return next(request).pipe(\n catchError((error: unknown) => {\n if (\n error instanceof HttpErrorResponse &&\n error.status === 401 &&\n request.url.startsWith(`${config.apiBaseUrl}/`) &&\n !request.url.endsWith(config.loginPath)\n ) {\n authService.clearSession();\n void router.navigate(config.loginRoute);\n }\n\n return throwError(() => error);\n }),\n );\n};\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pheme-ai/auth",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Session authentication utilities for Angular chat application prototypes.",
|
|
5
|
+
"author": "RazorNd",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"angular",
|
|
9
|
+
"authentication",
|
|
10
|
+
"session",
|
|
11
|
+
"xsrf"
|
|
12
|
+
],
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/RazorNd/pheme.git",
|
|
16
|
+
"directory": "projects/pheme/auth"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/RazorNd/pheme#readme",
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/RazorNd/pheme/issues"
|
|
21
|
+
},
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"@angular/common": "^22.0.6",
|
|
27
|
+
"@angular/core": "^22.0.6",
|
|
28
|
+
"@angular/forms": "^22.0.6",
|
|
29
|
+
"@angular/material": "^22.0.4",
|
|
30
|
+
"@angular/router": "^22.0.6",
|
|
31
|
+
"rxjs": "~7.8.2"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"tslib": "^2.8.1"
|
|
35
|
+
},
|
|
36
|
+
"sideEffects": false,
|
|
37
|
+
"module": "fesm2022/pheme-ai-auth.mjs",
|
|
38
|
+
"typings": "types/pheme-ai-auth.d.ts",
|
|
39
|
+
"exports": {
|
|
40
|
+
"./package.json": {
|
|
41
|
+
"default": "./package.json"
|
|
42
|
+
},
|
|
43
|
+
".": {
|
|
44
|
+
"types": "./types/pheme-ai-auth.d.ts",
|
|
45
|
+
"default": "./fesm2022/pheme-ai-auth.mjs"
|
|
46
|
+
},
|
|
47
|
+
"./pages/login": {
|
|
48
|
+
"types": "./types/pheme-ai-auth-pages-login.d.ts",
|
|
49
|
+
"default": "./fesm2022/pheme-ai-auth-pages-login.mjs"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"type": "module"
|
|
53
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as _angular_core from '@angular/core';
|
|
2
|
+
import { FormGroup, FormControl } from '@angular/forms';
|
|
3
|
+
|
|
4
|
+
declare class LoginPage {
|
|
5
|
+
private readonly authConfig;
|
|
6
|
+
private readonly authService;
|
|
7
|
+
private readonly route;
|
|
8
|
+
private readonly router;
|
|
9
|
+
protected readonly brandLogoUrl: string;
|
|
10
|
+
protected readonly brandName: string;
|
|
11
|
+
protected readonly form: FormGroup<{
|
|
12
|
+
username: FormControl<string>;
|
|
13
|
+
password: FormControl<string>;
|
|
14
|
+
}>;
|
|
15
|
+
protected readonly isSubmitting: _angular_core.WritableSignal<boolean>;
|
|
16
|
+
protected readonly errorMessage: _angular_core.WritableSignal<string>;
|
|
17
|
+
protected submit(): void;
|
|
18
|
+
private describeError;
|
|
19
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<LoginPage, never>;
|
|
20
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<LoginPage, "ph-login-page", never, {}, {}, never, never, true, never>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { LoginPage };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, EnvironmentProviders } from '@angular/core';
|
|
3
|
+
import { FormGroup, FormControl } from '@angular/forms';
|
|
4
|
+
import { CanActivateFn } from '@angular/router';
|
|
5
|
+
import { HttpInterceptorFn } from '@angular/common/http';
|
|
6
|
+
import { Observable } from 'rxjs';
|
|
7
|
+
|
|
8
|
+
interface SessionAuthConfig {
|
|
9
|
+
apiBaseUrl: string;
|
|
10
|
+
brandLogoUrl: string;
|
|
11
|
+
brandName: string;
|
|
12
|
+
csrfPath: string;
|
|
13
|
+
loginPath: string;
|
|
14
|
+
loginRoute: readonly string[];
|
|
15
|
+
storageKey: string;
|
|
16
|
+
}
|
|
17
|
+
declare const SESSION_AUTH_CONFIG: InjectionToken<SessionAuthConfig>;
|
|
18
|
+
declare function provideSessionAuth(config?: Partial<SessionAuthConfig>): EnvironmentProviders;
|
|
19
|
+
|
|
20
|
+
declare class LoginPage {
|
|
21
|
+
private readonly authConfig;
|
|
22
|
+
private readonly authService;
|
|
23
|
+
private readonly route;
|
|
24
|
+
private readonly router;
|
|
25
|
+
protected readonly brandLogoUrl: string;
|
|
26
|
+
protected readonly brandName: string;
|
|
27
|
+
protected readonly form: FormGroup<{
|
|
28
|
+
username: FormControl<string>;
|
|
29
|
+
password: FormControl<string>;
|
|
30
|
+
}>;
|
|
31
|
+
protected readonly isSubmitting: i0.WritableSignal<boolean>;
|
|
32
|
+
protected readonly errorMessage: i0.WritableSignal<string>;
|
|
33
|
+
protected submit(): void;
|
|
34
|
+
private describeError;
|
|
35
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<LoginPage, never>;
|
|
36
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<LoginPage, "ph-login-page", never, {}, {}, never, never, true, never>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
declare const sessionAuthGuard: CanActivateFn;
|
|
40
|
+
|
|
41
|
+
declare const sessionAuthInterceptor: HttpInterceptorFn;
|
|
42
|
+
|
|
43
|
+
interface LoginCredentials {
|
|
44
|
+
username: string;
|
|
45
|
+
password: string;
|
|
46
|
+
}
|
|
47
|
+
declare class SessionAuthService {
|
|
48
|
+
private readonly http;
|
|
49
|
+
private readonly config;
|
|
50
|
+
readonly isAuthenticated: i0.WritableSignal<boolean>;
|
|
51
|
+
login(credentials: LoginCredentials): Observable<void>;
|
|
52
|
+
clearSession(): void;
|
|
53
|
+
private setAuthenticated;
|
|
54
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SessionAuthService, never>;
|
|
55
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<SessionAuthService>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export { LoginPage, SESSION_AUTH_CONFIG, SessionAuthService, provideSessionAuth, sessionAuthGuard, sessionAuthInterceptor };
|
|
59
|
+
export type { LoginCredentials, SessionAuthConfig };
|