Skip to content

Commit

Permalink
wip: allow accountant / regulators to have read only access
Browse files Browse the repository at this point in the history
  • Loading branch information
nbittich committed Jan 27, 2024
1 parent ef1c4fc commit 0c657e7
Show file tree
Hide file tree
Showing 20 changed files with 150 additions and 87 deletions.
11 changes: 7 additions & 4 deletions src/app/core/guards/role.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,22 @@ import { KeycloakAuthGuard, KeycloakService } from 'keycloak-angular';
providedIn: 'root',
})
export class RoleGuard extends KeycloakAuthGuard {
constructor(protected readonly router: Router, protected readonly keycloak: KeycloakService) {
constructor(
protected readonly router: Router,
protected readonly keycloak: KeycloakService,
) {
super(router, keycloak);
}

public async isAccessAllowed(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
// Get the roles required from the route.
const requiredRoles = route.data.expectedRole;

// Allow the user to to proceed if no additional roles are required to access the route.
// Allow the user to proceed if no additional roles are required to access the route.
if (!(requiredRoles instanceof Array) || requiredRoles.length === 0) {
return true;
}
// Allow the user to proceed if all the required roles are present.
return requiredRoles.every((role) => this.roles.includes(role));
// Allow the user to proceed if any the required roles are present.
return requiredRoles.some((role) => this.roles.includes(role));
}
}
1 change: 1 addition & 0 deletions src/app/core/models/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ export interface MenuLink {
icon: IconProp;
routerLink: any[];
numberOfTimesClicked?: number;
roles: string[];
}
4 changes: 2 additions & 2 deletions src/app/core/models/user.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export interface User {
id: string;
email: string;
id?: string;
email?: string;
username: string;
authorities: string[];
}
Expand Down
8 changes: 6 additions & 2 deletions src/app/core/service/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@ export class AuthService implements OnDestroy {
keycloakSubscription: Subscription;
loggedIn: EventEmitter<any> = new EventEmitter<any>();
tokenRefreshed: EventEmitter<string> = new EventEmitter<any>();
constructor(private keycloakService: KeycloakService, private appInit: ApplicationInitStatus) {
constructor(
private keycloakService: KeycloakService,
private appInit: ApplicationInitStatus,
) {
this.appInit.donePromise.then(() => this.onInit());
}
ngOnDestroy(): void {
this.keycloakSubscription.unsubscribe();
}
onInit(): any {
async onInit() {
this.keycloakSubscription = this.keycloakService.keycloakEvents$.subscribe(async (e) => {
if (
e.type == KeycloakEventType.OnAuthError ||
Expand Down Expand Up @@ -57,6 +60,7 @@ export class AuthService implements OnDestroy {

getUser(): User {
return {
authorities: this.keycloakService.getUserRoles(),
username: this.keycloakService.getUsername(),
} as User;
}
Expand Down
80 changes: 44 additions & 36 deletions src/app/core/service/notification.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { HttpClient } from '@angular/common/http';
import { Inject, Injectable, PLATFORM_ID } from '@angular/core';
import { Inject, Injectable, OnInit, PLATFORM_ID } from '@angular/core';
import { Observable, of, RetryConfig, Subscription, throwError, timer } from 'rxjs';
import { ArtcodedNotification } from '@core/models/artcoded.notification';
import { OnApplicationEvent } from '@core/interface/on-application-event';
Expand All @@ -8,6 +8,7 @@ import { AuthService } from '@core/service/auth.service';
import { Title } from '@angular/platform-browser';
import { isPlatformBrowser } from '@angular/common';
import { ConfigInitService } from '@init/config-init.service';
import { PersonalInfoService } from './personal.info.service';

const GENERIC_RETRY_STRATEGY = () => {
return {
Expand Down Expand Up @@ -46,55 +47,62 @@ export class NotificationService {
private titleService: Title,
@Inject(PLATFORM_ID) private platformId: any,
private configService: ConfigInitService,
private authService: AuthService
) {}
private personalInfoService: PersonalInfoService,
private authService: AuthService,
) { }

latests(): Observable<ArtcodedNotification[]> {
return this.http.get<ArtcodedNotification[]>(
`${this.configService.getConfig()['BACKEND_URL']}/api/notification?ngsw-bypass=true`
`${this.configService.getConfig()['BACKEND_URL']}/api/notification?ngsw-bypass=true`,
);
}

update(notificationId: string, seen: boolean): Observable<any> {
return this.http.post<any>(
`${this.configService.getConfig()['BACKEND_URL']}/api/notification?id=${notificationId}&seen=${seen}`,
{}
{},
);
}

delete(notification: ArtcodedNotification): Observable<any> {
return this.http.delete<any>(
`${this.configService.getConfig()['BACKEND_URL']}/api/notification?id=${notification.id}`
`${this.configService.getConfig()['BACKEND_URL']}/api/notification?id=${notification.id}`,
);
}

private startListening() {
if (isPlatformBrowser(this.platformId)) {
console.log('start listening...');
this.components = [];
this.pollingSub = timer(0, 5000)
.pipe(
switchMap((counter) => {
return this.latests();
}),
retry(GENERIC_RETRY_STRATEGY()),
catchError((error) => of(error))
)
.subscribe((notifications) => {
this.updateTitle(notifications);
const newEvents = notifications?.filter((n) => !this.eventsAlreadySent.find((x) => x.id === n.id));
this.components.forEach((component) => {
const componentEvents = newEvents.filter((evt) => component.shouldHandle(evt));
if (componentEvents.length) {
component.handle(componentEvents);
if (component.shouldMarkEventAsSeenAfterConsumed()) {
this.markEventsAsSeen(componentEvents);
}
}
});
this.eventsAlreadySent.push(...newEvents);
});
this.loggedOutSubscription = this.authService.loggedOut.subscribe((o) => this.stopListening());
this.personalInfoService.me().subscribe((user) => {
if (user.authorities.includes('ADMIN')) {
console.log('start listening...');
this.components = [];
this.pollingSub = timer(0, 5000)
.pipe(
switchMap((_) => {
return this.latests();
}),
retry(GENERIC_RETRY_STRATEGY()),
catchError((error) => of(error)),
)
.subscribe((notifications) => {
this.updateTitle(notifications);
const newEvents = notifications?.filter((n) => !this.eventsAlreadySent.find((x) => x.id === n.id));
this.components.forEach((component) => {
const componentEvents = newEvents.filter((evt) => component.shouldHandle(evt));
if (componentEvents.length) {
component.handle(componentEvents);
if (component.shouldMarkEventAsSeenAfterConsumed()) {
this.markEventsAsSeen(componentEvents);
}
}
});
this.eventsAlreadySent.push(...newEvents);
});
this.loggedOutSubscription = this.authService.loggedOut.subscribe((_) => this.stopListening());
} else {
console.log("user doesn't have admin role. do not listening to notifications");
}
});
}
}

Expand All @@ -111,7 +119,7 @@ export class NotificationService {
if (!this.pollingSub) {
this.startListening();
}
this.components.push(onApplicationEvent);
this.components?.push(onApplicationEvent);
}

unsubscribe(onApplicationEvent: OnApplicationEvent): void {
Expand All @@ -122,10 +130,10 @@ export class NotificationService {
this.unseenCount = notifications?.filter((n) => !n.seen).length || 0;
this.titleService.setTitle(
(this.unseenCount === 0 ? '' : `(${this.unseenCount})`) +
this.titleService
.getTitle()
.replace(/\([\d]+\)/g, '')
.trim()
this.titleService
.getTitle()
.replace(/\([\d]+\)/g, '')
.trim(),
);
}

Expand Down
9 changes: 8 additions & 1 deletion src/app/core/service/personal.info.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,28 @@ import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { PersonalInfo } from '@core/models/personal.info';
import { ConfigInitService } from '@init/config-init.service';
import { User } from '@core/models/user';

@Injectable({
providedIn: 'root',
})
export class PersonalInfoService {
basePath: string;

constructor(private http: HttpClient, private configService: ConfigInitService) {
constructor(
private http: HttpClient,
private configService: ConfigInitService,
) {
this.basePath = `${configService.getConfig()['BACKEND_URL']}/api/personal-info`;
}

public get(): Observable<PersonalInfo> {
return this.http.get<PersonalInfo>(this.basePath);
}

public me(): Observable<User> {
return this.http.get<User>(this.basePath + '/@me');
}
public save(formData: FormData): Observable<PersonalInfo> {
return this.http.post<PersonalInfo>(this.basePath + '/submit', formData);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ const routes: Routes = [
path: '',
component: DocumentResultComponent,
canActivate: [AuthGuard, RoleGuard],
data: { expectedRole: ['ADMIN'] },
data: { expectedRole: ['ADMIN', 'REGULATOR_OR_ACCOUNTANT'] },
},
];

@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class AdministrativeDocumentRoutingModule {}
export class AdministrativeDocumentRoutingModule { }
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { SharedModule } from '@shared/shared.module';
import { NgbCollapseModule, NgbDropdownModule, NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { AdministrativeDocumentRoutingModule } from '@feature/administrative-document/file-upload-routing.module';
import { AdministrativeDocumentRoutingModule } from '@feature/administrative-document/administrative-document-routing.module';
import { DocumentEditorComponent } from './document-editor/document-editor.component';
import { AutosizeModule } from 'ngx-autosize';
import { NgxFileDropModule } from 'ngx-file-drop';
Expand All @@ -30,4 +30,4 @@ import { SplitPdfComponent } from './split-pdf/split-pdf.component';
NgbDropdownModule,
],
})
export class AdministrativeDocumentModule {}
export class AdministrativeDocumentModule { }
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ const routes: Routes = [
path: '',
component: BillableClientTableComponent,
canActivate: [AuthGuard, RoleGuard],
data: { expectedRole: ['ADMIN'] },
data: { expectedRole: ['ADMIN', 'REGULATOR_OR_ACCOUNTANT'] },
},
];

@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class BillableClientRoutingModule {}
export class BillableClientRoutingModule { }
6 changes: 3 additions & 3 deletions src/app/features/dossier/dossier-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ const routes: Routes = [
path: '',
component: DossierPageComponent,
canActivate: [AuthGuard, RoleGuard],
data: { expectedRole: ['ADMIN'] },
data: { expectedRole: ['ADMIN', 'REGULATOR_OR_ACCOUNTANT'] },
},
{
path: ':name',
component: DossierPageComponent,
canActivate: [AuthGuard, RoleGuard],
data: { expectedRole: ['ADMIN'] },
data: { expectedRole: ['ADMIN', 'REGULATOR_OR_ACCOUNTANT'] },
},
];

@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class DossierRoutingModule {}
export class DossierRoutingModule { }
6 changes: 3 additions & 3 deletions src/app/features/fee/fee-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ const routes: Routes = [
path: '',
component: FeePageComponent,
canActivate: [AuthGuard, RoleGuard],
data: { expectedRole: ['ADMIN'] },
data: { expectedRole: ['ADMIN', 'REGULATOR_OR_ACCOUNTANT'] },
},
{
path: ':name',
component: FeePageComponent,
canActivate: [AuthGuard, RoleGuard],
data: { expectedRole: ['ADMIN'] },
data: { expectedRole: ['ADMIN', 'REGULATOR_OR_ACCOUNTANT'] },
},
];

@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class FeeRoutingModule {}
export class FeeRoutingModule { }
4 changes: 2 additions & 2 deletions src/app/features/file-upload/file-upload-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ const routes: Routes = [
path: '',
component: FileUploadTableComponent,
canActivate: [AuthGuard, RoleGuard],
data: { expectedRole: ['ADMIN'] },
data: { expectedRole: ['ADMIN', 'REGULATOR_OR_ACCOUNTANT'] },
},
];

@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class FileUploadRoutingModule {}
export class FileUploadRoutingModule { }
6 changes: 3 additions & 3 deletions src/app/features/invoice/invoice-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ const routes: Routes = [
path: '',
component: InvoicePageComponent,
canActivate: [AuthGuard, RoleGuard],
data: { expectedRole: ['ADMIN'] },
data: { expectedRole: ['ADMIN', 'REGULATOR_OR_ACCOUNTANT'] },
},
{
path: ':name',
component: InvoicePageComponent,
canActivate: [AuthGuard, RoleGuard],
data: { expectedRole: ['ADMIN'] },
data: { expectedRole: ['ADMIN', 'REGULATOR_OR_ACCOUNTANT'] },
},
];

@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class InvoiceRoutingModule {}
export class InvoiceRoutingModule { }
6 changes: 3 additions & 3 deletions src/app/features/layout/navbar/navbar.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@
</select>
</div>
<div class="d-lg-inline me-1">
<a class="btn btn-link text-white no-focus" (click)="openCacheModal()">
<a class="btn btn-link text-white no-focus" (click)="openCacheModal()" *ngIf="user && hasRoleAdmin">
<fa-icon [icon]="['fas', 'database']"></fa-icon>
</a>
<button (click)="reload()" class="btn btn-link text-white">
<fa-icon [icon]="['fas', 'sync']"></fa-icon>
</button>
<app-notification></app-notification>
<app-notification *ngIf="user && hasRoleAdmin"></app-notification>

<app-user-menu></app-user-menu>
<app-user-menu *ngIf="user" [user]="user"></app-user-menu>
</div>
</nav>
Loading

0 comments on commit 0c657e7

Please sign in to comment.