UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

632 lines 83.7 kB
import { Injectable, Injector, Optional } from '@angular/core'; import { BasicAuth, BearerAuthFromSessionStorage, CookieAuth, FetchClient, Realtime, TenantLoginOptionsService, TenantService, UserService } from '@c8y/client'; import { AppStateService } from '../common/ui-state.service'; import { AlertService } from '../alert/alert.service'; import { gettext } from '../i18n/gettext'; import { ApiService } from '@c8y/ngx-components/api'; import { TenantUiService } from '../common/tenant-ui.service'; import { switchMap } from 'rxjs/operators'; import { BehaviorSubject, EMPTY } from 'rxjs'; import { LocationStrategy } from '@angular/common'; import { get, isEmpty, isString } from 'lodash-es'; import { TranslateService } from '@ngx-translate/core'; import { ModalService } from '../modal/modal.service'; import { Status } from '../common/status.model'; import * as i0 from "@angular/core"; import * as i1 from "@c8y/client"; import * as i2 from "../common/ui-state.service"; import * as i3 from "../alert/alert.service"; import * as i4 from "@c8y/ngx-components/api"; import * as i5 from "../common/tenant-ui.service"; import * as i6 from "@ngx-translate/core"; import * as i7 from "../modal/modal.service"; import * as i8 from "@angular/common"; /** * Service to manage the login. */ export class LoginService { constructor(injector, client, basicAuth, cookieAuth, ui, user, tenant, realtime, alert, api, tenantUiService, tenantLoginOptionsService, translateService, modalService, location) { this.injector = injector; this.client = client; this.basicAuth = basicAuth; this.cookieAuth = cookieAuth; this.ui = ui; this.user = user; this.tenant = tenant; this.realtime = realtime; this.alert = alert; this.api = api; this.tenantUiService = tenantUiService; this.tenantLoginOptionsService = tenantLoginOptionsService; this.translateService = translateService; this.modalService = modalService; this.location = location; this.rememberMe = false; this.TOKEN_KEY = '_tcy8'; this.TFATOKEN_KEY = 'TFAToken'; this.isFirstLogin = true; this.GREEN_MIN_LENGTH_DEFAULT = 8; this.automaticLoginInProgress$ = new BehaviorSubject(false); // tslint:disable:max-line-length this.ERROR_MESSAGES = { minlength: gettext('Password must have at least 8 characters and no more than 32.'), password_missmatch: gettext('Passwords do not match.'), maxlength: gettext('Password must have at least 8 characters and no more than 32.'), password_strength: gettext('Your password is not strong enough. Please include numbers, lower and upper case characters'), remote_error: gettext('Server error occurred.'), email: gettext('Invalid email address.'), password_change: gettext('Your password is expired. Please set a new password.'), password_reset_token_expired: gettext('Password reset link expired. Please enter your email address to receive a new one.'), tfa_pin_invalid: gettext('The code you entered is invalid. Please try again.'), pattern_newPassword: this.translateService.instant(gettext('Password must have at least 8 characters and no more than 32 and can only contain letters, numbers and following symbols: {{ symbols }}'), { symbols: '`~!@#$%^&*()_|+-=?;:\'",.<>{}[]\\/' }), internationalPhoneNumber: gettext('Must be a valid phone number (only digits, spaces, slashes ("/"), dashes ("-"), and plus ("+") allowed, for example: +49 9 876 543 210).'), phone_number_error: gettext('Could not update phone number.'), pinAlreadySent: gettext('The verification code was already sent. For a new verification code, please click on the link above.'), passwordConfirm: gettext('Passwords do not match.'), tfaExpired: gettext('Two-factor authentication token expired.') }; // tslint:enable:max-line-length this.SUCCESS_MESSAGES = { password_changed: gettext('Password changed. You can now log in using new password.'), password_reset_requested: gettext('Password reset request has been sent. Please check your email.'), resend_sms: gettext('Verification code SMS resent.'), send_sms: gettext('Verification code SMS sent.') }; this.passwordStrengthSetting = { enforcePasswordStrength: false, greenMinLength: this.GREEN_MIN_LENGTH_DEFAULT, passwordStrengthValidity: false }; this.localhostRegExp = new RegExp('localhost'); this.localhostIpRegExp = new RegExp('127.0.0.1'); this.showTenantRegExp = new RegExp('showTenant'); this.autoLogout(); this.initLoginOptions(); } /** * Returns the current tenant. * @return The tenant name. */ getTenant() { return this.client.tenant; } initLoginOptions() { const loginOptions = this.ui.state.loginOptions || []; this.loginMode = this.tenantUiService.getPreferredLoginOption(loginOptions); this.oauthOptions = this.tenantUiService.getOauth2Option(loginOptions) || {}; } redirectToOauth() { const { initRequest, flowControlledByUI } = this.oauthOptions; const fullPath = `${window.location.origin}${window.location.pathname}`; const redirectUrl = encodeURIComponent(fullPath); const originUriParam = `${initRequest.includes('?') ? '&' : '?'}originUri=${redirectUrl}`; const urlObject = new URL(initRequest); if (flowControlledByUI) { this.client .fetch(`/tenant/oauth${urlObject.search}${originUriParam}`) .then(res => this.handleErrorStatusCodes(res)) .then(res => res.json()) .then((res) => (window.location.href = res.redirectTo)) .catch(ex => this.showSsoError(ex)); } else { window.location.href = `${initRequest}${originUriParam}`; } } loginBySso({ code, sessionState }) { const params = { method: 'GET', headers: { Accept: 'text/html,application/xhtml+xml' } }; let url = `/tenant/oauth?code=${encodeURIComponent(code)}`; if (sessionState) { url += `&session_state=${encodeURIComponent(sessionState)}`; } return this.client .fetch(url, params) .then(res => this.handleErrorStatusCodes(res)) .catch(ex => { this.showSsoError(ex); throw new Error(); }); } autoLogout() { const errorPattern = /invalid\scredentials.*pin.*generate/i; const isTfaExpired = data => data && typeof data.message === 'string' && errorPattern.test(data.message); this.ui.currentUser .pipe(switchMap(u => u ? this.api.hookResponse(({ response }) => response.status === 401) : EMPTY)) .subscribe(async (apiCall) => { const { response } = apiCall; let willLogout = false; if (isTfaExpired(response.data)) { willLogout = true; } else { if (typeof response.json === 'function') { const data = await response.clone().json(); if (isTfaExpired(data)) { willLogout = true; } } } if (willLogout) { this.logout(false); setTimeout(() => this.alert.danger(this.ERROR_MESSAGES.tfaExpired), 500); } }); } /** * Gets the minimal number of characters that a password should have to be considered a “green” strong one. * @return The min length for password or default value. */ async getGreenMinLength() { const { greenMinLength } = (await this.getBasicAuthLoginOption()) || { greenMinLength: null }; this.passwordStrengthSetting.greenMinLength = greenMinLength || this.GREEN_MIN_LENGTH_DEFAULT; return this.passwordStrengthSetting.greenMinLength; } /** * Checks if password strength is enforced for system * by retrieving value of `enforceStrength` property from loginOptions response * @param refresh boolean used to refresh the app state where result of loginOptions response is stored. * If false, it takes value from memory, * if true, it refresh the app state value and then retrives data. * @return boolean value, true if enforced, false otherwise. */ async getEnforcePasswordStrength(refresh) { return this.getBasicAuthLoginOption(refresh).then(loginOption => { const enforcePasswordStrength = get(loginOption, 'enforceStrength'); if (isString(enforcePasswordStrength)) { this.passwordStrengthSetting.enforcePasswordStrength = enforcePasswordStrength === 'true' ? true : false; } else { this.passwordStrengthSetting.enforcePasswordStrength = !!enforcePasswordStrength; } return this.passwordStrengthSetting.enforcePasswordStrength; }); } /** * Checks if password strength is enforced for particular tenant * by retrieving value of `strengthValidity` property from loginOptions response * @param refresh boolean used to refresh the app state where result of loginOptions response is stored. * If false, it takes value from memory, * if true, it refresh the app state value and then retrives data. * @return boolean value, true if enforced, false otherwise. */ async getPasswordStrengthValidity(refresh) { return this.getBasicAuthLoginOption(refresh).then(loginOption => { const strengthValidity = get(loginOption, 'strengthValidity'); if (isString(strengthValidity)) { this.passwordStrengthSetting.passwordStrengthValidity = strengthValidity === 'true' ? true : false; } else { this.passwordStrengthSetting.passwordStrengthValidity = !!strengthValidity; } return this.passwordStrengthSetting.passwordStrengthValidity; }); } /** * Function determines if enforced strength checks should be enabled for current tenant * based on properties retrieved from loginOptions * @param options object containing specific options: * - {refresh: true} - refreshes values of app state and returns fresh values as result of call * @return boolean value, true if strength is enforced for tenant, false otherwise. */ async getPasswordStrengthEnforced(options) { const refresh = options && options.refresh; return Promise.all([ this.getEnforcePasswordStrength(refresh), this.getPasswordStrengthValidity(refresh) ]).then(values => { const [enforcePasswordStrength, passwordStrengthValidity] = values; return enforcePasswordStrength || passwordStrengthValidity; }); } /** * Clears all backend errors. */ cleanMessages() { this.alert.clearAll(); } /** * Adds a new success message * @param successKey The key of the success message as used in SUCCESS_MESSAGES */ addSuccessMessage(successKey) { const successMessage = this.SUCCESS_MESSAGES[successKey]; if (successMessage) { this.alert.add({ text: successMessage, type: 'success', timeout: 0 }); } } /** * Returns the current strategy. Defaults to cookie, if a token * is found in local or session storage we switch to basic auth. * @returns The current auth strategy. */ getAuthStrategy() { try { const authStrategy = new BearerAuthFromSessionStorage(); console.log(`Using BearerAuthFromSessionStorage`); return authStrategy; } catch (e) { // do nothing } let authStrategy = this.cookieAuth; const token = this.getStoredToken(); const tfa = this.getStoredTfaToken(); if (token) { authStrategy = this.basicAuth; this.setCredentials({ token, tfa }, this.basicAuth); } return authStrategy; } /** * Forces the use of basic auth as strategy with this credentials. * @param credentials The credentials to use. */ useBasicAuth(credentials) { this.setCredentials(credentials, this.basicAuth); return this.basicAuth; } /** * Tries to login a user with the given credentials. * If successful, the current tenant and user is set. If not an error * is thrown. It also verifies if the user is allowed to open the * current app. * @param auth The authentication strategy used. * @param credentials The credentials to try to login. */ async login(auth = this.getAuthStrategy(), credentials) { // To ensure backward compatibility, we need to verify whether the backend supports TFA // without requiring the use of /tenant with auth: base64. The tfaSupported flag indicates // whether authentication is possible exclusively via OAI-SECURE. // TfaSupported flag should be removed during: MTM-62641 const isOAISecureAndTFAIsSupported = (this.tenantUiService.isOauthInternal(this.loginMode) && this.loginMode.tfaSupported) || false; if (isOAISecureAndTFAIsSupported && (await this.switchLoginMode(credentials))) { auth = this.cookieAuth; } else { this.client.setAuth(auth); } const tenantRes = await this.tenant.current({ withParent: true }); const tenant = tenantRes.data; if (credentials) { credentials.tenant = tenant.name; } if (!isOAISecureAndTFAIsSupported && (await this.switchLoginMode(credentials))) { auth = this.cookieAuth; } const userRes = await this.user.current(); const user = userRes.data; await this.verifyAppAccess(); const supportUserName = this.getSupportUserName(credentials); const token = this.setCredentials({ tenant: tenant.name, user: (supportUserName ? `${supportUserName}$` : '') + user.userName }, auth); if (token) { this.storeBasicAuthToken(token); } await this.authFulfilled(tenant, user, supportUserName); } /** * Saves tenant, user and support user info to the app state. * @param tenant The current tenant object. * @param user The current user object. * @param supportUserName The current support user name. */ async authFulfilled(tenant, user, supportUserName) { if (!tenant) { const { data } = await this.tenant.current({ withParent: true }); tenant = data; this.client.tenant = tenant.name; } if (!user) { const { data } = await this.user.current(); user = data; } if (!supportUserName) { supportUserName = null; } this.ui.setUser({ user, supportUserName }); this.ui.currentTenant.next(tenant); } /** * Switch the login mode to CookieAuth if the * user has configured to use it in loginOptions. * @param credentials The credentials for that login */ async switchLoginMode(credentials) { const isPasswordGrantLogin = await this.isPasswordGrantLogin(credentials); if (isPasswordGrantLogin && credentials) { const res = await this.generateOauthToken(credentials); if (!res.ok) { try { const data = await res.json(); throw { res, data }; } catch (ex) { throw ex; } } this.client.setAuth(this.cookieAuth); this.cleanLocalStorage(); this.basicAuth.logout(); } return isPasswordGrantLogin; } async generateOauthToken(credentials) { if ((await this.isPasswordGrantLogin(credentials)) && credentials) { const params = new URLSearchParams({ grant_type: 'PASSWORD', username: credentials.user, password: credentials.password, ...(credentials.tfa !== undefined && { tfa_code: credentials.tfa }) }); return await new FetchClient().fetch(this.getUrlForOauth(credentials), { method: 'POST', body: params.toString(), headers: { 'content-type': 'application/x-www-form-urlencoded;charset=UTF-8' } }); } } async isPasswordGrantLogin(credentials) { let loginMode = this.loginMode; if (this.isSupportUser(credentials)) { if (!this.managementLoginMode) { this.managementLoginMode = await this.getManagementLoginMode(); } loginMode = this.managementLoginMode; } return this.tenantUiService.isOauthInternal(loginMode); } /** * Verifies if the provided credentials use a support user to log in or not. * @param credentials Credentials to check. * @returns {boolean} Returns true if user is a support user. */ isSupportUser(credentials) { return credentials && credentials.user.includes('$'); } /** * Verifies if the tenant input field should be shown * or not. * @returns If true, show the tenant input. */ showTenant() { return !this.ui.state.loginOptions || this.isLocal() || this.isShowTenant(); } /** * Verifies if the tenant setup should be shown * or not. * @returns If true, show the tenant input. */ showTenantSetup() { return !this.ui.state.loginOptions && !this.isLocal(); } /** * Logs the user out * @param reload If set to false, the page will not reload */ async logout(reload = true) { let resData = null; try { const [, cookieRes] = await this.reset(); resData = await cookieRes.json(); } catch (ex) { this.alert.removeLastDanger(); } finally { if (resData && resData.url) { this.redirect(resData.url); } else if (reload) { this.location.replaceState({}, '', '', ''); window.location.reload(); } } } /** * Resets the stored auth-data */ async reset() { this.cleanLocalStorage(); this.cleanSessionStorage(); this.realtime.disconnect(); this.ui.currentUser.next(null); return Promise.all([this.basicAuth.logout(), this.cookieAuth.logout()]); } /** * Saves the TFA token to local or session storage. * @param tfaToken The tfa token to save. * @param storage The storage to use (local or session). */ saveTFAToken(tfaToken, storage) { storage.setItem(this.TFATOKEN_KEY, tfaToken); } /** * Request the manifest -> on 401 user has no access to that application * and we throw the error up to the login form. */ async verifyAppAccess() { try { await this.ui.loadManifest(); } catch (ex) { if (!(ex.res && ex.res.status === 404 && this.isLocal())) { throw ex; } } } redirectToDomain(domain) { const originUrl = new URL(window.location.href); const redirectUrl = originUrl.href.replace(originUrl.hostname, domain); window.location.href = redirectUrl; } showSsoError(error) { const body = error ? this.translateService.instant(gettext('<p><strong>The following error was returned from the external authentication service:</strong></p><p><code>{{ error }}</code></p>.'), { error }) : gettext('SSO login failed. Contact the administrator.'); this.modalService.acknowledge(gettext('Login error'), body, Status.DANGER, gettext('OK')); } /** * Sets the tenant to the client and updates the credentials on the * auth strategy. * @param credentials The name of the tenant. * @param authStrategy The authentication strategy used. * @return Returns the token if basic auth, otherwise undefined. */ setCredentials(credentials, authStrategy) { if (credentials.tenant) { this.client.tenant = credentials.tenant; } // Check if a token is already set (case for support user login) // if yes -> we just need to update the user, and reuse the token // of the support user. // Therefore we need to pass user and tenant, to get // just the stored token and nothing else (see BasicAuth.ts:31). const token = this.basicAuth.updateCredentials({ tenant: credentials.tenant, user: credentials.user }); const newCredentials = { token, ...credentials }; return authStrategy.updateCredentials(newCredentials); } /** * Verifies if the current user is a developer or not. * Running on localhost means development mode. */ isLocal() { const hostname = window.location.hostname; return this.localhostIpRegExp.test(hostname) || this.localhostRegExp.test(hostname); } /** * Save the token to local or session storage. * @param token The token to save. * @param storage The storage to use (local or session). */ saveToken(token, storage) { storage.setItem(this.TOKEN_KEY, token); } storeBasicAuthToken(token) { this.saveToken(token, sessionStorage); if (this.rememberMe) { this.saveToken(token, localStorage); } } cleanLocalStorage() { localStorage.removeItem(this.TOKEN_KEY); localStorage.removeItem(this.TFATOKEN_KEY); } cleanSessionStorage() { sessionStorage.removeItem(this.TOKEN_KEY); sessionStorage.removeItem(this.TFATOKEN_KEY); } isShowTenant() { return this.showTenantRegExp.test(window.location.href); } redirect(url) { window.location.href = url; } async getBasicAuthLoginOption(refresh) { if (refresh) { await this.ui.refreshLoginOptions(); } const loginOptions = this.ui.state.loginOptions || []; const basicAuthLoginOption = loginOptions.find(({ type }) => type === 'BASIC'); return Promise.resolve(basicAuthLoginOption); } /** * Gets support user name from credentials. * @param credentials Credentials object (defaults to the stored one). * @returns Support user name. */ getSupportUserName(credentials = this.getStoredCredentials()) { if (!credentials) { return null; } const supportUserName = credentials.user.match(/^(.+\/)?((.+)\$)?(.+)?$/)[3]; return supportUserName; } /** * Gets credentials object from the stored token. * @returns Credentials object. */ getStoredCredentials() { const token = this.getStoredToken(); if (!token) { return null; } return this.decodeToken(token); } /** * Gets stored token from local storage or session storage. * @returns Stored token. */ getStoredToken() { return localStorage.getItem(this.TOKEN_KEY) || sessionStorage.getItem(this.TOKEN_KEY); } /** * Gets stored TFA token from local storage or session storage. * @returns Stored TFA token. */ getStoredTfaToken() { return localStorage.getItem(this.TFATOKEN_KEY) || sessionStorage.getItem(this.TFATOKEN_KEY); } /** * Decodes token to credentials object. * @param token Token to decode. * @returns Credentials object. */ decodeToken(token) { const decoded = decodeURIComponent(escape(window.atob(token))); const split = decoded.match(/(([^/]*)\/)?([^/:]+):(.+)/); return { tenant: split[2], user: split[3], password: split[4] }; } getUrlForOauth(credentials) { if (isEmpty(credentials.tenant) && this.loginMode.initRequest) { const urlParams = new URLSearchParams(this.loginMode.initRequest.split('?').pop()); credentials.tenant = urlParams.get('tenant_id'); } return !isEmpty(credentials.tenant) ? `tenant/oauth?tenant_id=${credentials.tenant}` : `tenant/oauth`; } async getManagementLoginMode() { const managementLoginOptions = (await this.tenantLoginOptionsService.listForManagement()).data; return this.tenantUiService.getPreferredLoginOption(managementLoginOptions); } async handleErrorStatusCodes(response) { if (response.status >= 400) { const data = await response.json(); const error = data.message || data.error_description || data.error; throw error; } return response; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: LoginService, deps: [{ token: i0.Injector }, { token: i1.FetchClient }, { token: i1.BasicAuth }, { token: i1.CookieAuth }, { token: i2.AppStateService }, { token: i1.UserService }, { token: i1.TenantService }, { token: i1.Realtime }, { token: i3.AlertService }, { token: i4.ApiService }, { token: i5.TenantUiService }, { token: i1.TenantLoginOptionsService }, { token: i6.TranslateService }, { token: i7.ModalService }, { token: i8.LocationStrategy, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: LoginService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: LoginService, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: i0.Injector }, { type: i1.FetchClient }, { type: i1.BasicAuth }, { type: i1.CookieAuth }, { type: i2.AppStateService }, { type: i1.UserService }, { type: i1.TenantService }, { type: i1.Realtime }, { type: i3.AlertService }, { type: i4.ApiService }, { type: i5.TenantUiService }, { type: i1.TenantLoginOptionsService }, { type: i6.TranslateService }, { type: i7.ModalService }, { type: i8.LocationStrategy, decorators: [{ type: Optional }] }] }); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibG9naW4uc2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL2NvcmUvbG9naW4vbG9naW4uc2VydmljZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFFLFFBQVEsRUFBRSxRQUFRLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFDL0QsT0FBTyxFQUNMLFNBQVMsRUFDVCw0QkFBNEIsRUFDNUIsVUFBVSxFQUNWLFdBQVcsRUFLWCxRQUFRLEVBQ1IseUJBQXlCLEVBQ3pCLGFBQWEsRUFDYixXQUFXLEVBQ1osTUFBTSxhQUFhLENBQUM7QUFDckIsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBQzdELE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUN0RCxPQUFPLEVBQUUsT0FBTyxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDMUMsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQ3JELE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUM5RCxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFDM0MsT0FBTyxFQUFFLGVBQWUsRUFBRSxLQUFLLEVBQUUsTUFBTSxNQUFNLENBQUM7QUFDOUMsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDbkQsT0FBTyxFQUFFLEdBQUcsRUFBRSxPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sV0FBVyxDQUFDO0FBQ25ELE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQ3ZELE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUN0RCxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sd0JBQXdCLENBQUM7Ozs7Ozs7Ozs7QUFHaEQ7O0dBRUc7QUFFSCxNQUFNLE9BQU8sWUFBWTtJQStE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