From 6a6d3af94e8fced39879e611b306192ea6a47cfc Mon Sep 17 00:00:00 2001 From: "adel.kalimullin" Date: Wed, 26 Feb 2025 09:47:36 +0300 Subject: [PATCH] SUPPORT-8956: front mfe --- .../security/context/SecurityContextImpl.java | 20 +---- frontend/.env | 1 + frontend/normalize-css-path.js | 32 ++++++++ frontend/normalize-css-path.ts | 33 +++++++++ frontend/package-lock.json | 32 ++++++++ frontend/package.json | 2 + frontend/src/resources/css/font-faces.css | 41 ++++++++++ .../resources/template/webbpm/jwt-form.html | 39 ++++++++++ .../resources/template/webbpm/mfe-webbpm.html | 14 ++++ .../template/webbpm/mfe-wrapper.html | 1 + frontend/src/ts/mfe-app-tools.ts | 74 +++++++++++++++++++ frontend/src/ts/mfe-main.aot.ts | 18 +++++ frontend/src/ts/modules/app/app.module.ts | 2 + .../app/component/app-header.component.ts | 20 +++-- .../src/ts/modules/app/guard/RolesGuard.ts | 47 ++++++++++++ .../interceptor/token.interceptor.service.ts | 19 +++++ .../ts/modules/app/provider/token.provider.ts | 3 + .../app-progress-indication.service.ts | 14 ++-- .../app/service/authorization.service.ts | 55 ++++++++++++++ .../mfe/component/mfe-webbpm.component.ts | 37 ++++++++++ .../mfe/component/mfe-wrapper.component.ts | 13 ++++ .../src/ts/modules/mfe/guard/MfeRolesGuard.ts | 10 +++ .../mfe-default-interceptors.prod.ts | 12 +++ .../mfe-http-backend-interceptor.ts | 62 ++++++++++++++++ .../modules/mfe/mfe-webbpm-routing.module.ts | 38 ++++++++++ .../src/ts/modules/mfe/mfe-webbpm.module.ts | 69 +++++++++++++++++ .../provider/mfe-configuration.provider.ts | 14 ++++ .../mfe/provider/mfe-token.provider.ts | 9 +++ .../mfe/service/mfe-app-config.service.ts | 29 ++++++++ .../mfe/service/mfe-app-version.service.ts | 30 ++++++++ .../mfe-progress-indication.service.ts | 21 ++++++ .../webbpm/component/token-form.component.ts | 49 ++++++++++++ .../modules/webbpm/guard/WebbpmRolesGuard.ts | 11 +++ .../interceptor/default-interceptors.prod.ts | 4 +- .../interceptor/default-interceptors.ts | 4 +- .../webbpm/provider/webbpm-token-provider.ts | 12 +++ .../modules/webbpm/webbpm-routing.module.ts | 8 +- .../src/ts/modules/webbpm/webbpm.module.ts | 10 ++- frontend/webpack.aot.config.js | 23 ++++++ .../Создание заявки на добавление пользователя.page | 4 +- 40 files changed, 898 insertions(+), 38 deletions(-) create mode 100644 frontend/.env create mode 100644 frontend/normalize-css-path.js create mode 100644 frontend/normalize-css-path.ts create mode 100644 frontend/src/resources/css/font-faces.css create mode 100644 frontend/src/resources/template/webbpm/jwt-form.html create mode 100644 frontend/src/resources/template/webbpm/mfe-webbpm.html create mode 100644 frontend/src/resources/template/webbpm/mfe-wrapper.html create mode 100644 frontend/src/ts/mfe-app-tools.ts create mode 100644 frontend/src/ts/mfe-main.aot.ts create mode 100644 frontend/src/ts/modules/app/guard/RolesGuard.ts create mode 100644 frontend/src/ts/modules/app/interceptor/token.interceptor.service.ts create mode 100644 frontend/src/ts/modules/app/provider/token.provider.ts create mode 100644 frontend/src/ts/modules/app/service/authorization.service.ts create mode 100644 frontend/src/ts/modules/mfe/component/mfe-webbpm.component.ts create mode 100644 frontend/src/ts/modules/mfe/component/mfe-wrapper.component.ts create mode 100644 frontend/src/ts/modules/mfe/guard/MfeRolesGuard.ts create mode 100644 frontend/src/ts/modules/mfe/interceptor/mfe-default-interceptors.prod.ts create mode 100644 frontend/src/ts/modules/mfe/interceptor/mfe-http-backend-interceptor.ts create mode 100644 frontend/src/ts/modules/mfe/mfe-webbpm-routing.module.ts create mode 100644 frontend/src/ts/modules/mfe/mfe-webbpm.module.ts create mode 100644 frontend/src/ts/modules/mfe/provider/mfe-configuration.provider.ts create mode 100644 frontend/src/ts/modules/mfe/provider/mfe-token.provider.ts create mode 100644 frontend/src/ts/modules/mfe/service/mfe-app-config.service.ts create mode 100644 frontend/src/ts/modules/mfe/service/mfe-app-version.service.ts create mode 100644 frontend/src/ts/modules/mfe/service/mfe-progress-indication.service.ts create mode 100644 frontend/src/ts/modules/webbpm/component/token-form.component.ts create mode 100644 frontend/src/ts/modules/webbpm/guard/WebbpmRolesGuard.ts create mode 100644 frontend/src/ts/modules/webbpm/provider/webbpm-token-provider.ts diff --git a/backend/src/main/java/ru/micord/ervu/account_applications/security/context/SecurityContextImpl.java b/backend/src/main/java/ru/micord/ervu/account_applications/security/context/SecurityContextImpl.java index 0d3b8438..ee9dd5a2 100644 --- a/backend/src/main/java/ru/micord/ervu/account_applications/security/context/SecurityContextImpl.java +++ b/backend/src/main/java/ru/micord/ervu/account_applications/security/context/SecurityContextImpl.java @@ -11,25 +11,13 @@ public class SecurityContextImpl implements SecurityContext { @Override public String getRecruitmentId() { - JwtTokenAuthentication jwtTokenAuthentication = (JwtTokenAuthentication) - SecurityContextHolder.getContext().getAuthentication(); - if (jwtTokenAuthentication != null) { - return jwtTokenAuthentication.getUserSession().recruitmentId(); + JwtTokenAuthentication auth = (JwtTokenAuthentication) SecurityContextHolder.getContext().getAuthentication(); + return auth != null ? auth.getUserSession().recruitmentId() : null; } - else { - return null; - } - } @Override public UserSession getUserSession() { - JwtTokenAuthentication jwtTokenAuthentication = (JwtTokenAuthentication) - SecurityContextHolder.getContext().getAuthentication(); - if (jwtTokenAuthentication != null) { - return jwtTokenAuthentication.getUserSession(); - } - else { - return null; - } + JwtTokenAuthentication auth = (JwtTokenAuthentication) SecurityContextHolder.getContext().getAuthentication(); + return auth != null ? auth.getUserSession() : null; } } diff --git a/frontend/.env b/frontend/.env new file mode 100644 index 00000000..66a2c1aa --- /dev/null +++ b/frontend/.env @@ -0,0 +1 @@ +MFE_BASE_URL=/mfe/accounts-applications diff --git a/frontend/normalize-css-path.js b/frontend/normalize-css-path.js new file mode 100644 index 00000000..705e04ed --- /dev/null +++ b/frontend/normalize-css-path.js @@ -0,0 +1,32 @@ +"use strict"; +exports.__esModule = true; +var fs = require('fs'); +var mfeBaseUrlKey = 'MFE_BASE_URL'; +var srcUrlRegex = /url\((\\?["'])?(?!data:)\S+(\\?["'])?\)/g; +function normalizeCssPaths(params) { + params.paths = params.paths ? params.paths : []; + params.paths.forEach(function (path) { return normalizeCssPath(path, params.outDir); }); +} +exports.normalizeCssPaths = normalizeCssPaths; +function normalizeCssPath(path, outputDirectory) { + console.log("Start processing " + path); + var css = fs.readFileSync(path, 'utf8'); + var counter = 0; + var processedCss = css.replace(srcUrlRegex, function (srcUrl) { + if (srcUrl.search(outputDirectory) != -1) + return srcUrl; + var fileName = getFileName(srcUrl); + var processedUrl = "url('" + outputDirectory + "/" + fileName + "')"; + counter++; + console.log("Replaced " + srcUrl + " -> " + processedUrl); + return processedUrl; + }); + console.log("Replaced " + counter + " urls"); + fs.writeFileSync(path, processedCss); +} +function getFileName(srcUrl) { + var url = srcUrl.substring(4, srcUrl.length - 1); // unbox 'url(...)' + url = url.replace(/(\\?["'])/g, ''); + var urlPaths = url.split('/'); + return urlPaths[urlPaths.length - 1].split('?')[0]; +} diff --git a/frontend/normalize-css-path.ts b/frontend/normalize-css-path.ts new file mode 100644 index 00000000..0fa39a3f --- /dev/null +++ b/frontend/normalize-css-path.ts @@ -0,0 +1,33 @@ +const fs = require('fs'); + +const srcUrlRegex = /url\((\\?["'])?(?!data:)\S+(\\?["'])?\)/g; + +export function normalizeCssPaths(params: {paths: string[], outDir: string}) { + params.paths = params.paths ? params.paths : []; + params.paths.forEach(path => normalizeCssPath(path, params.outDir)); +} + +function normalizeCssPath(path: string, outputDirectory: string) { + console.log(`Start processing ${path}`); + const css: string = fs.readFileSync(path, 'utf8'); + let counter = 0; + + const processedCss = css.replace(srcUrlRegex, (srcUrl: string) => { + if (srcUrl.search(outputDirectory) != -1) return srcUrl; + + let fileName = getFileName(srcUrl); + let processedUrl = `url('${outputDirectory}/${fileName}')`; + counter++; + console.log(`Replaced ${srcUrl} -> ${processedUrl}`); + return processedUrl; + }); + console.log(`Replaced ${counter} urls`); + fs.writeFileSync(path, processedCss); +} + +function getFileName(srcUrl: string): string { + let url = srcUrl.substring(4, srcUrl.length - 1); // unbox 'url(...)' + url = url.replace(/(\\?["'])/g, ''); + let urlPaths = url.split('/'); + return urlPaths[urlPaths.length - 1].split('?')[0]; +} \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index bc54a3a9..a73cb81f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -3276,6 +3276,38 @@ } } }, + "dotenv": { + "version": "16.4.7", + "resolved": "https://repo.micord.ru/repository/npm-all/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "dev": true + }, + "dotenv-defaults": { + "version": "2.0.2", + "resolved": "https://repo.micord.ru/repository/npm-all/dotenv-defaults/-/dotenv-defaults-2.0.2.tgz", + "integrity": "sha512-iOIzovWfsUHU91L5i8bJce3NYK5JXeAwH50Jh6+ARUdLiiGlYWfGw6UkzsYqaXZH/hjE/eCd/PlfM/qqyK0AMg==", + "dev": true, + "requires": { + "dotenv": "^8.2.0" + }, + "dependencies": { + "dotenv": { + "version": "8.6.0", + "resolved": "https://repo.micord.ru/repository/npm-all/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "dev": true + } + } + }, + "dotenv-webpack": { + "version": "8.1.0", + "resolved": "https://repo.micord.ru/repository/npm-all/dotenv-webpack/-/dotenv-webpack-8.1.0.tgz", + "integrity": "sha512-owK1JcsPkIobeqjVrk6h7jPED/W6ZpdFsMPR+5ursB7/SdgDyO+VzAU+szK8C8u3qUhtENyYnj8eyXMR5kkGag==", + "dev": true, + "requires": { + "dotenv-defaults": "^2.0.2" + } + }, "downloadjs": { "version": "1.4.8", "resolved": "https://repo.micord.ru/repository/npm-all/downloadjs/-/downloadjs-1.4.8.tgz", diff --git a/frontend/package.json b/frontend/package.json index 7d832ac8..038f0690 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -84,6 +84,8 @@ "cross-env": "5.2.1", "css-loader": "6.11.0", "del": "2.2.2", + "dotenv": "^16.4.5", + "dotenv-webpack": "^8.1.0", "file-loader": "6.2.0", "html-webpack-plugin": "5.6.0", "mini-css-extract-plugin": "2.9.1", diff --git a/frontend/src/resources/css/font-faces.css b/frontend/src/resources/css/font-faces.css new file mode 100644 index 00000000..d1fc0315 --- /dev/null +++ b/frontend/src/resources/css/font-faces.css @@ -0,0 +1,41 @@ +/* + * @font-face from bootstrap-icons + */ +@font-face { + font-display: block; + font-family: "bootstrap-icons"; + src: url("../../../node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff2?24e3eb84d0bcaf83d77f904c78ac1f47") format("woff2"), + url("../../../node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff?24e3eb84d0bcaf83d77f904c78ac1f47") format("woff"); +} + +/* + * @font-face from font-awesome + */ +@font-face { + font-family: 'FontAwesome'; + src: url('../../../node_modules/font-awesome/fonts/fontawesome-webfont.eot?v=4.7.0'); + src: url('../../../node_modules/font-awesome/fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'), + url('../../../node_modules/font-awesome/fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'), + url('../../../node_modules/font-awesome/fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'), + url('../../../node_modules/font-awesome/fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'), + url('../../../node_modules/font-awesome/fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'Golos'; + src: url('../fonts/golos-regular.ttf'); +} +@font-face { + font-family: 'GolosM'; + src: url('../fonts/golos-medium.ttf'); +} +@font-face { + font-family: 'GolosUI'; + src: url('../fonts/golos-ul-regular.ttf'); +} +@font-face { + font-family: 'GolosUIM'; + src: url('../fonts/golos-ul-medium.ttf'); +} \ No newline at end of file diff --git a/frontend/src/resources/template/webbpm/jwt-form.html b/frontend/src/resources/template/webbpm/jwt-form.html new file mode 100644 index 00000000..85557948 --- /dev/null +++ b/frontend/src/resources/template/webbpm/jwt-form.html @@ -0,0 +1,39 @@ +
+
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+ +
+
diff --git a/frontend/src/resources/template/webbpm/mfe-webbpm.html b/frontend/src/resources/template/webbpm/mfe-webbpm.html new file mode 100644 index 00000000..149e160a --- /dev/null +++ b/frontend/src/resources/template/webbpm/mfe-webbpm.html @@ -0,0 +1,14 @@ +
+
+ + + +
+
+ +
+
+ + +
+
diff --git a/frontend/src/resources/template/webbpm/mfe-wrapper.html b/frontend/src/resources/template/webbpm/mfe-wrapper.html new file mode 100644 index 00000000..b2bb26fe --- /dev/null +++ b/frontend/src/resources/template/webbpm/mfe-wrapper.html @@ -0,0 +1 @@ + diff --git a/frontend/src/ts/mfe-app-tools.ts b/frontend/src/ts/mfe-app-tools.ts new file mode 100644 index 00000000..522bd7e5 --- /dev/null +++ b/frontend/src/ts/mfe-app-tools.ts @@ -0,0 +1,74 @@ +import {platformBrowserDynamic} from "@angular/platform-browser-dynamic"; +import {MfeConfigurationProvider} from "./modules/mfe/provider/mfe-configuration.provider"; +import {NgModuleRef} from "@angular/core"; + +let childEventHandlerFromContainer = null; + +export type ChildEventType = 'navigate' | 'token-request' +export type ParentEventType = 'navigate'; + +export function fireMfeEventToContainer(eventType: ChildEventType, eventData: any): Promise { + if (typeof childEventHandlerFromContainer === 'function') { + return childEventHandlerFromContainer(eventType, eventData); + } + else { + throw new Error( + 'Event fired from child MFE to container before being bootstrapped as MFE App', + ); + } +} + +export function bootstrapMfeApp(createApp: () => Promise | void>) { + function mount( + element: HTMLElement, + settings: { + standalone: boolean, + useShadowDom?: boolean, + containerBaseUrl?: string, + componentBaseUrl?: string, + startUrl: string, + childEventHandler?: any, + params?: any + } = { + standalone: false, + startUrl: '/mfe/business-metrics' + }, + ) { + let containerBaseUrl = settings.containerBaseUrl || ''; // префикс ресурса + let startUrl = settings.startUrl || ''; // ресурс хост-приложения + MfeConfigurationProvider.setPageBaseUrl(joinPath(containerBaseUrl, startUrl)); + + element.appendChild(createContainerForBootstrap()) + + childEventHandlerFromContainer = settings.childEventHandler; + + createApp(); + return { + parentEventHandler(eventType: ParentEventType, url: string) { + }, + unmount() { + console.log("Unmounting business-metrics application"); + platformBrowserDynamic().destroy(); + }, + } + } + + return { + mount, + }; + +} + +function createContainerForBootstrap(): HTMLElement { + let mfeBootstrapContainer = document.createElement('div'); + mfeBootstrapContainer.setAttribute(MfeConfigurationProvider.BASE_COMPONENT_ATTRIBUTE, ''); + return mfeBootstrapContainer; +} + +export function joinPath(...paths: string[]): string { + return '/' + paths + .filter(path => path) + .map(path => path.endsWith('/') ? path.substring(0, path.length - 1) : path) + .map(path => path.startsWith('/') ? path.substring(1, path.length) : path) + .join('/') +} diff --git a/frontend/src/ts/mfe-main.aot.ts b/frontend/src/ts/mfe-main.aot.ts new file mode 100644 index 00000000..e8d5fa94 --- /dev/null +++ b/frontend/src/ts/mfe-main.aot.ts @@ -0,0 +1,18 @@ +import 'zone.js/dist/zone'; + +import {enableProdMode} from "@angular/core"; +import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; +import {bootstrapMfeApp} from './mfe-app-tools'; +// @ts-ignore +import {MfeWebbpmModuleNgFactory} from "./modules/mfe/mfe-webbpm.module.ngfactory"; + +window['dev_mode'] = false; +enableProdMode(); + +const mount = bootstrapMfeApp(() => { + return platformBrowserDynamic() + .bootstrapModuleFactory(MfeWebbpmModuleNgFactory) + .catch((err) => console.error(err)); +}).mount; + +export {mount}; diff --git a/frontend/src/ts/modules/app/app.module.ts b/frontend/src/ts/modules/app/app.module.ts index 2a9ff848..118de2c7 100644 --- a/frontend/src/ts/modules/app/app.module.ts +++ b/frontend/src/ts/modules/app/app.module.ts @@ -21,6 +21,7 @@ import {AppProgressIndicationComponent} from "./component/app-progress-indicatio import {AppProgressIndicationService} from "./service/app-progress-indication.service"; import {VBoxLoadValues} from "../../account_applications/component/container/VBoxLoadValues"; import {ErvuAccountTextFieldGridEditor} from "../../account_applications/component/editablegrid/editors/ErvuAccountTextFieldGridEditor"; +import {TokenInterceptor} from "./interceptor/token.interceptor.service"; registerLocaleData(localeRu); export const DIRECTIVES = [ @@ -53,6 +54,7 @@ export const DIRECTIVES = [ DIRECTIVES ], providers: [ + TokenInterceptor, { provide: ProgressIndicationService, useClass: AppProgressIndicationService } ], bootstrap: [], diff --git a/frontend/src/ts/modules/app/component/app-header.component.ts b/frontend/src/ts/modules/app/component/app-header.component.ts index 75c4f249..eaa14ca6 100644 --- a/frontend/src/ts/modules/app/component/app-header.component.ts +++ b/frontend/src/ts/modules/app/component/app-header.component.ts @@ -1,7 +1,5 @@ -import {ChangeDetectionStrategy, Component} from "@angular/core"; -import {Router} from "@angular/router"; -import {UserService, Session} from "@webbpm/base-package"; -import {Observable} from "rxjs"; +import {ChangeDetectionStrategy, ChangeDetectorRef, Component} from "@angular/core"; +import {AuthorizationService} from "../service/authorization.service"; @Component({ moduleId: module.id, @@ -11,10 +9,16 @@ import {Observable} from "rxjs"; }) export class AppHeaderComponent { - public currentSession: Observable; + name: string; + realm: string; - constructor(protected userService: UserService, - protected router: Router) { - this.currentSession = this.userService.getCurrentSession(); + constructor(protected authService: AuthorizationService, + protected cd: ChangeDetectorRef) { + authService.onSessionUpdate + .subscribe(session => { + this.name = session.name; + this.realm = session.realm; + cd.markForCheck() + }) } } diff --git a/frontend/src/ts/modules/app/guard/RolesGuard.ts b/frontend/src/ts/modules/app/guard/RolesGuard.ts new file mode 100644 index 00000000..c43a2fc8 --- /dev/null +++ b/frontend/src/ts/modules/app/guard/RolesGuard.ts @@ -0,0 +1,47 @@ +import { + ActivatedRouteSnapshot, + CanActivate, + Router, + RouterStateSnapshot, + UrlTree +} from "@angular/router"; +import {Injectable} from "@angular/core"; +import {AuthorizationService} from "../service/authorization.service"; +import {TokenProvider} from "../provider/token.provider"; + +@Injectable({providedIn: 'root'}) +export class RolesGuard implements CanActivate{ + + protected readonly allowedRoles: string[] = []; + + constructor(protected authService: AuthorizationService, + protected tokenProvider: TokenProvider, + protected router: Router) { + } + + async canActivate( + route: ActivatedRouteSnapshot, state: RouterStateSnapshot + ): Promise { + if (!await this.tokenProvider.getToken()) { + return this.getUrlOnFailure() + } + + if (!this.authService.isAuthorized()) { + return this.authService.getCurrentSession() + .then(() => this.checkRoles() ? true : this.getUrlOnFailure()) + .catch(() => this.getUrlOnFailure()); + } + else { + return this.checkRoles(); + } + } + + protected getUrlOnFailure(): UrlTree { + return null; + } + + protected checkRoles(): boolean { + return this.allowedRoles.length === 0 + || this.authService.hasAnyRole(this.allowedRoles); + } +} \ No newline at end of file diff --git a/frontend/src/ts/modules/app/interceptor/token.interceptor.service.ts b/frontend/src/ts/modules/app/interceptor/token.interceptor.service.ts new file mode 100644 index 00000000..52556fb4 --- /dev/null +++ b/frontend/src/ts/modules/app/interceptor/token.interceptor.service.ts @@ -0,0 +1,19 @@ +import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from "@angular/common/http"; +import {from, Observable} from "rxjs"; +import {TokenProvider} from "../provider/token.provider"; + +export class TokenInterceptor implements HttpInterceptor{ + constructor(protected tokenProvider: TokenProvider) { } + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + return from(this.handle(request, next)) + } + + private async handle(request: HttpRequest, next: HttpHandler): Promise> { + const token = await this.tokenProvider.getToken(); + request = request.clone({ + setHeaders: { Authorization: `Bearer ${token}` } + }); + return next.handle(request).toPromise(); + } +} \ No newline at end of file diff --git a/frontend/src/ts/modules/app/provider/token.provider.ts b/frontend/src/ts/modules/app/provider/token.provider.ts new file mode 100644 index 00000000..f88da2f2 --- /dev/null +++ b/frontend/src/ts/modules/app/provider/token.provider.ts @@ -0,0 +1,3 @@ +export class TokenProvider { + public getToken(): Promise { return null } +} \ No newline at end of file diff --git a/frontend/src/ts/modules/app/service/app-progress-indication.service.ts b/frontend/src/ts/modules/app/service/app-progress-indication.service.ts index 48100d5f..f894f86c 100644 --- a/frontend/src/ts/modules/app/service/app-progress-indication.service.ts +++ b/frontend/src/ts/modules/app/service/app-progress-indication.service.ts @@ -10,16 +10,16 @@ export class AppProgressIndicationService { event.stopPropagation(); }; - private counter: number = 0; - private focused: any; - private ngbModalRef: NgbModalRef; - private options: NgbModalOptions = { + protected counter: number = 0; + protected focused: any; + protected ngbModalRef: NgbModalRef; + protected options: NgbModalOptions = { backdrop: 'static', keyboard: false, windowClass: 'modal-center loader' }; - constructor(private ngbModal: NgbModal) { + constructor(protected ngbModal: NgbModal) { } public showProgressBar(): boolean { @@ -58,11 +58,11 @@ export class AppProgressIndicationService { } } - private showProgressIndicator() { + protected showProgressIndicator() { this.ngbModalRef = this.ngbModal.open(AppProgressIndicationComponent, this.options); } - private hideProgressIndicator() { + protected hideProgressIndicator() { this.ngbModalRef.dismiss('cancel'); this.ngbModalRef = null; } diff --git a/frontend/src/ts/modules/app/service/authorization.service.ts b/frontend/src/ts/modules/app/service/authorization.service.ts new file mode 100644 index 00000000..5e872045 --- /dev/null +++ b/frontend/src/ts/modules/app/service/authorization.service.ts @@ -0,0 +1,55 @@ +import {Injectable} from "@angular/core"; +import {Subject} from "rxjs"; +import {HttpClient} from "@angular/common/http"; + +export interface UserSession { + name: string, + realm: string, + recruitmentId: string, + roles: string[] +} + +@Injectable({providedIn: 'root'}) +export class AuthorizationService { + + private session: UserSession; + + public onSessionUpdate: Subject = new Subject(); + + constructor(protected httpClient: HttpClient) {} + + public getCurrentSession(): Promise { + if (this.session) return new Promise(resolve => resolve(this.session)) + return this.httpClient.get('session') + .toPromise() + .then((session: UserSession) => { + this.session = session; + this.onSessionUpdate.next(session); + return session; + }) + } + + isAuthorized(): boolean { + return !!this.session; + } + + hasAnyRole(roles: string[]): boolean { + return this.isAuthorized() && roles.some(role => this.getRoles().includes(role)); + } + + getName(): string { + return this.isAuthorized() ? this.session.name : null; + } + + getRealm(): string { + return this.isAuthorized() ? this.session.realm : null; + } + + getRecruitmentId(): string { + return this.isAuthorized() ? this.session.recruitmentId : null; + } + + getRoles(): string[] { + return this.isAuthorized() ? this.session.roles : null; + } +} \ No newline at end of file diff --git a/frontend/src/ts/modules/mfe/component/mfe-webbpm.component.ts b/frontend/src/ts/modules/mfe/component/mfe-webbpm.component.ts new file mode 100644 index 00000000..4e3a513c --- /dev/null +++ b/frontend/src/ts/modules/mfe/component/mfe-webbpm.component.ts @@ -0,0 +1,37 @@ +import {Component, ViewEncapsulation} from "@angular/core"; +import { + Event, + NavigationCancel, + NavigationEnd, + NavigationError, + NavigationStart, + Router +} from "@angular/router"; +import {ProgressIndicationService} from "@webbpm/base-package"; + + +@Component({ + moduleId: module.id, + encapsulation: ViewEncapsulation.ShadowDom, + selector: 'mfe-webbpm', + templateUrl: './../../../../../src/resources/template/webbpm/mfe-webbpm.html', + styleUrls: ['./../../../../../src/resources/css/style.css'], + }) +export class MfeWebbpmComponent { + public headerVisible: boolean = true; + public footerVisible: boolean = true; + + constructor(private router: Router, + private progressIndicationService: ProgressIndicationService) { + router.events.subscribe((event: Event) => { + if (event instanceof NavigationStart) { + progressIndicationService.showProgressBar(); + } + else if (event instanceof NavigationEnd + || event instanceof NavigationError + || event instanceof NavigationCancel) { + progressIndicationService.hideProgressBar(); + } + }) + } +} diff --git a/frontend/src/ts/modules/mfe/component/mfe-wrapper.component.ts b/frontend/src/ts/modules/mfe/component/mfe-wrapper.component.ts new file mode 100644 index 00000000..f9bd0839 --- /dev/null +++ b/frontend/src/ts/modules/mfe/component/mfe-wrapper.component.ts @@ -0,0 +1,13 @@ +import {Component} from "@angular/core"; +import {MfeConfigurationProvider} from "../provider/mfe-configuration.provider"; + + +@Component({ + moduleId: module.id, + selector: `[${MfeConfigurationProvider.BASE_COMPONENT_ATTRIBUTE}]`, + templateUrl: './../../../../../src/resources/template/webbpm/mfe-wrapper.html', + styleUrls: ['./../../../../../src/resources/css/font-faces.css'] + }) +export class MfeWrapperComponent { + +} \ No newline at end of file diff --git a/frontend/src/ts/modules/mfe/guard/MfeRolesGuard.ts b/frontend/src/ts/modules/mfe/guard/MfeRolesGuard.ts new file mode 100644 index 00000000..d4f7abe4 --- /dev/null +++ b/frontend/src/ts/modules/mfe/guard/MfeRolesGuard.ts @@ -0,0 +1,10 @@ +import {RolesGuard} from "../../app/guard/RolesGuard"; +import {UrlTree} from "@angular/router"; +import {Injectable} from "@angular/core"; + +@Injectable({providedIn: 'root'}) +export class MfeRolesGuard extends RolesGuard { + protected getUrlOnFailure(): UrlTree { + return this.router.createUrlTree(['access-denied']); + } +} \ No newline at end of file diff --git a/frontend/src/ts/modules/mfe/interceptor/mfe-default-interceptors.prod.ts b/frontend/src/ts/modules/mfe/interceptor/mfe-default-interceptors.prod.ts new file mode 100644 index 00000000..7b22df38 --- /dev/null +++ b/frontend/src/ts/modules/mfe/interceptor/mfe-default-interceptors.prod.ts @@ -0,0 +1,12 @@ +import {HTTP_INTERCEPTORS} from "@angular/common/http"; +import {MfeHttpBackendInterceptor} from "./mfe-http-backend-interceptor"; +import {FormDirtyInterceptor, HttpSecurityErrorInterceptor} from "@webbpm/base-package"; +import {TokenInterceptor} from "../../app/interceptor/token.interceptor.service"; + + +export const DEFAULT_HTTP_INTERCEPTOR_PROVIDERS = [ + {provide: HTTP_INTERCEPTORS, useClass: MfeHttpBackendInterceptor, multi: true}, + {provide: HTTP_INTERCEPTORS, useClass: HttpSecurityErrorInterceptor, multi: true}, + {provide: HTTP_INTERCEPTORS, useClass: FormDirtyInterceptor, multi: true}, + {provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true} +]; \ No newline at end of file diff --git a/frontend/src/ts/modules/mfe/interceptor/mfe-http-backend-interceptor.ts b/frontend/src/ts/modules/mfe/interceptor/mfe-http-backend-interceptor.ts new file mode 100644 index 00000000..ae1409ea --- /dev/null +++ b/frontend/src/ts/modules/mfe/interceptor/mfe-http-backend-interceptor.ts @@ -0,0 +1,62 @@ +import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from "@angular/common/http"; +import { + AppConfigService, + ApplicationSettingsProvider, + AppVersionService, + TokenHeaderUtil +} from "@webbpm/base-package"; +import {Observable} from "rxjs"; +import {MfeConfigurationProvider} from "../provider/mfe-configuration.provider"; +import {joinPath} from "../../../mfe-app-tools"; + + +export class MfeHttpBackendInterceptor implements HttpInterceptor { + private static readonly CONTENT_TYPE_HEADER = 'Content-Type'; + private static readonly ENABLE_VERSION_IN_URL: string = "enable.version.in.url"; + private static readonly BACKEND_URL: string = "backend.url"; + private static readonly BACKEND_CONTEXT: string = "backend.context"; + + constructor(private appConfigService: AppConfigService, + private appVersionService: AppVersionService) { + } + + intercept(req: HttpRequest, next: HttpHandler): Observable> { + req = TokenHeaderUtil.addHeader( + req, MfeHttpBackendInterceptor.CONTENT_TYPE_HEADER, 'application/json;charset=UTF-8' + ); + + let overrideObj + + if (!req.url.startsWith(ApplicationSettingsProvider.RESOURCES_PATH) + && !req.url.startsWith('http:') && !req.url.startsWith('https:')) { + + let appVersionInUrl = this.getVersion(); + let backendUrl = this.appConfigService.getParamValue(MfeHttpBackendInterceptor.BACKEND_URL); + let backendContext = this.appConfigService.getParamValue( + MfeHttpBackendInterceptor.BACKEND_CONTEXT); + + let url; + + if (backendUrl) { + url = backendUrl; + } + else if (backendContext) { + url = joinPath( + MfeConfigurationProvider.MFE_BASE_URL, + backendContext + ); + } + else { + url = ApplicationSettingsProvider.BACKEND_URL; + } + + overrideObj = {url: `${url}${appVersionInUrl}/${req.url}`}; + } + return next.handle(req.clone(overrideObj)); + } + + private getVersion(): string { + return this.appConfigService.getParamValue(MfeHttpBackendInterceptor.ENABLE_VERSION_IN_URL) == + "true" ? "-" + this.appVersionService.getAppVersion() : ""; + } +} \ No newline at end of file diff --git a/frontend/src/ts/modules/mfe/mfe-webbpm-routing.module.ts b/frontend/src/ts/modules/mfe/mfe-webbpm-routing.module.ts new file mode 100644 index 00000000..3dff4a5e --- /dev/null +++ b/frontend/src/ts/modules/mfe/mfe-webbpm-routing.module.ts @@ -0,0 +1,38 @@ +import {ConfirmExitGuard} from "@webbpm/base-package"; +import {RolesGuard} from "../app/guard/RolesGuard"; +import {RouterModule, Routes} from "@angular/router"; +import {AccessDeniedComponent} from "../app/component/access-denied.component"; +import {NgModule} from "@angular/core"; +import {APP_BASE_HREF} from "@angular/common"; +import {MfeConfigurationProvider} from "./provider/mfe-configuration.provider"; + +const webbpmRoutes: Routes = [ + { + path: 'access-denied', + component: AccessDeniedComponent, + }, + { + path: '', + loadChildren: 'generated-sources/page-conversion.module#PageconversionModule', + canActivate: [ConfirmExitGuard, RolesGuard], + pathMatch: 'full', + }, + { + path: '**', + redirectTo: '', + } +]; + +@NgModule({ + providers: [ + {provide: APP_BASE_HREF, useFactory: () => MfeConfigurationProvider.getPageBaseUrl()} + ], + imports: [RouterModule.forRoot(webbpmRoutes, { + useHash: false, + onSameUrlNavigation: "reload" + })], + exports: [RouterModule] + }) +export class MfeWebbpmRoutingModule { + +} diff --git a/frontend/src/ts/modules/mfe/mfe-webbpm.module.ts b/frontend/src/ts/modules/mfe/mfe-webbpm.module.ts new file mode 100644 index 00000000..ecf2e89f --- /dev/null +++ b/frontend/src/ts/modules/mfe/mfe-webbpm.module.ts @@ -0,0 +1,69 @@ +import {BrowserAnimationsModule} from "@angular/platform-browser/animations"; +import {BrowserModule} from "@angular/platform-browser"; +import {FormsModule} from "@angular/forms"; +import {NgbModule} from "@ng-bootstrap/ng-bootstrap"; +import {ToastNoAnimationModule} from "ngx-toastr"; +import {AgGridModule} from "ag-grid-angular"; +import {AppRoutingModule} from "../app/app-routing.module"; +import { + AppConfigService, + AppVersionService, + BpmnModule, + ComponentsModule, + CoreModule, ProgressIndicationService +} from "@webbpm/base-package"; +import {AppModule} from "../app/app.module"; +import {MfeWebbpmRoutingModule} from "./mfe-webbpm-routing.module"; +import {ErrorHandler, NgModule} from "@angular/core"; +import {MfeWrapperComponent} from "./component/mfe-wrapper.component"; +import {MfeWebbpmComponent} from "./component/mfe-webbpm.component"; +import {AccessDeniedComponent} from "../app/component/access-denied.component"; +import {MfeAppVersionService} from "./service/mfe-app-version.service"; +import {MfeAppConfigService} from "./service/mfe-app-config.service"; +import {GlobalErrorHandler} from "../webbpm/handler/global-error.handler.prod"; +import {MfeProgressIndicationService} from "./service/mfe-progress-indication.service"; +import {MfeRolesGuard} from "./guard/MfeRolesGuard"; +import {RolesGuard} from "../app/guard/RolesGuard"; +import {TokenProvider} from "../app/provider/token.provider"; +import {MfeTokenProvider} from "./provider/mfe-token.provider"; +import {DEFAULT_HTTP_INTERCEPTOR_PROVIDERS} from "./interceptor/mfe-default-interceptors.prod"; + + +let IMPORTS = [ + BrowserAnimationsModule, + BrowserModule, + FormsModule, + NgbModule, + ToastNoAnimationModule.forRoot(), + AgGridModule, + AppRoutingModule, + BpmnModule, + CoreModule, + ComponentsModule, + AppModule, + MfeWebbpmRoutingModule +]; + +@NgModule({ + imports: IMPORTS, + declarations: [ + MfeWrapperComponent, + MfeWebbpmComponent, + AccessDeniedComponent + ], + exports: [], + providers: [ + {provide: AppVersionService, useClass: MfeAppVersionService}, + {provide: AppConfigService, useClass: MfeAppConfigService}, + {provide: ErrorHandler, useClass: GlobalErrorHandler}, + {provide: ProgressIndicationService, useClass: MfeProgressIndicationService}, + {provide: RolesGuard, useClass: MfeRolesGuard}, + {provide: TokenProvider, useClass: MfeTokenProvider}, + DEFAULT_HTTP_INTERCEPTOR_PROVIDERS + ], + bootstrap: [ + MfeWrapperComponent + ] + }) +export class MfeWebbpmModule { +} diff --git a/frontend/src/ts/modules/mfe/provider/mfe-configuration.provider.ts b/frontend/src/ts/modules/mfe/provider/mfe-configuration.provider.ts new file mode 100644 index 00000000..82f6ca8b --- /dev/null +++ b/frontend/src/ts/modules/mfe/provider/mfe-configuration.provider.ts @@ -0,0 +1,14 @@ +export class MfeConfigurationProvider { + private static config; // way to avoid webpack optimization + public static MFE_BASE_URL = process.env.MFE_BASE_URL || ''; + public static BASE_COMPONENT_ATTRIBUTE = 'mfe-webbpm-account-applications'; + + public static getPageBaseUrl(): string { + return this.config.PAGE_BASE_URL + } + + public static setPageBaseUrl(pageBaseUrl: string) { + if (!this.config) this.config = {}; + this.config.PAGE_BASE_URL = pageBaseUrl; + } +} diff --git a/frontend/src/ts/modules/mfe/provider/mfe-token.provider.ts b/frontend/src/ts/modules/mfe/provider/mfe-token.provider.ts new file mode 100644 index 00000000..8ad174b8 --- /dev/null +++ b/frontend/src/ts/modules/mfe/provider/mfe-token.provider.ts @@ -0,0 +1,9 @@ +import {TokenProvider} from "../../app/provider/token.provider"; +import {fireMfeEventToContainer} from "../../../mfe-app-tools"; + + +export class MfeTokenProvider extends TokenProvider { + getToken(): Promise { + return fireMfeEventToContainer('token-request', {}); + } +} \ No newline at end of file diff --git a/frontend/src/ts/modules/mfe/service/mfe-app-config.service.ts b/frontend/src/ts/modules/mfe/service/mfe-app-config.service.ts new file mode 100644 index 00000000..13cfdd3f --- /dev/null +++ b/frontend/src/ts/modules/mfe/service/mfe-app-config.service.ts @@ -0,0 +1,29 @@ +import {Injectable} from "@angular/core"; +import {AppConfigService, ApplicationSettingsProvider} from "@webbpm/base-package"; +import {HttpClient} from "@angular/common/http"; +import {MfeConfigurationProvider} from "../provider/mfe-configuration.provider"; +import {joinPath} from "../../../mfe-app-tools"; + + +@Injectable({providedIn:'root'}) +export class MfeAppConfigService extends AppConfigService { + + load(): Promise { + let http: HttpClient = this['http']; + let url = joinPath( + MfeConfigurationProvider.MFE_BASE_URL, + ApplicationSettingsProvider.RESOURCES_PATH, + 'app-config.json' + ) + return http + .get(url) + .toPromise() + .then(configData => { + this['mapParam'] = configData; + }) + .catch((error: any): any => { + console.error('Account Applications configuration file "app-config.json" could not be read'); + return Promise.reject(error); + }); + } +} diff --git a/frontend/src/ts/modules/mfe/service/mfe-app-version.service.ts b/frontend/src/ts/modules/mfe/service/mfe-app-version.service.ts new file mode 100644 index 00000000..d4c1f107 --- /dev/null +++ b/frontend/src/ts/modules/mfe/service/mfe-app-version.service.ts @@ -0,0 +1,30 @@ +import {Injectable} from "@angular/core"; +import {ApplicationSettingsProvider, AppVersionService} from "@webbpm/base-package"; +import {HttpClient} from "@angular/common/http"; +import {MfeConfigurationProvider} from "../provider/mfe-configuration.provider"; +import {joinPath} from "../../../mfe-app-tools"; + + +@Injectable({providedIn:'root'}) +export class MfeAppVersionService extends AppVersionService { + + load(): Promise { + let http: HttpClient = this['http']; + let url = joinPath( + MfeConfigurationProvider.MFE_BASE_URL, + ApplicationSettingsProvider.RESOURCES_PATH, + 'app.version' + ) + return http + .get(url, {responseType: 'text'}) + .toPromise() + .then(version => { + this['appVersion'] = version; + console.log(`Account Applications application version = ${version}`); + }) + .catch((error: any): any => { + console.error('Account Applications file "app.version" could not be read'); + return Promise.reject(error); + }); + } +} diff --git a/frontend/src/ts/modules/mfe/service/mfe-progress-indication.service.ts b/frontend/src/ts/modules/mfe/service/mfe-progress-indication.service.ts new file mode 100644 index 00000000..e128c4fc --- /dev/null +++ b/frontend/src/ts/modules/mfe/service/mfe-progress-indication.service.ts @@ -0,0 +1,21 @@ +import {Injectable} from "@angular/core"; +import {AppProgressIndicationService} from "../../app/service/app-progress-indication.service"; +import { + AppProgressIndicationComponent +} from "../../app/component/app-progress-indication.component"; + + +@Injectable({providedIn: 'root'}) +export class MfeProgressIndicationService extends AppProgressIndicationService { + + protected showProgressIndicator() { + const options = {...this.options}; + options.container = this.getContainerForProgressIndication(); + this.ngbModalRef = this.ngbModal.open(AppProgressIndicationComponent, options); + } + + getContainerForProgressIndication(): HTMLElement { + return document.querySelector('mfe-webbpm') + .shadowRoot.querySelector('[webbpm]'); + } +} diff --git a/frontend/src/ts/modules/webbpm/component/token-form.component.ts b/frontend/src/ts/modules/webbpm/component/token-form.component.ts new file mode 100644 index 00000000..88f3fd40 --- /dev/null +++ b/frontend/src/ts/modules/webbpm/component/token-form.component.ts @@ -0,0 +1,49 @@ +import {Component} from "@angular/core"; +import {Router} from "@angular/router"; +import {WebbpmTokenProvider} from "../provider/webbpm-token-provider"; + +@Component({ + moduleId: module.id, + selector: 'token-form', + templateUrl: '../../../../../src/resources/template/webbpm/jwt-form.html' + }) +export class TokenFormComponent { + + name: string = ''; + realm: string = ''; + recruitmentId: string = ''; + role: string = ''; + + private readonly encodedHeader = this.encodeBase64Url(JSON.stringify({ + kid: "69d4a060-4053-4056-ae69-5e9bcf41125f", + typ: "JWT", + alg: "RS512" + })); + + constructor(private router: Router) { + } + + public enter() { + localStorage.setItem(WebbpmTokenProvider.ACCESS_TOKEN_STORAGE_KEY, this.generateToken()); + this.router.navigateByUrl(''); + } + + private generateToken(): string { + let claims = { + name: this.name, + realm: this.realm, + recruitmentId: this.recruitmentId, + roles: [this.role] + }; + return `${this.encodedHeader}.${this.encodeBase64Url(JSON.stringify(claims))}.` + } + + private encodeBase64Url(input: string): string { + let bytes = new TextEncoder().encode(input); + const byteArray = Array.from(bytes); + const binString = String.fromCharCode(...byteArray); + return btoa(binString).replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=+$/, ''); + } +} \ No newline at end of file diff --git a/frontend/src/ts/modules/webbpm/guard/WebbpmRolesGuard.ts b/frontend/src/ts/modules/webbpm/guard/WebbpmRolesGuard.ts new file mode 100644 index 00000000..5285cbe8 --- /dev/null +++ b/frontend/src/ts/modules/webbpm/guard/WebbpmRolesGuard.ts @@ -0,0 +1,11 @@ +import {Injectable} from "@angular/core"; +import {RolesGuard} from "../../app/guard/RolesGuard"; +import {UrlTree} from "@angular/router"; + + +@Injectable({providedIn: 'root'}) +export class WebbpmRolesGuard extends RolesGuard { + protected getUrlOnFailure(): UrlTree { + return this.router.createUrlTree(['token-form']); + } +} \ No newline at end of file diff --git a/frontend/src/ts/modules/webbpm/interceptor/default-interceptors.prod.ts b/frontend/src/ts/modules/webbpm/interceptor/default-interceptors.prod.ts index 07735d52..54dc22ea 100644 --- a/frontend/src/ts/modules/webbpm/interceptor/default-interceptors.prod.ts +++ b/frontend/src/ts/modules/webbpm/interceptor/default-interceptors.prod.ts @@ -4,9 +4,11 @@ import { HttpSecurityErrorInterceptor, HttpSecurityInterceptor } from "@webbpm/base-package"; +import {TokenInterceptor} from "../../app/interceptor/token.interceptor.service"; export const DEFAULT_HTTP_INTERCEPTOR_PROVIDERS = [ {provide: HTTP_INTERCEPTORS, useClass: HttpSecurityInterceptor, multi: true}, {provide: HTTP_INTERCEPTORS, useClass: HttpSecurityErrorInterceptor, multi: true}, - {provide: HTTP_INTERCEPTORS, useClass: FormDirtyInterceptor, multi: true} + {provide: HTTP_INTERCEPTORS, useClass: FormDirtyInterceptor, multi: true}, + {provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true} ]; diff --git a/frontend/src/ts/modules/webbpm/interceptor/default-interceptors.ts b/frontend/src/ts/modules/webbpm/interceptor/default-interceptors.ts index ee46e0c2..e8fb46e7 100644 --- a/frontend/src/ts/modules/webbpm/interceptor/default-interceptors.ts +++ b/frontend/src/ts/modules/webbpm/interceptor/default-interceptors.ts @@ -1,9 +1,11 @@ import {HTTP_INTERCEPTORS} from "@angular/common/http"; import {FormDirtyInterceptor, HttpSecurityInterceptor} from "@webbpm/base-package"; import {DevHttpSecurityErrorInterceptor} from "./http-security-error-interceptor.dev"; +import {TokenInterceptor} from "../../app/interceptor/token.interceptor.service"; export const DEFAULT_HTTP_INTERCEPTOR_PROVIDERS = [ {provide: HTTP_INTERCEPTORS, useClass: HttpSecurityInterceptor, multi: true}, {provide: HTTP_INTERCEPTORS, useClass: DevHttpSecurityErrorInterceptor, multi: true}, - {provide: HTTP_INTERCEPTORS, useClass: FormDirtyInterceptor, multi: true} + {provide: HTTP_INTERCEPTORS, useClass: FormDirtyInterceptor, multi: true}, + {provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true} ]; diff --git a/frontend/src/ts/modules/webbpm/provider/webbpm-token-provider.ts b/frontend/src/ts/modules/webbpm/provider/webbpm-token-provider.ts new file mode 100644 index 00000000..ea6e1d21 --- /dev/null +++ b/frontend/src/ts/modules/webbpm/provider/webbpm-token-provider.ts @@ -0,0 +1,12 @@ +import {TokenProvider} from "../../app/provider/token.provider"; + +export class WebbpmTokenProvider implements TokenProvider { + + public static readonly ACCESS_TOKEN_STORAGE_KEY = 'accessToken' + + getToken(): Promise { + return new Promise(resolve => { + resolve(localStorage.getItem(WebbpmTokenProvider.ACCESS_TOKEN_STORAGE_KEY)); + }); + } +} \ No newline at end of file diff --git a/frontend/src/ts/modules/webbpm/webbpm-routing.module.ts b/frontend/src/ts/modules/webbpm/webbpm-routing.module.ts index b1195740..9ff2c445 100644 --- a/frontend/src/ts/modules/webbpm/webbpm-routing.module.ts +++ b/frontend/src/ts/modules/webbpm/webbpm-routing.module.ts @@ -4,12 +4,18 @@ import { AuthenticationGuard, ConfirmExitGuard } from "@webbpm/base-package"; +import {TokenFormComponent} from "./component/token-form.component"; +import {RolesGuard} from "../app/guard/RolesGuard"; const webbpmRoutes: Routes = [ + { + path: 'token-form', + component: TokenFormComponent + }, { path: '', loadChildren: 'generated-sources/page-home.module#PagehomeModule', - canActivate: [AuthenticationGuard, ConfirmExitGuard], + canActivate: [ConfirmExitGuard, RolesGuard], pathMatch: 'full', }, { diff --git a/frontend/src/ts/modules/webbpm/webbpm.module.ts b/frontend/src/ts/modules/webbpm/webbpm.module.ts index 76038b5b..72ba0cb7 100644 --- a/frontend/src/ts/modules/webbpm/webbpm.module.ts +++ b/frontend/src/ts/modules/webbpm/webbpm.module.ts @@ -19,6 +19,11 @@ import {AppRoutingModule} from "../app/app-routing.module"; import {UserManagementRoutingModule} from "./user-management-routing.module"; import {GlobalErrorHandler} from "./handler/global-error.handler.prod"; import {DEFAULT_HTTP_INTERCEPTOR_PROVIDERS} from "./interceptor/default-interceptors.prod"; +import {TokenFormComponent} from "./component/token-form.component"; +import {WebbpmTokenProvider} from "./provider/webbpm-token-provider"; +import {RolesGuard} from "../app/guard/RolesGuard"; +import {TokenProvider} from "../app/provider/token.provider"; +import {WebbpmRolesGuard} from "./guard/WebbpmRolesGuard"; let IMPORTS = [ BrowserAnimationsModule, @@ -41,11 +46,14 @@ let IMPORTS = [ imports: IMPORTS, declarations: [ WebbpmComponent, - HomeComponent + HomeComponent, + TokenFormComponent ], exports: [], providers: [ {provide: ErrorHandler, useClass: GlobalErrorHandler}, + {provide: TokenProvider, useClass: WebbpmTokenProvider}, + {provide: RolesGuard, useClass: WebbpmRolesGuard}, DEFAULT_HTTP_INTERCEPTOR_PROVIDERS ], bootstrap: [ diff --git a/frontend/webpack.aot.config.js b/frontend/webpack.aot.config.js index 8b628161..353a6062 100644 --- a/frontend/webpack.aot.config.js +++ b/frontend/webpack.aot.config.js @@ -5,11 +5,19 @@ const CopyWebpackPlugin = require('copy-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const TerserPlugin = require('terser-webpack-plugin'); +const Dotenv = require('dotenv-webpack'); +const {config} = require('dotenv') +const { + container: {ModuleFederationPlugin} +} = webpack; +const {normalizeCssPaths} = require("./normalize-css-path"); function _path(p) { return path.join(__dirname, p); } +config(); + module.exports = { mode: 'production', entry: { @@ -90,6 +98,14 @@ module.exports = { }, plugins: [ + new Dotenv(), + () => normalizeCssPaths({ + paths: [ + './build/resources/css/font-faces.css.shim.ngstyle.js', + './build/resources/css/style.css.ngstyle.js' + ], + outDir: `${process.env.MFE_BASE_URL || ''}/src/resources`, + }), new HtmlWebpackPlugin({ template: 'index.webpack.html', filename: 'index.html', @@ -111,6 +127,13 @@ module.exports = { jQuery: "jquery", "window.jQuery": "jquery", Popper: ['popper.js', 'default'] + }), + new ModuleFederationPlugin({ + name: 'account-applications', + filename: 'remoteEntry.js', + exposes: { + "./account-applications": "./build/scripts/mfe-main.aot", + }, }) ], diff --git a/resources/src/main/resources/business-model/Список заявок на пользователя/Создание заявки на добавление пользователя.page b/resources/src/main/resources/business-model/Список заявок на пользователя/Создание заявки на добавление пользователя.page index 003033ab..726a90b9 100644 --- a/resources/src/main/resources/business-model/Список заявок на пользователя/Создание заявки на добавление пользователя.page +++ b/resources/src/main/resources/business-model/Список заявок на пользователя/Создание заявки на добавление пользователя.page @@ -2497,7 +2497,6 @@ a44f98ec-c47e-40b3-983f-73b5a72bb31d IP- адреса true - false false @@ -2714,8 +2713,6 @@ false - false - false label @@ -4058,6 +4055,7 @@ e4fd80bf-b534-4722-844b-c31f2a3e43fd AC_отправить_блокировка false + false false