Skip to content

Commit 8be5003

Browse files
committed
Implement login and register
Right now if register is successful, it will auto login. Login if successful will bring users to the '/' directory. If user is already logged in, attemptes to reach '/account' will be immediately redirected to '/'
1 parent 8605f38 commit 8be5003

File tree

4 files changed

+122
-22
lines changed

4 files changed

+122
-22
lines changed

frontend/src/app/account/login.component.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ export class LoginComponent {
3333
private route: ActivatedRoute
3434
) {
3535
// redirect to home if already logged in
36-
if (this.authenticationService.userValue) {
37-
this.router.navigate(['/']);
38-
}
36+
// if (this.authenticationService.userValue) {
37+
// this.router.navigate(['/']);
38+
// }
3939
}
4040

4141
userForm = {
@@ -48,8 +48,8 @@ export class LoginComponent {
4848
onSubmit() {
4949
if (this.userForm.username && this.userForm.password) {
5050
this.isProcessingLogin = true;
51+
5152
// authenticationService returns an observable that we can subscribe to
52-
console.log('Logging in');
5353
this.authenticationService.login(this.userForm.username, this.userForm.password)
5454
.pipe()
5555
.subscribe({

frontend/src/app/account/register.component.ts

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Component } from '@angular/core';
22
import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
3-
import { RouterLink } from '@angular/router';
3+
import { RouterLink, Router, ActivatedRoute } from '@angular/router';
44
import { SelectButtonModule } from 'primeng/selectbutton';
55
import { InputTextModule } from 'primeng/inputtext';
66
import { PasswordModule } from 'primeng/password';
@@ -12,6 +12,7 @@ import { PASSWORD_WEAK, STRONG_PASSWORD_REGEX, weakPasswordValidator } from './_
1212
import { mismatchPasswordValidator, PASSWORD_MISMATCH } from './_validators/mismatch-password.validator';
1313
import { invalidUsernameValidator, USERNAME_INVALID } from './_validators/invalid-username.validator';
1414
import { invalidPasswordValidator, PASSWORD_INVALID } from './_validators/invalid-password.validator';
15+
import { AuthenticationService } from '../services/authentication.service';
1516

1617
@Component({
1718
selector: 'app-register',
@@ -27,12 +28,22 @@ import { invalidPasswordValidator, PASSWORD_INVALID } from './_validators/invali
2728
ToastModule,
2829
ReactiveFormsModule,
2930
],
30-
providers: [MessageService],
31+
providers: [MessageService, AuthenticationService],
3132
templateUrl: './register.component.html',
3233
styleUrl: './account.component.css',
3334
})
3435
export class RegisterComponent {
35-
constructor(private messageService: MessageService) {}
36+
constructor(
37+
private messageService: MessageService,
38+
private authenticationService: AuthenticationService,
39+
private router: Router,
40+
private route: ActivatedRoute
41+
) {
42+
// redirect to home if already logged in
43+
if (this.authenticationService.userValue) {
44+
this.router.navigate(['/']);
45+
}
46+
}
3647

3748
userForm: FormGroup = new FormGroup(
3849
{
@@ -82,11 +93,36 @@ export class RegisterComponent {
8293
onSubmit() {
8394
if (this.userForm.valid) {
8495
this.isProcessingRegistration = true;
85-
this.showError();
86-
setTimeout(() => {
87-
this.isProcessingRegistration = false;
88-
console.log('Form Submitted', this.userForm.value);
89-
}, 3000);
96+
97+
this.authenticationService.createAccount(
98+
this.userForm.value.username,
99+
this.userForm.value.email,
100+
this.userForm.value.password)
101+
.pipe()
102+
.subscribe({
103+
next: () => {
104+
// get return url from route parameters or default to '/'
105+
const returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
106+
this.router.navigate([returnUrl]);
107+
},
108+
// error handling for registration because we assume there will be no errors with auto login
109+
error: error => {
110+
console.error(error);
111+
this.isProcessingRegistration = false;
112+
let errorMessage = 'An unknown error occurred';
113+
if (error.status === 400) {
114+
errorMessage = 'Missing Fields';
115+
}
116+
else if (error.status === 401) {
117+
errorMessage = 'There is already an account with that username or email';
118+
}
119+
else if (error.status === 500) {
120+
errorMessage = 'Database Server Error';
121+
}
122+
this.messageService.add({ severity: 'error', summary: 'Log In Error', detail: errorMessage });
123+
}
124+
})
125+
90126
} else {
91127
console.log('Invalid form');
92128
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { TestBed } from '@angular/core/testing';
2+
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
3+
import { HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http';
4+
import { JwtInterceptor } from './jwt.interceptor';
5+
import { AuthenticationService } from '../../services/authentication.service';
6+
import { environment } from '../../environments/environment';
7+
8+
describe('JwtInterceptor', () => {
9+
let httpMock: HttpTestingController;
10+
let httpClient: HttpClient;
11+
let mockAuthService: jasmine.SpyObj<AuthenticationService>;
12+
let mockAuthServiceNoUser: jasmine.SpyObj<AuthenticationService>;
13+
14+
beforeEach(() => {
15+
mockAuthService = jasmine.createSpyObj('AuthenticationService', ['userValue'], {
16+
userValue: { accessToken: 'fake-jwt-token' }
17+
});
18+
19+
TestBed.configureTestingModule({
20+
imports: [HttpClient],
21+
providers: [
22+
{ provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
23+
{ provide: AuthenticationService, useValue: mockAuthService },
24+
provideHttpClientTesting()
25+
]
26+
});
27+
28+
httpMock = TestBed.inject(HttpTestingController);
29+
httpClient = TestBed.inject(HttpClient);
30+
});
31+
32+
afterEach(() => {
33+
// Check if all Http requests were handled
34+
httpMock.verify();
35+
});
36+
37+
it('should add an Authorization header', () => {
38+
httpClient.get(`${environment.UserServiceApiUrl}/test`).subscribe();
39+
40+
const httpRequest = httpMock.expectOne(`${environment.UserServiceApiUrl}/test`);
41+
42+
expect(httpRequest.request.headers.has('Authorization')).toBeTruthy();
43+
expect(httpRequest.request.headers.get('Authorization')).toBe('Bearer fake-jwt-token');
44+
});
45+
46+
it('should not add an Authorization header if the user is not logged in', () => {
47+
mockAuthServiceNoUser = jasmine.createSpyObj('AuthenticationService', ['userValue'], {
48+
userValue: { }
49+
});
50+
51+
httpClient.get(`${environment.UserServiceApiUrl}/test`).subscribe();
52+
53+
const httpRequest = httpMock.expectOne(`${environment.UserServiceApiUrl}/test`);
54+
55+
expect(httpRequest.request.headers.has('Authorization')).toBeFalsy();
56+
});
57+
58+
it('should not add an Authorization header for non-API URLs', () => {
59+
httpClient.get('https://example.com/test').subscribe();
60+
61+
const httpRequest = httpMock.expectOne('https://example.com/test');
62+
63+
expect(httpRequest.request.headers.has('Authorization')).toBeFalsy();
64+
});
65+
});

frontend/src/app/services/authentication.service.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
22
import { Router } from '@angular/router';
33
import { HttpClient } from '@angular/common/http';
44
import { BehaviorSubject, Observable } from 'rxjs';
5-
import { map, tap } from 'rxjs/operators';
5+
import { map, switchMap, tap } from 'rxjs/operators';
66
import { environment } from '../environments/environment';
77
import { User } from '../models/user.model';
88

@@ -24,16 +24,9 @@ export class AuthenticationService {
2424
}
2525

2626
login(username: string, password: string) {
27-
console.log('in authentication service login');
28-
console.log(username, password);
29-
console.log(`${environment.UserServiceApiUrl}/auth/login`);
3027
return this.http.post<any>(`${environment.UserServiceApiUrl}/auth/login`,
3128
{ "username": username, "password": password })
32-
.pipe(
33-
tap(response => {
34-
console.log('HTTP POST response:', response);
35-
}),
36-
map(response => {
29+
.pipe(map(response => {
3730
// store user details and jwt token in local storage to keep user logged in between page refreshes
3831
const user = response.data;
3932
localStorage.setItem('user', JSON.stringify(user));
@@ -42,10 +35,16 @@ export class AuthenticationService {
4235
}));
4336
}
4437

38+
createAccount(username: string, email: string, password: string) {
39+
return this.http.post<any>(`${environment.UserServiceApiUrl}/users`,
40+
{ "username": username, "email": email, "password": password })
41+
.pipe(switchMap(() => this.login(username, password))); // auto login after registration
42+
}
43+
4544
logout() {
4645
// remove user from local storage to log user out
4746
localStorage.removeItem('user');
4847
this.userSubject.next(null);
49-
this.router.navigate(['/login']);
48+
this.router.navigate(['/account/login']);
5049
}
5150
}

0 commit comments

Comments
 (0)