]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
mgr/dashboard: frontend: JWT authentication implementation
authorRicardo Dias <rdias@suse.com>
Tue, 3 Jul 2018 10:34:11 +0000 (11:34 +0100)
committerRicardo Dias <rdias@suse.com>
Mon, 29 Oct 2018 15:47:14 +0000 (15:47 +0000)
Signed-off-by: Ricardo Dias <rdias@suse.com>
src/pybind/mgr/dashboard/frontend/package-lock.json
src/pybind/mgr/dashboard/frontend/package.json
src/pybind/mgr/dashboard/frontend/src/app/app.module.ts
src/pybind/mgr/dashboard/frontend/src/app/core/auth/login/login.component.html
src/pybind/mgr/dashboard/frontend/src/app/core/navigation/dashboard-help/dashboard-help.component.html
src/pybind/mgr/dashboard/frontend/src/app/core/navigation/dashboard-help/dashboard-help.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/auth.service.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/auth.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/models/credentials.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/models/login-response.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-storage.service.ts

index a7e597617e18a2d9df02a1eb8b3986f5b2a6a7d6..29a7140a0e5014ad1943fa9859c0f3b71e693694 100644 (file)
         "tslib": "^1.9.0"
       }
     },
+    "@auth0/angular-jwt": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/@auth0/angular-jwt/-/angular-jwt-2.0.0.tgz",
+      "integrity": "sha512-RVlXFpcqQ+9uCpzboU7Tm1ubaRVO2FrR5+RYuwHtTT4BXquVMEwOSbAuuaArFud/kMc00XYoGgiP1JkCfOAfpA==",
+      "requires": {
+        "url": "^0.11.0"
+      }
+    },
     "@babel/code-frame": {
       "version": "7.0.0-beta.56",
       "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.56.tgz",
         "balanced-match": {
           "version": "1.0.0",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "brace-expansion": {
           "version": "1.1.11",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "balanced-match": "^1.0.0",
             "concat-map": "0.0.1"
         "code-point-at": {
           "version": "1.1.0",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "concat-map": {
           "version": "0.0.1",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "console-control-strings": {
           "version": "1.1.0",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "core-util-is": {
           "version": "1.0.2",
         "inherits": {
           "version": "2.0.3",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "ini": {
           "version": "1.3.5",
           "version": "1.0.0",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "number-is-nan": "^1.0.0"
           }
           "version": "3.0.4",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "brace-expansion": "^1.1.7"
           }
         "minimist": {
           "version": "0.0.8",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "minipass": {
           "version": "2.2.4",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "safe-buffer": "^5.1.1",
             "yallist": "^3.0.0"
           "version": "0.5.1",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "minimist": "0.0.8"
           }
         "number-is-nan": {
           "version": "1.0.1",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "object-assign": {
           "version": "4.1.1",
           "version": "1.4.0",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "wrappy": "1"
           }
           "version": "1.0.2",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "code-point-at": "^1.0.0",
             "is-fullwidth-code-point": "^1.0.0",
     "querystring": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
-      "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
-      "dev": true
+      "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA="
     },
     "querystring-es3": {
       "version": "0.2.1",
       "version": "0.11.0",
       "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
       "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
-      "dev": true,
       "requires": {
         "punycode": "1.3.2",
         "querystring": "0.2.0"
         "punycode": {
           "version": "1.3.2",
           "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
-          "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=",
-          "dev": true
+          "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
         }
       }
     },
index 7f388544b512ea0873ce5c719130659d721f3090..1dfb7154ef804f5fa3b7ae5dbe86ccb4ad83a26e 100644 (file)
@@ -16,7 +16,6 @@
     "fix:prettier": "prettier --write \"{src,e2e}/**/*.{ts,scss}\"",
     "fix:tslint": "npm run lint:tslint -- --fix",
     "fix": "npm run fix:tslint; npm run fix:prettier"
-
   },
   "private": true,
   "jest": {
@@ -52,6 +51,7 @@
     "@angular/platform-browser": "6.1.4",
     "@angular/platform-browser-dynamic": "6.1.4",
     "@angular/router": "6.1.4",
+    "@auth0/angular-jwt": "^2.0.0",
     "@swimlane/ngx-datatable": "13.1.0",
     "@types/lodash": "4.14.116",
     "awesome-bootstrap-checkbox": "0.3.7",
index 2026b8ada9ce09b845e68828ce3140a2f3201387..47a4e44ad017005022978795b00530da2646c3b4 100644 (file)
@@ -3,6 +3,7 @@ import { ErrorHandler, NgModule } from '@angular/core';
 import { BrowserModule } from '@angular/platform-browser';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 
+import { JwtModule } from '@auth0/angular-jwt';
 import { ToastModule, ToastOptions } from 'ng2-toastr/ng2-toastr';
 import { AccordionModule } from 'ngx-bootstrap/accordion';
 import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
@@ -23,6 +24,10 @@ export class CustomOption extends ToastOptions {
   enableHTML = true;
 }
 
+export function jwtTokenGetter() {
+  return localStorage.getItem('access_token');
+}
+
 @NgModule({
   declarations: [AppComponent],
   imports: [
@@ -36,7 +41,12 @@ export class CustomOption extends ToastOptions {
     CephModule,
     AccordionModule.forRoot(),
     BsDropdownModule.forRoot(),
-    TabsModule.forRoot()
+    TabsModule.forRoot(),
+    JwtModule.forRoot({
+      config: {
+        tokenGetter: jwtTokenGetter
+      }
+    })
   ],
   exports: [SharedModule],
   providers: [
index a89d87ef1bb2054749ee27b5d6eca2dc91cbda72..a3d1d19af326f88bb0baba9eb6fa337b6f1830af 100644 (file)
           </div>
         </div>
 
-        <!-- Stay signed in -->
-        <div class="checkbox checkbox-primary">
-          <input id="stay_signed_in"
-                 name="stay_signed_in"
-                 type="checkbox"
-                 [(ngModel)]="model.stay_signed_in">
-          <label for="stay_signed_in"
-                 i18n="A checkbox on the login page to do not expire session on browser close">
-            Keep me logged in
-          </label>
-        </div>
-
         <input type="submit"
                class="btn btn-primary btn-block"
                [disabled]="loginForm.invalid"
index b2f0945691179ecdaab02c195bc84b90b738ee7f..b3b732761157b06fcc77a61bc8adb664f31376e2 100644 (file)
@@ -1,3 +1,7 @@
+<form #docsForm action="/docs" target="_blank" method="post">
+  <input type="hidden" name="token"/>
+</form>
+
 <div dropdown>
   <a dropdownToggle
      class="dropdown-toggle"
@@ -20,8 +24,7 @@
     <li>
       <a i18n
          class="dropdown-item"
-         href="/docs"
-         target="_blank">API
+         (click)="goToApiDocs()">API
       </a>
     </li>
     <li>
index 0774e86ea3daa35161c89e6371c909b4832259dc..bf7f1b574e1d880df7231a9884f6e50b76494f52 100644 (file)
@@ -1,8 +1,9 @@
-import { Component, OnInit } from '@angular/core';
+import { Component, OnInit, ViewChild } from '@angular/core';
 
 import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
 
 import { CephReleaseNamePipe } from '../../../shared/pipes/ceph-release-name.pipe';
+import { AuthStorageService } from '../../../shared/services/auth-storage.service';
 import { SummaryService } from '../../../shared/services/summary.service';
 import { AboutComponent } from '../about/about.component';
 
@@ -12,13 +13,16 @@ import { AboutComponent } from '../about/about.component';
   styleUrls: ['./dashboard-help.component.scss']
 })
 export class DashboardHelpComponent implements OnInit {
+  @ViewChild('docsForm')
+  docsFormElement;
   docsUrl: string;
   modalRef: BsModalRef;
 
   constructor(
     private summaryService: SummaryService,
     private cephReleaseNamePipe: CephReleaseNamePipe,
-    private modalService: BsModalService
+    private modalService: BsModalService,
+    private authStorageService: AuthStorageService
   ) {}
 
   ngOnInit() {
@@ -39,4 +43,10 @@ export class DashboardHelpComponent implements OnInit {
   openAboutModal() {
     this.modalRef = this.modalService.show(AboutComponent);
   }
+
+  goToApiDocs() {
+    const tokenInput = this.docsFormElement.nativeElement.children[0];
+    tokenInput.value = this.authStorageService.getToken();
+    this.docsFormElement.nativeElement.submit();
+  }
 }
index bfe7fdb2929dde8395f3b4d455c577dc4cf95b1b..fd2d9d8f57b3f6582b16b91e0bcb4eeff9ea7597 100644 (file)
@@ -32,13 +32,15 @@ describe('AuthService', () => {
     'should login and save the user',
     fakeAsync(() => {
       const fakeCredentials = { username: 'foo', password: 'bar' };
+      const fakeResponse = { username: 'foo', token: 'tokenbytes' };
       service.login(<any>fakeCredentials);
       const req = httpTesting.expectOne('api/auth');
       expect(req.request.method).toBe('POST');
       expect(req.request.body).toEqual(fakeCredentials);
-      req.flush(fakeCredentials);
+      req.flush(fakeResponse);
       tick();
       expect(localStorage.getItem('dashboard_username')).toBe('foo');
+      expect(localStorage.getItem('access_token')).toBe('tokenbytes');
     })
   );
 
index 9954e346b1a8d73a381e7a2d5afb230567768aac..68ed81f35b2312ce469b7ba98eb3b55ea2da6adc 100644 (file)
@@ -2,6 +2,7 @@ import { HttpClient } from '@angular/common/http';
 import { Injectable } from '@angular/core';
 
 import { Credentials } from '../models/credentials';
+import { LoginResponse } from '../models/login-response';
 import { AuthStorageService } from '../services/auth-storage.service';
 import { ApiModule } from './api.module';
 
@@ -15,8 +16,8 @@ export class AuthService {
     return this.http
       .post('api/auth', credentials)
       .toPromise()
-      .then((resp: Credentials) => {
-        this.authStorageService.set(resp.username, resp.permissions);
+      .then((resp: LoginResponse) => {
+        this.authStorageService.set(resp.username, resp.token, resp.permissions);
       });
   }
 
index 7e617d986797dd422250dc6b9d11f9d955e6b924..2c2b7d76e3991345424842c10e12554b7fb28258 100644 (file)
@@ -1,6 +1,4 @@
 export class Credentials {
   username: string;
   password: string;
-  permissions: any;
-  stay_signed_in = false;
 }
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/login-response.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/login-response.ts
new file mode 100644 (file)
index 0000000..4e8c5d1
--- /dev/null
@@ -0,0 +1,5 @@
+export class LoginResponse {
+  username: string;
+  token: string;
+  permissions: object;
+}
index 86afaff40fba16a9f60b380a0603acf791bc972b..b0053d0e3991824c9b316910b4e1226fdfd39c78 100644 (file)
@@ -9,15 +9,21 @@ import { ServicesModule } from './services.module';
 export class AuthStorageService {
   constructor() {}
 
-  set(username: string, permissions: any = {}) {
+  set(username: string, token: string, permissions: object = {}) {
     localStorage.setItem('dashboard_username', username);
+    localStorage.setItem('access_token', token);
     localStorage.setItem('dashboard_permissions', JSON.stringify(new Permissions(permissions)));
   }
 
   remove() {
+    localStorage.removeItem('access_token');
     localStorage.removeItem('dashboard_username');
   }
 
+  getToken(): string {
+    return localStorage.getItem('access_token');
+  }
+
   isLoggedIn() {
     return localStorage.getItem('dashboard_username') !== null;
   }