From a974f39217c7f25886d56b2975b6a144f7918b15 Mon Sep 17 00:00:00 2001 From: Annika Nowak Date: Wed, 10 Apr 2024 12:32:52 +0200 Subject: [PATCH 01/11] feat: angular auth lib initially created --- libs/angular-auth/.eslintrc.json | 33 +++++++++++++++++++ libs/angular-auth/README.md | 7 ++++ libs/angular-auth/jest.config.ts | 22 +++++++++++++ libs/angular-auth/project.json | 31 +++++++++++++++++ libs/angular-auth/src/index.ts | 1 + .../src/lib/angular-auth.module.ts | 7 ++++ libs/angular-auth/src/test-setup.ts | 8 +++++ libs/angular-auth/tsconfig.json | 29 ++++++++++++++++ libs/angular-auth/tsconfig.lib.json | 12 +++++++ libs/angular-auth/tsconfig.spec.json | 11 +++++++ tsconfig.base.json | 5 +-- 11 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 libs/angular-auth/.eslintrc.json create mode 100644 libs/angular-auth/README.md create mode 100644 libs/angular-auth/jest.config.ts create mode 100644 libs/angular-auth/project.json create mode 100644 libs/angular-auth/src/index.ts create mode 100644 libs/angular-auth/src/lib/angular-auth.module.ts create mode 100644 libs/angular-auth/src/test-setup.ts create mode 100644 libs/angular-auth/tsconfig.json create mode 100644 libs/angular-auth/tsconfig.lib.json create mode 100644 libs/angular-auth/tsconfig.spec.json diff --git a/libs/angular-auth/.eslintrc.json b/libs/angular-auth/.eslintrc.json new file mode 100644 index 00000000..cf0f0d68 --- /dev/null +++ b/libs/angular-auth/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts"], + "extends": ["plugin:@nx/angular", "plugin:@angular-eslint/template/process-inline-templates"], + "rules": { + "@angular-eslint/directive-selector": [ + "error", + { + "type": "attribute", + "prefix": "onecx", + "style": "camelCase" + } + ], + "@angular-eslint/component-selector": [ + "error", + { + "type": "element", + "prefix": "onecx", + "style": "kebab-case" + } + ] + } + }, + { + "files": ["*.html"], + "extends": ["plugin:@nx/angular-template"], + "rules": {} + } + ] +} diff --git a/libs/angular-auth/README.md b/libs/angular-auth/README.md new file mode 100644 index 00000000..a8dbc1b1 --- /dev/null +++ b/libs/angular-auth/README.md @@ -0,0 +1,7 @@ +# angular-auth + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test angular-auth` to execute the unit tests. diff --git a/libs/angular-auth/jest.config.ts b/libs/angular-auth/jest.config.ts new file mode 100644 index 00000000..41adcdb2 --- /dev/null +++ b/libs/angular-auth/jest.config.ts @@ -0,0 +1,22 @@ +/* eslint-disable */ +export default { + displayName: 'angular-auth', + preset: '../../jest.preset.js', + setupFilesAfterEnv: ['/src/test-setup.ts'], + coverageDirectory: '../../coverage/libs/angular-auth', + transform: { + '^.+\\.(ts|mjs|js|html)$': [ + 'jest-preset-angular', + { + tsconfig: '/tsconfig.spec.json', + stringifyContentPathRegex: '\\.(html|svg)$', + }, + ], + }, + transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'], + snapshotSerializers: [ + 'jest-preset-angular/build/serializers/no-ng-attributes', + 'jest-preset-angular/build/serializers/ng-snapshot', + 'jest-preset-angular/build/serializers/html-comment', + ], +} diff --git a/libs/angular-auth/project.json b/libs/angular-auth/project.json new file mode 100644 index 00000000..3a666655 --- /dev/null +++ b/libs/angular-auth/project.json @@ -0,0 +1,31 @@ +{ + "name": "angular-auth", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/angular-auth/src", + "prefix": "onecx", + "tags": [], + "projectType": "library", + "targets": { + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "libs/angular-auth/jest.config.ts", + "passWithNoTests": true + }, + "configurations": { + "ci": { + "ci": true, + "codeCoverage": true + } + } + }, + "lint": { + "executor": "@nx/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["libs/angular-auth/**/*.ts", "libs/angular-auth/**/*.html"] + } + } + } +} diff --git a/libs/angular-auth/src/index.ts b/libs/angular-auth/src/index.ts new file mode 100644 index 00000000..b28b260a --- /dev/null +++ b/libs/angular-auth/src/index.ts @@ -0,0 +1 @@ +export * from './lib/angular-auth.module' diff --git a/libs/angular-auth/src/lib/angular-auth.module.ts b/libs/angular-auth/src/lib/angular-auth.module.ts new file mode 100644 index 00000000..8afb8bb6 --- /dev/null +++ b/libs/angular-auth/src/lib/angular-auth.module.ts @@ -0,0 +1,7 @@ +import { NgModule } from '@angular/core' +import { CommonModule } from '@angular/common' + +@NgModule({ + imports: [CommonModule], +}) +export class AngularAuthModule {} diff --git a/libs/angular-auth/src/test-setup.ts b/libs/angular-auth/src/test-setup.ts new file mode 100644 index 00000000..a5e675ac --- /dev/null +++ b/libs/angular-auth/src/test-setup.ts @@ -0,0 +1,8 @@ +// @ts-expect-error https://thymikee.github.io/jest-preset-angular/docs/getting-started/test-environment +globalThis.ngJest = { + testEnvironmentOptions: { + errorOnUnknownElements: true, + errorOnUnknownProperties: true, + }, +} +import 'jest-preset-angular/setup-jest' diff --git a/libs/angular-auth/tsconfig.json b/libs/angular-auth/tsconfig.json new file mode 100644 index 00000000..92049739 --- /dev/null +++ b/libs/angular-auth/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "target": "es2022", + "useDefineForClassFields": false, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ], + "extends": "../../tsconfig.base.json", + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + } +} diff --git a/libs/angular-auth/tsconfig.lib.json b/libs/angular-auth/tsconfig.lib.json new file mode 100644 index 00000000..4cab05d4 --- /dev/null +++ b/libs/angular-auth/tsconfig.lib.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true, + "declarationMap": true, + "inlineSources": true, + "types": [] + }, + "exclude": ["src/**/*.spec.ts", "src/test-setup.ts", "jest.config.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/libs/angular-auth/tsconfig.spec.json b/libs/angular-auth/tsconfig.spec.json new file mode 100644 index 00000000..7870b7c0 --- /dev/null +++ b/libs/angular-auth/tsconfig.spec.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "target": "es2016", + "types": ["jest", "node"] + }, + "files": ["src/test-setup.ts"], + "include": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts"] +} diff --git a/tsconfig.base.json b/tsconfig.base.json index bc8c2af7..17be2d0c 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -18,16 +18,17 @@ "@onecx/accelerator": ["libs/accelerator/src/index.ts"], "@onecx/angular-accelerator": ["libs/angular-accelerator/src/index.ts"], "@onecx/angular-accelerator/testing": ["libs/angular-accelerator/testing/index.ts"], + "@onecx/angular-auth": ["libs/angular-auth/src/index.ts"], "@onecx/angular-integration-interface": ["libs/angular-integration-interface/src/index.ts"], "@onecx/angular-integration-interface/mocks": ["libs/angular-integration-interface/mocks/index.ts"], - "@onecx/angular-testing": ["libs/angular-testing/src/index.ts"], "@onecx/angular-remote-components": ["libs/angular-remote-components/src/index.ts"], "@onecx/angular-standalone-shell": ["libs/angular-standalone-shell/src/index.ts"], + "@onecx/angular-testing": ["libs/angular-testing/src/index.ts"], "@onecx/integration-interface": ["libs/integration-interface/src/index.ts"], "@onecx/keycloak-auth": ["libs/keycloak-auth/src/index.ts"], "@onecx/portal-integration-angular": ["libs/portal-integration-angular/src/index.ts"], - "@onecx/portal-integration-angular/testing": ["libs/portal-integration-angular/testing/index.ts"], "@onecx/portal-integration-angular/mocks": ["libs/portal-integration-angular/mocks/index.ts"], + "@onecx/portal-integration-angular/testing": ["libs/portal-integration-angular/testing/index.ts"], "@onecx/portal-layout-styles": ["libs/portal-layout-styles/src/index.ts"], "@onecx/shell-core": ["libs/shell-core/src/index.ts"] }, From 8391d41693970d20a19d26d7babd7695a3131eec Mon Sep 17 00:00:00 2001 From: Annika Nowak Date: Wed, 10 Apr 2024 15:21:52 +0200 Subject: [PATCH 02/11] feat: adaptions after generation of the lib --- libs/angular-auth/.eslintrc.json | 17 ++++++++-- libs/angular-auth/ng-package.json | 9 +++++ libs/angular-auth/package.json | 16 +++++++++ libs/angular-auth/project.json | 42 +++++++++++++++++++++--- libs/angular-auth/tsconfig.json | 1 - libs/angular-auth/tsconfig.lib.json | 3 +- libs/angular-auth/tsconfig.lib.prod.json | 11 +++++++ libs/angular-auth/tsconfig.spec.json | 2 +- 8 files changed, 91 insertions(+), 10 deletions(-) create mode 100644 libs/angular-auth/ng-package.json create mode 100644 libs/angular-auth/package.json create mode 100644 libs/angular-auth/tsconfig.lib.prod.json diff --git a/libs/angular-auth/.eslintrc.json b/libs/angular-auth/.eslintrc.json index cf0f0d68..d4c0a6b0 100644 --- a/libs/angular-auth/.eslintrc.json +++ b/libs/angular-auth/.eslintrc.json @@ -10,7 +10,7 @@ "error", { "type": "attribute", - "prefix": "onecx", + "prefix": "ocx", "style": "camelCase" } ], @@ -18,9 +18,15 @@ "error", { "type": "element", - "prefix": "onecx", + "prefix": "ocx", "style": "kebab-case" } + ], + "no-restricted-syntax": [ + "off", + { + "selector": "CallExpression[callee.object.name=\"console\"][callee.property.name=/^(debug|info|time|timeEnd|trace)$/]" + } ] } }, @@ -28,6 +34,13 @@ "files": ["*.html"], "extends": ["plugin:@nx/angular-template"], "rules": {} + }, + { + "files": ["*.json"], + "parser": "jsonc-eslint-parser", + "rules": { + "@nx/dependency-checks": "error" + } } ] } diff --git a/libs/angular-auth/ng-package.json b/libs/angular-auth/ng-package.json new file mode 100644 index 00000000..1f3ba465 --- /dev/null +++ b/libs/angular-auth/ng-package.json @@ -0,0 +1,9 @@ +{ + "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", + "dest": "../../dist/libs/angular-auth", + "lib": { + "entryFile": "src/index.ts" + }, + "assets": ["CHANGELOG.md", "./assets/**"] + } + \ No newline at end of file diff --git a/libs/angular-auth/package.json b/libs/angular-auth/package.json new file mode 100644 index 00000000..222d762e --- /dev/null +++ b/libs/angular-auth/package.json @@ -0,0 +1,16 @@ +{ + "name": "@onecx/angular-auth", + "version": "4.13.2", + "peerDependencies": { + "@angular/common": ">=15.2.7", + "@angular/core": ">=15.2.7", + "@onecx/angular-integration-interface": "^4", + "@onecx/integration-interface": "^4", + "keycloak-angular": "^13.0.0", + "keycloak-js": "^18.0.0", + "rxjs": "~7.8.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/libs/angular-auth/project.json b/libs/angular-auth/project.json index 3a666655..9de04aff 100644 --- a/libs/angular-auth/project.json +++ b/libs/angular-auth/project.json @@ -2,13 +2,33 @@ "name": "angular-auth", "$schema": "../../node_modules/nx/schemas/project-schema.json", "sourceRoot": "libs/angular-auth/src", - "prefix": "onecx", + "prefix": "ocx", "tags": [], "projectType": "library", "targets": { + "build": { + "executor": "@nx/angular:package", + "outputs": [ + "{workspaceRoot}/dist/{projectRoot}" + ], + "options": { + "project": "libs/angular-auth/ng-package.json" + }, + "configurations": { + "production": { + "tsConfig": "libs/angular-auth/tsconfig.lib.prod.json" + }, + "development": { + "tsConfig": "libs/angular-auth/tsconfig.lib.json" + } + }, + "defaultConfiguration": "production" + }, "test": { "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "outputs": [ + "{workspaceRoot}/coverage/{projectRoot}" + ], "options": { "jestConfig": "libs/angular-auth/jest.config.ts", "passWithNoTests": true @@ -22,10 +42,22 @@ }, "lint": { "executor": "@nx/linter:eslint", - "outputs": ["{options.outputFile}"], + "outputs": [ + "{options.outputFile}" + ], + "options": { + "lintFilePatterns": [ + "libs/angular-auth/**/*.ts", + "libs/angular-auth/**/*.html", + "libs/angular-auth/package.json" + ] + } + }, + "release": { + "executor": "nx-release:build-update-publish", "options": { - "lintFilePatterns": ["libs/angular-auth/**/*.ts", "libs/angular-auth/**/*.html"] + "libName": "portal-integration-angular" } } } -} +} \ No newline at end of file diff --git a/libs/angular-auth/tsconfig.json b/libs/angular-auth/tsconfig.json index 92049739..f70a8d1a 100644 --- a/libs/angular-auth/tsconfig.json +++ b/libs/angular-auth/tsconfig.json @@ -21,7 +21,6 @@ ], "extends": "../../tsconfig.base.json", "angularCompilerOptions": { - "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": true, "strictInputAccessModifiers": true, "strictTemplates": true diff --git a/libs/angular-auth/tsconfig.lib.json b/libs/angular-auth/tsconfig.lib.json index 4cab05d4..f9363c1f 100644 --- a/libs/angular-auth/tsconfig.lib.json +++ b/libs/angular-auth/tsconfig.lib.json @@ -5,7 +5,8 @@ "declaration": true, "declarationMap": true, "inlineSources": true, - "types": [] + "types": [], + "target": "es2022" }, "exclude": ["src/**/*.spec.ts", "src/test-setup.ts", "jest.config.ts", "src/**/*.test.ts"], "include": ["src/**/*.ts"] diff --git a/libs/angular-auth/tsconfig.lib.prod.json b/libs/angular-auth/tsconfig.lib.prod.json new file mode 100644 index 00000000..9a60db8a --- /dev/null +++ b/libs/angular-auth/tsconfig.lib.prod.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.lib.json", + "compilerOptions": { + "declarationMap": false, + "target": "es2022", + "useDefineForClassFields": false + }, + "angularCompilerOptions": { + "compilationMode": "partial" + } +} diff --git a/libs/angular-auth/tsconfig.spec.json b/libs/angular-auth/tsconfig.spec.json index 7870b7c0..004f70dc 100644 --- a/libs/angular-auth/tsconfig.spec.json +++ b/libs/angular-auth/tsconfig.spec.json @@ -3,7 +3,7 @@ "compilerOptions": { "outDir": "../../dist/out-tsc", "module": "commonjs", - "target": "es2016", + "target": "es2022", "types": ["jest", "node"] }, "files": ["src/test-setup.ts"], From 07ffcf627acccaae5cebc7444d3bd4215d56d755 Mon Sep 17 00:00:00 2001 From: Annika Nowak Date: Wed, 10 Apr 2024 15:38:04 +0200 Subject: [PATCH 03/11] feat: creation of angularAuthServiceWrapper and AngularAuthService --- .../src/lib/angular-auth-service-wrapper.ts | 22 +++++++++++++++++++ .../src/lib/angular-auth.service.ts | 13 +++++++++++ libs/angular-auth/tsconfig.json | 7 ++++-- 3 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 libs/angular-auth/src/lib/angular-auth-service-wrapper.ts create mode 100644 libs/angular-auth/src/lib/angular-auth.service.ts diff --git a/libs/angular-auth/src/lib/angular-auth-service-wrapper.ts b/libs/angular-auth/src/lib/angular-auth-service-wrapper.ts new file mode 100644 index 00000000..62ef6721 --- /dev/null +++ b/libs/angular-auth/src/lib/angular-auth-service-wrapper.ts @@ -0,0 +1,22 @@ +import { AngularAuthService } from './angular-auth.service' + +export class AngularAuthServiceWrapper implements AngularAuthService { + logout(): void { + throw new Error('Method not implemented.') + } + init(): Promise { + throw new Error('Method not implemented.') + } + auth_header_values(): string[] { + throw new Error('Method not implemented.') + } + additional_header_values(): string[] { + throw new Error('Method not implemented.') + } + hasRole(_role: string): boolean { + throw new Error('Method not implemented.') + } + getUserRoles(): string[] { + throw new Error('Method not implemented.') + } +} diff --git a/libs/angular-auth/src/lib/angular-auth.service.ts b/libs/angular-auth/src/lib/angular-auth.service.ts new file mode 100644 index 00000000..b1b0d4ff --- /dev/null +++ b/libs/angular-auth/src/lib/angular-auth.service.ts @@ -0,0 +1,13 @@ +export interface AngularAuthService { + logout(): void + + init(): Promise + + auth_header_values(): string[] + + additional_header_values(): string[] + + hasRole(_role: string): boolean + + getUserRoles(): string[] +} diff --git a/libs/angular-auth/tsconfig.json b/libs/angular-auth/tsconfig.json index f70a8d1a..cd40c79f 100644 --- a/libs/angular-auth/tsconfig.json +++ b/libs/angular-auth/tsconfig.json @@ -1,13 +1,13 @@ { "compilerOptions": { "target": "es2022", - "useDefineForClassFields": false, "forceConsistentCasingInFileNames": true, "strict": true, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "esModuleInterop": true }, "files": [], "include": [], @@ -15,6 +15,9 @@ { "path": "./tsconfig.lib.json" }, + { + "path": "./tsconfig.lib.prod.json" + }, { "path": "./tsconfig.spec.json" } From 135352f03f2fb33c30e10cd870976d05ef8f356f Mon Sep 17 00:00:00 2001 From: Annika Nowak Date: Thu, 11 Apr 2024 09:18:33 +0200 Subject: [PATCH 04/11] feat: angular injection token and token interceptor added AngularAuthModuleConfig created AuthService interface adapted --- .../src/lib/angular-auth-service-wrapper.ts | 5 +---- .../src/lib/angular-auth.module.ts | 4 ++++ .../src/lib/angular-auth.service.ts | 4 +--- .../src/lib/angular-injection-token.ts | 4 ++++ .../angular-auth/src/lib/token.interceptor.ts | 21 +++++++++++++++++++ 5 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 libs/angular-auth/src/lib/angular-injection-token.ts create mode 100644 libs/angular-auth/src/lib/token.interceptor.ts diff --git a/libs/angular-auth/src/lib/angular-auth-service-wrapper.ts b/libs/angular-auth/src/lib/angular-auth-service-wrapper.ts index 62ef6721..7e095583 100644 --- a/libs/angular-auth/src/lib/angular-auth-service-wrapper.ts +++ b/libs/angular-auth/src/lib/angular-auth-service-wrapper.ts @@ -7,10 +7,7 @@ export class AngularAuthServiceWrapper implements AngularAuthService { init(): Promise { throw new Error('Method not implemented.') } - auth_header_values(): string[] { - throw new Error('Method not implemented.') - } - additional_header_values(): string[] { + getHeaderValues(): Record { throw new Error('Method not implemented.') } hasRole(_role: string): boolean { diff --git a/libs/angular-auth/src/lib/angular-auth.module.ts b/libs/angular-auth/src/lib/angular-auth.module.ts index 8afb8bb6..52082f57 100644 --- a/libs/angular-auth/src/lib/angular-auth.module.ts +++ b/libs/angular-auth/src/lib/angular-auth.module.ts @@ -1,6 +1,10 @@ import { NgModule } from '@angular/core' import { CommonModule } from '@angular/common' +export interface AngularAuthModuleConfig { + tokenInterceptorWhitelist?: string[] +} + @NgModule({ imports: [CommonModule], }) diff --git a/libs/angular-auth/src/lib/angular-auth.service.ts b/libs/angular-auth/src/lib/angular-auth.service.ts index b1b0d4ff..60545100 100644 --- a/libs/angular-auth/src/lib/angular-auth.service.ts +++ b/libs/angular-auth/src/lib/angular-auth.service.ts @@ -3,9 +3,7 @@ export interface AngularAuthService { init(): Promise - auth_header_values(): string[] - - additional_header_values(): string[] + getHeaderValues(): Record hasRole(_role: string): boolean diff --git a/libs/angular-auth/src/lib/angular-injection-token.ts b/libs/angular-auth/src/lib/angular-injection-token.ts new file mode 100644 index 00000000..7594253b --- /dev/null +++ b/libs/angular-auth/src/lib/angular-injection-token.ts @@ -0,0 +1,4 @@ +import { InjectionToken } from '@angular/core' +import { AngularAuthModuleConfig } from './angular-auth.module' + +export const ANGULAR_AUTH_CONFIG: InjectionToken = new InjectionToken('ANGULAR_AUTH_CONFIG') diff --git a/libs/angular-auth/src/lib/token.interceptor.ts b/libs/angular-auth/src/lib/token.interceptor.ts new file mode 100644 index 00000000..31a88eb9 --- /dev/null +++ b/libs/angular-auth/src/lib/token.interceptor.ts @@ -0,0 +1,21 @@ +import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http' +import { AngularAuthService } from './angular-auth.service' +import { Injectable, Inject, Optional } from '@angular/core' +import { AUTH_SERVICE } from '@onecx/angular-integration-interface' +import { Observable, of } from 'rxjs' +import { AngularAuthModuleConfig } from './angular-auth.module' +import { ANGULAR_AUTH_CONFIG } from './angular-injection-token' + +const WHITELIST = ['assets'] + +@Injectable() +export class TokenInterceptor implements HttpInterceptor { + constructor( + @Inject(AUTH_SERVICE) private authService: AngularAuthService, + @Inject(ANGULAR_AUTH_CONFIG) @Optional() private angularModuleConfig: AngularAuthModuleConfig + ) {} + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + return of() + } +} From f828caebf1f3ada37dc4de5486b57ca6349270a7 Mon Sep 17 00:00:00 2001 From: Annika Nowak Date: Thu, 11 Apr 2024 10:17:17 +0200 Subject: [PATCH 05/11] feat: providers added to AngularAuthModule --- .../src/lib/angular-auth-service-wrapper.ts | 1 + .../src/lib/angular-auth.module.ts | 27 ++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/libs/angular-auth/src/lib/angular-auth-service-wrapper.ts b/libs/angular-auth/src/lib/angular-auth-service-wrapper.ts index 7e095583..17fb1725 100644 --- a/libs/angular-auth/src/lib/angular-auth-service-wrapper.ts +++ b/libs/angular-auth/src/lib/angular-auth-service-wrapper.ts @@ -5,6 +5,7 @@ export class AngularAuthServiceWrapper implements AngularAuthService { throw new Error('Method not implemented.') } init(): Promise { + // checks which service should be used throw new Error('Method not implemented.') } getHeaderValues(): Record { diff --git a/libs/angular-auth/src/lib/angular-auth.module.ts b/libs/angular-auth/src/lib/angular-auth.module.ts index 52082f57..70846e44 100644 --- a/libs/angular-auth/src/lib/angular-auth.module.ts +++ b/libs/angular-auth/src/lib/angular-auth.module.ts @@ -1,11 +1,36 @@ -import { NgModule } from '@angular/core' +import { APP_INITIALIZER, NgModule } from '@angular/core' import { CommonModule } from '@angular/common' +import { HTTP_INTERCEPTORS } from '@angular/common/http' +import { AUTH_SERVICE, ConfigurationService } from '@onecx/angular-integration-interface' +import { TokenInterceptor } from './token.interceptor' +import { AngularAuthService } from './angular-auth.service' +import { AngularAuthServiceWrapper } from './angular-auth-service-wrapper' export interface AngularAuthModuleConfig { + // TODO: adapt the config entry which should be adapted tokenInterceptorWhitelist?: string[] } +function appInitializer(configService: ConfigurationService, authService: AngularAuthService) { + return async () => { + await configService.isInitialized + await authService.init() + } +} +// the config entry determines which AUTH_SERVICE I want to use +enum configs { + AUTH_SERVICE = 'custom', + AUTH_SERVICE_CUSTOM_URL = 'https:/..../module.js', +} @NgModule({ imports: [CommonModule], + providers: [ + { + provide: AUTH_SERVICE, + useClass: AngularAuthServiceWrapper, + }, + { provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true }, + { provide: APP_INITIALIZER, useFactory: appInitializer, deps: [ConfigurationService, AUTH_SERVICE], multi: true }, + ], }) export class AngularAuthModule {} From 3cdf5cf4c6d384117f6b4fb6fe005a62505cbccc Mon Sep 17 00:00:00 2001 From: Annika Nowak Date: Thu, 11 Apr 2024 11:40:34 +0200 Subject: [PATCH 06/11] feat: add config keys and some corrections --- .../src/lib/angular-auth-service-wrapper.ts | 32 ++++++++++++------- .../src/lib/angular-auth.service.ts | 6 +--- .../angular-auth/src/lib/token.interceptor.ts | 4 +-- .../src/lib/model/config-key.model.ts | 2 ++ .../src/lib/token.interceptor.ts | 3 ++ 5 files changed, 29 insertions(+), 18 deletions(-) diff --git a/libs/angular-auth/src/lib/angular-auth-service-wrapper.ts b/libs/angular-auth/src/lib/angular-auth-service-wrapper.ts index 17fb1725..c158ed12 100644 --- a/libs/angular-auth/src/lib/angular-auth-service-wrapper.ts +++ b/libs/angular-auth/src/lib/angular-auth-service-wrapper.ts @@ -1,20 +1,30 @@ +import { filter } from 'rxjs/internal/operators/filter' import { AngularAuthService } from './angular-auth.service' - -export class AngularAuthServiceWrapper implements AngularAuthService { - logout(): void { - throw new Error('Method not implemented.') +import { EventsTopic } from '@onecx/integration-interface' +import { CONFIG_KEY, ConfigurationService } from '@onecx/angular-integration-interface' +import { Injector } from '@angular/core' +// rename in AuthServiceWrapper +export class AngularAuthServiceWrapper { + private eventsTopic$ = new EventsTopic() + private authService: AngularAuthService | undefined + // TODO: AngularAuthService --> AuthService + constructor(private configService: ConfigurationService, private injector: Injector) { + this.eventsTopic$ + .pipe(filter((e) => e.type === 'authentication#logoutButtonClicked')) + .subscribe(() => this.authService?.logout()) } - init(): Promise { + async init(): Promise { + await this.configService.isInitialized + const serviceTypeConfig = this.configService.getProperty(CONFIG_KEY.AUTH_SERVICE) ?? 'keycloak' + // TODO: injector injecten lassen + // switch case bauen // checks which service should be used + // initResult await authService.init() + // if(initResult){ --> TODO: appStateService.isAuthenticated --> publish(true), else nicht publish} + // return initresult throw new Error('Method not implemented.') } getHeaderValues(): Record { throw new Error('Method not implemented.') } - hasRole(_role: string): boolean { - throw new Error('Method not implemented.') - } - getUserRoles(): string[] { - throw new Error('Method not implemented.') - } } diff --git a/libs/angular-auth/src/lib/angular-auth.service.ts b/libs/angular-auth/src/lib/angular-auth.service.ts index 60545100..5da4ab12 100644 --- a/libs/angular-auth/src/lib/angular-auth.service.ts +++ b/libs/angular-auth/src/lib/angular-auth.service.ts @@ -1,11 +1,7 @@ export interface AngularAuthService { - logout(): void - init(): Promise getHeaderValues(): Record - hasRole(_role: string): boolean - - getUserRoles(): string[] + logout(): void } diff --git a/libs/angular-auth/src/lib/token.interceptor.ts b/libs/angular-auth/src/lib/token.interceptor.ts index 31a88eb9..58551552 100644 --- a/libs/angular-auth/src/lib/token.interceptor.ts +++ b/libs/angular-auth/src/lib/token.interceptor.ts @@ -1,17 +1,17 @@ import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http' -import { AngularAuthService } from './angular-auth.service' import { Injectable, Inject, Optional } from '@angular/core' import { AUTH_SERVICE } from '@onecx/angular-integration-interface' import { Observable, of } from 'rxjs' import { AngularAuthModuleConfig } from './angular-auth.module' import { ANGULAR_AUTH_CONFIG } from './angular-injection-token' +import { AngularAuthServiceWrapper } from './angular-auth-service-wrapper' const WHITELIST = ['assets'] @Injectable() export class TokenInterceptor implements HttpInterceptor { constructor( - @Inject(AUTH_SERVICE) private authService: AngularAuthService, + @Inject(AUTH_SERVICE) private authService: AngularAuthServiceWrapper, @Inject(ANGULAR_AUTH_CONFIG) @Optional() private angularModuleConfig: AngularAuthModuleConfig ) {} diff --git a/libs/angular-integration-interface/src/lib/model/config-key.model.ts b/libs/angular-integration-interface/src/lib/model/config-key.model.ts index beef9d41..0146be2c 100644 --- a/libs/angular-integration-interface/src/lib/model/config-key.model.ts +++ b/libs/angular-integration-interface/src/lib/model/config-key.model.ts @@ -22,4 +22,6 @@ export enum CONFIG_KEY { ONECX_PORTAL_HELP_DISABLED = 'ONECX_PORTAL_HELP_DISABLED', APP_VERSION = 'APP_VERSION', IS_SHELL = 'IS_SHELL', + AUTH_SERVICE = 'AUTH_SERVICE', + AUTH_SERVICE_CUSTOM_URL = 'AUTH_SERVICE_CUSTOM_URL', } diff --git a/libs/keycloak-auth/src/lib/token.interceptor.ts b/libs/keycloak-auth/src/lib/token.interceptor.ts index 0033e80e..5ac79127 100644 --- a/libs/keycloak-auth/src/lib/token.interceptor.ts +++ b/libs/keycloak-auth/src/lib/token.interceptor.ts @@ -24,6 +24,9 @@ export class TokenInterceptor implements HttpInterceptor { if (idToken) { const authenticatedReq: HttpRequest = request.clone({ headers: request.headers.set('apm-principal-token', idToken), + // let headers = request.headers + // for loop + // headers = headers.set }) return next.handle(authenticatedReq) } else { From 21040ff3ff91ecaf3c6fb8226cd4acb29e7af392 Mon Sep 17 00:00:00 2001 From: Annika Nowak Date: Thu, 11 Apr 2024 17:43:04 +0200 Subject: [PATCH 07/11] feat: removal of tokenInterceptorWhitelist, AngularAuthModuleConfig and Angular Injection Token --- .../src/lib/angular-auth-service-wrapper.ts | 48 +++-- .../src/lib/angular-auth.module.ts | 19 +- .../src/lib/angular-auth.service.ts | 2 +- .../src/lib/angular-injection-token.ts | 4 - .../auth_services/keycloak-auth.service.ts | 172 ++++++++++++++++++ .../angular-auth/src/lib/token.interceptor.ts | 22 ++- .../src/lib/token.interceptor.ts | 3 - 7 files changed, 223 insertions(+), 47 deletions(-) delete mode 100644 libs/angular-auth/src/lib/angular-injection-token.ts create mode 100644 libs/angular-auth/src/lib/auth_services/keycloak-auth.service.ts diff --git a/libs/angular-auth/src/lib/angular-auth-service-wrapper.ts b/libs/angular-auth/src/lib/angular-auth-service-wrapper.ts index c158ed12..1cc3e2fe 100644 --- a/libs/angular-auth/src/lib/angular-auth-service-wrapper.ts +++ b/libs/angular-auth/src/lib/angular-auth-service-wrapper.ts @@ -1,30 +1,44 @@ import { filter } from 'rxjs/internal/operators/filter' -import { AngularAuthService } from './angular-auth.service' +import { AuthService } from './angular-auth.service' import { EventsTopic } from '@onecx/integration-interface' -import { CONFIG_KEY, ConfigurationService } from '@onecx/angular-integration-interface' -import { Injector } from '@angular/core' -// rename in AuthServiceWrapper -export class AngularAuthServiceWrapper { +import { AppStateService, CONFIG_KEY, ConfigurationService } from '@onecx/angular-integration-interface' +import { Injectable, Injector } from '@angular/core' +@Injectable() +export class AuthServiceWrapper { private eventsTopic$ = new EventsTopic() - private authService: AngularAuthService | undefined - // TODO: AngularAuthService --> AuthService - constructor(private configService: ConfigurationService, private injector: Injector) { + private authService: AuthService | undefined + + constructor( + private configService: ConfigurationService, + private injector: Injector, + private appStateService: AppStateService + ) { this.eventsTopic$ .pipe(filter((e) => e.type === 'authentication#logoutButtonClicked')) .subscribe(() => this.authService?.logout()) } - async init(): Promise { + async init(): Promise { await this.configService.isInitialized const serviceTypeConfig = this.configService.getProperty(CONFIG_KEY.AUTH_SERVICE) ?? 'keycloak' - // TODO: injector injecten lassen - // switch case bauen - // checks which service should be used - // initResult await authService.init() - // if(initResult){ --> TODO: appStateService.isAuthenticated --> publish(true), else nicht publish} - // return initresult - throw new Error('Method not implemented.') + let initResult = this.getInitResult(serviceTypeConfig) + return initResult + } + async getInitResult(serviceTypeConfig: string): Promise { + let initResult + switch (serviceTypeConfig) { + case 'keycloak': + initResult = await this.authService?.init() + break + // TODO: Extend the other cases in the future + default: + break + } + if (initResult) { + this.appStateService.isAuthenticated$.publish() + } + return initResult } getHeaderValues(): Record { - throw new Error('Method not implemented.') + return this.authService?.getHeaderValues() ?? {} } } diff --git a/libs/angular-auth/src/lib/angular-auth.module.ts b/libs/angular-auth/src/lib/angular-auth.module.ts index 70846e44..b8f2c388 100644 --- a/libs/angular-auth/src/lib/angular-auth.module.ts +++ b/libs/angular-auth/src/lib/angular-auth.module.ts @@ -3,31 +3,22 @@ import { CommonModule } from '@angular/common' import { HTTP_INTERCEPTORS } from '@angular/common/http' import { AUTH_SERVICE, ConfigurationService } from '@onecx/angular-integration-interface' import { TokenInterceptor } from './token.interceptor' -import { AngularAuthService } from './angular-auth.service' -import { AngularAuthServiceWrapper } from './angular-auth-service-wrapper' +import { AuthService } from './angular-auth.service' +import { AuthServiceWrapper } from './angular-auth-service-wrapper' -export interface AngularAuthModuleConfig { - // TODO: adapt the config entry which should be adapted - tokenInterceptorWhitelist?: string[] -} - -function appInitializer(configService: ConfigurationService, authService: AngularAuthService) { +function appInitializer(configService: ConfigurationService, authService: AuthService) { return async () => { await configService.isInitialized await authService.init() } } -// the config entry determines which AUTH_SERVICE I want to use -enum configs { - AUTH_SERVICE = 'custom', - AUTH_SERVICE_CUSTOM_URL = 'https:/..../module.js', -} + @NgModule({ imports: [CommonModule], providers: [ { provide: AUTH_SERVICE, - useClass: AngularAuthServiceWrapper, + useClass: AuthServiceWrapper, }, { provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true }, { provide: APP_INITIALIZER, useFactory: appInitializer, deps: [ConfigurationService, AUTH_SERVICE], multi: true }, diff --git a/libs/angular-auth/src/lib/angular-auth.service.ts b/libs/angular-auth/src/lib/angular-auth.service.ts index 5da4ab12..c1ea0008 100644 --- a/libs/angular-auth/src/lib/angular-auth.service.ts +++ b/libs/angular-auth/src/lib/angular-auth.service.ts @@ -1,4 +1,4 @@ -export interface AngularAuthService { +export interface AuthService { init(): Promise getHeaderValues(): Record diff --git a/libs/angular-auth/src/lib/angular-injection-token.ts b/libs/angular-auth/src/lib/angular-injection-token.ts deleted file mode 100644 index 7594253b..00000000 --- a/libs/angular-auth/src/lib/angular-injection-token.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { InjectionToken } from '@angular/core' -import { AngularAuthModuleConfig } from './angular-auth.module' - -export const ANGULAR_AUTH_CONFIG: InjectionToken = new InjectionToken('ANGULAR_AUTH_CONFIG') diff --git a/libs/angular-auth/src/lib/auth_services/keycloak-auth.service.ts b/libs/angular-auth/src/lib/auth_services/keycloak-auth.service.ts new file mode 100644 index 00000000..ea0b4196 --- /dev/null +++ b/libs/angular-auth/src/lib/auth_services/keycloak-auth.service.ts @@ -0,0 +1,172 @@ +import { Injectable } from '@angular/core' +import { AppStateService, ConfigurationService, CONFIG_KEY } from '@onecx/angular-integration-interface' +import { KeycloakEventType, KeycloakOptions, KeycloakService } from 'keycloak-angular' +import { KeycloakConfig } from 'keycloak-js' +import { AuthService } from '../angular-auth.service' + +const KC_REFRESH_TOKEN_LS = 'onecx_kc_refreshToken' +const KC_ID_TOKEN_LS = 'onecx_kc_idToken' +const KC_TOKEN_LS = 'onecx_kc_token' + +@Injectable() +export class KeycloakAuthService implements AuthService { + constructor( + private keycloakService: KeycloakService, + private configService: ConfigurationService, + private appStateService: AppStateService + ) {} + + public async init(): Promise { + console.time('KeycloakAuthService') + // load previous tokens, saved after successful login of keycloak success callback + let token = localStorage.getItem(KC_TOKEN_LS) + let idToken = localStorage.getItem(KC_ID_TOKEN_LS) + let refreshToken = localStorage.getItem(KC_REFRESH_TOKEN_LS) + if (token && refreshToken) { + const parsedToken = JSON.parse(atob(refreshToken.split('.')[1])) + if (parsedToken.exp * 1000 < new Date().getTime()) { + //refresh expired, drop everything + token = null + refreshToken = null + idToken = null + this.clearKCStateFromLocalstorage() + } + } + + this.setupEventListener() + + // try constructing the KC config from values in env + let kcConfig: KeycloakConfig | string = this.getValidKCConfig() + // If any of the required props is missing, fallback to loading KC conf from file + if (!kcConfig.clientId || !kcConfig.realm || !kcConfig.url) { + kcConfig = './assets/keycloak.json' + } + + const enableSilentSSOCheck = this.configService.getProperty(CONFIG_KEY.KEYCLOAK_ENABLE_SILENT_SSO) === 'true' + + const kcOptions: KeycloakOptions = { + loadUserProfileAtStartUp: false, + config: kcConfig, + initOptions: { + onLoad: 'check-sso', + checkLoginIframe: false, + silentCheckSsoRedirectUri: enableSilentSSOCheck ? this.getSilentSSOUrl() : undefined, + idToken: idToken || undefined, + refreshToken: refreshToken || undefined, + token: token || undefined, + }, + enableBearerInterceptor: false, + bearerExcludedUrls: ['/assets'], + } + + return this.keycloakService + .init(kcOptions) + .catch((err) => { + console.log(`Keycloak err: ${err}, try force login`) + return this.keycloakService.login() + }) + .then((loginOk) => { + // this will be false if our silent login did not work + if (loginOk) { + return this.keycloakService.getToken() + } else { + // we want to block bootstrap process now + return this.keycloakService.login().then(() => 'login') + } + }) + .then(async () => { + await this.appStateService.isAuthenticated$.publish() + }) + .then(() => { + console.timeEnd('KeycloakAuthService') + return true + }) + .catch((err) => { + console.log(`KC ERROR ${err} as json ${JSON.stringify(err)}`) + throw err + }) + } + + private getValidKCConfig(): KeycloakConfig { + const clientId = this.configService.getProperty(CONFIG_KEY.KEYCLOAK_CLIENT_ID) + if (!clientId) { + throw new Error('Invalid KC config, missing clientId') + } + const realm = this.configService.getProperty(CONFIG_KEY.KEYCLOAK_REALM) + if (!realm) { + throw new Error('Invalid KC config, missing realm') + } + return { + url: this.configService.getProperty(CONFIG_KEY.KEYCLOAK_URL), + clientId, + realm, + } + } + + private setupEventListener() { + this.keycloakService.keycloakEvents$.subscribe((ke) => { + // we are logged in, get tokens and store them in localstorage + if (this.keycloakService.getKeycloakInstance().token) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + localStorage.setItem(KC_TOKEN_LS, this.keycloakService.getKeycloakInstance().token!) + } else { + localStorage.removeItem(KC_TOKEN_LS) + } + if (this.keycloakService.getKeycloakInstance().idToken) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + localStorage.setItem(KC_ID_TOKEN_LS, this.keycloakService.getKeycloakInstance().idToken!) + } else { + localStorage.removeItem(KC_ID_TOKEN_LS) + } + if (this.keycloakService.getKeycloakInstance().refreshToken) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + localStorage.setItem(KC_REFRESH_TOKEN_LS, this.keycloakService.getKeycloakInstance().refreshToken!) + } else { + localStorage.removeItem(KC_REFRESH_TOKEN_LS) + } + if (ke.type === KeycloakEventType.OnAuthLogout) { + console.log('SSO logout nav to root') + this.clearKCStateFromLocalstorage() + this.keycloakService.login() + } + }) + } + + private clearKCStateFromLocalstorage() { + localStorage.removeItem(KC_ID_TOKEN_LS) + localStorage.removeItem(KC_TOKEN_LS) + localStorage.removeItem(KC_REFRESH_TOKEN_LS) + } + + private getSilentSSOUrl() { + let currentBase = document.getElementsByTagName('base')[0].href + if (currentBase === '/') { + currentBase = '' + } + return `${currentBase}/assets/silent-check-sso.html` + } + + getIdToken(): string | null { + return localStorage.getItem(KC_ID_TOKEN_LS) + } + + logout(): void { + this.keycloakService.logout() + } + + getAuthProviderName(): string { + return 'keycloak-auth' + } + + hasRole(_role: string): boolean { + return false + } + + getUserRoles(): string[] { + return [] + } + + getHeaderValues(): Record { + return {} + } +} diff --git a/libs/angular-auth/src/lib/token.interceptor.ts b/libs/angular-auth/src/lib/token.interceptor.ts index 58551552..91f18799 100644 --- a/libs/angular-auth/src/lib/token.interceptor.ts +++ b/libs/angular-auth/src/lib/token.interceptor.ts @@ -2,20 +2,26 @@ import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/c import { Injectable, Inject, Optional } from '@angular/core' import { AUTH_SERVICE } from '@onecx/angular-integration-interface' import { Observable, of } from 'rxjs' -import { AngularAuthModuleConfig } from './angular-auth.module' -import { ANGULAR_AUTH_CONFIG } from './angular-injection-token' -import { AngularAuthServiceWrapper } from './angular-auth-service-wrapper' +import { AuthServiceWrapper } from './angular-auth-service-wrapper' const WHITELIST = ['assets'] @Injectable() export class TokenInterceptor implements HttpInterceptor { - constructor( - @Inject(AUTH_SERVICE) private authService: AngularAuthServiceWrapper, - @Inject(ANGULAR_AUTH_CONFIG) @Optional() private angularModuleConfig: AngularAuthModuleConfig - ) {} + constructor(@Inject(AUTH_SERVICE) private authService: AuthServiceWrapper) {} intercept(request: HttpRequest, next: HttpHandler): Observable> { - return of() + const skip = WHITELIST.some((str) => request.url.includes(str)) + if (skip) { + return next.handle(request) + } + let headers = this.authService.getHeaderValues() + for (const header in headers) { + const authenticatedReq: HttpRequest = request.clone({ + headers: request.headers.set('apm-principal-token', header), + }) + next.handle(authenticatedReq) + } + return next.handle(request) } } diff --git a/libs/keycloak-auth/src/lib/token.interceptor.ts b/libs/keycloak-auth/src/lib/token.interceptor.ts index 5ac79127..0033e80e 100644 --- a/libs/keycloak-auth/src/lib/token.interceptor.ts +++ b/libs/keycloak-auth/src/lib/token.interceptor.ts @@ -24,9 +24,6 @@ export class TokenInterceptor implements HttpInterceptor { if (idToken) { const authenticatedReq: HttpRequest = request.clone({ headers: request.headers.set('apm-principal-token', idToken), - // let headers = request.headers - // for loop - // headers = headers.set }) return next.handle(authenticatedReq) } else { From 1ede3123dfc73ac38165e3f5dea3348222e5f4ed Mon Sep 17 00:00:00 2001 From: Annika Nowak Date: Mon, 15 Apr 2024 14:50:20 +0200 Subject: [PATCH 08/11] feat: adaptions --- .../src/lib/angular-auth-service-wrapper.ts | 37 +++++++++++-------- .../src/lib/angular-auth.module.ts | 14 +++++-- .../auth_services/keycloak-auth.service.ts | 25 ++++--------- .../angular-auth/src/lib/token.interceptor.ts | 17 +++++---- 4 files changed, 48 insertions(+), 45 deletions(-) diff --git a/libs/angular-auth/src/lib/angular-auth-service-wrapper.ts b/libs/angular-auth/src/lib/angular-auth-service-wrapper.ts index 1cc3e2fe..47f4ffe2 100644 --- a/libs/angular-auth/src/lib/angular-auth-service-wrapper.ts +++ b/libs/angular-auth/src/lib/angular-auth-service-wrapper.ts @@ -3,6 +3,7 @@ import { AuthService } from './angular-auth.service' import { EventsTopic } from '@onecx/integration-interface' import { AppStateService, CONFIG_KEY, ConfigurationService } from '@onecx/angular-integration-interface' import { Injectable, Injector } from '@angular/core' +import { KeycloakAuthService } from './auth_services/keycloak-auth.service' @Injectable() export class AuthServiceWrapper { private eventsTopic$ = new EventsTopic() @@ -10,8 +11,8 @@ export class AuthServiceWrapper { constructor( private configService: ConfigurationService, - private injector: Injector, - private appStateService: AppStateService + private appStateService: AppStateService, + private injector: Injector ) { this.eventsTopic$ .pipe(filter((e) => e.type === 'authentication#logoutButtonClicked')) @@ -19,26 +20,32 @@ export class AuthServiceWrapper { } async init(): Promise { await this.configService.isInitialized - const serviceTypeConfig = this.configService.getProperty(CONFIG_KEY.AUTH_SERVICE) ?? 'keycloak' - let initResult = this.getInitResult(serviceTypeConfig) + + this.initializeAuthService() + let initResult = this.getInitResult() return initResult } - async getInitResult(serviceTypeConfig: string): Promise { - let initResult - switch (serviceTypeConfig) { - case 'keycloak': - initResult = await this.authService?.init() - break - // TODO: Extend the other cases in the future - default: - break - } + async getInitResult(): Promise { + let initResult = await this.authService?.init() + if (initResult) { - this.appStateService.isAuthenticated$.publish() + await this.appStateService.isAuthenticated$.publish() } return initResult } getHeaderValues(): Record { return this.authService?.getHeaderValues() ?? {} } + + initializeAuthService(): void { + const serviceTypeConfig = this.configService.getProperty(CONFIG_KEY.AUTH_SERVICE) ?? 'keycloak' + switch (serviceTypeConfig) { + case 'keycloak': + this.authService = this.injector.get(KeycloakAuthService) + break + // TODO: Extend the other cases in the future + default: + throw new Error('Configured AuthService not found') + } + } } diff --git a/libs/angular-auth/src/lib/angular-auth.module.ts b/libs/angular-auth/src/lib/angular-auth.module.ts index b8f2c388..319f14fd 100644 --- a/libs/angular-auth/src/lib/angular-auth.module.ts +++ b/libs/angular-auth/src/lib/angular-auth.module.ts @@ -5,6 +5,8 @@ import { AUTH_SERVICE, ConfigurationService } from '@onecx/angular-integration-i import { TokenInterceptor } from './token.interceptor' import { AuthService } from './angular-auth.service' import { AuthServiceWrapper } from './angular-auth-service-wrapper' +import { KeycloakAuthService } from './auth_services/keycloak-auth.service' +import { KeycloakService } from 'keycloak-angular' function appInitializer(configService: ConfigurationService, authService: AuthService) { return async () => { @@ -16,12 +18,16 @@ function appInitializer(configService: ConfigurationService, authService: AuthSe @NgModule({ imports: [CommonModule], providers: [ + AuthServiceWrapper, + { provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true }, { - provide: AUTH_SERVICE, - useClass: AuthServiceWrapper, + provide: APP_INITIALIZER, + useFactory: appInitializer, + deps: [ConfigurationService, AuthServiceWrapper], + multi: true, }, - { provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true }, - { provide: APP_INITIALIZER, useFactory: appInitializer, deps: [ConfigurationService, AUTH_SERVICE], multi: true }, + KeycloakAuthService, + KeycloakService, ], }) export class AngularAuthModule {} diff --git a/libs/angular-auth/src/lib/auth_services/keycloak-auth.service.ts b/libs/angular-auth/src/lib/auth_services/keycloak-auth.service.ts index ea0b4196..3ef8f96b 100644 --- a/libs/angular-auth/src/lib/auth_services/keycloak-auth.service.ts +++ b/libs/angular-auth/src/lib/auth_services/keycloak-auth.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core' -import { AppStateService, ConfigurationService, CONFIG_KEY } from '@onecx/angular-integration-interface' +import { ConfigurationService, CONFIG_KEY } from '@onecx/angular-integration-interface' import { KeycloakEventType, KeycloakOptions, KeycloakService } from 'keycloak-angular' import { KeycloakConfig } from 'keycloak-js' import { AuthService } from '../angular-auth.service' @@ -10,22 +10,16 @@ const KC_TOKEN_LS = 'onecx_kc_token' @Injectable() export class KeycloakAuthService implements AuthService { - constructor( - private keycloakService: KeycloakService, - private configService: ConfigurationService, - private appStateService: AppStateService - ) {} + constructor(private keycloakService: KeycloakService, private configService: ConfigurationService) {} public async init(): Promise { console.time('KeycloakAuthService') - // load previous tokens, saved after successful login of keycloak success callback let token = localStorage.getItem(KC_TOKEN_LS) let idToken = localStorage.getItem(KC_ID_TOKEN_LS) let refreshToken = localStorage.getItem(KC_REFRESH_TOKEN_LS) if (token && refreshToken) { const parsedToken = JSON.parse(atob(refreshToken.split('.')[1])) if (parsedToken.exp * 1000 < new Date().getTime()) { - //refresh expired, drop everything token = null refreshToken = null idToken = null @@ -35,9 +29,7 @@ export class KeycloakAuthService implements AuthService { this.setupEventListener() - // try constructing the KC config from values in env let kcConfig: KeycloakConfig | string = this.getValidKCConfig() - // If any of the required props is missing, fallback to loading KC conf from file if (!kcConfig.clientId || !kcConfig.realm || !kcConfig.url) { kcConfig = './assets/keycloak.json' } @@ -66,17 +58,12 @@ export class KeycloakAuthService implements AuthService { return this.keycloakService.login() }) .then((loginOk) => { - // this will be false if our silent login did not work if (loginOk) { return this.keycloakService.getToken() } else { - // we want to block bootstrap process now return this.keycloakService.login().then(() => 'login') } }) - .then(async () => { - await this.appStateService.isAuthenticated$.publish() - }) .then(() => { console.timeEnd('KeycloakAuthService') return true @@ -105,7 +92,6 @@ export class KeycloakAuthService implements AuthService { private setupEventListener() { this.keycloakService.keycloakEvents$.subscribe((ke) => { - // we are logged in, get tokens and store them in localstorage if (this.keycloakService.getKeycloakInstance().token) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion localStorage.setItem(KC_TOKEN_LS, this.keycloakService.getKeycloakInstance().token!) @@ -147,7 +133,10 @@ export class KeycloakAuthService implements AuthService { } getIdToken(): string | null { - return localStorage.getItem(KC_ID_TOKEN_LS) + return this.keycloakService.getKeycloakInstance().idToken ?? null + } + getAccessToken(): string | null { + return this.keycloakService.getKeycloakInstance().token ?? null } logout(): void { @@ -167,6 +156,6 @@ export class KeycloakAuthService implements AuthService { } getHeaderValues(): Record { - return {} + return { 'apm-principal-token': this.getIdToken() ?? '', Authorization: `Bearer${this.getAccessToken()}` } } } diff --git a/libs/angular-auth/src/lib/token.interceptor.ts b/libs/angular-auth/src/lib/token.interceptor.ts index 91f18799..3abb72d8 100644 --- a/libs/angular-auth/src/lib/token.interceptor.ts +++ b/libs/angular-auth/src/lib/token.interceptor.ts @@ -8,20 +8,21 @@ const WHITELIST = ['assets'] @Injectable() export class TokenInterceptor implements HttpInterceptor { - constructor(@Inject(AUTH_SERVICE) private authService: AuthServiceWrapper) {} + constructor(private authService: AuthServiceWrapper) {} intercept(request: HttpRequest, next: HttpHandler): Observable> { const skip = WHITELIST.some((str) => request.url.includes(str)) if (skip) { return next.handle(request) } - let headers = this.authService.getHeaderValues() - for (const header in headers) { - const authenticatedReq: HttpRequest = request.clone({ - headers: request.headers.set('apm-principal-token', header), - }) - next.handle(authenticatedReq) + let headerValues = this.authService.getHeaderValues() + let headers = request.headers + for (const header in headerValues) { + headers = headers.set(header, headerValues[header]) } - return next.handle(request) + const authenticatedReq: HttpRequest = request.clone({ + headers: headers, + }) + return next.handle(authenticatedReq) } } From 4738e40acdf1ad4c8052fee6627d3b099665cd82 Mon Sep 17 00:00:00 2001 From: Annika Nowak Date: Mon, 15 Apr 2024 15:00:03 +0200 Subject: [PATCH 09/11] feat: remove angular prefix from service and serviceWrapper --- libs/angular-auth/src/lib/angular-auth.module.ts | 4 ++-- ...ngular-auth-service-wrapper.ts => auth-service-wrapper.ts} | 2 +- .../src/lib/{angular-auth.service.ts => auth.service.ts} | 0 .../src/lib/auth_services/keycloak-auth.service.ts | 2 +- libs/angular-auth/src/lib/token.interceptor.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename libs/angular-auth/src/lib/{angular-auth-service-wrapper.ts => auth-service-wrapper.ts} (97%) rename libs/angular-auth/src/lib/{angular-auth.service.ts => auth.service.ts} (100%) diff --git a/libs/angular-auth/src/lib/angular-auth.module.ts b/libs/angular-auth/src/lib/angular-auth.module.ts index 319f14fd..b835486f 100644 --- a/libs/angular-auth/src/lib/angular-auth.module.ts +++ b/libs/angular-auth/src/lib/angular-auth.module.ts @@ -3,8 +3,8 @@ import { CommonModule } from '@angular/common' import { HTTP_INTERCEPTORS } from '@angular/common/http' import { AUTH_SERVICE, ConfigurationService } from '@onecx/angular-integration-interface' import { TokenInterceptor } from './token.interceptor' -import { AuthService } from './angular-auth.service' -import { AuthServiceWrapper } from './angular-auth-service-wrapper' +import { AuthService } from './auth.service' +import { AuthServiceWrapper } from './auth-service-wrapper' import { KeycloakAuthService } from './auth_services/keycloak-auth.service' import { KeycloakService } from 'keycloak-angular' diff --git a/libs/angular-auth/src/lib/angular-auth-service-wrapper.ts b/libs/angular-auth/src/lib/auth-service-wrapper.ts similarity index 97% rename from libs/angular-auth/src/lib/angular-auth-service-wrapper.ts rename to libs/angular-auth/src/lib/auth-service-wrapper.ts index 47f4ffe2..976c81b6 100644 --- a/libs/angular-auth/src/lib/angular-auth-service-wrapper.ts +++ b/libs/angular-auth/src/lib/auth-service-wrapper.ts @@ -1,5 +1,5 @@ import { filter } from 'rxjs/internal/operators/filter' -import { AuthService } from './angular-auth.service' +import { AuthService } from './auth.service' import { EventsTopic } from '@onecx/integration-interface' import { AppStateService, CONFIG_KEY, ConfigurationService } from '@onecx/angular-integration-interface' import { Injectable, Injector } from '@angular/core' diff --git a/libs/angular-auth/src/lib/angular-auth.service.ts b/libs/angular-auth/src/lib/auth.service.ts similarity index 100% rename from libs/angular-auth/src/lib/angular-auth.service.ts rename to libs/angular-auth/src/lib/auth.service.ts diff --git a/libs/angular-auth/src/lib/auth_services/keycloak-auth.service.ts b/libs/angular-auth/src/lib/auth_services/keycloak-auth.service.ts index 3ef8f96b..252f2355 100644 --- a/libs/angular-auth/src/lib/auth_services/keycloak-auth.service.ts +++ b/libs/angular-auth/src/lib/auth_services/keycloak-auth.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core' import { ConfigurationService, CONFIG_KEY } from '@onecx/angular-integration-interface' import { KeycloakEventType, KeycloakOptions, KeycloakService } from 'keycloak-angular' import { KeycloakConfig } from 'keycloak-js' -import { AuthService } from '../angular-auth.service' +import { AuthService } from '../auth.service' const KC_REFRESH_TOKEN_LS = 'onecx_kc_refreshToken' const KC_ID_TOKEN_LS = 'onecx_kc_idToken' diff --git a/libs/angular-auth/src/lib/token.interceptor.ts b/libs/angular-auth/src/lib/token.interceptor.ts index 3abb72d8..c8274b9b 100644 --- a/libs/angular-auth/src/lib/token.interceptor.ts +++ b/libs/angular-auth/src/lib/token.interceptor.ts @@ -2,7 +2,7 @@ import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/c import { Injectable, Inject, Optional } from '@angular/core' import { AUTH_SERVICE } from '@onecx/angular-integration-interface' import { Observable, of } from 'rxjs' -import { AuthServiceWrapper } from './angular-auth-service-wrapper' +import { AuthServiceWrapper } from './auth-service-wrapper' const WHITELIST = ['assets'] From e944d9fc275dc787f8d6018075f4758e665e3caf Mon Sep 17 00:00:00 2001 From: Annika Nowak Date: Mon, 15 Apr 2024 15:00:40 +0200 Subject: [PATCH 10/11] fix: removal of unused imports --- libs/angular-auth/src/lib/angular-auth.module.ts | 2 +- libs/angular-auth/src/lib/token.interceptor.ts | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/libs/angular-auth/src/lib/angular-auth.module.ts b/libs/angular-auth/src/lib/angular-auth.module.ts index b835486f..b17c87f6 100644 --- a/libs/angular-auth/src/lib/angular-auth.module.ts +++ b/libs/angular-auth/src/lib/angular-auth.module.ts @@ -1,7 +1,7 @@ import { APP_INITIALIZER, NgModule } from '@angular/core' import { CommonModule } from '@angular/common' import { HTTP_INTERCEPTORS } from '@angular/common/http' -import { AUTH_SERVICE, ConfigurationService } from '@onecx/angular-integration-interface' +import { ConfigurationService } from '@onecx/angular-integration-interface' import { TokenInterceptor } from './token.interceptor' import { AuthService } from './auth.service' import { AuthServiceWrapper } from './auth-service-wrapper' diff --git a/libs/angular-auth/src/lib/token.interceptor.ts b/libs/angular-auth/src/lib/token.interceptor.ts index c8274b9b..883e78ea 100644 --- a/libs/angular-auth/src/lib/token.interceptor.ts +++ b/libs/angular-auth/src/lib/token.interceptor.ts @@ -1,7 +1,6 @@ import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http' -import { Injectable, Inject, Optional } from '@angular/core' -import { AUTH_SERVICE } from '@onecx/angular-integration-interface' -import { Observable, of } from 'rxjs' +import { Injectable } from '@angular/core' +import { Observable } from 'rxjs' import { AuthServiceWrapper } from './auth-service-wrapper' const WHITELIST = ['assets'] From da12b991e95b2f474be460f7c98fc6738fd11163 Mon Sep 17 00:00:00 2001 From: Annika Nowak Date: Mon, 15 Apr 2024 15:40:11 +0200 Subject: [PATCH 11/11] fix: remove const problems --- libs/angular-auth/src/lib/auth-service-wrapper.ts | 4 ++-- libs/angular-auth/src/lib/token.interceptor.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/angular-auth/src/lib/auth-service-wrapper.ts b/libs/angular-auth/src/lib/auth-service-wrapper.ts index 976c81b6..151b225e 100644 --- a/libs/angular-auth/src/lib/auth-service-wrapper.ts +++ b/libs/angular-auth/src/lib/auth-service-wrapper.ts @@ -22,11 +22,11 @@ export class AuthServiceWrapper { await this.configService.isInitialized this.initializeAuthService() - let initResult = this.getInitResult() + const initResult = this.getInitResult() return initResult } async getInitResult(): Promise { - let initResult = await this.authService?.init() + const initResult = await this.authService?.init() if (initResult) { await this.appStateService.isAuthenticated$.publish() diff --git a/libs/angular-auth/src/lib/token.interceptor.ts b/libs/angular-auth/src/lib/token.interceptor.ts index 883e78ea..500786e7 100644 --- a/libs/angular-auth/src/lib/token.interceptor.ts +++ b/libs/angular-auth/src/lib/token.interceptor.ts @@ -14,7 +14,7 @@ export class TokenInterceptor implements HttpInterceptor { if (skip) { return next.handle(request) } - let headerValues = this.authService.getHeaderValues() + const headerValues = this.authService.getHeaderValues() let headers = request.headers for (const header in headerValues) { headers = headers.set(header, headerValues[header])