-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathauth.service.ts
More file actions
148 lines (132 loc) · 4.74 KB
/
Copy pathauth.service.ts
File metadata and controls
148 lines (132 loc) · 4.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import { inject, Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { BehaviorSubject, firstValueFrom, Observable, of, switchMap, throwError } from 'rxjs';
import { AppConfigService } from './app-config.service';
import { UserProfile } from '../models/user.model';
import { catchError, map, tap } from 'rxjs/operators';
import { Router } from '@angular/router';
import { LoggingService } from './logging.service';
@Injectable({
providedIn: 'root',
})
export class AuthService {
private readonly config = inject(AppConfigService);
private http = inject(HttpClient);
private log = inject(LoggingService).forContext('AuthService');
private router = inject(Router);
private initialized = false;
private tokenSubject = new BehaviorSubject<string | null>(null);
private userSubject = new BehaviorSubject<UserProfile | null>(null);
readonly user$ = this.userSubject.asObservable();
readonly token$ = this.tokenSubject.asObservable();
readonly isAuthenticated$ = this.userSubject.pipe(map((user) => !!user));
private get authUrl(): string {
// DRF endpoint that redirects to the IAM login URL
const baseApiUrl = this.config.apiUrl.replace(/\/v\d+\/?$/, '');
return `${baseApiUrl}/oauth/`;
}
private handleError(error: HttpErrorResponse): Observable<never> {
let errorMessage = 'An unexpected error occurred.';
if (error.status === 403) {
errorMessage = 'You are not authorized to perform this action.';
}
return throwError(() => new Error(errorMessage));
}
init(): Promise<UserProfile | null> {
if (this.initialized) {
this.log.debug('init: already initialized');
return Promise.resolve(this.userSubject.value);
}
this.initialized = true;
this.log.debug('init: calling userinfo API to check auth status');
// check if the user is authenticated by calling the userinfo endpoint
return firstValueFrom(
this.http.get<UserProfile>(`${this.authUrl}userinfo/`).pipe(
map((response) => ({
username: response.username,
first_name: response.first_name,
last_name: response.last_name,
email: response.email,
initials: ((response.first_name?.[0] || '') + (response.last_name?.[0] || '')).toUpperCase(),
groups: response.groups,
permissions: response.permissions,
})),
tap((user) => {
this.log.debug('checkAuth: got user from userinfo API');
this.setUser(user);
}),
switchMap((user) =>
this.getUserToken().pipe(
map((token) => {
this.log.debug('checkAuth: got user token');
return user;
}),
catchError((err) => {
this.log.debug('checkAuth: error getting user token', err && err.status, err && err.message);
return of(user);
}),
),
),
catchError((err) => {
this.log.debug('checkAuth: error', err && err.status, err && err.message);
this.setUser(null);
return of(null);
}),
),
);
}
login(): void {
// redirect to IAM login page keeping session in the same tab
window.open(`${this.authUrl}login/iam/`, '_self');
}
checkAuth(): Observable<UserProfile | null> {
this.log.debug('checkAuth: returning value from userSubject');
return of(this.userSubject.value);
}
setUser(user: UserProfile | null): void {
this.userSubject.next(user);
}
getUserToken(): Observable<string> {
return this.http.get<{ token: string }>(`${this.authUrl}usertoken/`).pipe(
map((response) => {
this.setToken(response.token);
return response.token;
}),
catchError(this.handleError),
);
}
setToken(token: string): void {
this.tokenSubject.next(token);
}
getToken(): Observable<string | null> {
return this.token$;
}
logout(): void {
// clear local session immediately so UI shows Login button without waiting for network
this.clearSession();
// notify backend to clear server session cookie, include credentials
this.http
.get(`${this.authUrl}logout/`, { withCredentials: true })
.pipe(
catchError((err) => {
// ignore error but log
this.log.error('Logout API error', err);
return of(null);
}),
)
.subscribe(() => {
try {
this.router.navigate(['/']);
} catch (e) {
// ignore navigation errors
this.log.debug('Navigation error after logout', e);
}
});
}
clearSession(): void {
// Clear the user and the token in the subject
this.log.debug('clearSession -> clearing client state');
this.tokenSubject.next(null);
this.userSubject.next(null);
}
}