Skip to content

Commit ac75ed8

Browse files
committed
Cleaned up and added comments
1 parent f96cf36 commit ac75ed8

15 files changed

Lines changed: 204 additions & 54 deletions

src/app/authentication/authentication.module.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { CommonModule } from '@angular/common';
22
import { HTTP_INTERCEPTORS } from '@angular/common/http';
33
import { NgModule } from '@angular/core';
44
import { BasicAuthenticationInterceptor } from './interceptor/basic-authentication.interceptor';
5-
import { UnauthorizedInterceptor } from './interceptor/unauthorized.interceptor';
5+
import { UnauthorizedErrorInterceptor } from './interceptor/unauthorized.interceptor';
66

77
@NgModule({
88
declarations: [],
@@ -11,7 +11,7 @@ import { UnauthorizedInterceptor } from './interceptor/unauthorized.interceptor'
1111
],
1212
providers: [
1313
{ provide: HTTP_INTERCEPTORS, useClass: BasicAuthenticationInterceptor, multi: true },
14-
{ provide: HTTP_INTERCEPTORS, useClass: UnauthorizedInterceptor, multi: true }
14+
{ provide: HTTP_INTERCEPTORS, useClass: UnauthorizedErrorInterceptor, multi: true }
1515
]
1616
})
1717
export class AuthenticationModule { }

src/app/authentication/guard/logged-in.guard.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTr
33
import { Observable } from 'rxjs';
44
import { AuthenticationService } from '../service/authentication.service';
55

6+
/**
7+
* Logged in guard. Allows access only if the user in session is logged in. Otherwise redirects
8+
* to the login form.
9+
*/
610
@Injectable({
711
providedIn: 'root'
812
})

src/app/authentication/guard/logged-out.guard.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTr
33
import { Observable } from 'rxjs';
44
import { AuthenticationService } from '../service/authentication.service';
55

6+
/**
7+
* Logged out guard. Allows access only if the user in session is logged out. Otherwise redirects
8+
* to the app root URL.
9+
*/
610
@Injectable({
711
providedIn: 'root'
812
})

src/app/authentication/interceptor/basic-authentication.interceptor.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import { environment } from '@environments/environment';
44
import { Observable } from 'rxjs';
55
import { AuthenticationService } from '../service/authentication.service';
66

7-
7+
/**
8+
* Basic HTTP authentication interceptor. Adds the basic authentication token to all request to
9+
* API requests, as long as the user in session is correctly logged in.
10+
*/
811
@Injectable()
912
export class BasicAuthenticationInterceptor implements HttpInterceptor {
1013

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import { TestBed } from '@angular/core/testing';
22

3-
import { UnauthorizedInterceptor } from './unauthorized.interceptor';
3+
import { UnauthorizedErrorInterceptor } from './unauthorized.interceptor';
44

5-
describe('UnauthorizedInterceptor', () => {
5+
describe('UnauthorizedErrorInterceptor', () => {
66
beforeEach(() => TestBed.configureTestingModule({
77
providers: [
8-
UnauthorizedInterceptor
8+
UnauthorizedErrorInterceptor
99
]
1010
}));
1111

1212
it('should be created', () => {
13-
const interceptor: UnauthorizedInterceptor = TestBed.inject(UnauthorizedInterceptor);
13+
const interceptor: UnauthorizedErrorInterceptor = TestBed.inject(UnauthorizedErrorInterceptor);
1414
expect(interceptor).toBeTruthy();
1515
});
1616
});

src/app/authentication/interceptor/unauthorized.interceptor.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ import { environment } from '@environments/environment';
44
import { catchError, Observable, throwError } from 'rxjs';
55
import { AuthenticationService } from '../service/authentication.service';
66

7+
/**
8+
* Unauthorized error interceptor. Logs out the user in session on an authorization error.
9+
*/
710
@Injectable()
8-
export class UnauthorizedInterceptor implements HttpInterceptor {
11+
export class UnauthorizedErrorInterceptor implements HttpInterceptor {
912

1013
constructor(
1114
private authenticationService: AuthenticationService
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
1+
/**
2+
* Login status. Shows the status after a login attempt.
3+
*/
14
export class LoginStatus {
5+
/**
6+
* Username of the login attempt.
7+
*/
28
username: string = '';
9+
/**
10+
* Logged in flag. If it is true, then the user managed to log in.
11+
*/
312
logged: boolean = false;
13+
/**
14+
* Authentication token generated by the backend.
15+
*/
416
token: string = '';
517
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
1+
/**
2+
* Application user. Part of the authentication model.
3+
*/
14
export class User {
5+
/**
6+
* User username.
7+
*/
28
username: string = '';
9+
/**
10+
* Logged in flag. If it is true, then the user is logged in.
11+
*/
312
logged: boolean = false;
13+
/**
14+
* Authentication token for the user.
15+
*/
416
token: string = '';
517
}

src/app/authentication/service/authentication.service.ts

Lines changed: 77 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,25 @@ import { Injectable } from '@angular/core';
33
import { ApiResponse } from '@app/api/model/api-response';
44
import { environment } from '@environments/environment';
55
import { BehaviorSubject, Observable } from 'rxjs';
6-
import { map } from 'rxjs/operators';
6+
import { map, tap } from 'rxjs/operators';
77
import { LoginStatus } from '../model/login-status';
88
import { User } from '../model/user';
99

10+
/**
11+
* Authentication service. Handles login and logout operations.
12+
*
13+
* Login requires sending a request to the login endpoint. While logout just requires cleaning up
14+
* the local context.
15+
*
16+
* ## Remember me
17+
*
18+
* This functionality allows storing the user into the local context, and recovering it automatically.
19+
* All this requires is setting the rememberMe flag to true. The service will take care of the rest.
20+
*
21+
* This is donde just by storing the user into the local context in a successful login. If said
22+
* login was successful. The service will always try to read a user from the local context when it
23+
* starts. Also any user in the local context will be removed on a logout.
24+
*/
1025
@Injectable({
1126
providedIn: 'root',
1227
})
@@ -29,29 +44,66 @@ export class AuthenticationService {
2944
this.user = this.userSubject.asObservable();
3045
}
3146

47+
/**
48+
* Logs in a user. This requires sending a login request. If the request fails it returns an
49+
* empty user, otherwise it returns the user.
50+
*
51+
* If the 'remember me' option is active, the user will be stored in the local storage.
52+
*
53+
* @param username username for login
54+
* @param password password for login
55+
* @returns the user resulting from the login
56+
*/
3257
public login(username: string, password: string): Observable<User> {
3358
return this.http.post<ApiResponse<LoginStatus>>(this.loginUrl, { username, password })
3459
.pipe(map(response => response.content))
35-
.pipe(map(r => this.loadUser(r)));
60+
.pipe(map(response => this.toUser(response)))
61+
.pipe(tap(user => this.storeUser(user)));
3662
}
3763

64+
/**
65+
* Logs out the current user.
66+
*/
3867
public logout() {
68+
// Store empty user
3969
this.userSubject.next(new User());
70+
71+
// Clear local storage
4072
localStorage.removeItem(this.userKey);
4173
}
4274

75+
/**
76+
* Returns the user currently in session.
77+
* @returns the user currently in session
78+
*/
4379
public getUser(): User {
4480
return this.userSubject.value;
4581
}
4682

83+
/**
84+
* Returns the user currently in session as an observable. This allows reacting to new logins or logouts.
85+
*
86+
* @returns the user currently in session as an observable
87+
*/
4788
public getUserObservable(): Observable<User> {
4889
return this.user;
4990
}
5091

92+
/**
93+
* Sets the status of the remember me option. If active the user will be stored on a succesful login.
94+
*
95+
* @param remember remember me flag
96+
*/
5197
public setRememberMe(remember: boolean) {
5298
this.rememberMe = remember;
5399
}
54100

101+
/**
102+
* Reads the user from the local storage. This allows recovering users stored as part of the 'remember me'
103+
* functionality.
104+
*
105+
* @returns the user stored in the local storage as part of the 'remember me'
106+
*/
55107
private readUserFromLocal(): BehaviorSubject<User> {
56108
let subject: BehaviorSubject<User>;
57109

@@ -70,7 +122,13 @@ export class AuthenticationService {
70122
return subject;
71123
}
72124

73-
private loadUser(status: LoginStatus): User {
125+
/**
126+
* Maps the login status into a user.
127+
*
128+
* @param status status to map
129+
* @returns user generated from the login status
130+
*/
131+
private toUser(status: LoginStatus): User {
74132
let loggedUser;
75133

76134
loggedUser = new User();
@@ -79,15 +137,24 @@ export class AuthenticationService {
79137
loggedUser.username = status.username;
80138
loggedUser.logged = status.logged;
81139
loggedUser.token = status.token;
82-
83-
if (this.rememberMe) {
84-
// Store user
85-
localStorage.setItem(this.userKey, JSON.stringify(loggedUser));
86-
}
87140
}
88141

89-
this.userSubject.next(loggedUser);
90-
return this.getUser();
142+
return loggedUser;
143+
}
144+
145+
/**
146+
* Stores the received user. This takes two steps, first it is stored in the local subject. Then, if
147+
* the 'remember me' option is enabled, it will be stored in the local storage.
148+
*
149+
* @param user user to store
150+
*/
151+
private storeUser(user: User) {
152+
this.userSubject.next(user);
153+
154+
if (this.rememberMe) {
155+
// Store user
156+
localStorage.setItem(this.userKey, JSON.stringify(user));
157+
}
91158
}
92159

93160
}

src/app/login/components/login-form/login-form.component.html

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,24 @@
22
<div class="form-group">
33
<label for="username">Username</label>
44
<input type="text" formControlName="username" class="form-control"
5-
[ngClass]="{ 'is-invalid': isFormInvalid('username') }" />
6-
<div *ngIf="isFormInvalid('username') && form.get('username')?.hasError('required')" class="invalid-feedback">
5+
[ngClass]="{ 'is-invalid': isFieldInvalid('username') }" />
6+
<div *ngIf="isFieldInvalid('username') && form.get('username')?.hasError('required')" class="invalid-feedback">
77
Username is required
88
</div>
99
</div>
1010
<div class="form-group">
1111
<label for="password">Password</label>
1212
<input type="password" formControlName="password" class="form-control"
13-
[ngClass]="{ 'is-invalid': isFormInvalid('password') }" />
14-
<div *ngIf="isFormInvalid('password') && form.get('password')?.hasError('required')" class="invalid-feedback">
13+
[ngClass]="{ 'is-invalid': isFieldInvalid('password') }" />
14+
<div *ngIf="isFieldInvalid('password') && form.get('password')?.hasError('required')" class="invalid-feedback">
1515
Password is required
1616
</div>
1717
</div>
1818
<div class="form-group">
1919
<label for="rememberMe">Remember me</label>
2020
<input type="checkbox" formControlName="rememberMe" class="form-check-input">
2121
</div>
22-
<button [disabled]="!canLogin()" class="btn btn-primary" [attr.aria-disabled]="!canLogin()" aria-label="Login">
23-
<span *ngIf="loading" class="spinner-border spinner-border-sm mr-1"></span>
22+
<button [disabled]="!this.form.valid" class="btn btn-primary" [attr.aria-disabled]="!this.form.valid" aria-label="Login">
2423
Login
2524
</button>
2625
</form>

0 commit comments

Comments
 (0)