From 490848ebed1f2fa2dc428371fc46729003d5469c Mon Sep 17 00:00:00 2001 From: Florent Poittevin <florent.poittevin@unige.ch> Date: Tue, 16 Apr 2019 15:13:25 +0200 Subject: [PATCH] Add OAuth --- README.md | 24 +++++++++--- package-lock.json | 22 +++++++++++ package.json | 58 +++++++++++++++-------------- src/app/app.component.html | 24 +++--------- src/app/app.component.ts | 13 ++++++- src/app/app.module.ts | 43 ++++++++++++++++----- src/app/app.service.ts | 18 +++++++++ src/app/auth.config.ts | 27 ++++++++++++++ src/app/dlcm.interceptor.ts | 14 +++++++ src/app/dlcm.storage.ts | 29 +++++++++++++++ src/app/home/home.component.html | 10 +++++ src/app/home/home.component.scss | 0 src/app/home/home.component.spec.ts | 25 +++++++++++++ src/app/home/home.component.ts | 25 +++++++++++++ src/index.html | 2 +- 15 files changed, 270 insertions(+), 64 deletions(-) create mode 100644 src/app/app.service.ts create mode 100644 src/app/auth.config.ts create mode 100644 src/app/dlcm.interceptor.ts create mode 100644 src/app/dlcm.storage.ts create mode 100644 src/app/home/home.component.html create mode 100644 src/app/home/home.component.scss create mode 100644 src/app/home/home.component.spec.ts create mode 100644 src/app/home/home.component.ts diff --git a/README.md b/README.md index 183b0be17..59354e971 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,24 @@ -# DLCMFrontend +# DLCM-Frontend This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.3.8. +## Prerequisites + +- Install [NodeJS LTS](https://nodejs.org/en/) (currently version 10.15.3, check with `node -v`. Include currently npm version 6.4.1, check with `npm -v`). +- Install [Angular CLI](https://angular.io/cli) (currently version 7.3.8, check with `ng version`). +- For user of Jetbrains IDE (IntelliJ, WebStorm), go to `File > Settings > Appaearance & Behavior > System Settings` and disable the option `Use "safe write" (save changes to a temporary file first)`. + +### Apache + +Same as DLCM-Frontend in Php to mock Shibboleth + +## Install + +`npm install` + ## Development server -Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. +Run `npm start` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. ## Code scaffolding @@ -12,15 +26,15 @@ Run `ng generate component component-name` to generate a new component. You can ## Build -Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. +Run `npm build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. ## Running unit tests -Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). +Run `npm test` to execute the unit tests via [Karma](https://karma-runner.github.io). ## Running end-to-end tests -Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). +Run `npm e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). ## Further help diff --git a/package-lock.json b/package-lock.json index 70ee5382e..34c72b8b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -623,6 +623,14 @@ "webpack-sources": "1.3.0" } }, + "@ngxs/store": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@ngxs/store/-/store-3.4.3.tgz", + "integrity": "sha512-pVwxFxCxr09+zL37ot1hf6EhgpftNLSAseBZwHUlx40x2qAT+jat8XJlj4j0p+BoVEjlaVZn8zE6qMm2oSPlZw==", + "requires": { + "tslib": "^1.9.0" + } + }, "@schematics/angular": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-7.3.8.tgz", @@ -997,6 +1005,15 @@ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", "dev": true }, + "angular-oauth2-oidc": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/angular-oauth2-oidc/-/angular-oauth2-oidc-5.0.2.tgz", + "integrity": "sha512-jtOv4IWEjSFfBHVE4seWGWT/ZfWJ95QJ1JaFhVVGJEF64ibGuPwV3ztwTOUl98QHi/Yg4PXXDAisb31JnIbxBw==", + "requires": { + "jsrsasign": "^8.0.12", + "tslib": "^1.9.0" + } + }, "ansi-colors": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", @@ -5505,6 +5522,11 @@ "verror": "1.10.0" } }, + "jsrsasign": { + "version": "8.0.12", + "resolved": "https://registry.npmjs.org/jsrsasign/-/jsrsasign-8.0.12.tgz", + "integrity": "sha1-Iqu5ZW00owuVMENnIINeicLlwxY=" + }, "jszip": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.2.1.tgz", diff --git a/package.json b/package.json index b459bccc0..e0f202d1c 100644 --- a/package.json +++ b/package.json @@ -11,38 +11,40 @@ }, "private": true, "dependencies": { - "@angular/animations": "~7.2.0", - "@angular/common": "~7.2.0", - "@angular/compiler": "~7.2.0", - "@angular/core": "~7.2.0", - "@angular/forms": "~7.2.0", - "@angular/platform-browser": "~7.2.0", - "@angular/platform-browser-dynamic": "~7.2.0", - "@angular/router": "~7.2.0", + "@angular/animations": "^7.2.0", + "@angular/common": "^7.2.0", + "@angular/compiler": "^7.2.0", + "@angular/core": "^7.2.0", + "@angular/forms": "^7.2.0", + "@angular/platform-browser": "^7.2.0", + "@angular/platform-browser-dynamic": "^7.2.0", + "@angular/router": "^7.2.0", + "@ngxs/store": "^3.4.3", + "angular-oauth2-oidc": "^5.0.2", "core-js": "^2.5.4", - "rxjs": "~6.3.3", + "rxjs": "^6.3.3", "tslib": "^1.9.0", - "zone.js": "~0.8.26" + "zone.js": "^0.8.26" }, "devDependencies": { - "@angular-devkit/build-angular": "~0.13.0", - "@angular/cli": "~7.3.8", - "@angular/compiler-cli": "~7.2.0", - "@angular/language-service": "~7.2.0", - "@types/node": "~8.9.4", - "@types/jasmine": "~2.8.8", - "@types/jasminewd2": "~2.0.3", - "codelyzer": "~4.5.0", - "jasmine-core": "~2.99.1", - "jasmine-spec-reporter": "~4.2.1", - "karma": "~4.0.0", - "karma-chrome-launcher": "~2.2.0", - "karma-coverage-istanbul-reporter": "~2.0.1", - "karma-jasmine": "~1.1.2", + "@angular-devkit/build-angular": "^0.13.0", + "@angular/cli": "^7.3.8", + "@angular/compiler-cli": "^7.2.0", + "@angular/language-service": "^7.2.0", + "@types/node": "^8.9.4", + "@types/jasmine": "^2.8.8", + "@types/jasminewd2": "^2.0.3", + "codelyzer": "^4.5.0", + "jasmine-core": "^2.99.1", + "jasmine-spec-reporter": "^4.2.1", + "karma": "^4.0.0", + "karma-chrome-launcher": "^2.2.0", + "karma-coverage-istanbul-reporter": "^2.0.1", + "karma-jasmine": "^1.1.2", "karma-jasmine-html-reporter": "^0.2.2", - "protractor": "~5.4.0", - "ts-node": "~7.0.0", - "tslint": "~5.11.0", - "typescript": "~3.2.2" + "protractor": "^5.4.0", + "ts-node": "^7.0.0", + "tslint": "^5.11.0", + "typescript": "^3.2.2" } } diff --git a/src/app/app.component.html b/src/app/app.component.html index 0f3d9d8b9..0ea2e42fa 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,21 +1,7 @@ -<!--The content below is only a placeholder and can be replaced.--> -<div style="text-align:center"> - <h1> - Welcome to {{ title }}! - </h1> - <img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg=="> -</div> -<h2>Here are some links to help you start: </h2> -<ul> - <li> - <h2><a target="_blank" rel="noopener" href="https://angular.io/tutorial">Tour of Heroes</a></h2> - </li> - <li> - <h2><a target="_blank" rel="noopener" href="https://angular.io/cli">CLI Documentation</a></h2> - </li> - <li> - <h2><a target="_blank" rel="noopener" href="https://blog.angular.io/">Angular blog</a></h2> - </li> -</ul> +<h1> + Welcome to {{ title }}! +</h1> + +<dlcm-home></dlcm-home> <router-outlet></router-outlet> diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 1e55cbdb0..f99d76118 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,10 +1,19 @@ -import { Component } from '@angular/core'; +import {Component} from '@angular/core'; + +import {OAuthService} from 'angular-oauth2-oidc'; @Component({ selector: 'dlcm-root', templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'] + styleUrls: ['./app.component.scss'], }) export class AppComponent { title = 'DLCM-Frontend'; + + constructor(private oauthService: OAuthService) { + // This method just tries to parse the token(s) within the url when + // the auth-server redirects the user back to the web-app + // It doesn't send the user the the login page + this.oauthService.tryLogin(); + } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 2c3ba2995..dcb476299 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,18 +1,43 @@ -import { BrowserModule } from '@angular/platform-browser'; -import { NgModule } from '@angular/core'; +import {HTTP_INTERCEPTORS, HttpClientModule} from '@angular/common/http'; +import {NgModule} from '@angular/core'; +import {BrowserModule} from '@angular/platform-browser'; +import {NgxsModule} from '@ngxs/store'; -import { AppRoutingModule } from './app-routing.module'; -import { AppComponent } from './app.component'; +import {AuthConfig, JwksValidationHandler, OAuthModule, OAuthModuleConfig, OAuthStorage, ValidationHandler} from 'angular-oauth2-oidc'; + +import {AppRoutingModule} from './app-routing.module'; +import {AppComponent} from './app.component'; +import {HomeComponent} from './home/home.component'; +import {DlcmStorage} from './dlcm.storage'; +import {DlcmInterceptor} from './dlcm.interceptor'; +import {authConfig, authModuleConfig} from './auth.config'; @NgModule({ declarations: [ - AppComponent + AppComponent, + HomeComponent, ], imports: [ BrowserModule, - AppRoutingModule + AppRoutingModule, + NgxsModule.forRoot([ + // MyState + ]), + HttpClientModule, + OAuthModule.forRoot(authModuleConfig), + ], + providers: [ + {provide: OAuthModuleConfig, useValue: authModuleConfig}, + {provide: ValidationHandler, useClass: JwksValidationHandler}, + {provide: OAuthStorage, useClass: DlcmStorage}, + {provide: AuthConfig, useValue: authConfig}, + { + provide: HTTP_INTERCEPTORS, + useClass: DlcmInterceptor, + multi: true, + }, ], - providers: [], - bootstrap: [AppComponent] + bootstrap: [AppComponent], }) -export class AppModule { } +export class AppModule { +} diff --git a/src/app/app.service.ts b/src/app/app.service.ts new file mode 100644 index 000000000..403cc92a7 --- /dev/null +++ b/src/app/app.service.ts @@ -0,0 +1,18 @@ +import {Injectable} from '@angular/core'; +import {Router} from '@angular/router'; +import {HttpClient} from '@angular/common/http'; + +@Injectable() +export class AppService { + + constructor(private router: Router, + private http: HttpClient) { + } + + getOrganizationUnits() { + this.http.get('http://localhost:16118/dlcm/access/oai-sets/organizational-units') + .subscribe((data) => { + console.log('test : ', data); + }); + } +} diff --git a/src/app/auth.config.ts b/src/app/auth.config.ts new file mode 100644 index 000000000..913b672f5 --- /dev/null +++ b/src/app/auth.config.ts @@ -0,0 +1,27 @@ +import {AuthConfig, OAuthModuleConfig} from 'angular-oauth2-oidc'; + +export const authConfig: AuthConfig = { + oidc: false, + loginUrl: 'http://localhost/dlcm/authorisation/authorize', + redirectUri: window.location.origin, + responseType: 'code', + dummyClientSecret: '123abc', + clientId: 'local-angular', + scope: 'READ', + requireHttps: false, + tokenEndpoint: 'http://localhost/dlcm/authorisation/token', + requestAccessToken: true, + showDebugInformation: true, + useIdTokenHintForSilentRefresh: true, + customQueryParams: { + response_type: 'implicit', + }, +}; + +export const authModuleConfig: OAuthModuleConfig = { + // Inject "Authorization: Bearer ..." header for these APIs: + resourceServer: { + allowedUrls: ['http://localhost'], + sendAccessToken: true, + }, +}; diff --git a/src/app/dlcm.interceptor.ts b/src/app/dlcm.interceptor.ts new file mode 100644 index 000000000..f0fe16974 --- /dev/null +++ b/src/app/dlcm.interceptor.ts @@ -0,0 +1,14 @@ +import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http'; +import {Injectable} from '@angular/core'; +import {Observable} from 'rxjs'; + +@Injectable() +export class DlcmInterceptor implements HttpInterceptor { + constructor() { + } + + intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { + request = request.clone(); + return next.handle(request); + } +} diff --git a/src/app/dlcm.storage.ts b/src/app/dlcm.storage.ts new file mode 100644 index 000000000..86b514d62 --- /dev/null +++ b/src/app/dlcm.storage.ts @@ -0,0 +1,29 @@ +import {OAuthStorage} from 'angular-oauth2-oidc'; +import {Injectable} from '@angular/core'; + +/** + * Custom storage in memory for sensible data like token + * (cf best practice https://auth0.com/docs/security/store-tokens) + */ +@Injectable() +export class DlcmStorage extends OAuthStorage { + private storageValues: Map<string, string>; + + constructor() { + super(); + this.storageValues = new Map<string, string>(); + } + + getItem(key: string): string | null { + return this.storageValues[key]; + } + + removeItem(key: string): void { + this.storageValues.delete(key); + } + + setItem(key: string, data: string): void { + this.storageValues.set(key, data); + } + +} diff --git a/src/app/home/home.component.html b/src/app/home/home.component.html new file mode 100644 index 000000000..daa6b8f33 --- /dev/null +++ b/src/app/home/home.component.html @@ -0,0 +1,10 @@ +<div> + Token : {{token}} +</div> +<div> + Claims : {{claims}} +</div> + +<button class="btn btn-default" (click)="login()"> + Login +</button> diff --git a/src/app/home/home.component.scss b/src/app/home/home.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/home/home.component.spec.ts b/src/app/home/home.component.spec.ts new file mode 100644 index 000000000..490e81bdf --- /dev/null +++ b/src/app/home/home.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HomeComponent } from './home.component'; + +describe('HomeComponent', () => { + let component: HomeComponent; + let fixture: ComponentFixture<HomeComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ HomeComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(HomeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts new file mode 100644 index 000000000..b29a37cf8 --- /dev/null +++ b/src/app/home/home.component.ts @@ -0,0 +1,25 @@ +import {Component} from '@angular/core'; +import {OAuthService} from 'angular-oauth2-oidc'; + +@Component({ + selector: 'dlcm-home', + templateUrl: './home.component.html', + styleUrls: ['./home.component.scss'], +}) +export class HomeComponent { + + constructor(private oauthService: OAuthService) { + } + + get token() { + return this.oauthService.getAccessToken(); + } + + get claims() { + return this.oauthService.getIdentityClaims(); + } + + login() { + this.oauthService.initImplicitFlow(); + } +} diff --git a/src/index.html b/src/index.html index 205094712..3966aebad 100644 --- a/src/index.html +++ b/src/index.html @@ -2,7 +2,7 @@ <html lang="en"> <head> <meta charset="utf-8"> - <title>DLCMFrontend</title> + <title>DLCM - Frontend</title> <base href="/"> <meta name="viewport" content="width=device-width, initial-scale=1"> -- GitLab