diff --git a/package-lock.json b/package-lock.json index 6edb7288a578b51ed2a2a74c29a850ba8774a06d..02dd16f83f1f746eef2f30535e54278dfc5cfd65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -517,11 +517,6 @@ } } }, - "angular-in-memory-web-api": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/angular-in-memory-web-api/-/angular-in-memory-web-api-0.5.3.tgz", - "integrity": "sha512-1QPwwXG8R/2s7EbHh13HDiJYsk4sdBHNxHJHZHJ/Kxb4T9OG+bb1kGcXzY9UrJkEVxOtUW0ozvL4p/HmeIEszg==" - }, "ansi-html": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", diff --git a/package.json b/package.json index 197d184587bd870444fc7ee3490092aac570fb22..8f70d9d4d76372ef5573a4c0b28b3bb02a2ad05f 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,6 @@ "@angular/platform-browser": "^5.2.0", "@angular/platform-browser-dynamic": "^5.2.0", "@angular/router": "^5.2.0", - "angular-in-memory-web-api": "^0.5.3", "core-js": "^2.4.1", "hammerjs": "^2.0.8", "rxjs": "^5.5.6", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 4a858f65242a5d9f0b5d051bbc546ce0932815f3..ea85e15dc18aa146017531781826b87d6fa7cfaa 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -48,9 +48,7 @@ import { MatTooltipModule } from '@angular/material'; import { ContentAnswersComponent } from './content-answers/content-answers.component'; -import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api'; -import { HttpClientModule } from '@angular/common/http'; -import { InMemoryDataService } from './in-memory-data.service'; +import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; import { RoomComponent } from './room/room.component'; import { RoomCreationComponent } from './room-creation/room-creation.component'; import { LoginScreenComponent } from './login-screen/login-screen.component'; @@ -81,6 +79,7 @@ import { AddContentComponent } from './add-content/add-content.component'; import { ParticipantContentCarouselPageComponent } from './participant-content-carousel-page/participant-content-carousel-page.component'; import { ParticipantTextContentComponent } from './participant-text-content/participant-text-content.component'; import { CreatorTextContentComponent } from './creator-text-content/creator-text-content.component'; +import { AuthenticationInterceptor } from './authentication.interceptor'; @NgModule({ declarations: [ @@ -162,12 +161,14 @@ import { CreatorTextContentComponent } from './creator-text-content/creator-text MatToolbarModule, MatTooltipModule, ReactiveFormsModule, - HttpClientModule, - HttpClientInMemoryWebApiModule.forRoot( - InMemoryDataService, { dataEncapsulation: false } - ) + HttpClientModule ], providers: [ + { + provide: HTTP_INTERCEPTORS, + useClass: AuthenticationInterceptor, + multi: true + }, NotificationService, AuthenticationService, AuthenticationGuard, diff --git a/src/app/auth-provider.ts b/src/app/auth-provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..6bd0f9a053f2107415b61f346d03a1bc503aa922 --- /dev/null +++ b/src/app/auth-provider.ts @@ -0,0 +1,9 @@ +export enum AuthProvider { + ARSNOVA, + ARSNOVA_GUEST, + LDAP, + CAS, + GOOGLE, + FACEBOOK, + TWITTER +} diff --git a/src/app/authentication.guard.ts b/src/app/authentication.guard.ts index cec297d1bb377e163c193e08af9f23227feccee4..4e4f004072101b72a224b54a4236be5b33d5202d 100644 --- a/src/app/authentication.guard.ts +++ b/src/app/authentication.guard.ts @@ -1,6 +1,5 @@ import { Injectable } from '@angular/core'; import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router'; -import { Observable } from 'rxjs/Observable'; import { AuthenticationService } from './authentication.service'; import 'rxjs/add/operator/map'; import 'rxjs/add/observable/of'; diff --git a/src/app/authentication.interceptor.ts b/src/app/authentication.interceptor.ts new file mode 100644 index 0000000000000000000000000000000000000000..071cba6ae663016ab49bd4fcf16ead90792b2df7 --- /dev/null +++ b/src/app/authentication.interceptor.ts @@ -0,0 +1,43 @@ +import { Injectable } from '@angular/core'; +import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from '@angular/common/http'; +import 'rxjs/add/operator/do'; +import { AuthenticationService } from './authentication.service'; +import { NotificationService } from './notification.service'; +import { Router } from '@angular/router'; +import { Observable } from 'rxjs/Observable'; + +const AUTH_HEADER_KEY = 'Arsnova-Auth-Token'; + +@Injectable() +export class AuthenticationInterceptor implements HttpInterceptor { + + constructor(private authenticationService: AuthenticationService, + private notificationService: NotificationService, + private router: Router) { + } + + intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { + if (this.authenticationService.isLoggedIn()) { + const token = this.authenticationService.getToken(); + const cloned = req.clone({ + headers: req.headers.set(AUTH_HEADER_KEY, token) + }); + + return next.handle(cloned).do((event: HttpEvent<any>) => { + if (event instanceof HttpResponse) { + // Possible to do something with the response here + } + }, (err: any) => { + if (err instanceof HttpErrorResponse) { + // Catch 401 errors + if (err.status === 401) { + this.notificationService.show('You are not logged in'); + this.router.navigate(['home']); + } + } + }); + } else { + return next.handle(req); + } + } +} diff --git a/src/app/authentication.service.ts b/src/app/authentication.service.ts index b04d62359d9a76595424c7b1f42da931d741b6df..344a6562d14f931d8c211daded76d407e21a74ae 100644 --- a/src/app/authentication.service.ts +++ b/src/app/authentication.service.ts @@ -4,35 +4,71 @@ import { Observable } from 'rxjs/Observable'; import { of } from 'rxjs/observable/of'; import { UserRole } from './user-roles.enum'; import { DataStoreService } from './data-store.service'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { ClientAuthentication } from './client-authentication'; -// TODO: connect to API -// TODO: persist user data (shouldn't get lost on page refresh) @Injectable() export class AuthenticationService { private readonly STORAGE_KEY: string = 'USER'; private user: User; + private apiUrl = { + base : 'https://arsnova-staging.mni.thm.de/api', + v2 : 'https://arsnova-staging.mni.thm.de/api/v2', + auth : '/auth', + login : '/login', + user : '/user', + register : '/register', + registered : '/registered', + resetPassword : '/resetpassword', + guest : '/guest' + }; + private httpOptions = { + headers: new HttpHeaders({}) + }; - constructor(private dataStoreService: DataStoreService) { + constructor(private dataStoreService: DataStoreService, + private http: HttpClient) { if (dataStoreService.has(this.STORAGE_KEY)) { // Load user data from local data store if available this.user = JSON.parse(dataStoreService.get(this.STORAGE_KEY)); } } - login(email: string, password: string, role: UserRole): Observable<boolean> { - this.user = new User(1, '', email, role, 'TOKEN'); - // Store user data in local storage to retain the data when the user reloads the page - this.dataStoreService.set(this.STORAGE_KEY, JSON.stringify(this.user)); + login(email: string, password: string, userRole: UserRole): Observable<boolean> { + const connectionUrl: string = this.apiUrl.base + this.apiUrl.auth + this.apiUrl.login + this.apiUrl.registered; + + return this.checkLogin(this.http.post<ClientAuthentication>(connectionUrl, { + loginId: email, + password: password + }, this.httpOptions), userRole); + } + + guestLogin(): Observable<boolean> { + const connectionUrl: string = this.apiUrl.base + this.apiUrl.auth + this.apiUrl.login + this.apiUrl.guest; - return of(true); + return this.checkLogin(this.http.post<ClientAuthentication>(connectionUrl, null, this.httpOptions), UserRole.PARTICIPANT); } register(email: string, password: string): Observable<boolean> { - return of(true); + const connectionUrl: string = this.apiUrl.base + this.apiUrl.user + this.apiUrl.register; + + return this.http.post<boolean>(connectionUrl, { + loginId: email, + password: password + }, this.httpOptions).map(() => { + return true; + }); } resetPassword(email: string): Observable<boolean> { - return of(true); + const connectionUrl: string = this.apiUrl.v2 + this.apiUrl.user + email + this.apiUrl.resetPassword; + + return this.http.post(connectionUrl, { + key: null, + password: null + }, this.httpOptions).map(() => { + return true; + }); } logout() { @@ -45,6 +81,11 @@ export class AuthenticationService { return this.user; } + private setUser(user: User): void { + this.user = user; + this.dataStoreService.set(this.STORAGE_KEY, JSON.stringify(this.user)); + } + isLoggedIn(): boolean { return this.user !== undefined; } @@ -53,4 +94,25 @@ export class AuthenticationService { return this.isLoggedIn() ? this.user.role : undefined; } + getToken(): string { + return this.user.token; + } + + checkLogin(clientAuthentication: Observable<ClientAuthentication>, userRole: UserRole): Observable<boolean> { + return clientAuthentication.map(result => { + if (result) { + this.setUser(new User( + result.userId, + result.loginId, + result.authProvider, + result.token, userRole)); + return true; + } else { + return false; + } + }).catch(() => { + return of(false); + }); + } + } diff --git a/src/app/client-authentication.ts b/src/app/client-authentication.ts new file mode 100644 index 0000000000000000000000000000000000000000..f25df38ac7cbb66cf0e0ba20727dbd8be1b3b386 --- /dev/null +++ b/src/app/client-authentication.ts @@ -0,0 +1,15 @@ +import { AuthProvider } from './auth-provider'; + +export class ClientAuthentication { + userId: string; + loginId: string; + authProvider: AuthProvider; + token: string; + + constructor(userId: string, loginId: string, authProvider: AuthProvider, token: string) { + this.userId = userId; + this.loginId = loginId; + this.authProvider = authProvider; + this.token = token; + } +} diff --git a/src/app/comment.service.ts b/src/app/comment.service.ts index af4feb56941aa2f19501288812013c89c8855b8d..c969de6e0dceb915c756ceb20325d6d8a4540531 100644 --- a/src/app/comment.service.ts +++ b/src/app/comment.service.ts @@ -40,7 +40,7 @@ export class CommentService extends ErrorHandlingService { ); } - searchComments(roomId: string, userId: number): Observable<Comment[]> { + searchComments(roomId: string, userId: string): Observable<Comment[]> { const url = `${this.commentsUrl}/?roomId=${roomId}&userId=${userId}`; return this.http.get<Comment[]>(url).pipe( tap (_ => ''), diff --git a/src/app/comment.ts b/src/app/comment.ts index 4cf12e8cf2efcba7da36ef727db8702d82870e9a..ed636d119cf2032562b9c8ac0d958b6c24ece93d 100644 --- a/src/app/comment.ts +++ b/src/app/comment.ts @@ -1,7 +1,7 @@ export class Comment { id: string; roomId: string; - userId: number; + userId: string; revision: string; subject: string; body: string; diff --git a/src/app/create-comment/create-comment.component.ts b/src/app/create-comment/create-comment.component.ts index 3d62dfa17e3017d5c45b0519e606b68ea0a7b7d2..ee5833c4c6dd9db80a6cf8699e28e21b4ae64f0e 100644 --- a/src/app/create-comment/create-comment.component.ts +++ b/src/app/create-comment/create-comment.component.ts @@ -46,6 +46,7 @@ export class CreateCommentComponent implements OnInit { return; } this.commentService.addComment({ + id: '', roomId: this.room.id, userId: this.user.id, subject: subject, diff --git a/src/app/login/login.component.html b/src/app/login/login.component.html index 6a4876b571b7a370a9a2f5ef726b7e4c88a287e8..0005f0e6e3e7f175608430a7b6e585a0f5055f16 100644 --- a/src/app/login/login.component.html +++ b/src/app/login/login.component.html @@ -14,7 +14,5 @@ <mat-error *ngIf="passwordFormControl.hasError('required')">Password is <strong>required</strong>.</mat-error> </mat-form-field> <button mat-raised-button color="primary" type="submit">Login</button> - <button mat-raised-button *ngIf="role === UserRole.PARTICIPANT" (click)="guestLogin()">Login as - guest - </button> + <button mat-raised-button *ngIf="role === UserRole.PARTICIPANT" (click)="guestLogin()" type="button">Login as guest</button> </form> diff --git a/src/app/login/login.component.ts b/src/app/login/login.component.ts index bb630ccc1b471064523a2ded42c40195a7b01eda..cc6bcb187d7af8b45ff9b3eef16a616ba879422b 100644 --- a/src/app/login/login.component.ts +++ b/src/app/login/login.component.ts @@ -5,6 +5,8 @@ import { NotificationService } from '../notification.service'; import { ErrorStateMatcher } from '@angular/material'; import { FormControl, FormGroupDirective, NgForm, Validators } from '@angular/forms'; import { UserRole } from '../user-roles.enum'; +import { ClientAuthentication } from '../client-authentication'; +import { User } from '../user'; export class LoginErrorStateMatcher implements ErrorStateMatcher { isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { @@ -50,7 +52,7 @@ export class LoginComponent implements OnInit { } guestLogin(): void { - this.authenticationService.login('guest', 'guest', this.role).subscribe(loginSuccessful => this.checkLogin(loginSuccessful)); + this.authenticationService.guestLogin().subscribe(loginSuccessful => this.checkLogin(loginSuccessful)); } private checkLogin(loginSuccessful: boolean) { diff --git a/src/app/password-reset/password-reset.component.ts b/src/app/password-reset/password-reset.component.ts index 36e2a9c27d1fc815afe7a4ce21db4af225239a26..07a439cda34f9251667cee38f415c5acd45769c3 100644 --- a/src/app/password-reset/password-reset.component.ts +++ b/src/app/password-reset/password-reset.component.ts @@ -37,13 +37,9 @@ export class PasswordResetComponent implements OnInit { username = username.trim(); if (!this.usernameFormControl.hasError('required') && !this.usernameFormControl.hasError('email')) { - this.authenticationService.resetPassword(username).subscribe(result => { - if (result) { - this.notificationService.show('Password was reset. Please check your mail!'); - this.dialogRef.close(); - } else { - this.notificationService.show('Could not reset your password. Is your email address correct?'); - } + this.authenticationService.resetPassword(username).subscribe(() => { + this.notificationService.show('Password was reset. Please check your mail!'); + this.dialogRef.close(); }); } else { this.notificationService.show('Please fit the requirements shown above.'); diff --git a/src/app/register/register.component.ts b/src/app/register/register.component.ts index 85fd4fe2fc437f8913a98d6bf528128175e53dc4..1416e8b1cdd4057429fc23d1a7d9082d2e72301c 100644 --- a/src/app/register/register.component.ts +++ b/src/app/register/register.component.ts @@ -55,17 +55,13 @@ export class RegisterComponent implements OnInit { ngOnInit() { } - register(username: string, password1: string, password2: string): void { + register(username: string, password1: string): void { if (!this.usernameFormControl.hasError('required') && !this.usernameFormControl.hasError('email') && !this.password1FormControl.hasError('required') && !this.password2FormControl.hasError('required') && !this.password2FormControl.hasError('passwordIsEqual')) { - this.authenticationService.register(username, password1).subscribe(result => { - if (result) { - this.notificationService.show('Successfully registered. Please check your mail!'); - this.dialogRef.close(); - } else { - this.notificationService.show('Oops! Something went wrong on our side...'); - } + this.authenticationService.register(username, password1).subscribe(() => { + this.notificationService.show('Successfully registered. Please check your mail!'); + this.dialogRef.close(); }); } else { this.notificationService.show('Please fit the requirements shown above.'); diff --git a/src/app/room-list/room-list.component.ts b/src/app/room-list/room-list.component.ts index 83c3ada7cfa81a0f733dbdd003b5700c4d0bf27a..323b9e57570e9531ea90112243b23616852671a4 100644 --- a/src/app/room-list/room-list.component.ts +++ b/src/app/room-list/room-list.component.ts @@ -33,7 +33,7 @@ export class RoomListComponent implements OnInit { } getRooms(): void { - this.roomService.getRooms().subscribe(rooms => { + this.roomService.getRoomsCreator().subscribe(rooms => { this.rooms = rooms; this.closedRooms = this.rooms.filter(room => room.closed); this.isLoading = false; diff --git a/src/app/room.service.ts b/src/app/room.service.ts index 54c0520deb38ad974abba53663e460873af13695..1c9f0c68b1f4138879acb305a0c3afd41b8014bd 100644 --- a/src/app/room.service.ts +++ b/src/app/room.service.ts @@ -4,6 +4,7 @@ import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable } from 'rxjs/Observable'; import { catchError, tap } from 'rxjs/operators'; import { ErrorHandlingService } from './error-handling.service'; +import { AuthenticationService } from './authentication.service'; const httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json' }) diff --git a/src/app/user.ts b/src/app/user.ts index 99ed66b0aa3147d13824cb0862574d23d58168f6..e37a93bde4dc3b5714ca8169ae115db99aa59fcf 100644 --- a/src/app/user.ts +++ b/src/app/user.ts @@ -1,17 +1,18 @@ +import { AuthProvider } from './auth-provider'; import { UserRole } from './user-roles.enum'; export class User { - id: number; - name: string; - email: string; - role: UserRole; + id: string; + loginId: string; + authProvider: AuthProvider; token: string; + role: UserRole; - constructor(id: number, name: string, email: string, role: UserRole, token: string) { + constructor(id: string, loginId: string, authProvider: AuthProvider, token: string, role: UserRole) { this.id = id; - this.name = name; - this.email = email; - this.role = role; + this.loginId = loginId; + this.authProvider = authProvider; this.token = token; + this.role = role; } }