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';
|
import { KeycloakProfile } from 'keycloak-js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export abstract class BaseUserService<Interface extends IIqserUser, Class extends IqserUser & Interface> extends EntitiesService<
|
export abstract class BaseUserService<
|
||||||
Interface,
|
Interface extends IIqserUser = IIqserUser,
|
||||||
Class
|
Class extends IqserUser & Interface = IqserUser & Interface,
|
||||||
> {
|
> extends EntitiesService<Interface, Class> {
|
||||||
readonly currentUser$: Observable<Class | undefined>;
|
readonly currentUser$: Observable<Class | undefined>;
|
||||||
protected abstract readonly _defaultModelPath: string;
|
protected abstract readonly _defaultModelPath: string;
|
||||||
protected abstract readonly _entityClass: new (entityInterface: Interface | KeycloakProfile, ...args: unknown[]) => Class;
|
protected abstract readonly _entityClass: new (entityInterface: Interface | KeycloakProfile, ...args: unknown[]) => Class;
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { IqserUser } from './user.model';
|
import { IqserUser } from './user.model';
|
||||||
import { IIqserUser } from './types/user.response';
|
|
||||||
import { BaseUserService } from './base-user.service';
|
import { BaseUserService } from './base-user.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DefaultUserService extends BaseUserService<IIqserUser, IqserUser> {
|
export class DefaultUserService extends BaseUserService {
|
||||||
protected readonly _defaultModelPath = 'user';
|
protected readonly _defaultModelPath = 'user';
|
||||||
protected readonly _entityClass = IqserUser;
|
protected readonly _entityClass = IqserUser;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,5 +3,10 @@ export * from './types/create-user.request';
|
|||||||
export * from './types/reset-password.request';
|
export * from './types/reset-password.request';
|
||||||
export * from './types/my-profile-update.request';
|
export * from './types/my-profile-update.request';
|
||||||
export * from './types/profile-update.request';
|
export * from './types/profile-update.request';
|
||||||
|
export * from './types/auth-module-options';
|
||||||
export * from './user.model';
|
export * from './user.model';
|
||||||
export * from './base-user.service';
|
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 { BaseUserService } from '../base-user.service';
|
||||||
import { IIqserUser } from './user.response';
|
import { IIqserUser } from './user.response';
|
||||||
import { IqserUser } from '../user.model';
|
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>> {
|
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 { CommonModule } from '@angular/common';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
@ -38,6 +38,7 @@ import { CommonUiOptions } from './utils/types/common-ui-options';
|
|||||||
import { BaseUserPreferenceService } from './services';
|
import { BaseUserPreferenceService } from './services';
|
||||||
import { BaseConfigService } from './services/base-config.service';
|
import { BaseConfigService } from './services/base-config.service';
|
||||||
import { DefaultUserPreferenceService } from './services/default-user-preference.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 matModules = [MatIconModule, MatProgressSpinnerModule, MatButtonModule, MatDialogModule, MatCheckboxModule, MatTooltipModule];
|
||||||
const modules = [
|
const modules = [
|
||||||
@ -75,29 +76,25 @@ const pipes = [SortByPipe, HumanizePipe, CapitalizePipe];
|
|||||||
imports: [CommonModule, ...matModules, ...modules, FormsModule, ReactiveFormsModule, KeycloakAngularModule, MatProgressBarModule],
|
imports: [CommonModule, ...matModules, ...modules, FormsModule, ReactiveFormsModule, KeycloakAngularModule, MatProgressBarModule],
|
||||||
exports: [...components, ...pipes, ...modules],
|
exports: [...components, ...pipes, ...modules],
|
||||||
})
|
})
|
||||||
export class CommonUiModule {
|
export class CommonUiModule extends ModuleWithOptions {
|
||||||
static forRoot<T extends BaseUserPreferenceService, C extends BaseConfigService>(
|
static forRoot<T extends BaseUserPreferenceService, C extends BaseConfigService>(
|
||||||
options?: CommonUiOptions<T, C>,
|
options: CommonUiOptions<T, C>,
|
||||||
): ModuleWithProviders<CommonUiModule> {
|
): ModuleWithProviders<CommonUiModule> {
|
||||||
const userPreferenceService = this._getService(
|
const userPreferenceService = this._getService(
|
||||||
BaseUserPreferenceService,
|
BaseUserPreferenceService,
|
||||||
DefaultUserPreferenceService,
|
DefaultUserPreferenceService,
|
||||||
options?.existingUserPreferenceService,
|
options.existingUserPreferenceService,
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ngModule: CommonUiModule,
|
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 './headers-configuration';
|
||||||
export * from './context.component';
|
export * from './context.component';
|
||||||
export * from './tokens';
|
export * from './tokens';
|
||||||
|
export * from './module-with-options';
|
||||||
export * from './base-app-config';
|
export * from './base-app-config';
|
||||||
export * from './types/common-ui-options';
|
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';
|
import { Type } from '@angular/core';
|
||||||
|
|
||||||
export interface CommonUiOptions<T extends BaseUserPreferenceService, C extends BaseConfigService> {
|
export interface CommonUiOptions<T extends BaseUserPreferenceService, C extends BaseConfigService> {
|
||||||
existingUserPreferenceService: Type<T>;
|
existingUserPreferenceService?: Type<T>;
|
||||||
configServiceFactory: () => C;
|
configServiceFactory: () => C;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user