From ce4196a099ea0bb394eb731611875745d0bc9dec Mon Sep 17 00:00:00 2001 From: Quildra Date: Wed, 20 Dec 2023 11:26:03 +0000 Subject: [PATCH] More work on auth0 integration --- .../src/seasons/seasons.controller.ts | 7 +- packages/bridge-ui/src/app/app.config.ts | 30 +++++++- .../components/top-bar/top-bar.component.html | 13 ++-- .../components/top-bar/top-bar.component.ts | 26 +++---- .../season-details.component.html | 8 +- .../season-details.component.ts | 9 +-- .../app/pages/seasons/seasons.component.html | 2 +- .../app/pages/seasons/seasons.component.ts | 7 +- .../src/app/services/auth.service.ts | 76 ------------------- .../src/app/services/seasons.service.ts | 2 +- ....service.spec.ts => users.service.spec.ts} | 8 +- .../src/app/services/users.service.ts | 67 ++++++++++++++++ 12 files changed, 128 insertions(+), 127 deletions(-) delete mode 100644 packages/bridge-ui/src/app/services/auth.service.ts rename packages/bridge-ui/src/app/services/{auth.service.spec.ts => users.service.spec.ts} (56%) create mode 100644 packages/bridge-ui/src/app/services/users.service.ts diff --git a/packages/bridge-server/src/seasons/seasons.controller.ts b/packages/bridge-server/src/seasons/seasons.controller.ts index 202fdbd..29279ed 100644 --- a/packages/bridge-server/src/seasons/seasons.controller.ts +++ b/packages/bridge-server/src/seasons/seasons.controller.ts @@ -1,7 +1,7 @@ import { Body, Controller, Get, Param, Post, UseGuards } from '@nestjs/common'; import { SeasonsService } from './seasons.service'; import { SeasonStandingsService } from 'src/season-standings/season-standings.service'; -import { AuthGuard } from 'src/auth/auth.guard'; +import { AuthGuard } from '@nestjs/passport'; @Controller('seasons') export class SeasonsController { @@ -25,10 +25,9 @@ export class SeasonsController { return this.seasonStandingsService.updateStandings(params.id); } - @UseGuards(AuthGuard) - @Post() + @UseGuards(AuthGuard('jwt')) + @Post('create') create(@Body() body: any) { return this.seasonsService.create(body.title, body.subTitle, body.startingDate); } - } diff --git a/packages/bridge-ui/src/app/app.config.ts b/packages/bridge-ui/src/app/app.config.ts index 09fc0cd..b42aa88 100644 --- a/packages/bridge-ui/src/app/app.config.ts +++ b/packages/bridge-ui/src/app/app.config.ts @@ -30,12 +30,34 @@ export const appConfig: ApplicationConfig = { httpInterceptor: { allowedList: [ { - // Match any request that starts 'https://{yourDomain}/api/v2/' (note the asterisk) - //uri: 'https://ponyta.pkmn.cloud/*', - uri: 'http://localhost:3000/*', + uriMatcher: (uri) => { + let is_create = uri.match('.+\/create'); + return is_create != null; + }, + tokenOptions: { + authorizationParams: { + audience: 'https://ponyta.pkmn.cloud' + } + } + }, + { + uriMatcher: (uri) => { + let is_update = uri.match('.+\/update'); + return is_update != null; + }, + tokenOptions: { + authorizationParams: { + audience: 'https://ponyta.pkmn.cloud' + } + } + }, + { + uriMatcher: (uri) => { + let is_delete = uri.match('.+\/delete'); + return is_delete != null; + }, tokenOptions: { authorizationParams: { - // The attached token should target this audience audience: 'https://ponyta.pkmn.cloud' } } diff --git a/packages/bridge-ui/src/app/components/top-bar/top-bar.component.html b/packages/bridge-ui/src/app/components/top-bar/top-bar.component.html index 2005461..4546a3c 100644 --- a/packages/bridge-ui/src/app/components/top-bar/top-bar.component.html +++ b/packages/bridge-ui/src/app/components/top-bar/top-bar.component.html @@ -12,17 +12,18 @@ [options] = "options" (themeChange)="themeChangeHandler($event)"> - - @if(isAuthenticated == false) { - } @else { - + } \ No newline at end of file diff --git a/packages/bridge-ui/src/app/components/top-bar/top-bar.component.ts b/packages/bridge-ui/src/app/components/top-bar/top-bar.component.ts index 8392843..3227762 100644 --- a/packages/bridge-ui/src/app/components/top-bar/top-bar.component.ts +++ b/packages/bridge-ui/src/app/components/top-bar/top-bar.component.ts @@ -13,10 +13,9 @@ import { ThemeMenuComponent } from '../theme-menu/theme-menu.component'; import { ThemeService } from '../../services/theme.service'; import { ThemeOption } from '../../models/theme-option.model'; -import { LoginDialogComponent } from '../login-dialog/login-dialog.component'; -import { AuthService } from '../../services/auth.service'; - -//import { AuthService } from '@auth0/auth0-angular'; +import { AuthService } from '@auth0/auth0-angular'; +import { UsersService } from '../../services/users.service'; +import { MatTooltipModule } from '@angular/material/tooltip'; @Component({ selector: 'app-top-bar', @@ -27,6 +26,7 @@ import { AuthService } from '../../services/auth.service'; MatToolbarModule, MatButtonModule, MatIconModule, + MatTooltipModule, ThemeMenuComponent ], templateUrl: './top-bar.component.html', @@ -37,16 +37,18 @@ export class TopBarComponent { constructor( private readonly themeService: ThemeService, public authService: AuthService, + public usersService: UsersService, public dialog: MatDialog ) {} options: Array = []; - isAuthenticated: boolean = false; - async ngOnInit() { + ngOnInit() { this.themeService.getThemeOptions().subscribe(data => { this.options = data; }); + + this.usersService.refreshUserDetails(); } themeChangeHandler(seletedTheme: string) { @@ -54,20 +56,10 @@ export class TopBarComponent { } onLoginClick() { - this.authService.login(); + this.authService.loginWithRedirect(); } onLogoutClick() { this.authService.logout(); } - - isAuthed() { - return this.authService.isAuthenticated(); - } - - onProfile() { - this.authService.testProfile().subscribe(data => { - console.log(data) - }); - } } diff --git a/packages/bridge-ui/src/app/pages/season-details/season-details.component.html b/packages/bridge-ui/src/app/pages/season-details/season-details.component.html index 6eaf301..01e7a5f 100644 --- a/packages/bridge-ui/src/app/pages/season-details/season-details.component.html +++ b/packages/bridge-ui/src/app/pages/season-details/season-details.component.html @@ -3,9 +3,11 @@

{{season.title}}

{{season.subTitle}}

- @if(isAuthed()) { - - + @if(usersService.isAuthenticated) { + @if(usersService.canCreateRaces()) { + + } +
} diff --git a/packages/bridge-ui/src/app/pages/season-details/season-details.component.ts b/packages/bridge-ui/src/app/pages/season-details/season-details.component.ts index 30253da..efa4bb8 100644 --- a/packages/bridge-ui/src/app/pages/season-details/season-details.component.ts +++ b/packages/bridge-ui/src/app/pages/season-details/season-details.component.ts @@ -14,10 +14,11 @@ import { UploadReplayDialogComponent } from '../../components/upload-replay-dial import { SeasonsService } from '../../services/seasons.service'; import { RacesService } from '../../services/races.service'; -import { AuthService } from '../../services/auth.service'; +import { AuthService } from '@auth0/auth0-angular'; import { Season } from '../../models/season.model'; import { Race } from '../../models/race.model'; import { NewRaceDialogComponent } from '../../components/new-race-dialog/new-race-dialog.component'; +import { UsersService } from '../../services/users.service'; @Component({ selector: 'app-season-details', @@ -46,7 +47,7 @@ export class SeasonDetailsComponent { private seasonsService: SeasonsService, private racesService: RacesService, private dialog: MatDialog, - private authService: AuthService, + public usersService: UsersService, ) {} ngOnInit() { @@ -60,10 +61,6 @@ export class SeasonDetailsComponent { }); } - isAuthed() { - return this.authService.isAuthenticated(); - } - openUploadReplayDialog(id: string) { this.dialog.open(UploadReplayDialogComponent, { diff --git a/packages/bridge-ui/src/app/pages/seasons/seasons.component.html b/packages/bridge-ui/src/app/pages/seasons/seasons.component.html index ab95b4b..4220b7c 100644 --- a/packages/bridge-ui/src/app/pages/seasons/seasons.component.html +++ b/packages/bridge-ui/src/app/pages/seasons/seasons.component.html @@ -1,5 +1,5 @@
- @if(isAuthed()){ + @if(userService.canCreateSeasons()){ } @for (season of seasons; track season) { diff --git a/packages/bridge-ui/src/app/pages/seasons/seasons.component.ts b/packages/bridge-ui/src/app/pages/seasons/seasons.component.ts index 9588ba2..8d99e68 100644 --- a/packages/bridge-ui/src/app/pages/seasons/seasons.component.ts +++ b/packages/bridge-ui/src/app/pages/seasons/seasons.component.ts @@ -2,12 +2,12 @@ import { Component, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { SeasonsService } from '../../services/seasons.service'; -import { AuthService } from '../../services/auth.service'; import { SeasonCardComponent } from '../../components/season-card/season-card.component'; import { SeasonCardNewComponent } from '../../components/season-card-new/season-card-new.component'; import { Season } from '../../models/season.model'; +import { UsersService } from '../../services/users.service'; @Component({ selector: 'app-seasons', @@ -26,7 +26,7 @@ export class SeasonsComponent { constructor( private seasonService: SeasonsService, - private authService: AuthService + public userService: UsersService ) {} ngOnInit() { @@ -35,7 +35,4 @@ export class SeasonsComponent { }); } - isAuthed() { - return this.authService.isAuthenticated(); - } } diff --git a/packages/bridge-ui/src/app/services/auth.service.ts b/packages/bridge-ui/src/app/services/auth.service.ts deleted file mode 100644 index 7bf4f65..0000000 --- a/packages/bridge-ui/src/app/services/auth.service.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Injectable } from '@angular/core'; -import { HttpClient, HttpHeaders } from "@angular/common/http"; -import { Observable } from 'rxjs'; -import { jwtDecode } from "jwt-decode"; -import { ServerEndpointService } from './server-endpoint.service'; - -import { AuthService as Auth0Service } from '@auth0/auth0-angular'; - -@Injectable({ - providedIn: 'root' -}) -export class AuthService { - - _isAuthenticated:boolean = false; - - constructor( - private httpClient: HttpClient, - private serverEndpointService: ServerEndpointService, - private auth0: Auth0Service - ) - { - localStorage.removeItem('token'); - - this.auth0.isAuthenticated$.subscribe(authed => { - this._isAuthenticated = authed; - }); - - this.auth0.user$.subscribe(user => { - console.log(user); - }) - - this.auth0.idTokenClaims$.subscribe(data => { - console.log(data) - if (data && data.__raw) { - localStorage.setItem('token', data.__raw); - } - }) - } - - login() { - this.auth0.loginWithRedirect(); - } - - testProfile(): Observable { - return this.httpClient.get(this.serverEndpointService.getCurrentEndpoint()+"authz/test") - } - - logout(): void { - this.auth0.logout(); - localStorage.removeItem('token'); - } - - isAuthenticated(): boolean { - if(!this._isAuthenticated) { - return false; - } - - const token = localStorage.getItem('token'); - - if (!token) { - return false; - } - - try { - const decoded: any = jwtDecode(token); - - // Check if the token is expired - const isTokenExpired = decoded.exp < Date.now() / 1000; - - return !isTokenExpired; - } catch (error) { - console.error('Error decoding JWT:', error); - return false; - } - } -} diff --git a/packages/bridge-ui/src/app/services/seasons.service.ts b/packages/bridge-ui/src/app/services/seasons.service.ts index b379eed..c18939a 100644 --- a/packages/bridge-ui/src/app/services/seasons.service.ts +++ b/packages/bridge-ui/src/app/services/seasons.service.ts @@ -81,7 +81,7 @@ export class SeasonsService { } create(title: string, subTitle: string, startingDate: Date) { - return this.httpClient.post(this.serverEndpointService.getCurrentEndpoint()+"seasons/", {title, subTitle, startingDate}); + return this.httpClient.post(this.serverEndpointService.getCurrentEndpoint()+"seasons/create", {title, subTitle, startingDate}); } updateSeason(id: string) { diff --git a/packages/bridge-ui/src/app/services/auth.service.spec.ts b/packages/bridge-ui/src/app/services/users.service.spec.ts similarity index 56% rename from packages/bridge-ui/src/app/services/auth.service.spec.ts rename to packages/bridge-ui/src/app/services/users.service.spec.ts index f1251ca..f81244a 100644 --- a/packages/bridge-ui/src/app/services/auth.service.spec.ts +++ b/packages/bridge-ui/src/app/services/users.service.spec.ts @@ -1,13 +1,13 @@ import { TestBed } from '@angular/core/testing'; -import { AuthService } from './auth.service'; +import { UsersService } from './users.service'; -describe('AuthService', () => { - let service: AuthService; +describe('UsersService', () => { + let service: UsersService; beforeEach(() => { TestBed.configureTestingModule({}); - service = TestBed.inject(AuthService); + service = TestBed.inject(UsersService); }); it('should be created', () => { diff --git a/packages/bridge-ui/src/app/services/users.service.ts b/packages/bridge-ui/src/app/services/users.service.ts new file mode 100644 index 0000000..1d02d85 --- /dev/null +++ b/packages/bridge-ui/src/app/services/users.service.ts @@ -0,0 +1,67 @@ +import { Injectable } from '@angular/core'; +import { AuthService, User } from '@auth0/auth0-angular'; + +import * as jwt_decode from 'jwt-decode'; + +@Injectable({ + providedIn: 'root' +}) +export class UsersService { + + isAuthenticated = false; + permissions: string[] = []; + user: User | null | undefined = null; + + constructor( + public authService: AuthService, + ) { } + + private hasPermission(permission: string) { + return this.permissions.includes(permission); + } + + refreshUserDetails() { + this.authService.isAuthenticated$.subscribe(isAuthed => { + this.isAuthenticated = isAuthed; + console.log(this.isAuthenticated); + + if(!this.isAuthenticated) { return } + + this.authService.user$.subscribe(data => { + this.user = data; + }) + + this.authService.getAccessTokenSilently().subscribe(data => { + try { + let decoded = jwt_decode.jwtDecode(data) as any; + this.permissions = decoded['permissions']; + } + catch (error) { + + } + }) + }) + } + + getUserName() :string { + if(!this.isAuthenticated || !this.user) {return ""} + + return this.user.nickname || ""; + } + + canCreateSeasons() : boolean { + return this.hasPermission("create:seasons") + } + + canEditSeasons() : boolean { + return this.hasPermission("edit:seasons") + } + + canDeleteSeasons() : boolean { + return this.hasPermission("delete:seasons") + } + + canCreateRaces(): boolean { + return this.hasPermission("create:races") + } +}