login and tenant ui fixes

This commit is contained in:
Efstratios Giannopoulos 2024-05-31 14:07:23 +03:00
parent 46347d4583
commit f05962a3b2
21 changed files with 74 additions and 318 deletions

View File

@ -1,33 +0,0 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, CanLoad, Route, Router, RouterStateSnapshot } from '@angular/router';
import { AuthService } from './services/auth/auth.service';
import { AppRole } from './common/enum/app-role';
@Injectable()
export class AdminAuthGuard implements CanActivate, CanLoad {
constructor(private auth: AuthService, private router: Router) {
}
isAdmin(): boolean {
if (!this.auth.currentAccountIsAuthenticated()) { return false; }
return this.auth.hasRole(AppRole.Admin);
}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
const url: string = state.url;
if (!this.isAdmin()) {
this.router.navigate(['/unauthorized'], { queryParams: { returnUrl: url } });
return false;
}
return true;
}
canLoad(route: Route): boolean {
const url = `/${route.path}`;
if (!this.isAdmin()) {
this.router.navigate(['/unauthorized'], { queryParams: { returnUrl: url } });
return false;
}
return true;
}
}

View File

@ -1,6 +1,5 @@
import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core';
import { CookieService } from 'ngx-cookie-service';
import { AdminAuthGuard } from './admin-auth-guard.service';
import { AuthGuard } from './auth-guard.service';
import { AuthService } from './services/auth/auth.service';
import { ContactSupportService } from './services/contact-support/contact-support.service';
@ -20,8 +19,6 @@ import { ProgressIndicationService } from './services/progress-indication/progre
import { TimezoneService } from './services/timezone/timezone-service';
import { CollectionUtils } from './services/utilities/collection-utils.service';
import { TypeUtils } from './services/utilities/type-utils.service';
import { SpecialAuthGuard } from './special-auth-guard.service';
//import { KeycloakService } from 'keycloak-angular';
import { CanDeactivateGuard } from '@app/library/deactivate/can-deactivate.guard';
import { HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service';
import { FilterService } from '@common/modules/text-filter/filter-service';
@ -70,8 +67,6 @@ export class CoreServiceModule {
AuthService,
CookieService,
BaseHttpV2Service,
AdminAuthGuard,
SpecialAuthGuard,
AuthGuard,
CultureService,
TimezoneService,

View File

@ -279,7 +279,7 @@ export class AuthService extends BaseService {
})
.catch((error) => this.onAuthenticateError(error));
} else {
this.zone.run(() => this.router.navigate([returnUrl]));
this.zone.run(() => this.router.navigateByUrl(returnUrl));
}
}
@ -327,7 +327,7 @@ export class AuthService extends BaseService {
this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-LOGIN'),
SnackBarNotificationLevel.Success
);
this.zone.run(() => this.router.navigate([returnUrl]));
this.zone.run(() => this.router.navigateByUrl(returnUrl));
}
onAuthenticateSuccessReload(): void {

View File

@ -3,7 +3,6 @@ import { AppAccount } from '@app/core/model/auth/principal';
import { Observable } from 'rxjs';
import { ConfigurationService } from '../configuration/configuration.service';
import { BaseHttpV2Service } from '../http/base-http-v2.service';
import { map } from 'rxjs/operators';
import { Tenant } from '@app/core/model/tenant/tenant';
@Injectable()

View File

@ -1,55 +0,0 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, CanLoad, Route, Router, RouterStateSnapshot } from '@angular/router';
import { AuthService } from './services/auth/auth.service';
import { AppRole } from './common/enum/app-role';
@Injectable()
export class SpecialAuthGuard implements CanActivate, CanLoad {
constructor(private auth: AuthService, private router: Router) {
}
hasPermission(permission: AppRole): boolean {
if (!this.auth.currentAccountIsAuthenticated()) { return false; }
return this.auth.hasRole(permission);
}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
const url: string = state.url;
const permissions = route.data['authContext']['permissions'];
let count = permissions.length;
if (count < 0 || count === undefined) {
return false;
}
for (let i = 0; i < permissions.length; i++) {
if (!this.hasPermission(permissions[i])) {
count--;
}
}
if (count === 0) {
this.router.navigate(['/unauthorized'], { queryParams: { returnUrl: url } });
return false;
} else {
return true;
}
}
canLoad(route: Route): boolean {
const url = `/${route.path}`;
const permissions = route.data['authContext']['permissions'];
let count = permissions.length;
if (count < 0 || count === undefined) {
return false;
}
for (let i = 0; i < permissions.length; i++) {
if (!this.hasPermission(permissions[i])) {
count--;
}
}
if (count === 0) {
this.router.navigate(['/unauthorized'], { queryParams: { returnUrl: url } });
return false;
} else {
return true;
}
}
}

View File

@ -1,15 +1,15 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { MaintenanceTasksComponent } from './maintenance-tasks.component';
import { AdminAuthGuard } from '@app/core/admin-auth-guard.service';
import { AuthGuard } from '@app/core/auth-guard.service';
const routes: Routes = [
{ path: '', component: MaintenanceTasksComponent, canActivate: [AdminAuthGuard] },
{ path: '', component: MaintenanceTasksComponent, canActivate: [AuthGuard] },
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class MaintenanceTasksRoutingModule { }
export class MaintenanceTasksRoutingModule { }

View File

@ -1,6 +1,5 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AdminAuthGuard } from '@app/core/admin-auth-guard.service';
import { PrefillingSourceEditorComponent } from './editor/prefilling-source-editor.component';
import { PrefillingSourceListingComponent } from './listing/prefilling-source-listing.component';
import { AppPermission } from '@app/core/common/enum/permission.enum';

View File

@ -1,6 +1,5 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AdminAuthGuard } from '@app/core/admin-auth-guard.service';
import { ReferenceTypeEditorComponent } from './editor/reference-type-editor.component';
import { ReferenceTypeListingComponent } from './listing/reference-type-listing.component';
import { AuthGuard } from '@app/core/auth-guard.service';
@ -16,8 +15,8 @@ const routes: Routes = [
component: ReferenceTypeListingComponent,
canActivate: [AuthGuard]
},
{
path: 'new',
{
path: 'new',
component: ReferenceTypeEditorComponent,
canActivate: [AuthGuard],
canDeactivate: [PendingChangesGuard],

View File

@ -1,6 +1,5 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AdminAuthGuard } from '@app/core/admin-auth-guard.service';
import { ReferenceEditorComponent } from './editor/reference-editor.component';
import { ReferenceListingComponent } from './listing/reference-listing.component';
import { AppPermission } from '@app/core/common/enum/permission.enum';

View File

@ -1,6 +1,5 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AdminAuthGuard } from '@app/core/admin-auth-guard.service';
import { TenantEditorComponent } from './editor/tenant-editor.component';
import { TenantListingComponent } from './listing/tenant-listing.component';
import { AppPermission } from '@app/core/common/enum/permission.enum';

View File

@ -1,7 +1,10 @@
import { Component, Input, NgZone, OnInit } from '@angular/core';
import { ActivatedRoute, Params, Router } from '@angular/router';
import { AuthService } from '@app/core/services/auth/auth.service';
import { PrincipalService } from '@app/core/services/http/principal.service';
import { BaseComponent } from '@common/base/base.component';
import { BaseHttpParams } from '@common/http/base-http-params';
import { InterceptorType } from '@common/http/interceptors/interceptor-type';
import { KeycloakService } from 'keycloak-angular';
import { from } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@ -24,6 +27,7 @@ export class LoginComponent extends BaseComponent implements OnInit {
private router: Router,
private authService: AuthService,
private route: ActivatedRoute,
private principalService: PrincipalService,
private keycloakService: KeycloakService
) { super(); }
@ -35,13 +39,34 @@ export class LoginComponent extends BaseComponent implements OnInit {
if (!this.authService.selectedTenant()) {
this.authService.selectedTenant('default');
}
this.authService.prepareAuthRequest(from(this.keycloakService.getToken())).pipe(takeUntil(this._destroyed)).subscribe(
() => {
let returnUrL = this.returnUrl;
let queryParams: Params = {};
this.zone.run(() => this.router.navigate([returnUrL], { queryParams }));
},
(error) => this.authService.authenticate('/'));
const params = new BaseHttpParams();
params.interceptorContext = {
excludedInterceptors: [InterceptorType.TenantHeaderInterceptor]
};
this.principalService.myTenants({ params: params }).subscribe(myTenants => {
if (myTenants) {
if (this.authService.selectedTenant()) {
if (myTenants.findIndex(x => x.code.toLocaleLowerCase() == this.authService.selectedTenant().toLocaleLowerCase()) < 0) {
this.authService.selectedTenant(null);
}
}
if (!this.authService.selectedTenant()) {
if (myTenants.length > 0) {
this.authService.selectedTenant(myTenants[0]?.code);
}
}
} else {
this.authService.selectedTenant(null);
}
this.authService.prepareAuthRequest(from(this.keycloakService.getToken())).pipe(takeUntil(this._destroyed)).subscribe(
() => {
let returnUrL = this.returnUrl;
this.zone.run(() => this.router.navigateByUrl(returnUrL));
},
(error) => this.authService.authenticate('/'));
}, (error) => this.authService.authenticate('/'));
}
}

View File

@ -6,7 +6,6 @@ import { CommonFormsModule } from '@common/forms/common-forms.module';
import { CommonUiModule } from '@common/ui/common-ui.module';
import { MergeEmailConfirmation } from './merge-email-confirmation/merge-email-confirmation.component';
import { UnlinkEmailConfirmation } from './unlink-email-confirmation/unlink-email-confirmation.component';
import { PostLoginComponent } from './post-login/post-login.component';
@NgModule({
imports: [
@ -17,8 +16,7 @@ import { PostLoginComponent } from './post-login/post-login.component';
declarations: [
LoginComponent,
MergeEmailConfirmation,
UnlinkEmailConfirmation,
PostLoginComponent
UnlinkEmailConfirmation
],
exports: [
LoginComponent

View File

@ -4,7 +4,7 @@ import { LoginComponent } from './login.component';
import { MergeEmailConfirmation } from './merge-email-confirmation/merge-email-confirmation.component';
import { UnlinkEmailConfirmation } from './unlink-email-confirmation/unlink-email-confirmation.component';
import { AuthGuard } from '@app/core/auth-guard.service';
import { PostLoginComponent } from './post-login/post-login.component';
// import { PostLoginComponent } from './post-login/post-login.component';
const routes: Routes = [
{ path: '', component: LoginComponent },
@ -13,10 +13,10 @@ const routes: Routes = [
component: MergeEmailConfirmation,
canActivate: [AuthGuard]
},
{
path: 'post',
component: PostLoginComponent
},
// {
// path: 'post',
// component: PostLoginComponent
// },
{ path: 'unlink/confirmation/:token', component: UnlinkEmailConfirmation },
];

View File

@ -1,38 +0,0 @@
<div class="container-fluid">
<div class="tenant-choose-dialog row">
<div class="col-12 col-md-8 offset-md-2 col-lg-4 offset-lg-4">
<mat-card>
<mat-card-header>
<h1 mat-card-title> {{'TENANT-CHOOSE-DIALOG.TITLE' | translate}}</h1>
</mat-card-header>
<mat-card-content>
<form *ngIf="formGroup" class="dialog-form" (ngSubmit)="formSubmit()" [formGroup]="formGroup">
<div class="row">
<mat-form-field appearance="fill" class="col-md-12">
<mat-label>{{'TENANT-CHOOSE-DIALOG.FIELDS.TENANT-CODE' | translate}}</mat-label>
<mat-select formControlName="tenantCode">
<mat-option *ngFor="let tenant of tenants" [value]="tenant.code">
{{tenant.code}}
</mat-option>
</mat-select>
<mat-error *ngIf="formGroup.get('tenantCode').hasError('backendError')">{{formGroup.get('tenantCode').getError('backendError')?.message}}</mat-error>
<mat-error *ngIf="formGroup.get('tenantCode').hasError('required')">{{'COMMONS.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<div class="col-12">
<div class="row editor-actions">
<div class="col mt-2">
<button type="button" class="default-btn" (click)="cancel()">{{'TENANT-CHOOSE-DIALOG.ACTIONS.BACK' | translate}}</button>
</div>
<div class="col-auto mt-2">
<!-- <button mat-raised-button color="primary" (click)="save($event)" type="primary">{{'TENANT-CHOOSE-DIALOG.ACTIONS.SUBMIT' | translate}}</button> -->
<button type="button" class="normal-btn" (click)="save($event)">{{'TENANT-CHOOSE-DIALOG.ACTIONS.SUBMIT' | translate}}</button>
</div>
</div>
</div>
</form>
</mat-card-content>
</mat-card>
</div>
</div>
</div>

View File

@ -1,17 +0,0 @@
.tenant-choose-dialog {
padding-top: 1em;
.editor-actions {
margin-top: 30px;
}
.confirmation-message {
padding-bottom: 1rem;
font-size: 0.8rem;
}
.display-flex {
display: flex;
font-size: 0.8rem !important;
}
}

View File

@ -1,113 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { Tenant } from '@app/core/model/tenant/tenant';
import { AuthService } from '@app/core/services/auth/auth.service';
import { ConfigurationService } from '@app/core/services/configuration/configuration.service';
import { PrincipalService } from '@app/core/services/http/principal.service';
import { BaseComponent } from '@common/base/base.component';
import { FormService } from '@common/forms/form-service';
import { BaseHttpParams } from '@common/http/base-http-params';
import { InterceptorType } from '@common/http/interceptors/interceptor-type';
import { KeycloakService } from 'keycloak-angular';
import { from } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Component({
templateUrl: './post-login.component.html',
styleUrls: ['./post-login.component.scss'],
})
export class PostLoginComponent extends BaseComponent implements OnInit {
formGroup: UntypedFormGroup = null;
protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
protected tenants: Partial<Tenant>[] = [];
protected selectedTenant = null;
returnUrl: string;
constructor(
private formService: FormService,
private route: ActivatedRoute,
private router: Router,
protected installationConfiguration: ConfigurationService,
private authService: AuthService,
private keycloakService: KeycloakService,
private principalService: PrincipalService
) {
super();
}
ngOnInit(): void {
this.formGroup = this.formBuilder.group({
tenantCode: ['', [Validators.required]]
});
if (this.authService.hasAccessToken()) {
this.loadUserTenants();
} else {
this.keycloakService.getToken().then(token => {
this.authService.currentAuthenticationToken(token);
this.loadUserTenants();
});
}
}
loadUserTenants() {
const params = new BaseHttpParams();
params.interceptorContext = {
excludedInterceptors: [InterceptorType.TenantHeaderInterceptor]
};
if (this.authService.selectedTenant()) {
this.loadUser();
return;
}
this.principalService.myTenants({ params: params }).subscribe(myTenants => {
if (myTenants) {
if (myTenants.length > 1) {
this.tenants = myTenants;
} else if (myTenants.length === 1) {
this.authService.selectedTenant(myTenants[0]?.code);
this.loadUser();
} else {
this.authService.selectedTenant(null);
//this.loadUser();
}
} else {
this.authService.selectedTenant(null);
//this.loadUser();
}
});
}
loadUser(): void {
const returnUrl = '/home';
// const returnUrl = this.route.snapshot.queryParamMap.get('returnUrl') || '/home';
this.authService.prepareAuthRequest(from(this.keycloakService.getToken()), {}).pipe(takeUntil(this._destroyed)).subscribe(() => this.authService.onAuthenticateSuccess(returnUrl), (error) => this.authService.onAuthenticateError(error));
}
save(e: Event): void {
e.preventDefault();
this.formSubmit();
this.loadUser();
}
cancel(): void {
this.router.navigate(['/logout']);
}
formSubmit(): void {
this.formService.touchAllFormFields(this.formGroup);
if (!this.isFormValid()) { return; }
this.selectedTenant = this.formGroup.value['tenantCode'];
this.authService.selectedTenant(this.selectedTenant);
}
public isFormValid(): Boolean {
return this.formGroup.valid;
}
clearErrorModel() {
this.formService.validateAllFormFields(this.formGroup);
}
}

View File

@ -51,7 +51,7 @@ export class LoginService extends BaseService {
if (this.authService.currentAccountIsAuthenticated() && this.authService.getUserProfileCulture()) { this.cultureService.cultureSelected(this.authService.getUserProfileCulture()); }
if (this.authService.currentAccountIsAuthenticated() && this.authService.getUserProfileLanguage()) { this.language.changeLanguage(this.authService.getUserProfileLanguage()); }
const redirectUrl = returnUrl || '/';
this.router.navigate([redirectUrl]);
this.zone.run(() => this.router.navigateByUrl(redirectUrl));
});
}

View File

@ -1,11 +1,11 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { SupportiveMaterialEditorComponent } from './supportive-material-editor.component';
import { AdminAuthGuard } from '@app/core/admin-auth-guard.service';
import { AuthGuard } from '@app/core/auth-guard.service';
const routes: Routes = [
{ path: '', component: SupportiveMaterialEditorComponent, canActivate: [AdminAuthGuard] },
{ path: '', component: SupportiveMaterialEditorComponent, canActivate: [AuthGuard] },
];
@NgModule({

View File

@ -12,32 +12,32 @@ import { Observable, from } from "rxjs";
import { takeUntil } from "rxjs/operators";
@Component({
selector: 'app-tenant-switch',
templateUrl: 'tenant-switch.component.html',
styleUrls: ['tenant-switch.component.scss']
selector: 'app-tenant-switch',
templateUrl: 'tenant-switch.component.html',
styleUrls: ['tenant-switch.component.scss']
})
export class TenantSwitchComponent extends BaseComponent implements OnInit {
tenants: Observable<Array<Tenant>>;
constructor(
private router: Router,
private keycloakService: KeycloakService,
private principalService: PrincipalService,
private keycloakService: KeycloakService,
private principalService: PrincipalService,
private authService: AuthService,
) {
super();
}
get currentTenant(): string {
return this.authService.selectedTenant();
}
get currentTenant(): string {
return this.authService.selectedTenant();
}
ngOnInit() {
this.tenants = this.loadUserTenants(); //TODO
//this.tenantChange.emit(this.getCurrentLanguage())
}
loadUserTenants(): Observable<Array<Tenant>> {
loadUserTenants(): Observable<Array<Tenant>> {
const params = new BaseHttpParams();
params.interceptorContext = {
excludedInterceptors: [InterceptorType.TenantHeaderInterceptor]
@ -46,9 +46,9 @@ export class TenantSwitchComponent extends BaseComponent implements OnInit {
}
onTenantSelected(selectedTenant: MatButtonToggleChange) {
if (selectedTenant.value === undefined || selectedTenant.value === '') return;
if (selectedTenant.value === undefined || selectedTenant.value === '') return;
this.formSubmit(selectedTenant.value);
this.formSubmit(selectedTenant.value);
this.loadUser();
}
@ -58,12 +58,12 @@ export class TenantSwitchComponent extends BaseComponent implements OnInit {
loadUser(): void {
this.authService.prepareAuthRequest(from(this.keycloakService.getToken()), {})
.pipe(takeUntil(this._destroyed))
.subscribe(
() => {
this.authService.onAuthenticateSuccessReload();
},
(error) => this.authService.onAuthenticateError(error)
);
.pipe(takeUntil(this._destroyed))
.subscribe(
() => {
this.authService.onAuthenticateSuccessReload();
},
(error) => this.authService.onAuthenticateError(error)
);
}
}

View File

@ -16,14 +16,14 @@ export class StatusCodeInterceptor extends BaseInterceptor {
type: InterceptorType;
interceptRequest(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent <any>> {
return next.handle(req).pipe(tap(event => { }, err => {
if (err.status === 480) {
this.router.navigate(['confirmation']);
}
const error: HttpError = this.httpErrorHandlingService.getError(err);
if (error.statusCode === 403 && error.errorCode === 103) {
this.authService.selectedTenant('default');
this.router.navigate(['/']);
}
// if (err.status === 480) {
// this.router.navigate(['confirmation']);
// }
// const error: HttpError = this.httpErrorHandlingService.getError(err);
// if (error.statusCode === 403 && error.errorCode === 103) {
// this.authService.selectedTenant('default');
// this.router.navigate(['/']);
// }
}));
}

View File

@ -1,6 +1,5 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AdminAuthGuard } from '@app/core/admin-auth-guard.service';
import { AppPermission } from '@app/core/common/enum/permission.enum';
import { AuthGuard } from '@app/core/auth-guard.service';
import { BreadcrumbService } from '@app/ui/misc/breadcrumb/breadcrumb.service';