diff --git a/docs/app/docs/page/blocks/basic-blocks/ngd-methods-block.component.ts b/docs/app/docs/page/blocks/basic-blocks/ngd-methods-block.component.ts index 1570649845..15bb662e03 100644 --- a/docs/app/docs/page/blocks/basic-blocks/ngd-methods-block.component.ts +++ b/docs/app/docs/page/blocks/basic-blocks/ngd-methods-block.component.ts @@ -24,7 +24,7 @@ import { Component, Input } from '@angular/core'; {{ method.name }}
static method -
+
parameters: {{param.name}}: {{param.type}}, diff --git a/docs/articles/auth-install.md b/docs/articles/auth-install.md index 4222db0db6..2827c1d939 100644 --- a/docs/articles/auth-install.md +++ b/docs/articles/auth-install.md @@ -7,7 +7,10 @@ ## Installation steps -1) First, let's install the module as it's distributed as an npm package, but make sure you have the [Nebular theme module up and running](https://akveo.github.io/nebular/#/docs/installation/add-into-existing-project). +1) First, let's install the module as it's distributed as an npm package, but make sure you have the [Nebular Theme module up and running](https://akveo.github.io/nebular/#/docs/installation/add-into-existing-project). +Nebular Theme is required to use built-in Auth Components. If you are not going to use those at all, you can use `Auth Module` without `Nebular Theme`. + +Let's assume that we need to setup email & password authentication based on Nebular Auth and NbEmailPassAuthProvider. `npm i @nebular/auth` @@ -39,9 +42,9 @@ ``` -We also specify a `forms` key, which configures available options for the UI components, but let's leave it empty for now and get back to it in the [Configuring UI](#/docs/auth/configuring-ui) article. +We also specified a `forms` key, which configures available options for the Auth Components. We'll leave it empty for now and get back to it in the [Configuring UI](#/docs/auth/configuring-ui) article. -4) Next, we need to configure UI part, let's add UI components into your `app-routing.module.ts`: +4) Next, we need to configure Auth Components routes, let's add those into your `app-routing.module.ts`: ```typescript @@ -91,13 +94,6 @@ export const routes: Routes = [ ]; ``` -
-
Note
-
- The components are wrapped by `NbAuthBlockComponent`, which is optional and just provides some basic styling for the page. -
-
- 5) Last but not least - install the component styles into your themes.scss ([more details](/#/docs/guides/enabling-theme-system)): ```scss diff --git a/docs/articles/auth-intro.md b/docs/articles/auth-intro.md index 2dd4a45453..317336e639 100644 --- a/docs/articles/auth-intro.md +++ b/docs/articles/auth-intro.md @@ -17,14 +17,19 @@ Authentication UI components: - Register - Password Recover - Password Reset + +You can use the built-in components or create your custom ones. -Two auth providers: - - Dummy auth provider - simple provider for testing purposes, could be used to simulate backend responses while API is in the development; - - EmailPass auth provider - the most common email/password authentication strategy. +Auth providers: + - `NbDummyAuthProvider` - simple provider for testing purposes, could be used to simulate backend responses while API is in the development; + - `NbEmailPassAuthProvider` - the most common email/password authentication strategy. Other helper services: - - Token Service, JWT token, and Simple token - helper services for token management handling; - - JWT and Simple HTTP interceptors - intercepts the token into your HTTP requests. + - `NbAuthService` - facade service to communicate with a configured provider; + - `NbTokenService` - service that allows you to manage authentication token - get, set, clear and also listen to token changes over time; + - `NbTokenLocalStorage` - storage service for storing tokens in a browser local storage; + - `NbAuthJWTToken` and `NbAuthSimpleToken` - helper classes to work with your token; + - `NbAuthJWTInterceptor` and `NbAuthSimpleInterceptor` - http interceptors to inject token value into http requests.
diff --git a/docs/articles/auth-provider.md b/docs/articles/auth-provider.md index 0d966bd180..28e5cb1c49 100644 --- a/docs/articles/auth-provider.md +++ b/docs/articles/auth-provider.md @@ -1,7 +1,7 @@ ## A provider -In Nebular terms `auth provider` is a class containing authentication logic using by the application UI. -It accepts user input (login/email/password/oauth token/etc), communicates the input to the backend API and finally provides some resulting output back to the UI layer. +In Nebular terms `auth provider` is a class containing authentication logic specific for some authentication flow (email&password, OAuth, etc). +It accepts user input (login/email/password/token/etc), communicates the input to the backend API and finally provides the resulting output back to the Auth UI layer. Currently, there are two Auth Providers available out of the box: Two auth providers: diff --git a/docs/articles/auth-token.md b/docs/articles/auth-token.md index 1313bff7a9..ca3154b611 100644 --- a/docs/articles/auth-token.md +++ b/docs/articles/auth-token.md @@ -3,8 +3,8 @@ At this step, we assume that Nebular Auth module is up and running, you have successfully configured an auth provider and adjusted auth look & fell accordingly with your requirements. -It's time to get a user token after successful authentication to be able to communicate with the server and, for instance, show the username in the header of the application. -Let's assume that your backend returns a JWT token so that we can use the token payload to extract the user info out of it. +It's time to get a user token after successful authentication to be able to communicate with the server and, for instance, show a username in the header of the application. +Let's assume that your backend returns a JWT token so that we can use the token payload to extract a user info out of it. 1) Firstly, let's tell Nebular that we are waiting for JWT token, to do that we just need to provide a respective class. Open your `app.module.ts` and add the following: @@ -17,12 +17,12 @@ import { NB_AUTH_TOKEN_WRAPPER_TOKEN, NbAuthJWTToken } from '@nebular/auth'; providers: [ ... - { provide: NB_AUTH_TOKEN_WRAPPER_TOKEN, useClass: NbAuthJWTToken }, + { provide: NB_AUTH_TOKEN_WRAPPER_TOKEN, useValue: NbAuthJWTToken }, ], }); ``` -This line tells Angular to inject `NbAuthJWTToken` (instead of the default `NbAuthSimpleToken`) which is a simple wrapper class around a value your API service returns. +This line tells Angular to inject `NbAuthJWTToken` (instead of the default `NbAuthSimpleToken`) which is a wrapper class around a value your API service returns. 2) Then, let's configure where Nebular should look for the token in the login/register response data. By default Nebular expects that your token is located under the `data.token` keys of the JSON response: @@ -34,7 +34,7 @@ This line tells Angular to inject `NbAuthJWTToken` (instead of the default `NbAu } ``` -Imagine that our API returns the token as `{token: 'some-jwt-token'}` not wrapping your response in `data` property, let's tell that to Nebular: +We'll assume that our API returns a token as just `{token: 'some-jwt-token'}` not wrapping your response in the `data` property, let's tell that to Nebular: ```typescript @NgModule({ @@ -98,7 +98,7 @@ export class HeaderComponent { this.authService.onTokenChange() .subscribe((token: NbAuthJWTToken) => { - if (token.getValue()) { + if (token.isValid()) { this.user = token.getPayload(); // here we receive a payload from the token and assigne it to our `user` variable } @@ -108,7 +108,7 @@ export class HeaderComponent { } ``` -6) Lastly, let's grad the `user` variable and put it in the template to show the user info. The `nb-user` component is a great fit for this: +6) Lastly, let's grad a `user` variable and put it in the template to show the user info. The `nb-user` component is a great fit for this: ```typescript diff --git a/docs/articles/security-acl-configuration.md b/docs/articles/security-acl-configuration.md index 6e08fcc8d9..71e00b80a1 100644 --- a/docs/articles/security-acl-configuration.md +++ b/docs/articles/security-acl-configuration.md @@ -130,7 +130,7 @@ export class RoleProvider implements NbRoleProvider { return this.authService.onTokenChange() .pipe( map((token: NbAuthJWTToken) => { - return token ? token.getPayload()['role'] : 'guest'; + return token.isValid() ? token.getPayload()['role'] : 'guest'; }), ); } diff --git a/docs/output.json b/docs/output.json index 188f199491..86aa8ef496 100644 --- a/docs/output.json +++ b/docs/output.json @@ -379,7 +379,7 @@ "platform": null, "name": "getToken", "type": [ - "Observable" + "Observable" ], "isStatic": false, "shortDescription": "Retrieves current authenticated token stored" @@ -393,7 +393,7 @@ "Observable" ], "isStatic": false, - "shortDescription": "Returns true if auth token is presented in the token storage\n// TODO: check exp date for JWT token\n// TODO: to implement previous todo lets use isValid method of token" + "shortDescription": "Returns true if auth token is presented in the token storage" }, { "examples": [], @@ -422,7 +422,7 @@ "Observable" ], "isStatic": false, - "shortDescription": "Returns authentication status stream\n // TODO: check exp date for JWT token\n // TODO: to implement previous todo lets use isValid method of token" + "shortDescription": "Returns authentication status stream" }, { "examples": [], @@ -430,11 +430,31 @@ "platform": null, "name": "onTokenChange", "type": [ - "Observable" + "Observable" ], "isStatic": false, "shortDescription": "Returns tokens stream" }, + { + "examples": [], + "params": [ + { + "name": "result", + "type": "NbAuthResult", + "required": null, + "shortDescription": "", + "description": "" + } + ], + "platform": null, + "name": "processResultToken", + "type": [ + "Observable" + ], + "isStatic": false, + "shortDescription": "", + "description": "" + }, { "examples": [], "params": [ @@ -510,11 +530,31 @@ "styles": [] }, { - "kind": "service", + "kind": "class", "platform": null, "examples": [], "props": [], "methods": [ + { + "examples": [], + "params": [ + { + "name": "token", + "type": "string", + "required": null, + "shortDescription": "", + "description": "" + } + ], + "platform": null, + "name": "constructor", + "type": [ + "NbAuthJWTToken" + ], + "isStatic": false, + "shortDescription": "", + "description": "" + }, { "examples": [], "params": [], @@ -524,7 +564,7 @@ "any" ], "isStatic": false, - "shortDescription": "TODO: check for this.token to be not null\nReturns payload object" + "shortDescription": "Returns payload object" }, { "examples": [], @@ -550,23 +590,25 @@ }, { "examples": [], - "params": [ - { - "name": "token", - "type": "string", - "required": null, - "shortDescription": "", - "description": "" - } + "params": [], + "platform": null, + "name": "isValid", + "type": [ + "boolean" ], + "isStatic": false, + "shortDescription": "Is data expired" + }, + { + "examples": [], + "params": [], "platform": null, - "name": "setValue", + "name": "toString", "type": [ - "void" + "string" ], "isStatic": false, - "shortDescription": "", - "description": "" + "shortDescription": "Validate value and convert to string, if value is not valid return empty string" } ], "name": "NbAuthJWTToken", @@ -574,11 +616,31 @@ "styles": [] }, { - "kind": "service", + "kind": "class", "platform": null, "examples": [], "props": [], "methods": [ + { + "examples": [], + "params": [ + { + "name": "token", + "type": "string", + "required": null, + "shortDescription": "", + "description": "" + } + ], + "platform": null, + "name": "constructor", + "type": [ + "NbAuthSimpleToken" + ], + "isStatic": false, + "shortDescription": "", + "description": "" + }, { "examples": [], "params": [], @@ -592,23 +654,25 @@ }, { "examples": [], - "params": [ - { - "name": "token", - "type": "string", - "required": null, - "shortDescription": "", - "description": "" - } + "params": [], + "platform": null, + "name": "isValid", + "type": [ + "boolean" ], + "isStatic": false, + "shortDescription": "Is non empty and valid" + }, + { + "examples": [], + "params": [], "platform": null, - "name": "setValue", + "name": "toString", "type": [ - "void" + "string" ], "isStatic": false, - "shortDescription": "", - "description": "" + "shortDescription": "Validate value and convert to string, if value is not valid return empty string" } ], "name": "NbAuthSimpleToken", @@ -618,28 +682,15 @@ { "kind": "service", "platform": null, - "examples": [ - { - "shortDescription": "Injecting NbAuthJWTToken, so that NbTokenService will now return NbAuthJWTToken instead\nof the default NbAuthSimpleToken", - "description": "", - "code": "\n// import token and service into your AppModule\nimport { NB_AUTH_TOKEN_WRAPPER_TOKEN, NbAuthJWTToken} from '@nebular/auth';\n\n// add to a list of providers\nproviders: [\n // ...\n { provide: NB_AUTH_TOKEN_WRAPPER_TOKEN, useClass: NbAuthJWTToken },\n],\n" - } - ], + "examples": [], "props": [], "methods": [ { "examples": [], "params": [ { - "name": "options", - "type": "any", - "required": null, - "shortDescription": "", - "description": "" - }, - { - "name": "tokenWrapper", - "type": "NbAuthSimpleToken", + "name": "tokenClass", + "type": "NbTokenClass", "required": null, "shortDescription": "", "description": "" @@ -648,7 +699,7 @@ "platform": null, "name": "constructor", "type": [ - "NbTokenService" + "NbTokenLocalStorage" ], "isStatic": false, "shortDescription": "", @@ -660,10 +711,10 @@ "platform": null, "name": "clear", "type": [ - "Observable" + "void" ], "isStatic": false, - "shortDescription": "Removes the token" + "shortDescription": "Clears token from localStorage" }, { "examples": [], @@ -671,44 +722,106 @@ "platform": null, "name": "get", "type": [ - "Observable" + "NbAuthToken" ], "isStatic": false, - "shortDescription": "Returns observable of current token" + "shortDescription": "Returns token from localStorage" }, { "examples": [], "params": [ { - "name": "key", - "type": "string", + "name": "token", + "type": "\n", "required": null, - "shortDescription": "", - "description": "" + "description": "\n" } ], "platform": null, - "name": "getConfigValue", + "name": "set", "type": [ - "any" + "void" ], "isStatic": false, - "shortDescription": "", - "description": "" + "shortDescription": "Sets token to localStorage" }, { "examples": [], "params": [ { "name": "token", - "type": "NbAuthSimpleToken", + "type": "string", + "required": null, + "description": "\n" + } + ], + "platform": null, + "name": "setRaw", + "type": [ + "void" + ], + "isStatic": false, + "shortDescription": "Sets raw (string) token to localStorage" + } + ], + "name": "NbTokenLocalStorage", + "description": "The token storage is provided into auth module the following way:\n```\n{ provide: NbTokenStorage, useClass: NbTokenLocalStorage },\n```\n\nIf you need to change the storage behaviour or provide your own - just extend your class from basic `NbTokenStorage`\nor `NbTokenLocalStorage` and provide in your `app.module`:\n```\n{ provide: NbTokenStorage, useClass: NbTokenCustomStorage },\n```\n\n", + "shortDescription": "Service that uses browser localStorage as a storage.", + "styles": [] + }, + { + "kind": "service", + "platform": null, + "examples": [], + "props": [], + "methods": [ + { + "examples": [], + "params": [ + { + "name": "tokenStorage", + "type": "NbTokenStorage", "required": null, "shortDescription": "", "description": "" } ], "platform": null, - "name": "publishToken", + "name": "constructor", + "type": [ + "NbTokenService" + ], + "isStatic": false, + "shortDescription": "", + "description": "" + }, + { + "examples": [], + "params": [], + "platform": null, + "name": "clear", + "type": [ + "Observable" + ], + "isStatic": false, + "shortDescription": "Removes the token and published token value" + }, + { + "examples": [], + "params": [], + "platform": null, + "name": "get", + "type": [ + "Observable" + ], + "isStatic": false, + "shortDescription": "Returns observable of current token" + }, + { + "examples": [], + "params": [], + "platform": null, + "name": "publishStoredToken", "type": [ "void" ], @@ -720,8 +833,8 @@ "examples": [], "params": [ { - "name": "rawToken", - "type": "string", + "name": "token", + "type": "NbAuthToken", "required": null } ], @@ -731,27 +844,24 @@ "Observable" ], "isStatic": false, - "shortDescription": "Sets the token into the storage. This method is used by the NbAuthService automatically." + "shortDescription": "Sets a token into the storage. This method is used by the NbAuthService automatically." }, { "examples": [], "params": [ { - "name": "config", - "type": "any", - "required": null, - "shortDescription": "", - "description": "" + "name": "token", + "type": "string", + "required": null } ], "platform": null, - "name": "setConfig", + "name": "setRaw", "type": [ - "void" + "Observable" ], "isStatic": false, - "shortDescription": "", - "description": "" + "shortDescription": "Sets a raw token into the storage. This method is used by the NbAuthService automatically." }, { "examples": [], @@ -759,14 +869,14 @@ "platform": null, "name": "tokenChange", "type": [ - "Observable" + "Observable" ], "isStatic": false, "shortDescription": "Publishes token when it changes." } ], "name": "NbTokenService", - "shortDescription": "Nebular token service. Provides access to the stored token.\nBy default returns NbAuthSimpleToken instance,\nbut you can inject NbAuthJWTToken if you need additional methods for JWT token.", + "shortDescription": "Service that allows you to manage authentication token - get, set, clear and also listen to token changes over time.", "styles": [] }, { diff --git a/docs/structure.ts b/docs/structure.ts index 6bbc9f2ee1..2bd9778df5 100644 --- a/docs/structure.ts +++ b/docs/structure.ts @@ -605,6 +605,17 @@ export const STRUCTURE = [ }, ], }, + { + type: 'page', + name: 'NbTokenLocalStorage', + children: [ + { + type: 'block', + block: 'component', + blockData: 'NbTokenLocalStorage', + }, + ], + }, ], }, { diff --git a/gulpfile.js b/gulpfile.js index 7832656e69..3eafe41421 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -64,6 +64,7 @@ const ROLLUP_GLOBALS = { 'rxjs/operators/startWith': 'Rx.operators', 'rxjs/operators/auditTime': 'Rx.operators', 'rxjs/operators/switchMap': 'Rx.operators', + 'rxjs/operators/switchMapTo': 'Rx.operators', 'rxjs/operators/finalize': 'Rx.operators', 'rxjs/operators/catchError': 'Rx.operators', 'rxjs/operators/share': 'Rx.operators', diff --git a/karma.conf.js b/karma.conf.js index 5c4c88d921..009bf2cf41 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -72,7 +72,7 @@ module.exports = function (config) { timeout: 600, pollingTimeout: 20000, video: false, - }, + } }; if (process.env['TRAVIS']) { diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 4207988fef..b1461fb468 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -26,7 +26,7 @@ import { } from '@nebular/theme'; import { - NB_AUTH_TOKEN_WRAPPER_TOKEN, + NB_AUTH_TOKEN_CLASS, NbAuthJWTToken, NbAuthModule, NbEmailPassAuthProvider, @@ -228,7 +228,7 @@ const NB_TEST_COMPONENTS = [ ], providers: [ AuthGuard, - { provide: NB_AUTH_TOKEN_WRAPPER_TOKEN, useClass: NbAuthJWTToken }, + { provide: NB_AUTH_TOKEN_CLASS, useValue: NbAuthJWTToken }, { provide: HTTP_INTERCEPTORS, useClass: NbAuthJWTInterceptor, multi: true }, { provide: NbRoleProvider, useClass: RoleProvider }, ], diff --git a/src/app/role.provider.ts b/src/app/role.provider.ts index 474673c9e6..4b319e3141 100644 --- a/src/app/role.provider.ts +++ b/src/app/role.provider.ts @@ -15,7 +15,7 @@ export class RoleProvider implements NbRoleProvider { return this.authService.onTokenChange() .pipe( map((token: NbAuthJWTToken) => { - return token && token.getPayload() ? token.getPayload()['role'] : 'guest'; + return token.isValid() ? token.getPayload()['role'] : 'guest'; }), ); } diff --git a/src/framework/auth/auth.module.ts b/src/framework/auth/auth.module.ts index 552fbe1a8c..c6ab425864 100644 --- a/src/framework/auth/auth.module.ts +++ b/src/framework/auth/auth.module.ts @@ -9,18 +9,18 @@ import { NbLayoutModule, NbCardModule, NbCheckboxModule } from '@nebular/theme'; import { NbAuthService } from './services/auth.service'; import { NbDummyAuthProvider } from './providers/dummy-auth.provider'; import { NbEmailPassAuthProvider } from './providers/email-pass-auth.provider'; - +import { NbTokenService } from './services/token/token.service'; +import { NbAuthSimpleToken } from './services/token/token'; +import { NbTokenLocalStorage, NbTokenStorage } from './services/token/token-storage'; import { defaultSettings, - NB_AUTH_USER_OPTIONS_TOKEN, - NB_AUTH_OPTIONS_TOKEN, - NB_AUTH_PROVIDERS_TOKEN, - NB_AUTH_TOKEN_WRAPPER_TOKEN, - NbAuthOptions, NB_AUTH_INTERCEPTOR_HEADER, + NB_AUTH_USER_OPTIONS, + NB_AUTH_OPTIONS, + NB_AUTH_PROVIDERS, + NbAuthOptions, NB_AUTH_INTERCEPTOR_HEADER, NB_AUTH_TOKEN_CLASS, } from './auth.options'; import { NbAuthComponent } from './components/auth.component'; -import { NbAuthSimpleToken, NbTokenService } from './services/token.service'; import { NbAuthBlockComponent } from './components/auth-block/auth-block.component'; import { NbLoginComponent } from './components/login/login.component'; @@ -84,16 +84,17 @@ export class NbAuthModule { return { ngModule: NbAuthModule, providers: [ - { provide: NB_AUTH_USER_OPTIONS_TOKEN, useValue: nbAuthOptions }, - { provide: NB_AUTH_OPTIONS_TOKEN, useFactory: nbOptionsFactory, deps: [NB_AUTH_USER_OPTIONS_TOKEN] }, - { provide: NB_AUTH_PROVIDERS_TOKEN, useValue: {} }, - { provide: NB_AUTH_TOKEN_WRAPPER_TOKEN, useClass: NbAuthSimpleToken }, + { provide: NB_AUTH_USER_OPTIONS, useValue: nbAuthOptions }, + { provide: NB_AUTH_OPTIONS, useFactory: nbOptionsFactory, deps: [NB_AUTH_USER_OPTIONS] }, + { provide: NB_AUTH_PROVIDERS, useValue: {} }, + { provide: NB_AUTH_TOKEN_CLASS, useValue: NbAuthSimpleToken }, { provide: NB_AUTH_INTERCEPTOR_HEADER, useValue: 'Authorization' }, { provide: NbAuthService, useFactory: nbAuthServiceFactory, - deps: [NB_AUTH_OPTIONS_TOKEN, NbTokenService, Injector], + deps: [NB_AUTH_OPTIONS, NbTokenService, Injector], }, + { provide: NbTokenStorage, useClass: NbTokenLocalStorage }, NbTokenService, NbDummyAuthProvider, NbEmailPassAuthProvider, diff --git a/src/framework/auth/auth.options.ts b/src/framework/auth/auth.options.ts index 0abb7403cc..7146e62d36 100644 --- a/src/framework/auth/auth.options.ts +++ b/src/framework/auth/auth.options.ts @@ -1,4 +1,5 @@ import { InjectionToken } from '@angular/core'; +import { NbAuthToken } from './services'; export interface NbAuthOptions { forms?: any; @@ -81,8 +82,8 @@ export const defaultSettings: any = { }, }; -export const NB_AUTH_OPTIONS_TOKEN = new InjectionToken('Nebular Auth Options'); -export const NB_AUTH_USER_OPTIONS_TOKEN = new InjectionToken('Nebular User Auth Options'); -export const NB_AUTH_PROVIDERS_TOKEN = new InjectionToken('Nebular Auth Providers'); -export const NB_AUTH_TOKEN_WRAPPER_TOKEN = new InjectionToken('Nebular Auth Token'); +export const NB_AUTH_OPTIONS = new InjectionToken('Nebular Auth Options'); +export const NB_AUTH_USER_OPTIONS = new InjectionToken('Nebular User Auth Options'); +export const NB_AUTH_PROVIDERS = new InjectionToken('Nebular Auth Providers'); +export const NB_AUTH_TOKEN_CLASS = new InjectionToken('Nebular Token Class'); export const NB_AUTH_INTERCEPTOR_HEADER = new InjectionToken('Nebular Simple Interceptor Header'); diff --git a/src/framework/auth/components/login/login.component.ts b/src/framework/auth/components/login/login.component.ts index 203ba9af46..a9ec65e63b 100644 --- a/src/framework/auth/components/login/login.component.ts +++ b/src/framework/auth/components/login/login.component.ts @@ -5,7 +5,7 @@ */ import { Component, Inject } from '@angular/core'; import { Router } from '@angular/router'; -import { NB_AUTH_OPTIONS_TOKEN, NbAuthSocialLink } from '../../auth.options'; +import { NB_AUTH_OPTIONS, NbAuthSocialLink } from '../../auth.options'; import { getDeepFromObject } from '../../helpers'; import { NbAuthService } from '../../services/auth.service'; @@ -120,7 +120,7 @@ export class NbLoginComponent { socialLinks: NbAuthSocialLink[] = []; constructor(protected service: NbAuthService, - @Inject(NB_AUTH_OPTIONS_TOKEN) protected config = {}, + @Inject(NB_AUTH_OPTIONS) protected config = {}, protected router: Router) { this.redirectDelay = this.getConfigValue('forms.login.redirectDelay'); diff --git a/src/framework/auth/components/logout/logout.component.ts b/src/framework/auth/components/logout/logout.component.ts index a81334ec5c..ab7dd82038 100644 --- a/src/framework/auth/components/logout/logout.component.ts +++ b/src/framework/auth/components/logout/logout.component.ts @@ -6,7 +6,7 @@ import { Component, Inject, OnInit } from '@angular/core'; import { Router } from '@angular/router'; -import { NB_AUTH_OPTIONS_TOKEN } from '../../auth.options'; +import { NB_AUTH_OPTIONS } from '../../auth.options'; import { getDeepFromObject } from '../../helpers'; import { NbAuthService } from '../../services/auth.service'; import { NbAuthResult } from '../../services/auth-result'; @@ -23,7 +23,7 @@ export class NbLogoutComponent implements OnInit { provider: string = ''; constructor(protected service: NbAuthService, - @Inject(NB_AUTH_OPTIONS_TOKEN) protected config = {}, + @Inject(NB_AUTH_OPTIONS) protected config = {}, protected router: Router) { this.redirectDelay = this.getConfigValue('forms.logout.redirectDelay'); this.provider = this.getConfigValue('forms.logout.provider'); diff --git a/src/framework/auth/components/register/register.component.ts b/src/framework/auth/components/register/register.component.ts index e7bd7f52b7..79fcfbd17f 100644 --- a/src/framework/auth/components/register/register.component.ts +++ b/src/framework/auth/components/register/register.component.ts @@ -5,7 +5,7 @@ */ import { Component, Inject } from '@angular/core'; import { Router } from '@angular/router'; -import { NB_AUTH_OPTIONS_TOKEN, NbAuthSocialLink } from '../../auth.options'; +import { NB_AUTH_OPTIONS, NbAuthSocialLink } from '../../auth.options'; import { getDeepFromObject } from '../../helpers'; import { NbAuthService } from '../../services/auth.service'; @@ -160,7 +160,7 @@ export class NbRegisterComponent { socialLinks: NbAuthSocialLink[] = []; constructor(protected service: NbAuthService, - @Inject(NB_AUTH_OPTIONS_TOKEN) protected config = {}, + @Inject(NB_AUTH_OPTIONS) protected config = {}, protected router: Router) { this.redirectDelay = this.getConfigValue('forms.register.redirectDelay'); diff --git a/src/framework/auth/components/request-password/request-password.component.ts b/src/framework/auth/components/request-password/request-password.component.ts index 21a97b959e..f1561b9f56 100644 --- a/src/framework/auth/components/request-password/request-password.component.ts +++ b/src/framework/auth/components/request-password/request-password.component.ts @@ -5,7 +5,7 @@ */ import { Component, Inject } from '@angular/core'; import { Router } from '@angular/router'; -import { NB_AUTH_OPTIONS_TOKEN } from '../../auth.options'; +import { NB_AUTH_OPTIONS } from '../../auth.options'; import { getDeepFromObject } from '../../helpers'; import { NbAuthService } from '../../services/auth.service'; @@ -76,7 +76,7 @@ export class NbRequestPasswordComponent { user: any = {}; constructor(protected service: NbAuthService, - @Inject(NB_AUTH_OPTIONS_TOKEN) protected config = {}, + @Inject(NB_AUTH_OPTIONS) protected config = {}, protected router: Router) { this.redirectDelay = this.getConfigValue('forms.requestPassword.redirectDelay'); diff --git a/src/framework/auth/components/reset-password/reset-password.component.ts b/src/framework/auth/components/reset-password/reset-password.component.ts index b9c17f822f..dea06aae29 100644 --- a/src/framework/auth/components/reset-password/reset-password.component.ts +++ b/src/framework/auth/components/reset-password/reset-password.component.ts @@ -5,7 +5,7 @@ */ import { Component, Inject } from '@angular/core'; import { Router } from '@angular/router'; -import { NB_AUTH_OPTIONS_TOKEN } from '../../auth.options'; +import { NB_AUTH_OPTIONS } from '../../auth.options'; import { getDeepFromObject } from '../../helpers'; import { NbAuthService } from '../../services/auth.service'; @@ -98,7 +98,7 @@ export class NbResetPasswordComponent { user: any = {}; constructor(protected service: NbAuthService, - @Inject(NB_AUTH_OPTIONS_TOKEN) protected config = {}, + @Inject(NB_AUTH_OPTIONS) protected config = {}, protected router: Router) { this.redirectDelay = this.getConfigValue('forms.resetPassword.redirectDelay'); diff --git a/src/framework/auth/services/auth-result.ts b/src/framework/auth/services/auth-result.ts index 9636ce2716..92b90aaf44 100644 --- a/src/framework/auth/services/auth-result.ts +++ b/src/framework/auth/services/auth-result.ts @@ -1,19 +1,18 @@ -import { NbAuthSimpleToken } from './token.service'; +import { NbAuthToken } from './token/token'; export class NbAuthResult { - protected token: any; - // TODO add field rawToken: string; + protected token: NbAuthToken; + protected rawToken: string; protected errors: string[] = []; protected messages: string[] = []; - // TODO pass arguments in options object constructor(protected success: boolean, protected response?: any, protected redirect?: any, errors?: any, messages?: any, - token?: NbAuthSimpleToken) { + rawToken?: string) { this.errors = this.errors.concat([errors]); if (errors instanceof Array) { @@ -25,21 +24,24 @@ export class NbAuthResult { this.messages = messages; } + this.rawToken = rawToken; + } + + setToken(token: NbAuthToken) { this.token = token; + this.rawToken = token.toString(); } getResponse(): any { return this.response; } - // TODO is it really return value? - getTokenValue(): any { - return this.token; + getRawToken(): any { + return this.rawToken; } - // TODO could we get rid of this method - replaceToken(token: NbAuthSimpleToken): any { - this.token = token + getToken(): any { + return this.token; } getRedirect(): any { diff --git a/src/framework/auth/services/auth.service.ts b/src/framework/auth/services/auth.service.ts index 18acb2302e..4aa3d65031 100644 --- a/src/framework/auth/services/auth.service.ts +++ b/src/framework/auth/services/auth.service.ts @@ -8,13 +8,13 @@ import { Inject, Injectable, Injector, Optional } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { switchMap } from 'rxjs/operators/switchMap'; import { map } from 'rxjs/operators/map'; -import { tap } from 'rxjs/operators/tap'; import { of as observableOf } from 'rxjs/observable/of'; import { NbAbstractAuthProvider } from '../providers/abstract-auth.provider'; -import { NbAuthSimpleToken, NbTokenService } from './token.service'; -import { NB_AUTH_PROVIDERS_TOKEN } from '../auth.options'; +import { NB_AUTH_PROVIDERS } from '../auth.options'; import { NbAuthResult } from './auth-result'; +import { NbTokenService } from './token/token.service'; +import { NbAuthToken } from './token/token'; /** * Common authentication service. @@ -25,43 +25,41 @@ export class NbAuthService { constructor(protected tokenService: NbTokenService, protected injector: Injector, - @Optional() @Inject(NB_AUTH_PROVIDERS_TOKEN) protected providers = {}) { + @Optional() @Inject(NB_AUTH_PROVIDERS) protected providers = {}) { } /** * Retrieves current authenticated token stored * @returns {Observable} */ - getToken(): Observable { + getToken(): Observable { return this.tokenService.get(); } /** * Returns true if auth token is presented in the token storage - * // TODO: check exp date for JWT token - * // TODO: to implement previous todo lets use isValid method of token * @returns {Observable} */ isAuthenticated(): Observable { - return this.getToken().pipe(map(token => !!(token && token.getValue()))); + return this.getToken() + .pipe(map((token: NbAuthToken) => token.isValid())); } /** * Returns tokens stream * @returns {Observable} */ - onTokenChange(): Observable { + onTokenChange(): Observable { return this.tokenService.tokenChange(); } /** * Returns authentication status stream - * // TODO: check exp date for JWT token - * // TODO: to implement previous todo lets use isValid method of token * @returns {Observable} */ onAuthenticationChange(): Observable { - return this.onTokenChange().pipe(map((token: NbAuthSimpleToken) => !!(token && token.getValue()))); + return this.onTokenChange() + .pipe(map((token: NbAuthToken) => token.isValid())); } /** @@ -79,20 +77,7 @@ export class NbAuthService { return this.getProvider(provider).authenticate(data) .pipe( switchMap((result: NbAuthResult) => { - // TODO move this duplicate code in the separate method (see register) - // TODO is it necessary to chech for token here - if (result.isSuccess() && result.getTokenValue()) { - return this.tokenService.set(result.getTokenValue()) - .pipe( - switchMap(() => this.tokenService.get()), - map((token: NbAuthSimpleToken) => { - result.replaceToken(token); - return result; - }), - ); - } - - return observableOf(result); + return this.processResultToken(result); }), ); } @@ -112,18 +97,7 @@ export class NbAuthService { return this.getProvider(provider).register(data) .pipe( switchMap((result: NbAuthResult) => { - if (result.isSuccess() && result.getTokenValue()) { - return this.tokenService.set(result.getTokenValue()) - .pipe( - switchMap(_ => this.tokenService.get()), - map((token: NbAuthSimpleToken) => { - result.replaceToken(token); - return result; - }), - ); - } - - return observableOf(result); + return this.processResultToken(result); }), ); } @@ -141,11 +115,12 @@ export class NbAuthService { logout(provider: string): Observable { return this.getProvider(provider).logout() .pipe( - tap((result: NbAuthResult) => { + switchMap((result: NbAuthResult) => { if (result.isSuccess()) { - this.tokenService.clear().subscribe(() => { - }); + this.tokenService.clear() + .pipe(map(() => result)); } + return observableOf(result); }), ); } @@ -178,6 +153,21 @@ export class NbAuthService { return this.getProvider(provider).resetPassword(data); } + private processResultToken(result: NbAuthResult) { + if (result.isSuccess() && result.getRawToken()) { + return this.tokenService.setRaw(result.getRawToken()) + .pipe( + switchMap(() => this.tokenService.get()), + map((token: NbAuthToken) => { + result.setToken(token); + return result; + }), + ); + } + + return observableOf(result); + } + private getProvider(provider: string): NbAbstractAuthProvider { if (!this.providers[provider]) { throw new TypeError(`Nb auth provider '${provider}' is not registered`); diff --git a/src/framework/auth/services/auth.spec.ts b/src/framework/auth/services/auth.spec.ts index d6bb24f367..5a3e93c911 100644 --- a/src/framework/auth/services/auth.spec.ts +++ b/src/framework/auth/services/auth.spec.ts @@ -4,29 +4,34 @@ * Licensed under the MIT License. See License.txt in the project root for license information. */ -import { NbAuthSimpleToken, NbTokenService } from './token.service'; import { TestBed } from '@angular/core/testing'; -import { NB_AUTH_OPTIONS_TOKEN, NB_AUTH_TOKEN_WRAPPER_TOKEN, NB_AUTH_USER_OPTIONS_TOKEN } from '../auth.options'; -import { NbAuthService } from './auth.service'; import { Injector } from '@angular/core'; +import { HttpResponse } from '@angular/common/http'; +import { NB_AUTH_OPTIONS, NB_AUTH_TOKEN_CLASS, NB_AUTH_USER_OPTIONS } from '../auth.options'; +import { NbAuthService } from './auth.service'; import { NbDummyAuthProvider } from '../providers/dummy-auth.provider'; import { nbAuthServiceFactory, nbOptionsFactory } from '../auth.module'; -import { HttpResponse } from '@angular/common/http'; import { of as observableOf } from 'rxjs/observable/of'; import { first } from 'rxjs/operators'; import { NbAuthResult } from './auth-result'; import { delay } from 'rxjs/operators/delay'; +import { NbTokenService } from './token/token.service'; +import { NbAuthSimpleToken, nbCreateToken } from './token/token'; +import { NbTokenLocalStorage, NbTokenStorage } from './token/token-storage'; describe('auth-service', () => { let authService: NbAuthService; let tokenService: NbTokenService; let dummyAuthProvider: NbDummyAuthProvider; const testTokenValue = 'test-token'; + const replacedTokenValue = 'replaced-value'; const resp401 = new HttpResponse({body: {}, status: 401}); const resp200 = new HttpResponse({body: {}, status: 200}); - const tokenWrapper = new NbAuthSimpleToken(); + const testToken = nbCreateToken(NbAuthSimpleToken, testTokenValue); + const replacedToken = nbCreateToken(NbAuthSimpleToken, replacedTokenValue); + const emptyToken = nbCreateToken(NbAuthSimpleToken, null); const failResult = new NbAuthResult(false, resp401, @@ -38,9 +43,7 @@ describe('auth-service', () => { '/', [], ['Successfully logged in.'], - - // TODO in case we dont set this optional param we will not replace to new one during authenticate - tokenWrapper); + testTokenValue); const successLogoutResult = new NbAuthResult(true, resp200, @@ -63,10 +66,10 @@ describe('auth-service', () => { beforeEach(() => { TestBed.configureTestingModule({ providers: [ - {provide: NB_AUTH_OPTIONS_TOKEN, useValue: {}}, - {provide: NB_AUTH_TOKEN_WRAPPER_TOKEN, useClass: NbAuthSimpleToken}, + { provide: NB_AUTH_OPTIONS, useValue: {} }, + { provide: NB_AUTH_TOKEN_CLASS, useValue: NbAuthSimpleToken }, { - provide: NB_AUTH_USER_OPTIONS_TOKEN, useValue: { + provide: NB_AUTH_USER_OPTIONS, useValue: { forms: { login: { redirectDelay: 3000, @@ -83,12 +86,14 @@ describe('auth-service', () => { }, }, }, - {provide: NB_AUTH_OPTIONS_TOKEN, useFactory: nbOptionsFactory, deps: [NB_AUTH_USER_OPTIONS_TOKEN]}, + { provide: NB_AUTH_OPTIONS, useFactory: nbOptionsFactory, deps: [NB_AUTH_USER_OPTIONS] }, + { provide: NbTokenStorage, useClass: NbTokenLocalStorage }, + NbTokenService, { provide: NbAuthService, useFactory: nbAuthServiceFactory, - deps: [NB_AUTH_OPTIONS_TOKEN, NbTokenService, Injector], + deps: [NB_AUTH_OPTIONS, NbTokenService, Injector], }, NbDummyAuthProvider, ], @@ -96,13 +101,12 @@ describe('auth-service', () => { authService = TestBed.get(NbAuthService); tokenService = TestBed.get(NbTokenService); dummyAuthProvider = TestBed.get(NbDummyAuthProvider); - tokenWrapper.setValue(testTokenValue); }); it('get test token before set', () => { const spy = spyOn(tokenService, 'get') .and - .returnValue(observableOf(tokenWrapper)); + .returnValue(observableOf(testToken)); authService.getToken().subscribe((val: NbAuthSimpleToken) => { expect(spy).toHaveBeenCalled(); @@ -114,7 +118,7 @@ describe('auth-service', () => { it('is authenticated true if token exists', () => { const spy = spyOn(tokenService, 'get') .and - .returnValue(observableOf(tokenWrapper)); + .returnValue(observableOf(testToken)); authService.isAuthenticated().subscribe((isAuth: boolean) => { expect(spy).toHaveBeenCalled(); @@ -124,10 +128,9 @@ describe('auth-service', () => { ); it('is authenticated false if token doesn\'t exist', () => { - tokenWrapper.setValue(''); const spy = spyOn(tokenService, 'get') .and - .returnValue(observableOf(tokenWrapper)); + .returnValue(observableOf(emptyToken)); authService.isAuthenticated().subscribe((isAuth: boolean) => { expect(spy).toHaveBeenCalled(); @@ -139,7 +142,7 @@ describe('auth-service', () => { it('onTokenChange return correct stream and gets test token', (done) => { const spy = spyOn(tokenService, 'tokenChange') .and - .returnValue(observableOf(tokenWrapper)); + .returnValue(observableOf(testToken)); authService.onTokenChange() .pipe(first()) @@ -151,23 +154,6 @@ describe('auth-service', () => { }, ); - // TODO Could be removed if token immutable - it('onTokenChange return correct stream and retrieve null token', (done) => { - tokenWrapper.setValue(null); - const spy = spyOn(tokenService, 'tokenChange') - .and - .returnValue(observableOf(tokenWrapper)); - - authService.onTokenChange() - .pipe(first()) - .subscribe((token: NbAuthSimpleToken) => { - expect(spy).toHaveBeenCalled(); - expect(token.getValue()).toBeNull(); - done(); - }); - }, - ); - it('authenticate failed', (done) => { const spy = spyOn(dummyAuthProvider, 'authenticate') .and @@ -183,7 +169,7 @@ describe('auth-service', () => { expect(authRes.getMessages()).toEqual([]); expect(authRes.getErrors()).toEqual(['Something went wrong.']); expect(authRes.getRedirect()).toBeNull(); - expect(authRes.getTokenValue()).toBeUndefined(); + expect(authRes.getRawToken()).toBeUndefined(); expect(authRes.getResponse()).toEqual(resp401); done(); }) @@ -198,26 +184,27 @@ describe('auth-service', () => { delay(1000), )); - const tokenServiceSetSpy = spyOn(tokenService, 'set') + const tokenServiceSetSpy = spyOn(tokenService, 'setRaw') .and - .returnValue(observableOf('STUB')); + .returnValue(observableOf(null)); - tokenWrapper.setValue('Replaced'); - const tokenServiceGetSpy = spyOn(tokenService, 'get') + const tokenServiceGetSpy = spyOn(tokenService, 'get') .and - .returnValue(observableOf(tokenWrapper)); + .returnValue(observableOf(replacedToken)); authService.authenticate('dummy').subscribe((authRes: NbAuthResult) => { expect(providerSpy).toHaveBeenCalled(); expect(tokenServiceSetSpy).toHaveBeenCalled(); expect(tokenServiceGetSpy).toHaveBeenCalled(); + expect(authRes.isFailure()).toBeFalsy(); expect(authRes.isSuccess()).toBeTruthy(); expect(authRes.getMessages()).toEqual(['Successfully logged in.']); expect(authRes.getErrors()).toEqual([]); expect(authRes.getRedirect()).toEqual('/'); - expect(authRes.getTokenValue()).toEqual(tokenWrapper); + expect(authRes.getRawToken()).toEqual(replacedToken.getValue()); + expect(authRes.getToken()).toEqual(replacedToken); expect(authRes.getResponse()).toEqual(resp200); done(); }) @@ -239,7 +226,8 @@ describe('auth-service', () => { expect(authRes.getMessages()).toEqual([]); expect(authRes.getErrors()).toEqual(['Something went wrong.']); expect(authRes.getRedirect()).toBeNull(); - expect(authRes.getTokenValue()).toBeUndefined(); + expect(authRes.getRawToken()).toBeUndefined(); + expect(authRes.getToken()).toBeUndefined(); expect(authRes.getResponse()).toEqual(resp401); done(); }) @@ -254,14 +242,13 @@ describe('auth-service', () => { delay(1000), )); - const tokenServiceSetSpy = spyOn(tokenService, 'set') + const tokenServiceSetSpy = spyOn(tokenService, 'setRaw') .and - .returnValue(observableOf('STUB')); + .returnValue(observableOf(null)); - tokenWrapper.setValue('Replaced'); const tokenServiceGetSpy = spyOn(tokenService, 'get') .and - .returnValue(observableOf(tokenWrapper)); + .returnValue(observableOf(replacedToken)); authService.register('dummy').subscribe((authRes: NbAuthResult) => { expect(providerSpy).toHaveBeenCalled(); @@ -273,7 +260,8 @@ describe('auth-service', () => { expect(authRes.getMessages()).toEqual(['Successfully logged in.']); expect(authRes.getErrors()).toEqual([]); expect(authRes.getRedirect()).toEqual('/'); - expect(authRes.getTokenValue()).toEqual(tokenWrapper); + expect(authRes.getRawToken()).toEqual(replacedToken.getValue()); + expect(authRes.getToken()).toEqual(replacedToken); expect(authRes.getResponse()).toEqual(resp200); done(); }) @@ -296,7 +284,7 @@ describe('auth-service', () => { expect(authRes.getMessages()).toEqual([]); expect(authRes.getErrors()).toEqual(['Something went wrong.']); expect(authRes.getRedirect()).toBeNull(); - expect(authRes.getTokenValue()).toBeUndefined(); + expect(authRes.getRawToken()).toBeUndefined(); expect(authRes.getResponse()).toEqual(resp401); done(); }) @@ -321,7 +309,7 @@ describe('auth-service', () => { expect(authRes.getMessages()).toEqual(['Successfully logged out.']); expect(authRes.getErrors()).toEqual([]); expect(authRes.getRedirect()).toEqual('/'); - expect(authRes.getTokenValue()).toBeUndefined(); + expect(authRes.getRawToken()).toBeUndefined(); expect(authRes.getResponse()).toEqual(resp200); done(); }) @@ -344,7 +332,7 @@ describe('auth-service', () => { expect(authRes.getMessages()).toEqual([]); expect(authRes.getErrors()).toEqual(['Something went wrong.']); expect(authRes.getRedirect()).toBeNull(); - expect(authRes.getTokenValue()).toBeUndefined(); + expect(authRes.getRawToken()).toBeUndefined(); expect(authRes.getResponse()).toEqual(resp401); done(); }) @@ -367,7 +355,7 @@ describe('auth-service', () => { expect(authRes.getMessages()).toEqual(['Successfully requested password.']); expect(authRes.getErrors()).toEqual([]); expect(authRes.getRedirect()).toEqual('/'); - expect(authRes.getTokenValue()).toBeUndefined(); + expect(authRes.getRawToken()).toBeUndefined(); expect(authRes.getResponse()).toEqual(resp200); done(); }) @@ -390,7 +378,7 @@ describe('auth-service', () => { expect(authRes.getMessages()).toEqual([]); expect(authRes.getErrors()).toEqual(['Something went wrong.']); expect(authRes.getRedirect()).toBeNull(); - expect(authRes.getTokenValue()).toBeUndefined(); + expect(authRes.getRawToken()).toBeUndefined(); expect(authRes.getResponse()).toEqual(resp401); done(); }) @@ -413,7 +401,7 @@ describe('auth-service', () => { expect(authRes.getMessages()).toEqual(['Successfully reset password.']); expect(authRes.getErrors()).toEqual([]); expect(authRes.getRedirect()).toEqual('/'); - expect(authRes.getTokenValue()).toBeUndefined(); + expect(authRes.getRawToken()).toBeUndefined(); expect(authRes.getResponse()).toEqual(resp200); done(); }) diff --git a/src/framework/auth/services/index.ts b/src/framework/auth/services/index.ts index ee84364522..9990c10ea8 100644 --- a/src/framework/auth/services/index.ts +++ b/src/framework/auth/services/index.ts @@ -1,4 +1,7 @@ export * from './auth.service'; -export * from './token.service'; +export * from './auth-result'; export * from './interceptors/jwt-interceptor'; export * from './interceptors/simple-interceptor'; +export * from './token/token'; +export * from './token/token-storage'; +export * from './token/token.service'; diff --git a/src/framework/auth/services/interceptors/jwt-interceptor.ts b/src/framework/auth/services/interceptors/jwt-interceptor.ts index 2af7420b54..b4245d64d3 100644 --- a/src/framework/auth/services/interceptors/jwt-interceptor.ts +++ b/src/framework/auth/services/interceptors/jwt-interceptor.ts @@ -4,7 +4,7 @@ import { Observable } from 'rxjs/Observable'; import { switchMap } from 'rxjs/operators/switchMap'; import { NbAuthService } from '../auth.service'; -import { NbAuthJWTToken } from '../token.service'; +import { NbAuthJWTToken } from '../token/token'; @Injectable() export class NbAuthJWTInterceptor implements HttpInterceptor { diff --git a/src/framework/auth/services/interceptors/simple-interceptor.ts b/src/framework/auth/services/interceptors/simple-interceptor.ts index e3d9a20ad8..f9fb8d2050 100644 --- a/src/framework/auth/services/interceptors/simple-interceptor.ts +++ b/src/framework/auth/services/interceptors/simple-interceptor.ts @@ -4,8 +4,8 @@ import { Observable } from 'rxjs/Observable'; import { switchMap } from 'rxjs/operators/switchMap'; import { NbAuthService } from '../auth.service'; -import { NbAuthJWTToken } from '../token.service'; import { NB_AUTH_INTERCEPTOR_HEADER } from '../../auth.options'; +import { NbAuthJWTToken } from '../token/token'; @Injectable() export class NbAuthSimpleInterceptor implements HttpInterceptor { diff --git a/src/framework/auth/services/token.service.ts b/src/framework/auth/services/token.service.ts deleted file mode 100644 index fcc9c6465c..0000000000 --- a/src/framework/auth/services/token.service.ts +++ /dev/null @@ -1,182 +0,0 @@ -import { Inject, Injectable } from '@angular/core'; -import { BehaviorSubject } from 'rxjs/BehaviorSubject'; -import { Observable } from 'rxjs/Observable'; -import { of as observableOf } from 'rxjs/observable/of'; -import { switchMap } from 'rxjs/operators/switchMap'; -import { tap } from 'rxjs/operators/tap'; -import { share } from 'rxjs/operators/share'; - -import { NB_AUTH_OPTIONS_TOKEN, NB_AUTH_TOKEN_WRAPPER_TOKEN } from '../auth.options'; -import { deepExtend, getDeepFromObject, urlBase64Decode } from '../helpers'; - -/** - * Wrapper for simple (text) token - */ -@Injectable() -export class NbAuthSimpleToken { - - protected token: string = ''; - - setValue(token: string) { - this.token = token; - } - - /** - * Returns the token value - * @returns string - */ - getValue() { - return this.token; - } -} - -/** - * Wrapper for JWT token with additional methods. - */ -@Injectable() -export class NbAuthJWTToken extends NbAuthSimpleToken { - - /** - * TODO: check for this.token to be not null - * Returns payload object - * @returns any - */ - getPayload(): any { - const parts = this.token.split('.'); - - if (parts.length !== 3) { - throw new Error(`The token ${this.token} is not valid JWT token and must consist of three parts.`); - } - - const decoded = urlBase64Decode(parts[1]); - if (!decoded) { - throw new Error(`The token ${this.token} is not valid JWT token and cannot be decoded.`); - } - - return JSON.parse(decoded); - } - - /** - * Returns expiration date - * @returns Date - */ - getTokenExpDate(): Date { - const decoded = this.getPayload(); - if (!decoded.hasOwnProperty('exp')) { - return null; - } - - const date = new Date(0); - date.setUTCSeconds(decoded.exp); - - return date; - } -} - -/** - * Nebular token service. Provides access to the stored token. - * By default returns NbAuthSimpleToken instance, - * but you can inject NbAuthJWTToken if you need additional methods for JWT token. - * - * @example Injecting NbAuthJWTToken, so that NbTokenService will now return NbAuthJWTToken instead - * of the default NbAuthSimpleToken - * - * ``` - * // import token and service into your AppModule - * import { NB_AUTH_TOKEN_WRAPPER_TOKEN, NbAuthJWTToken} from '@nebular/auth'; - * - * // add to a list of providers - * providers: [ - * // ... - * { provide: NB_AUTH_TOKEN_WRAPPER_TOKEN, useClass: NbAuthJWTToken }, - * ], - * ``` - */ -@Injectable() -export class NbTokenService { - - protected defaultConfig: any = { - token: { - key: 'auth_app_token', - - getter: (): Observable => { - const tokenValue = localStorage.getItem(this.getConfigValue('token.key')); - this.tokenWrapper.setValue(tokenValue); - return observableOf(this.tokenWrapper); - }, - - setter: (token: string | NbAuthSimpleToken): Observable => { - const raw = token instanceof NbAuthSimpleToken ? token.getValue() : token; - localStorage.setItem(this.getConfigValue('token.key'), raw); - return observableOf(null); - }, - - deleter: (): Observable => { - localStorage.removeItem(this.getConfigValue('token.key')); - return observableOf(null); - }, - }, - }; - protected config: any = {}; - protected token$: BehaviorSubject = new BehaviorSubject(null); - - constructor(@Inject(NB_AUTH_OPTIONS_TOKEN) protected options: any, - @Inject(NB_AUTH_TOKEN_WRAPPER_TOKEN) protected tokenWrapper: NbAuthSimpleToken) { - this.setConfig(options); - - this.get().subscribe(token => this.publishToken(token)); - } - - setConfig(config: any): void { - this.config = deepExtend({}, this.defaultConfig, config); - } - - getConfigValue(key: string): any { - return getDeepFromObject(this.config, key, null); - } - - /** - * Sets the token into the storage. This method is used by the NbAuthService automatically. - * @param {string} rawToken - * @returns {Observable} - */ - set(rawToken: string): Observable { - return this.getConfigValue('token.setter')(rawToken) - .pipe( - switchMap(() => this.get()), - tap((token: NbAuthSimpleToken) => { - this.publishToken(token); - }), - ); - } - - /** - * Returns observable of current token - * @returns {Observable} - */ - get(): Observable { - return this.getConfigValue('token.getter')(); - } - - /** - * Publishes token when it changes. - * @returns {Observable} - */ - tokenChange(): Observable { - return this.token$.pipe(share()); - } - - /** - * Removes the token - * @returns {Observable} - */ - clear(): Observable { - this.publishToken(null); - - return this.getConfigValue('token.deleter')(); - } - - protected publishToken(token: NbAuthSimpleToken): void { - this.token$.next(token); - } -} diff --git a/src/framework/auth/services/token/token-service.spec.ts b/src/framework/auth/services/token/token-service.spec.ts new file mode 100644 index 0000000000..7949093e71 --- /dev/null +++ b/src/framework/auth/services/token/token-service.spec.ts @@ -0,0 +1,138 @@ +/** + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { async, inject, TestBed } from '@angular/core/testing'; + +import { NbTokenLocalStorage, NbTokenStorage } from './token-storage'; +import { NB_AUTH_TOKEN_CLASS } from '../../auth.options'; +import { NbAuthSimpleToken, NbAuthToken, nbCreateToken, NbTokenClass } from './token'; +import { NbTokenService } from './token.service'; + +const noop = () => {}; + +describe('token-service', () => { + + let tokenService: NbTokenService; + let tokenClass: NbTokenClass; + const testTokenKey = 'auth_app_token'; + const testTokenValue = 'test-token'; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + { provide: NbTokenStorage, useClass: NbTokenLocalStorage }, + { provide: NB_AUTH_TOKEN_CLASS, useValue: NbAuthSimpleToken }, + NbTokenService, + ], + }); + }); + + beforeEach(async(inject( + [NbTokenService, NB_AUTH_TOKEN_CLASS], + (_tokenService, _tokenClass) => { + tokenService = _tokenService; + tokenClass = _tokenClass; + }, + ))); + + afterEach(() => { + localStorage.removeItem(testTokenKey); + }); + + it('set test raw token', () => { + tokenService.setRaw(testTokenValue).subscribe(noop); + expect(localStorage.getItem(testTokenKey)).toEqual(testTokenValue); + }); + + it('setter set raw invalid token to localStorage as raw value', () => { + tokenService.setRaw(null).subscribe(noop); + expect(localStorage.getItem(testTokenKey)).toEqual('null'); + tokenService.setRaw(undefined).subscribe(noop); + expect(localStorage.getItem(testTokenKey)).toEqual('undefined'); + }); + + it('set test token', () => { + const token = nbCreateToken(tokenClass, testTokenValue); + + tokenService.set(token).subscribe(noop); + expect(localStorage.getItem(testTokenKey)).toEqual(testTokenValue); + }); + + it('setter set invalid token to localStorage as empty string', () => { + let token; + + token = nbCreateToken(tokenClass, null); + tokenService.set(token).subscribe(noop); + expect(localStorage.getItem(testTokenKey)).toEqual(''); + + token = nbCreateToken(tokenClass, undefined); + tokenService.set(token).subscribe(noop); + expect(localStorage.getItem(testTokenKey)).toEqual(''); + }); + + it('get return null in case token was not set', () => { + tokenService.get() + .subscribe((token: NbAuthToken) => { + expect(token.getValue()).toBeNull(); + expect(token.isValid()).toBe(false); + }) + }); + + it('should return correct value', () => { + localStorage.setItem(testTokenKey, testTokenValue); + + tokenService.get() + .subscribe((token: NbAuthToken) => { + expect(token.getValue()).toEqual(testTokenValue); + }); + }); + + it('clear remove token', () => { + localStorage.setItem(testTokenKey, testTokenValue); + + tokenService.clear().subscribe(noop); + + expect(localStorage.getItem(testTokenKey)).toBeNull(); + }); + + it('clear remove token only', () => { + localStorage.setItem(testTokenKey, testTokenValue); + localStorage.setItem(testTokenKey + '2', testTokenValue); + + tokenService.clear().subscribe(noop); + + expect(localStorage.getItem(testTokenKey + '2')).toEqual(testTokenValue); + expect(localStorage.getItem(testTokenKey)).toBeNull(); + }); + + it('token should be published', (done) => { + tokenService.set(nbCreateToken(tokenClass, testTokenValue)).subscribe(noop); + tokenService.tokenChange() + .subscribe((token: NbAuthToken) => { + expect(token.getValue()).toEqual(testTokenValue); + done(); + }); + }); + + it('raw token should be published as token object', (done) => { + tokenService.setRaw(testTokenValue).subscribe(noop); + tokenService.tokenChange() + .subscribe((token: NbAuthToken) => { + expect(token.getValue()).toEqual(testTokenValue); + done(); + }); + }); + + it('clear should be published', (done) => { + tokenService.set(nbCreateToken(tokenClass, testTokenValue)).subscribe(noop); + tokenService.clear().subscribe(noop); + tokenService.tokenChange() + .subscribe((token: NbAuthSimpleToken) => { + expect(token.getValue()).toBeNull(); + done(); + }); + }); +}); diff --git a/src/framework/auth/services/token/token-storage.spec.ts b/src/framework/auth/services/token/token-storage.spec.ts new file mode 100644 index 0000000000..15d55c74ef --- /dev/null +++ b/src/framework/auth/services/token/token-storage.spec.ts @@ -0,0 +1,103 @@ +/** + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { async, inject, TestBed } from '@angular/core/testing'; + +import { NbTokenLocalStorage, NbTokenStorage } from './token-storage'; +import { NB_AUTH_TOKEN_CLASS } from '../../auth.options'; +import { NbAuthSimpleToken, nbCreateToken, NbTokenClass } from './token'; + +describe('token-storage', () => { + + let tokenStorage: NbTokenStorage; + let tokenClass: NbTokenClass; + const testTokenKey = 'auth_app_token'; + const testTokenValue = 'test-token'; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + { provide: NbTokenStorage, useClass: NbTokenLocalStorage }, + { provide: NB_AUTH_TOKEN_CLASS, useValue: NbAuthSimpleToken }, + ], + }); + }); + + beforeEach(async(inject( + [NbTokenStorage, NB_AUTH_TOKEN_CLASS], + (_tokenStorage, _tokenClass) => { + tokenStorage = _tokenStorage; + tokenClass = _tokenClass; + }, + ))); + + afterEach(() => { + localStorage.removeItem(testTokenKey); + }); + + it('set test raw token', () => { + tokenStorage.setRaw(testTokenValue); + expect(localStorage.getItem(testTokenKey)).toEqual(testTokenValue); + }); + + it('setter set raw invalid token to localStorage as raw value', () => { + tokenStorage.setRaw(null); + expect(localStorage.getItem(testTokenKey)).toEqual('null'); + tokenStorage.setRaw(undefined); + expect(localStorage.getItem(testTokenKey)).toEqual('undefined'); + }); + + it('set test token', () => { + const token = nbCreateToken(tokenClass, testTokenValue); + + tokenStorage.set(token); + expect(localStorage.getItem(testTokenKey)).toEqual(testTokenValue); + }); + + it('setter set invalid token to localStorage as empty string', () => { + let token; + + token = nbCreateToken(tokenClass, null); + tokenStorage.set(token); + expect(localStorage.getItem(testTokenKey)).toEqual(''); + + token = nbCreateToken(tokenClass, undefined); + tokenStorage.set(token); + expect(localStorage.getItem(testTokenKey)).toEqual(''); + }); + + it('get return null in case token was not set', () => { + const token = tokenStorage.get(); + expect(token.getValue()).toBeNull(); + expect(token.isValid()).toBe(false); + }); + + + it('should return correct value', () => { + localStorage.setItem(testTokenKey, testTokenValue); + + const token = tokenStorage.get(); + expect(token.getValue()).toEqual(testTokenValue); + }); + + it('clear remove token', () => { + localStorage.setItem(testTokenKey, testTokenValue); + + tokenStorage.clear(); + + expect(localStorage.getItem(testTokenKey)).toBeNull(); + }); + + it('clear remove token only', () => { + localStorage.setItem(testTokenKey, testTokenValue); + localStorage.setItem(testTokenKey + '2', testTokenValue); + + tokenStorage.clear(); + + expect(localStorage.getItem(testTokenKey + '2')).toEqual(testTokenValue); + expect(localStorage.getItem(testTokenKey)).toBeNull(); + }); +}); diff --git a/src/framework/auth/services/token/token-storage.ts b/src/framework/auth/services/token/token-storage.ts new file mode 100644 index 0000000000..5b44739736 --- /dev/null +++ b/src/framework/auth/services/token/token-storage.ts @@ -0,0 +1,67 @@ +import { Inject, Injectable } from '@angular/core'; + +import { NB_AUTH_TOKEN_CLASS } from '../../auth.options'; +import { NbAuthToken, nbCreateToken, NbTokenClass } from './token'; + +export abstract class NbTokenStorage { + + abstract get(): NbAuthToken; + abstract set(token: NbAuthToken); + abstract setRaw(token: string); + abstract clear(); +} + +/** + * Service that uses browser localStorage as a storage. + * + * The token storage is provided into auth module the following way: + * ``` + * { provide: NbTokenStorage, useClass: NbTokenLocalStorage }, + * ``` + * + * If you need to change the storage behaviour or provide your own - just extend your class from basic `NbTokenStorage` + * or `NbTokenLocalStorage` and provide in your `app.module`: + * ``` + * { provide: NbTokenStorage, useClass: NbTokenCustomStorage }, + * ``` + * + */ +@Injectable() +export class NbTokenLocalStorage implements NbTokenStorage { + + protected key = 'auth_app_token'; + + constructor(@Inject(NB_AUTH_TOKEN_CLASS) protected tokenClass: NbTokenClass) { + } + + /** + * Returns token from localStorage + * @returns {NbAuthToken} + */ + get(): NbAuthToken { + return nbCreateToken(this.tokenClass, localStorage.getItem(this.key)); + } + + /** + * Sets token to localStorage + * @param {NbAuthToken} token + */ + set(token: NbAuthToken) { + localStorage.setItem(this.key, token.toString()); + } + + /** + * Sets raw (string) token to localStorage + * @param {string} token + */ + setRaw(token: string) { + localStorage.setItem(this.key, token); + } + + /** + * Clears token from localStorage + */ + clear() { + localStorage.removeItem(this.key); + } +} diff --git a/src/framework/auth/services/token/token.service.ts b/src/framework/auth/services/token/token.service.ts new file mode 100644 index 0000000000..3ac474f995 --- /dev/null +++ b/src/framework/auth/services/token/token.service.ts @@ -0,0 +1,82 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; +import { BehaviorSubject } from 'rxjs/BehaviorSubject'; +import { of as observableOf } from 'rxjs/observable/of'; +import { filter } from 'rxjs/operators/filter'; +import { share } from 'rxjs/operators/share'; + +import { NbTokenStorage } from './token-storage'; +import { NbAuthToken } from './token'; + +/** + * Service that allows you to manage authentication token - get, set, clear and also listen to token changes over time. + */ +@Injectable() +export class NbTokenService { + + protected token$: BehaviorSubject = new BehaviorSubject(null); + + constructor(protected tokenStorage: NbTokenStorage) { + this.publishStoredToken(); + } + + /** + * Publishes token when it changes. + * @returns {Observable} + */ + tokenChange(): Observable { + return this.token$ + .pipe( + filter(value => !!value), + share(), + ); + } + + /** + * Sets a token into the storage. This method is used by the NbAuthService automatically. + * + * @param {NbAuthToken} token + * @returns {Observable} + */ + set(token: NbAuthToken): Observable { + this.tokenStorage.set(token); + this.publishStoredToken(); + return observableOf(null); + } + + /** + * Sets a raw token into the storage. This method is used by the NbAuthService automatically. + * + * @param {string} token + * @returns {Observable} + */ + setRaw(token: string): Observable { + this.tokenStorage.setRaw(token); + this.publishStoredToken(); + return observableOf(null); + } + + /** + * Returns observable of current token + * @returns {Observable} + */ + get(): Observable { + const token = this.tokenStorage.get(); + return observableOf(token); + } + + /** + * Removes the token and published token value + * + * @returns {Observable} + */ + clear(): Observable { + this.tokenStorage.clear(); + this.publishStoredToken(); + return observableOf(null); + } + + protected publishStoredToken() { + this.token$.next(this.tokenStorage.get()); + } +} diff --git a/src/framework/auth/services/token/token.spec.ts b/src/framework/auth/services/token/token.spec.ts new file mode 100644 index 0000000000..e9ffd511f1 --- /dev/null +++ b/src/framework/auth/services/token/token.spec.ts @@ -0,0 +1,77 @@ +/** + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { NbAuthJWTToken } from './token'; + + +describe('auth JWT token', () => { + // tslint:disable + const validJWTToken = new NbAuthJWTToken('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzY290Y2guaW8iLCJleHAiOjI1MTczMTQwNjYxNzUsIm5hbWUiOiJDaHJpcyBTZXZpbGxlamEiLCJhZG1pbiI6dHJ1ZX0=.03f329983b86f7d9a9f5fef85305880101d5e302afafa20154d094b229f75773'); + const emptyJWTToken = new NbAuthJWTToken('..'); + const invalidBase64JWTToken = new NbAuthJWTToken('h%2BHY.h%2BHY.h%2BHY'); + + const invalidJWTToken = new NbAuthJWTToken('.'); + + const noExpJWTToken = new NbAuthJWTToken('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzY290Y2guaW8iLCJuYW1lIjoiQ2hyaXMgU2V2aWxsZWphIiwiYWRtaW4iOnRydWV9.03f329983b86f7d9a9f5fef85305880101d5e302afafa20154d094b229f75773'); + + const expiredJWTToken = new NbAuthJWTToken('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzY290Y2guaW8iLCJleHAiOjEzMDA4MTkzODAsIm5hbWUiOiJDaHJpcyBTZXZpbGxlamEiLCJhZG1pbiI6dHJ1ZX0.03f329983b86f7d9a9f5fef85305880101d5e302afafa20154d094b229f75773'); + // tslint:enable + + it('getPayload success', () => { + expect(validJWTToken.getPayload()) + .toEqual(JSON.parse('{"iss":"scotch.io","exp":2517314066175,"name":"Chris Sevilleja","admin":true}')); + }); + + it('getPayload, not valid JWT token, must consist of three parts', () => { + expect(() => { + invalidJWTToken.getPayload(); + }) + .toThrow(new Error( + `The token ${invalidJWTToken.getValue()} is not valid JWT token and must consist of three parts.`)); + }); + + it('getPayload, not valid JWT token, cannot be decoded', () => { + expect(() => { + emptyJWTToken.getPayload(); + }) + .toThrow(new Error( + `The token ${emptyJWTToken.getValue()} is not valid JWT token and cannot be decoded.`)); + }); + + it('getPayload, not valid base64 in JWT token, cannot be decoded', () => { + expect(() => { + invalidBase64JWTToken.getPayload(); + }) + .toThrow(new Error( + `The token ${invalidBase64JWTToken.getValue()} is not valid JWT token and cannot be parsed.`)); + }); + + it('getTokenExpDate success', () => { + const date = new Date(0); + date.setUTCSeconds(2517314066175); + expect(validJWTToken.getTokenExpDate()).toEqual(date); + }); + + it('getTokenExpDate is empty', () => { + expect(noExpJWTToken.getTokenExpDate()).toBeNull(); + }); + + it('no exp date token is valid', () => { + expect(noExpJWTToken.isValid()).toEqual(true); + }); + + it('isValid success', () => { + expect(validJWTToken.isValid()).toEqual(true); + }); + + it('isValid fail', () => { + // without token + expect(new NbAuthJWTToken('').isValid()).toBeFalsy(); + + // expired date + expect(expiredJWTToken.isValid()).toBeFalsy(); + }); +}); diff --git a/src/framework/auth/services/token/token.ts b/src/framework/auth/services/token/token.ts new file mode 100644 index 0000000000..6d3e63c6e2 --- /dev/null +++ b/src/framework/auth/services/token/token.ts @@ -0,0 +1,108 @@ +import { urlBase64Decode } from '../../helpers'; + +export interface NbAuthToken { + getValue(): string; + isValid(): boolean; + toString(): string; +} + +export interface NbTokenClass { + new (raw: string): NbAuthToken +} + +export function nbCreateToken(tokenClass: NbTokenClass, token: string) { + return new tokenClass(token); +} + +/** + * Wrapper for simple (text) token + */ +export class NbAuthSimpleToken implements NbAuthToken { + + constructor(readonly token: string) { + } + + /** + * Returns the token value + * @returns string + */ + getValue(): string { + return this.token; + } + + /** + * Is non empty and valid + * @returns {boolean} + */ + isValid(): boolean { + return !!this.token; + } + + /** + * Validate value and convert to string, if value is not valid return empty string + * @returns {string} + */ + toString(): string { + return !!this.token ? this.token : ''; + } +} + +/** + * Wrapper for JWT token with additional methods. + */ +export class NbAuthJWTToken extends NbAuthSimpleToken { + + /** + * Returns payload object + * @returns any + */ + getPayload(): any { + + if (!this.token) { + throw new Error('Cannot extract payload from an empty token.'); + } + + const parts = this.token.split('.'); + + if (parts.length !== 3) { + throw new Error(`The token ${this.token} is not valid JWT token and must consist of three parts.`); + } + + let decoded; + try { + decoded = urlBase64Decode(parts[1]); + } catch (e) { + throw new Error(`The token ${this.token} is not valid JWT token and cannot be parsed.`); + } + + if (!decoded) { + throw new Error(`The token ${this.token} is not valid JWT token and cannot be decoded.`); + } + + return JSON.parse(decoded); + } + + /** + * Returns expiration date + * @returns Date + */ + getTokenExpDate(): Date { + const decoded = this.getPayload(); + if (!decoded.hasOwnProperty('exp')) { + return null; + } + + const date = new Date(0); + date.setUTCSeconds(decoded.exp); + + return date; + } + + /** + * Is data expired + * @returns {boolean} + */ + isValid(): boolean { + return super.isValid() && (!this.getTokenExpDate() || new Date() < this.getTokenExpDate()); + } +} diff --git a/src/framework/theme/components/checkbox/_checkbox.component.theme.scss b/src/framework/theme/components/checkbox/_checkbox.component.theme.scss index 80ea98c3f2..b0c7130d22 100644 --- a/src/framework/theme/components/checkbox/_checkbox.component.theme.scss +++ b/src/framework/theme/components/checkbox/_checkbox.component.theme.scss @@ -47,6 +47,7 @@ margin: 0; min-height: inherit; padding: 0.375rem 1.5rem; + line-height: 0; } .customised-control-indicator { diff --git a/src/framework/theme/services/theme.spec.ts b/src/framework/theme/services/theme.spec.ts index 055024e85a..20b1fcaa20 100644 --- a/src/framework/theme/services/theme.spec.ts +++ b/src/framework/theme/services/theme.spec.ts @@ -114,6 +114,7 @@ describe('theme-service', () => { current = change.variables; }); try { + // TODO could be rewrite with usage of done() expect(current).not.toBeUndefined(); expect(current.fontMain).toEqual('"Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'); expect(current.bg).toEqual('#ffffff');