add auth module
This commit is contained in:
parent
f1fa9464a9
commit
21c581bc61
31
src/lib/auth/auth.guard.ts
Normal file
31
src/lib/auth/auth.guard.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, Router } from '@angular/router';
|
||||
import { KeycloakAuthGuard, KeycloakService } from 'keycloak-angular';
|
||||
import { BaseConfigService } from '../services';
|
||||
import { BaseUserService } from './base-user.service';
|
||||
|
||||
@Injectable()
|
||||
export class AuthGuard extends KeycloakAuthGuard {
|
||||
constructor(
|
||||
protected readonly _router: Router,
|
||||
protected readonly _keycloak: KeycloakService,
|
||||
private readonly _configService: BaseConfigService,
|
||||
private readonly _userService: BaseUserService,
|
||||
) {
|
||||
super(_router, _keycloak);
|
||||
}
|
||||
|
||||
async isAccessAllowed(route: ActivatedRouteSnapshot): Promise<boolean> {
|
||||
if (!this.authenticated) {
|
||||
const kcIdpHint = route.queryParamMap.get('kc_idp_hint');
|
||||
await this._keycloak.login({
|
||||
idpHint: kcIdpHint ?? this._configService.values.OAUTH_IDP_HINT,
|
||||
redirectUri: window.location.href,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
await this._userService.loadCurrentUser();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
76
src/lib/auth/auth.module.ts
Normal file
76
src/lib/auth/auth.module.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import { APP_INITIALIZER, ModuleWithProviders, NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
|
||||
import { KeycloakAngularModule, KeycloakOptions, KeycloakService } from 'keycloak-angular';
|
||||
import { DefaultUserService } from './default-user.service';
|
||||
import { IIqserUser } from './types/user.response';
|
||||
import { BaseUserService } from './base-user.service';
|
||||
import { BASE_HREF, ModuleWithOptions } from '../utils';
|
||||
import { AuthModuleOptions } from './types/auth-module-options';
|
||||
import { IqserUser } from './user.model';
|
||||
import { RoleGuard } from './role.guard';
|
||||
import { AuthGuard } from './auth.guard';
|
||||
import { BaseConfigService } from '../services';
|
||||
|
||||
function getKeycloakOptions(baseUrl: string, configService: BaseConfigService): KeycloakOptions {
|
||||
let url: string = configService.values.OAUTH_URL;
|
||||
url = url.replace(/\/$/, ''); // remove trailing slash
|
||||
const realm = url.substring(url.lastIndexOf('/') + 1, url.length);
|
||||
url = url.substring(0, url.lastIndexOf('/realms'));
|
||||
return {
|
||||
config: {
|
||||
url: url,
|
||||
realm: realm,
|
||||
clientId: configService.values.OAUTH_CLIENT_ID,
|
||||
},
|
||||
initOptions: {
|
||||
checkLoginIframe: false,
|
||||
onLoad: 'check-sso',
|
||||
silentCheckSsoRedirectUri: window.location.origin + baseUrl + '/assets/oauth/silent-refresh.html',
|
||||
flow: 'standard',
|
||||
},
|
||||
enableBearerInterceptor: true,
|
||||
};
|
||||
}
|
||||
|
||||
function configureAutomaticRedirectToLoginScreen(keyCloakService: KeycloakService) {
|
||||
keyCloakService.getKeycloakInstance().onAuthRefreshError = () => {
|
||||
void keyCloakService.logout().then();
|
||||
};
|
||||
}
|
||||
|
||||
export function keycloakInitializer(
|
||||
keycloakService: KeycloakService,
|
||||
configService: BaseConfigService,
|
||||
baseUrl: string,
|
||||
): () => Promise<void> {
|
||||
const x = keycloakService.init(getKeycloakOptions(baseUrl, configService));
|
||||
return () => x.then(() => configureAutomaticRedirectToLoginScreen(keycloakService));
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, HttpClientModule, KeycloakAngularModule],
|
||||
providers: [
|
||||
AuthGuard,
|
||||
RoleGuard,
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
useFactory: keycloakInitializer,
|
||||
multi: true,
|
||||
deps: [KeycloakService, BaseConfigService, BASE_HREF],
|
||||
},
|
||||
],
|
||||
})
|
||||
export class AuthModule extends ModuleWithOptions {
|
||||
static forRoot<I extends IIqserUser, C extends IqserUser & I, S extends BaseUserService<I, C>>(
|
||||
options: AuthModuleOptions<I, C, S>,
|
||||
): ModuleWithProviders<AuthModule> {
|
||||
const userService = this._getService(BaseUserService, DefaultUserService, options.existingUserService);
|
||||
|
||||
return {
|
||||
ngModule: AuthModule,
|
||||
providers: [userService],
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -16,10 +16,10 @@ import { IProfileUpdateRequest } from './types/profile-update.request';
|
||||
import { KeycloakProfile } from 'keycloak-js';
|
||||
|
||||
@Injectable()
|
||||
export abstract class BaseUserService<Interface extends IIqserUser, Class extends IqserUser & Interface> extends EntitiesService<
|
||||
Interface,
|
||||
Class
|
||||
> {
|
||||
export abstract class BaseUserService<
|
||||
Interface extends IIqserUser = IIqserUser,
|
||||
Class extends IqserUser & Interface = IqserUser & Interface,
|
||||
> extends EntitiesService<Interface, Class> {
|
||||
readonly currentUser$: Observable<Class | undefined>;
|
||||
protected abstract readonly _defaultModelPath: string;
|
||||
protected abstract readonly _entityClass: new (entityInterface: Interface | KeycloakProfile, ...args: unknown[]) => Class;
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { IqserUser } from './user.model';
|
||||
import { IIqserUser } from './types/user.response';
|
||||
import { BaseUserService } from './base-user.service';
|
||||
|
||||
@Injectable()
|
||||
export class DefaultUserService extends BaseUserService<IIqserUser, IqserUser> {
|
||||
export class DefaultUserService extends BaseUserService {
|
||||
protected readonly _defaultModelPath = 'user';
|
||||
protected readonly _entityClass = IqserUser;
|
||||
}
|
||||
|
||||
@ -3,5 +3,10 @@ export * from './types/create-user.request';
|
||||
export * from './types/reset-password.request';
|
||||
export * from './types/my-profile-update.request';
|
||||
export * from './types/profile-update.request';
|
||||
export * from './types/auth-module-options';
|
||||
export * from './user.model';
|
||||
export * from './base-user.service';
|
||||
export * from './default-user.service';
|
||||
export * from './auth.module';
|
||||
export * from './auth.guard';
|
||||
export * from './role.guard';
|
||||
|
||||
29
src/lib/auth/role.guard.ts
Normal file
29
src/lib/auth/role.guard.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
|
||||
import { LoadingService } from '../loading';
|
||||
import { BaseUserService } from './base-user.service';
|
||||
|
||||
@Injectable()
|
||||
export class RoleGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly _router: Router,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _userService: BaseUserService,
|
||||
) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
|
||||
const currentUser = this._userService.currentUser;
|
||||
if (!currentUser || !currentUser.hasAnyRole) {
|
||||
await this._router.navigate(['/auth-error']);
|
||||
this._loadingService.stop();
|
||||
return false;
|
||||
}
|
||||
|
||||
const requiredRoles = route.data['requiredRoles'] as string[];
|
||||
if (requiredRoles) {
|
||||
return currentUser.hasAny(requiredRoles);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,8 @@
|
||||
import { Type } from '@angular/core';
|
||||
import { BaseUserService } from '../base-user.service';
|
||||
import { IIqserUser } from './user.response';
|
||||
import { IqserUser } from '../user.model';
|
||||
import { Type } from '@angular/core';
|
||||
|
||||
export interface AuthModuleOptions<I extends IIqserUser, C extends IqserUser & I, T extends BaseUserService<I, C> = BaseUserService<I, C>> {
|
||||
existingUserService: Type<T>;
|
||||
existingUserService?: Type<T>;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ModuleWithProviders, NgModule, Provider, Type } from '@angular/core';
|
||||
import { ModuleWithProviders, NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
@ -38,6 +38,7 @@ import { CommonUiOptions } from './utils/types/common-ui-options';
|
||||
import { BaseUserPreferenceService } from './services';
|
||||
import { BaseConfigService } from './services/base-config.service';
|
||||
import { DefaultUserPreferenceService } from './services/default-user-preference.service';
|
||||
import { ModuleWithOptions } from './utils/module-with-options';
|
||||
|
||||
const matModules = [MatIconModule, MatProgressSpinnerModule, MatButtonModule, MatDialogModule, MatCheckboxModule, MatTooltipModule];
|
||||
const modules = [
|
||||
@ -75,29 +76,25 @@ const pipes = [SortByPipe, HumanizePipe, CapitalizePipe];
|
||||
imports: [CommonModule, ...matModules, ...modules, FormsModule, ReactiveFormsModule, KeycloakAngularModule, MatProgressBarModule],
|
||||
exports: [...components, ...pipes, ...modules],
|
||||
})
|
||||
export class CommonUiModule {
|
||||
export class CommonUiModule extends ModuleWithOptions {
|
||||
static forRoot<T extends BaseUserPreferenceService, C extends BaseConfigService>(
|
||||
options?: CommonUiOptions<T, C>,
|
||||
options: CommonUiOptions<T, C>,
|
||||
): ModuleWithProviders<CommonUiModule> {
|
||||
const userPreferenceService = this._getService(
|
||||
BaseUserPreferenceService,
|
||||
DefaultUserPreferenceService,
|
||||
options?.existingUserPreferenceService,
|
||||
options.existingUserPreferenceService,
|
||||
);
|
||||
|
||||
return {
|
||||
ngModule: CommonUiModule,
|
||||
providers: [userPreferenceService],
|
||||
providers: [
|
||||
userPreferenceService,
|
||||
{
|
||||
provide: BaseConfigService,
|
||||
useFactory: options.configServiceFactory,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
private static _getService<B, D, E extends B>(base: B, _default: Type<D>, existing?: E): Provider {
|
||||
if (existing) {
|
||||
return {
|
||||
provide: base,
|
||||
useExisting: existing,
|
||||
};
|
||||
}
|
||||
return { provide: base, useClass: _default };
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,5 +18,6 @@ export * from './custom-route-reuse.strategy';
|
||||
export * from './headers-configuration';
|
||||
export * from './context.component';
|
||||
export * from './tokens';
|
||||
export * from './module-with-options';
|
||||
export * from './base-app-config';
|
||||
export * from './types/common-ui-options';
|
||||
|
||||
14
src/lib/utils/module-with-options.ts
Normal file
14
src/lib/utils/module-with-options.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Provider, Type } from '@angular/core';
|
||||
|
||||
export class ModuleWithOptions {
|
||||
protected static _getService<B, D, E>(base: B, _default: Type<D>, existing?: E): Provider {
|
||||
if (existing) {
|
||||
return {
|
||||
provide: base,
|
||||
useExisting: existing,
|
||||
};
|
||||
}
|
||||
|
||||
return { provide: base, useClass: _default };
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,6 @@ import { BaseConfigService, BaseUserPreferenceService } from '../../services';
|
||||
import { Type } from '@angular/core';
|
||||
|
||||
export interface CommonUiOptions<T extends BaseUserPreferenceService, C extends BaseConfigService> {
|
||||
existingUserPreferenceService: Type<T>;
|
||||
existingUserPreferenceService?: Type<T>;
|
||||
configServiceFactory: () => C;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user