UNPKG

@nebular/auth

Version:
1,458 lines (1,444 loc) 154 kB
import * as i0 from '@angular/core'; import { InjectionToken, Injectable, Inject, Component, ChangeDetectionStrategy, Injector, NgModule } from '@angular/core'; import * as i3 from '@angular/common'; import { CommonModule } from '@angular/common'; import * as i2 from '@angular/router'; import { RouterModule } from '@angular/router'; import * as i5 from '@angular/forms'; import { FormsModule } from '@angular/forms'; import * as i4 from '@nebular/theme'; import { NB_WINDOW, NbLayoutModule, NbCardModule, NbCheckboxModule, NbAlertModule, NbInputModule, NbButtonModule, NbIconModule } from '@nebular/theme'; import { BehaviorSubject, of, Subject } from 'rxjs'; import { filter, share, map, switchMap, delay, catchError, takeUntil } from 'rxjs/operators'; import * as i1 from '@angular/common/http'; import { HttpResponse, HttpHeaders, HttpErrorResponse } from '@angular/common/http'; const socialLinks = []; const defaultAuthOptions = { strategies: [], forms: { login: { redirectDelay: 500, // delay before redirect after a successful login, while success message is shown to the user strategy: 'email', // provider id key. If you have multiple strategies, or what to use your own rememberMe: true, // whether to show or not the `rememberMe` checkbox showMessages: { success: true, error: true, }, socialLinks: socialLinks, // social links at the bottom of a page }, register: { redirectDelay: 500, strategy: 'email', showMessages: { success: true, error: true, }, terms: true, socialLinks: socialLinks, }, requestPassword: { redirectDelay: 500, strategy: 'email', showMessages: { success: true, error: true, }, socialLinks: socialLinks, }, resetPassword: { redirectDelay: 500, strategy: 'email', showMessages: { success: true, error: true, }, socialLinks: socialLinks, }, logout: { redirectDelay: 500, strategy: 'email', }, validation: { password: { required: true, minLength: 4, maxLength: 50, }, email: { required: true, }, fullName: { required: false, minLength: 4, maxLength: 50, }, }, }, }; const NB_AUTH_OPTIONS = new InjectionToken('Nebular Auth Options'); const NB_AUTH_USER_OPTIONS = new InjectionToken('Nebular User Auth Options'); const NB_AUTH_STRATEGIES = new InjectionToken('Nebular Auth Strategies'); const NB_AUTH_TOKENS = new InjectionToken('Nebular Auth Tokens'); const NB_AUTH_INTERCEPTOR_HEADER = new InjectionToken('Nebular Simple Interceptor Header'); const NB_AUTH_TOKEN_INTERCEPTOR_FILTER = new InjectionToken('Nebular Interceptor Filter'); /** * Extending object that entered in first argument. * * Returns extended object or false if have no target object or incorrect type. * * If you wish to clone source object (without modify it), just use empty new * object as first argument, like this: * deepExtend({}, yourObj_1, [yourObj_N]); */ const deepExtend = function (...objects) { if (arguments.length < 1 || typeof arguments[0] !== 'object') { return false; } if (arguments.length < 2) { return arguments[0]; } const target = arguments[0]; // convert arguments to array and cut off target object const args = Array.prototype.slice.call(arguments, 1); let val, src; args.forEach(function (obj) { // skip argument if it is array or isn't object if (typeof obj !== 'object' || Array.isArray(obj)) { return; } Object.keys(obj).forEach(function (key) { src = target[key]; // source value val = obj[key]; // new value // recursion prevention if (val === target) { return; /** * if new value isn't object then just overwrite by new value * instead of extending. */ } else if (typeof val !== 'object' || val === null) { target[key] = val; return; // just clone arrays (and recursive clone objects inside) } else if (Array.isArray(val)) { target[key] = deepCloneArray(val); return; // custom cloning and overwrite for specific objects } else if (isSpecificValue(val)) { target[key] = cloneSpecificValue(val); return; // overwrite by new value if source isn't object or array } else if (typeof src !== 'object' || src === null || Array.isArray(src)) { target[key] = deepExtend({}, val); return; // source value and new value is objects both, extending... } else { target[key] = deepExtend(src, val); return; } }); }); return target; }; function isSpecificValue(val) { return (val instanceof Date || val instanceof RegExp) ? true : false; } function cloneSpecificValue(val) { if (val instanceof Date) { return new Date(val.getTime()); } else if (val instanceof RegExp) { return new RegExp(val); } else { throw new Error('cloneSpecificValue: Unexpected situation'); } } /** * Recursive cloning array. */ function deepCloneArray(arr) { const clone = []; arr.forEach(function (item, index) { if (typeof item === 'object' && item !== null) { if (Array.isArray(item)) { clone[index] = deepCloneArray(item); } else if (isSpecificValue(item)) { clone[index] = cloneSpecificValue(item); } else { clone[index] = deepExtend({}, item); } } else { clone[index] = item; } }); return clone; } // getDeepFromObject({result: {data: 1}}, 'result.data', 2); // returns 1 function getDeepFromObject(object = {}, name, defaultValue) { const keys = name.split('.'); // clone the object let level = deepExtend({}, object || {}); keys.forEach((k) => { if (level && typeof level[k] !== 'undefined') { level = level[k]; } else { level = undefined; } }); return typeof level === 'undefined' ? defaultValue : level; } function urlBase64Decode(str) { let output = str.replace(/-/g, '+').replace(/_/g, '/'); switch (output.length % 4) { case 0: { break; } case 2: { output += '=='; break; } case 3: { output += '='; break; } default: { throw new Error('Illegal base64url string!'); } } return b64DecodeUnicode(output); } function b64decode(str) { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; let output = ''; str = String(str).replace(/=+$/, ''); if (str.length % 4 === 1) { throw new Error(`'atob' failed: The string to be decoded is not correctly encoded.`); } for ( // initialize result and counters let bc = 0, bs, buffer, idx = 0; // get next character buffer = str.charAt(idx++); // character found in table? initialize bit storage and add its ascii value; ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, // and if not first of each 4 characters, // convert the first 8 bits to one ascii character bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0) { // try to find character in table (0-63, not found => -1) buffer = chars.indexOf(buffer); } return output; } // https://developer.mozilla.org/en/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_Unicode_Problem function b64DecodeUnicode(str) { return decodeURIComponent(Array.prototype.map.call(b64decode(str), (c) => { return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); }).join('')); } class NbAuthToken { constructor() { this.payload = null; } getName() { return this.constructor.NAME; } getPayload() { return this.payload; } } class NbAuthTokenNotFoundError extends Error { constructor(message) { super(message); Object.setPrototypeOf(this, new.target.prototype); } } class NbAuthIllegalTokenError extends Error { constructor(message) { super(message); Object.setPrototypeOf(this, new.target.prototype); } } class NbAuthEmptyTokenError extends NbAuthIllegalTokenError { constructor(message) { super(message); Object.setPrototypeOf(this, new.target.prototype); } } class NbAuthIllegalJWTTokenError extends NbAuthIllegalTokenError { constructor(message) { super(message); Object.setPrototypeOf(this, new.target.prototype); } } function nbAuthCreateToken(tokenClass, token, ownerStrategyName, createdAt) { return new tokenClass(token, ownerStrategyName, createdAt); } function decodeJwtPayload(payload) { if (payload.length === 0) { throw new NbAuthEmptyTokenError('Cannot extract from an empty payload.'); } const parts = payload.split('.'); if (parts.length !== 3) { throw new NbAuthIllegalJWTTokenError(`The payload ${payload} is not valid JWT payload and must consist of three parts.`); } let decoded; try { decoded = urlBase64Decode(parts[1]); } catch (e) { throw new NbAuthIllegalJWTTokenError(`The payload ${payload} is not valid JWT payload and cannot be parsed.`); } if (!decoded) { throw new NbAuthIllegalJWTTokenError(`The payload ${payload} is not valid JWT payload and cannot be decoded.`); } return JSON.parse(decoded); } /** * Wrapper for simple (text) token */ class NbAuthSimpleToken extends NbAuthToken { static { this.NAME = 'nb:auth:simple:token'; } constructor(token, ownerStrategyName, createdAt) { super(); this.token = token; this.ownerStrategyName = ownerStrategyName; this.createdAt = createdAt; try { this.parsePayload(); } catch (err) { if (!(err instanceof NbAuthTokenNotFoundError)) { // token is present but has got a problem, including illegal throw err; } } this.createdAt = this.prepareCreatedAt(createdAt); } parsePayload() { this.payload = null; } prepareCreatedAt(date) { return date ? date : new Date(); } /** * Returns the token's creation date * @returns {Date} */ getCreatedAt() { return this.createdAt; } /** * Returns the token value * @returns string */ getValue() { return this.token; } getOwnerStrategyName() { return this.ownerStrategyName; } /** * Is non empty and valid * @returns {boolean} */ isValid() { return !!this.getValue(); } /** * Validate value and convert to string, if value is not valid return empty string * @returns {string} */ toString() { return !!this.token ? this.token : ''; } } /** * Wrapper for JWT token with additional methods. */ class NbAuthJWTToken extends NbAuthSimpleToken { static { this.NAME = 'nb:auth:jwt:token'; } /** * for JWT token, the iat (issued at) field of the token payload contains the creation Date */ prepareCreatedAt(date) { const decoded = this.getPayload(); return decoded && decoded.iat ? new Date(Number(decoded.iat) * 1000) : super.prepareCreatedAt(date); } /** * Returns payload object * @returns any */ parsePayload() { if (!this.token) { throw new NbAuthTokenNotFoundError('Token not found. '); } this.payload = decodeJwtPayload(this.token); } /** * Returns expiration date * @returns Date */ getTokenExpDate() { const decoded = this.getPayload(); if (decoded && !decoded.hasOwnProperty('exp')) { return null; } const date = new Date(0); date.setUTCSeconds(decoded.exp); // 'cause jwt token are set in seconds return date; } /** * Is data expired * @returns {boolean} */ isValid() { return super.isValid() && (!this.getTokenExpDate() || new Date() < this.getTokenExpDate()); } } const prepareOAuth2Token = (data) => { if (typeof data === 'string') { try { return JSON.parse(data); } catch (e) { } } return data; }; /** * Wrapper for OAuth2 token whose access_token is a JWT Token */ class NbAuthOAuth2Token extends NbAuthSimpleToken { static { this.NAME = 'nb:auth:oauth2:token'; } constructor(data = {}, ownerStrategyName, createdAt) { // we may get it as string when retrieving from a storage super(prepareOAuth2Token(data), ownerStrategyName, createdAt); } /** * Returns the token value * @returns string */ getValue() { return this.token.access_token; } /** * Returns the refresh token * @returns string */ getRefreshToken() { return this.token.refresh_token; } /** * put refreshToken in the token payload * @param refreshToken */ setRefreshToken(refreshToken) { this.token.refresh_token = refreshToken; } /** * Parses token payload * @returns any */ parsePayload() { if (!this.token) { throw new NbAuthTokenNotFoundError('Token not found.'); } else { if (!Object.keys(this.token).length) { throw new NbAuthEmptyTokenError('Cannot extract payload from an empty token.'); } } this.payload = this.token; } /** * Returns the token type * @returns string */ getType() { return this.token.token_type; } /** * Is data expired * @returns {boolean} */ isValid() { return super.isValid() && (!this.getTokenExpDate() || new Date() < this.getTokenExpDate()); } /** * Returns expiration date * @returns Date */ getTokenExpDate() { if (!this.token.hasOwnProperty('expires_in')) { return null; } return new Date(this.createdAt.getTime() + Number(this.token.expires_in) * 1000); } /** * Convert to string * @returns {string} */ toString() { return JSON.stringify(this.token); } } /** * Wrapper for OAuth2 token embedding JWT tokens */ class NbAuthOAuth2JWTToken extends NbAuthOAuth2Token { static { this.NAME = 'nb:auth:oauth2:jwt:token'; } parsePayload() { super.parsePayload(); this.parseAccessTokenPayload(); } parseAccessTokenPayload() { const accessToken = this.getValue(); if (!accessToken) { throw new NbAuthTokenNotFoundError('access_token key not found.'); } this.accessTokenPayload = decodeJwtPayload(accessToken); } /** * Returns access token payload * @returns any */ getAccessTokenPayload() { return this.accessTokenPayload; } /** * for Oauth2 JWT token, the iat (issued at) field of the access_token payload */ prepareCreatedAt(date) { const payload = this.accessTokenPayload; return payload && payload.iat ? new Date(Number(payload.iat) * 1000) : super.prepareCreatedAt(date); } /** * Is token valid * @returns {boolean} */ isValid() { return this.accessTokenPayload && super.isValid(); } /** * Returns expiration date : * - exp if set, * - super.getExpDate() otherwise * @returns Date */ getTokenExpDate() { if (this.accessTokenPayload && this.accessTokenPayload.hasOwnProperty('exp')) { const date = new Date(0); date.setUTCSeconds(this.accessTokenPayload.exp); return date; } else { return super.getTokenExpDate(); } } } const NB_AUTH_FALLBACK_TOKEN = new InjectionToken('Nebular Auth Options'); /** * Creates a token parcel which could be stored/restored */ class NbAuthTokenParceler { constructor(fallbackClass, tokenClasses) { this.fallbackClass = fallbackClass; this.tokenClasses = tokenClasses; } wrap(token) { return JSON.stringify({ name: token.getName(), ownerStrategyName: token.getOwnerStrategyName(), createdAt: token.getCreatedAt().getTime(), value: token.toString(), }); } unwrap(value) { let tokenClass = this.fallbackClass; let tokenValue = ''; let tokenOwnerStrategyName = ''; let tokenCreatedAt = null; const tokenPack = this.parseTokenPack(value); if (tokenPack) { tokenClass = this.getClassByName(tokenPack.name) || this.fallbackClass; tokenValue = tokenPack.value; tokenOwnerStrategyName = tokenPack.ownerStrategyName; tokenCreatedAt = new Date(Number(tokenPack.createdAt)); } return nbAuthCreateToken(tokenClass, tokenValue, tokenOwnerStrategyName, tokenCreatedAt); } // TODO: this could be moved to a separate token registry getClassByName(name) { return this.tokenClasses.find((tokenClass) => tokenClass.NAME === name); } parseTokenPack(value) { try { return JSON.parse(value); } catch (e) { } return null; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.3", ngImport: i0, type: NbAuthTokenParceler, deps: [{ token: NB_AUTH_FALLBACK_TOKEN }, { token: NB_AUTH_TOKENS }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.1.3", ngImport: i0, type: NbAuthTokenParceler }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.3", ngImport: i0, type: NbAuthTokenParceler, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: undefined, decorators: [{ type: Inject, args: [NB_AUTH_FALLBACK_TOKEN] }] }, { type: undefined, decorators: [{ type: Inject, args: [NB_AUTH_TOKENS] }] }] }); class NbTokenStorage { } /** * Service that uses browser localStorage as a storage. * * The token storage is provided into auth module the following way: * ```ts * { 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`: * ```ts * { provide: NbTokenStorage, useClass: NbTokenCustomStorage }, * ``` * */ class NbTokenLocalStorage extends NbTokenStorage { constructor(parceler) { super(); this.parceler = parceler; this.key = 'auth_app_token'; } /** * Returns token from localStorage * @returns {NbAuthToken} */ get() { const raw = localStorage.getItem(this.key); return this.parceler.unwrap(raw); } /** * Sets token to localStorage * @param {NbAuthToken} token */ set(token) { const raw = this.parceler.wrap(token); localStorage.setItem(this.key, raw); } /** * Clears token from localStorage */ clear() { localStorage.removeItem(this.key); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.3", ngImport: i0, type: NbTokenLocalStorage, deps: [{ token: NbAuthTokenParceler }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.1.3", ngImport: i0, type: NbTokenLocalStorage }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.3", ngImport: i0, type: NbTokenLocalStorage, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: NbAuthTokenParceler }] }); /** * Service that allows you to manage authentication token - get, set, clear and also listen to token changes over time. */ class NbTokenService { constructor(tokenStorage) { this.tokenStorage = tokenStorage; this.token$ = new BehaviorSubject(null); this.publishStoredToken(); } /** * Publishes token when it changes. * @returns {Observable<NbAuthToken>} */ tokenChange() { 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<any>} */ set(token) { this.tokenStorage.set(token); this.publishStoredToken(); return of(null); } /** * Returns observable of current token * @returns {Observable<NbAuthToken>} */ get() { const token = this.tokenStorage.get(); return of(token); } /** * Removes the token and published token value * * @returns {Observable<any>} */ clear() { this.tokenStorage.clear(); this.publishStoredToken(); return of(null); } publishStoredToken() { this.token$.next(this.tokenStorage.get()); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.3", ngImport: i0, type: NbTokenService, deps: [{ token: NbTokenStorage }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.1.3", ngImport: i0, type: NbTokenService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.3", ngImport: i0, type: NbTokenService, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: NbTokenStorage }] }); /** * @license * Copyright Akveo. All Rights Reserved. * Licensed under the MIT License. See License.txt in the project root for license information. */ /** * Common authentication service. * Should be used to as an interlayer between UI Components and Auth Strategy. */ class NbAuthService { constructor(tokenService, strategies) { this.tokenService = tokenService; this.strategies = strategies; } /** * Retrieves current authenticated token stored * @returns {Observable<any>} */ getToken() { return this.tokenService.get(); } /** * Returns true if auth token is present in the token storage * @returns {Observable<boolean>} */ isAuthenticated() { return this.getToken() .pipe(map((token) => token.isValid())); } /** * Returns true if valid auth token is present in the token storage. * If not, calls the strategy refreshToken, and returns isAuthenticated() if success, false otherwise * @returns {Observable<boolean>} */ isAuthenticatedOrRefresh() { return this.getToken() .pipe(switchMap(token => { if (token.getValue() && !token.isValid()) { return this.refreshToken(token.getOwnerStrategyName(), token) .pipe(switchMap(res => { if (res.isSuccess()) { return this.isAuthenticated(); } else { return of(false); } })); } else { return of(token.isValid()); } })); } /** * Returns tokens stream * @returns {Observable<NbAuthSimpleToken>} */ onTokenChange() { return this.tokenService.tokenChange(); } /** * Returns authentication status stream * @returns {Observable<boolean>} */ onAuthenticationChange() { return this.onTokenChange() .pipe(map((token) => token.isValid())); } /** * Authenticates with the selected strategy * Stores received token in the token storage * * Example: * authenticate('email', {email: 'email@example.com', password: 'test'}) * * @param strategyName * @param data * @returns {Observable<NbAuthResult>} */ authenticate(strategyName, data) { return this.getStrategy(strategyName).authenticate(data) .pipe(switchMap((result) => { return this.processResultToken(result); })); } /** * Registers with the selected strategy * Stores received token in the token storage * * Example: * register('email', {email: 'email@example.com', name: 'Some Name', password: 'test'}) * * @param strategyName * @param data * @returns {Observable<NbAuthResult>} */ register(strategyName, data) { return this.getStrategy(strategyName).register(data) .pipe(switchMap((result) => { return this.processResultToken(result); })); } /** * Sign outs with the selected strategy * Removes token from the token storage * * Example: * logout('email') * * @param strategyName * @returns {Observable<NbAuthResult>} */ logout(strategyName) { return this.getStrategy(strategyName).logout() .pipe(switchMap((result) => { if (result.isSuccess()) { this.tokenService.clear() .pipe(map(() => result)); } return of(result); })); } /** * Sends forgot password request to the selected strategy * * Example: * requestPassword('email', {email: 'email@example.com'}) * * @param strategyName * @param data * @returns {Observable<NbAuthResult>} */ requestPassword(strategyName, data) { return this.getStrategy(strategyName).requestPassword(data); } /** * Tries to reset password with the selected strategy * * Example: * resetPassword('email', {newPassword: 'test'}) * * @param strategyName * @param data * @returns {Observable<NbAuthResult>} */ resetPassword(strategyName, data) { return this.getStrategy(strategyName).resetPassword(data); } /** * Sends a refresh token request * Stores received token in the token storage * * Example: * refreshToken('email', {token: token}) * * @param {string} strategyName * @param data * @returns {Observable<NbAuthResult>} */ refreshToken(strategyName, data) { return this.getStrategy(strategyName).refreshToken(data) .pipe(switchMap((result) => { return this.processResultToken(result); })); } /** * Get registered strategy by name * * Example: * getStrategy('email') * * @param {string} provider * @returns {NbAbstractAuthProvider} */ getStrategy(strategyName) { const found = this.strategies.find((strategy) => strategy.getName() === strategyName); if (!found) { throw new TypeError(`There is no Auth Strategy registered under '${strategyName}' name`); } return found; } processResultToken(result) { if (result.isSuccess() && result.getToken()) { return this.tokenService.set(result.getToken()) .pipe(map((token) => { return result; })); } return of(result); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.3", ngImport: i0, type: NbAuthService, deps: [{ token: NbTokenService }, { token: NB_AUTH_STRATEGIES }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.1.3", ngImport: i0, type: NbAuthService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.3", ngImport: i0, type: NbAuthService, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: NbTokenService }, { type: undefined, decorators: [{ type: Inject, args: [NB_AUTH_STRATEGIES] }] }] }); class NbAuthStrategy { // we should keep this any and validation should be done in `register` method instead // otherwise it won't be possible to pass an empty object setOptions(options) { this.options = deepExtend({}, this.defaultOptions, options); } getOption(key) { return getDeepFromObject(this.options, key, null); } createToken(value, failWhenInvalidToken) { const token = nbAuthCreateToken(this.getOption('token.class'), value, this.getName()); // At this point, nbAuthCreateToken failed with NbAuthIllegalTokenError which MUST be intercepted by strategies // Or token is created. It MAY be created even if backend did not return any token, in this case it is !Valid if (failWhenInvalidToken && !token.isValid()) { // If we require a valid token (i.e. isValid), then we MUST throw NbAuthIllegalTokenError so that the strategies // intercept it throw new NbAuthIllegalTokenError('Token is empty or invalid.'); } return token; } getName() { return this.getOption('name'); } createFailResponse(data) { return new HttpResponse({ body: {}, status: 401 }); } createSuccessResponse(data) { return new HttpResponse({ body: {}, status: 200 }); } getActionEndpoint(action) { const actionEndpoint = this.getOption(`${action}.endpoint`); const baseEndpoint = this.getOption('baseEndpoint'); return actionEndpoint ? baseEndpoint + actionEndpoint : ''; } getHeaders() { const customHeaders = this.getOption('headers') ?? {}; if (customHeaders instanceof HttpHeaders) { return customHeaders; } let headers = new HttpHeaders(); Object.entries(customHeaders).forEach(([key, value]) => { headers = headers.append(key, value); }); return headers; } } class NbAuthResult { // TODO: better pass object constructor(success, response, redirect, errors, messages, token = null) { this.success = success; this.response = response; this.redirect = redirect; this.errors = []; this.messages = []; this.errors = this.errors.concat([errors]); if (errors instanceof Array) { this.errors = errors; } this.messages = this.messages.concat([messages]); if (messages instanceof Array) { this.messages = messages; } this.token = token; } getResponse() { return this.response; } getToken() { return this.token; } getRedirect() { return this.redirect; } getErrors() { return this.errors.filter(val => !!val); } getMessages() { return this.messages.filter(val => !!val); } isSuccess() { return this.success; } isFailure() { return !this.success; } } class NbAuthStrategyOptions { } /** * @license * Copyright Akveo. All Rights Reserved. * Licensed under the MIT License. See License.txt in the project root for license information. */ class NbDummyAuthStrategyOptions extends NbAuthStrategyOptions { constructor() { super(...arguments); this.token = { class: NbAuthSimpleToken, }; this.delay = 1000; this.alwaysFail = false; } } const dummyStrategyOptions = new NbDummyAuthStrategyOptions(); /** * Dummy auth strategy. Could be useful for auth setup when backend is not available yet. * * * Strategy settings. * * ```ts * export class NbDummyAuthStrategyOptions extends NbAuthStrategyOptions { * name = 'dummy'; * token = { * class: NbAuthSimpleToken, * }; * delay? = 1000; * alwaysFail? = false; * } * ``` */ class NbDummyAuthStrategy extends NbAuthStrategy { constructor() { super(...arguments); this.defaultOptions = dummyStrategyOptions; } static setup(options) { return [NbDummyAuthStrategy, options]; } authenticate(data) { return of(this.createDummyResult(data)).pipe(delay(this.getOption('delay'))); } register(data) { return of(this.createDummyResult(data)).pipe(delay(this.getOption('delay'))); } requestPassword(data) { return of(this.createDummyResult(data)).pipe(delay(this.getOption('delay'))); } resetPassword(data) { return of(this.createDummyResult(data)).pipe(delay(this.getOption('delay'))); } logout(data) { return of(this.createDummyResult(data)).pipe(delay(this.getOption('delay'))); } refreshToken(data) { return of(this.createDummyResult(data)).pipe(delay(this.getOption('delay'))); } createDummyResult(data) { if (this.getOption('alwaysFail')) { return new NbAuthResult(false, this.createFailResponse(data), null, ['Something went wrong.']); } try { const token = this.createToken('test token', true); return new NbAuthResult(true, this.createSuccessResponse(data), '/', [], ['Successfully logged in.'], token); } catch (err) { return new NbAuthResult(false, this.createFailResponse(data), null, [err.message]); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.3", ngImport: i0, type: NbDummyAuthStrategy, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.1.3", ngImport: i0, type: NbDummyAuthStrategy }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.3", ngImport: i0, type: NbDummyAuthStrategy, decorators: [{ type: Injectable }] }); /** * @license * Copyright Akveo. All Rights Reserved. * Licensed under the MIT License. See License.txt in the project root for license information. */ var NbOAuth2ResponseType; (function (NbOAuth2ResponseType) { NbOAuth2ResponseType["CODE"] = "code"; NbOAuth2ResponseType["TOKEN"] = "token"; })(NbOAuth2ResponseType || (NbOAuth2ResponseType = {})); // TODO: client_credentials var NbOAuth2GrantType; (function (NbOAuth2GrantType) { NbOAuth2GrantType["AUTHORIZATION_CODE"] = "authorization_code"; NbOAuth2GrantType["PASSWORD"] = "password"; NbOAuth2GrantType["REFRESH_TOKEN"] = "refresh_token"; })(NbOAuth2GrantType || (NbOAuth2GrantType = {})); var NbOAuth2ClientAuthMethod; (function (NbOAuth2ClientAuthMethod) { NbOAuth2ClientAuthMethod["NONE"] = "none"; NbOAuth2ClientAuthMethod["BASIC"] = "basic"; NbOAuth2ClientAuthMethod["REQUEST_BODY"] = "request-body"; })(NbOAuth2ClientAuthMethod || (NbOAuth2ClientAuthMethod = {})); class NbOAuth2AuthStrategyOptions extends NbAuthStrategyOptions { constructor() { super(...arguments); this.baseEndpoint = ''; this.clientId = ''; this.clientSecret = ''; this.clientAuthMethod = NbOAuth2ClientAuthMethod.NONE; this.redirect = { success: '/', failure: null, }; this.defaultErrors = ['Something went wrong, please try again.']; this.defaultMessages = ['You have been successfully authenticated.']; this.authorize = { endpoint: 'authorize', responseType: NbOAuth2ResponseType.CODE, requireValidToken: true, }; this.token = { endpoint: 'token', grantType: NbOAuth2GrantType.AUTHORIZATION_CODE, requireValidToken: true, class: NbAuthOAuth2Token, }; this.refresh = { endpoint: 'token', grantType: NbOAuth2GrantType.REFRESH_TOKEN, requireValidToken: true, }; } } const auth2StrategyOptions = new NbOAuth2AuthStrategyOptions(); /** * @license * Copyright Akveo. All Rights Reserved. * Licensed under the MIT License. See License.txt in the project root for license information. */ /** * OAuth2 authentication strategy. * * Strategy settings: * * ```ts * export enum NbOAuth2ResponseType { * CODE = 'code', * TOKEN = 'token', * } * * export enum NbOAuth2GrantType { * AUTHORIZATION_CODE = 'authorization_code', * PASSWORD = 'password', * REFRESH_TOKEN = 'refresh_token', * } * * export class NbOAuth2AuthStrategyOptions { * name: string; * baseEndpoint?: string = ''; * clientId: string = ''; * clientSecret: string = ''; * clientAuthMethod: string = NbOAuth2ClientAuthMethod.NONE; * redirect?: { success?: string; failure?: string } = { * success: '/', * failure: null, * }; * defaultErrors?: any[] = ['Something went wrong, please try again.']; * defaultMessages?: any[] = ['You have been successfully authenticated.']; * authorize?: { * endpoint?: string; * redirectUri?: string; * responseType?: string; * requireValidToken: true, * scope?: string; * state?: string; * params?: { [key: string]: string }; * } = { * endpoint: 'authorize', * responseType: NbOAuth2ResponseType.CODE, * }; * token?: { * endpoint?: string; * grantType?: string; * requireValidToken: true, * redirectUri?: string; * scope?: string; * class: NbAuthTokenClass, * } = { * endpoint: 'token', * grantType: NbOAuth2GrantType.AUTHORIZATION_CODE, * class: NbAuthOAuth2Token, * }; * refresh?: { * endpoint?: string; * grantType?: string; * scope?: string; * requireValidToken: true, * } = { * endpoint: 'token', * grantType: NbOAuth2GrantType.REFRESH_TOKEN, * }; * } * ``` * */ class NbOAuth2AuthStrategy extends NbAuthStrategy { static setup(options) { return [NbOAuth2AuthStrategy, options]; } get responseType() { return this.getOption('authorize.responseType'); } get clientAuthMethod() { return this.getOption('clientAuthMethod'); } constructor(http, route, window) { super(); this.http = http; this.route = route; this.window = window; this.redirectResultHandlers = { [NbOAuth2ResponseType.CODE]: () => { return of(this.route.snapshot.queryParams).pipe(switchMap((params) => { if (params.code) { return this.requestToken(params.code); } return of(new NbAuthResult(false, params, this.getOption('redirect.failure'), this.getOption('defaultErrors'), [])); })); }, [NbOAuth2ResponseType.TOKEN]: () => { const module = 'authorize'; const requireValidToken = this.getOption(`${module}.requireValidToken`); return of(this.route.snapshot.fragment).pipe(map((fragment) => this.parseHashAsQueryParams(fragment)), map((params) => { if (!params.error) { return new NbAuthResult(true, params, this.getOption('redirect.success'), [], this.getOption('defaultMessages'), this.createToken(params, requireValidToken)); } return new NbAuthResult(false, params, this.getOption('redirect.failure'), this.getOption('defaultErrors'), []); }), catchError((err) => { const errors = []; if (err instanceof NbAuthIllegalTokenError) { errors.push(err.message); } else { errors.push('Something went wrong.'); } return of(new NbAuthResult(false, err, this.getOption('redirect.failure'), errors)); })); }, }; this.redirectResults = { [NbOAuth2ResponseType.CODE]: () => { return of(this.route.snapshot.queryParams).pipe(map((params) => !!(params && (params.code || params.error)))); }, [NbOAuth2ResponseType.TOKEN]: () => { return of(this.route.snapshot.fragment).pipe(map((fragment) => this.parseHashAsQueryParams(fragment)), map((params) => !!(params && (params.access_token || params.error)))); }, }; this.defaultOptions = auth2StrategyOptions; } authenticate(data) { if (this.getOption('token.grantType') === NbOAuth2GrantType.PASSWORD) { return this.passwordToken(data.email, data.password); } else { return this.isRedirectResult().pipe(switchMap((result) => { if (!result) { this.authorizeRedirect(); return of(new NbAuthResult(true)); } return this.getAuthorizationResult(); })); } } getAuthorizationResult() { const redirectResultHandler = this.redirectResultHandlers[this.responseType]; if (redirectResultHandler) { return redirectResultHandler.call(this); } throw new Error(`'${this.responseType}' responseType is not supported, only 'token' and 'code' are supported now`); } refreshToken(token) { const module = 'refresh'; const url = this.getActionEndpoint(module); const requireValidToken = this.getOption(`${module}.requireValidToken`); return this.http.post(url, this.buildRefreshRequestData(token), { headers: this.getHeaders() }).pipe(map((res) => { return new NbAuthResult(true, res, this.getOption('redirect.success'), [], this.getOption('defaultMessages'), this.createRefreshedToken(res, token, requireValidToken)); }), catchError((res) => this.handleResponseError(res))); } passwordToken(username, password) { const module = 'token'; const url = this.getActionEndpoint(module); const requireValidToken = this.getOption(`${module}.requireValidToken`); return this.http.post(url, this.buildPasswordRequestData(username, password), { headers: this.getHeaders() }).pipe(map((res) => { return new NbAuthResult(true, res, this.getOption('redirect.success'), [], this.getOption('defaultMessages'), this.createToken(res, requireValidToken)); }), catchError((res) => this.handleResponseError(res))); } authorizeRedirect() { this.window.location.href = this.buildRedirectUrl(); } isRedirectResult() { return this.redirectResults[this.responseType].call(this); } requestToken(code) { const module = 'token'; const url = this.getActionEndpoint(module); const requireValidToken = this.getOption(`${module}.requireValidToken`); return this.http.post(url, this.buildCodeRequestData(code), { headers: this.getHeaders() }).pipe(map((res) => { return new NbAuthResult(true, res, this.getOption('redirect.success'), [], this.getOption('defaultMessages'), this.createToken(res, requireValidToken)); }), catchError((res) => this.handleResponseError(res))); } buildCodeRequestData(code) { const params = { grant_type: this.getOption('token.grantType'), code: code, redirect_uri: this.getOption('token.redirectUri'), client_id: this.getOption('clientId'), }; return this.urlEncodeParameters(this.cleanParams(this.addCredentialsToParams(params))); } buildRefreshRequestData(token) { const params = { grant_type: this.getOption('refresh.grantType'), refresh_token: token.getRefreshToken(), scope: this.getOption('refresh.scope'), client_id: this.getOption('clientId'), }; return this.urlEncodeParameters(this.cleanParams(this.addCredentialsToParams(params))); } buildPasswordRequestData(username, password) { const params = { grant_type: this.getOption('token.grantType'), username: username, password: password, scope: this.getOption('token.scope'), }; return this.urlEncodeParameters(this.cleanParams(this.addCredentialsToParams(params))); } buildAuthHeader() { if (this.clientAuthMethod === NbOAuth2ClientAuthMethod.BASIC) { if (this.getOption('clientId') && this.getOption('clientSecret')) { return new HttpHeaders({ Authorization: 'Basic ' + btoa(this.getOption('clientId') + ':' + this.getOption('clientSecret')), }); } else { throw Error('For basic client authentication method, please provide both clientId & clientSecret.'); } } return undefined; } getHeaders() { let headers = super.getHeaders(); headers = headers.append('Content-Type', 'application/x-www-form-urlencoded'); const authHeaders = this.buildAuthHeader(); if (authHeaders === undefined) { return headers; } for (const headerKey of authHeaders.keys()) { for (const headerValue of authHeaders.getAll(headerKey)) { headers = headers.append(headerKey, headerValue); } } return headers; } cleanParams(params) { Object.entries(params).forEach(([key, val]) => !val && delete params[key]); return params; } addCredentialsToParams(params) { if (this.clientAuthMethod === NbOAuth2ClientAuthMethod.REQUEST_BODY) { if (this.getOption('clientId') && this.getOption('clientSecret')) { return { ...params, client_id: this.getOption('clientId'), client_secret: this.getOption('clientSecret'), }; } else { throw Error('For request body client authentication method, please provide both clientId & clientSecret.'); } } return params; } handleResponseError(res) { let errors = []; if (res instanceof HttpErrorResponse) { if (res.error.error_description) { errors.push(res.error.error_description); } else { errors = this.getOption('defaultErrors'); } } else if (res instanceof NbAuthIllegalTokenError) { errors.push(res.message); } else { errors.push('Something went wrong.'); } return of(new NbAuthResult(false, res, this.getOption('redirect.failure'), errors, [])); } buildRedirectUrl() { const params = { response_type: this.getOption('authorize.responseType'), client_id: this.getOption('clientId'), redirect_uri: this.getOption('authorize.redirectUri'), scope: this.getOption('authorize.scope'), state: this.getOption('authorize.state'), ...this.getOption('authorize.params'), }; const endpoint = this.getActionEndpoint('authorize'); const query = this.urlEncodeParameters(this.cleanParams(params)); return `${endpoint}?${query}`; } parseHashAsQueryParams(hash) { return hash ? hash.split('&').reduce((acc, part) => { const item = part.split('='); acc[item[0]] = decodeURIComponent(item[1]); return acc; }, {}) : {}; } urlEncodeParameters(params) { return Object.keys(params) .map((k) => { return `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`; }) .join('&'); } createRefreshedToken(res, existingToken, requireValidToken) { const refreshedToken = this.createToken(res, requireValidToken); if (!refreshedToken.getRefreshToken() && existingToken.getRefreshToken()) { refreshedToken.setRefreshToken(existingToken.getRefreshToken()); } return refreshedToken; } register(data) { throw new Error('`register` is not supported by `NbOAuth2AuthStrategy`, use `authenticate`.'); } requestPassword(data) { throw new Error('`requestPassword` is not supported by `NbOAuth2AuthStrategy`, use `authenticate`.'); } resetPassword(data = {}) { throw new Error('`resetPassword` is not supported by `NbOAuth2AuthStrategy`, use `authenticate`.'); } logout() { return of(new NbAuthResult(true)); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.3", ngImport: i0, type: NbOAuth2AuthStrategy,