UNPKG

kod-temp

Version:

Workano Communications SDK

410 lines (409 loc) 14.5 kB
import { BACKEND_LDAP_USER, DEFAULT_BACKEND_USER, DETAULT_EXPIRATION, } from "../api/auth"; import getApiClient, { setCurrentServer, setApiToken, setRefreshToken, setApiClientId, setRefreshExpiration, setOnRefreshToken, setFetchOptions, setRefreshTenantId, setRefreshDomainName, setOnRefreshTokenError, getFetchOptions, setRequestTimeout, } from "../service/getApiClient"; import IssueReporter from "../service/IssueReporter"; import Workano from "./index"; import SipLine from "../domain/SipLine"; import { obfuscateToken } from "../utils/string"; export class InvalidSubscription extends Error { } export class InvalidAuthorization extends Error { } export class NoTenantIdError extends Error { } export class NoDomainNameError extends Error { } export class NoSamlRouteError extends Error { } export class SamlConfigError extends Error { } const logger = IssueReporter.loggerFor("simple-auth"); export class Auth { clientId; expiration; minSubscriptionType; authorizationName; host; session; onRefreshTokenCallback; onRefreshTokenCallbackError; onHostFromHeadersCallback; authenticated; mobile; BACKEND_WAZO; BACKEND_LDAP; usingEdgeServer; onSetUsingEdgeServer; constructor() { this.expiration = DETAULT_EXPIRATION; this.authenticated = false; this.minSubscriptionType = null; this.BACKEND_WAZO = DEFAULT_BACKEND_USER; this.BACKEND_LDAP = BACKEND_LDAP_USER; } init(clientId, expiration, minSubscriptionType, authorizationName, mobile) { this.clientId = clientId; this.expiration = expiration; this.minSubscriptionType = typeof minSubscriptionType === "undefined" ? null : minSubscriptionType; this.authorizationName = authorizationName; this.host = null; this.session = null; this.mobile = mobile || false; setApiClientId(this.clientId); setRefreshExpiration(this.expiration); setOnRefreshToken((token, session) => { logger.info("on refresh token done", { token: obfuscateToken(token), }); setApiToken(token); Workano.Websocket.updateToken(token); if (this.onRefreshTokenCallback) { this.onRefreshTokenCallback(token, session); } }); setOnRefreshTokenError((error) => { logger.error("on refresh token error", error); if (this.onRefreshTokenCallbackError) { this.onRefreshTokenCallbackError(error); } }); } setFetchOptions(options) { setFetchOptions(options); } async logIn(username, password, backend, extra) { let tenantId = null; let domainName = null; if (typeof extra === "string") { tenantId = extra; } if (extra && typeof extra === "object") { domainName = extra.domainName; } if (backend && backend !== this.BACKEND_WAZO && !tenantId && !domainName) { if (!tenantId) { throw new NoTenantIdError("No tenant id"); } if (!domainName) { throw new NoDomainNameError("No domain name"); } } this.authenticated = false; this.session = null; const rawSession = await getApiClient().auth.logIn({ username, password, backend: backend, tenantId: tenantId, domainName: domainName, expiration: this.expiration, mobile: this.mobile, }); // Used for Proxy Authentication if (rawSession) { const stackHostFromHeaders = rawSession.getHostFromHeader(); if (stackHostFromHeaders) { this.setHost(stackHostFromHeaders); if (this.onHostFromHeadersCallback) { this.onHostFromHeadersCallback(stackHostFromHeaders); } } } if (backend) { getApiClient().setRefreshBackend(backend); } if (tenantId) { getApiClient().setRefreshTenantId(tenantId); } if (domainName) { getApiClient().setRefreshDomainName(domainName); } return this._onAuthenticated(rawSession); } async samlLogIn(samlSessionId) { const rawSession = await getApiClient().auth.samlLogIn(samlSessionId); return this._onAuthenticated(rawSession); } async initiateIdpAuthentication(domain, redirectUrl) { let response; try { response = await getApiClient().auth.initiateIdpAuthentication(domain, redirectUrl); } catch (e) { logger.error("Error during IdP authentication initiation:", e.message); return; } if (!response.ok) { if (response.status === 404) { throw new NoSamlRouteError("No route found for saml sso"); } if (response.status === 500) { throw new SamlConfigError("SAML/server configuration error"); } const error = await response.json(); throw new Error(error.message || error.reason); } return response.json(); } async logInViaRefreshToken(refreshToken) { const rawSession = await getApiClient().auth.refreshToken(refreshToken, "", this.expiration, this.mobile); return this._onAuthenticated(rawSession); } async validateToken(token, refreshToken, headerUserUuid) { if (!token) { return; } if (this.usingEdgeServer && typeof headerUserUuid === "string") { logger.info("using edge server, setting user UUID header", { headerUserUuid, }); this.setHttpUserUuidHeader(headerUserUuid); } if (refreshToken) { setRefreshToken(refreshToken); } // Check if the token is valid try { const rawSession = await getApiClient().auth.authenticate(token); return this._onAuthenticated(rawSession); } catch (e) { logger.error("on validate token error", e); console.warn(e); } } async generateNewToken(refreshToken) { return getApiClient().auth.refreshToken(refreshToken, "", this.expiration); } async logout(deleteRefreshToken = true) { try { Workano.Websocket.close(true); if (this.clientId && deleteRefreshToken) { await getApiClient().auth.deleteRefreshToken(this.clientId); } } catch (e) { // Nothing to } try { if (this.session?.token) { await getApiClient().auth.logOut(this.session.token); } } catch (e) { // Nothing to } setApiToken(null); setRefreshToken(null); this.session = null; this.authenticated = false; this.usingEdgeServer = undefined; setFetchOptions({}); } setOnHostFromHeaders(callback) { this.onHostFromHeadersCallback = callback; } setOnSetUsingEdgeServer(callback) { logger.info("setting onSetUsingEdgeServer callback", { callback: typeof callback === "function", }); this.onSetUsingEdgeServer = callback; } setOnRefreshToken(callback) { this.onRefreshTokenCallback = callback; } setOnRefreshTokenError(callback) { this.onRefreshTokenCallbackError = callback; } checkAuthorizations(session, authorizationName) { if (!authorizationName) { return; } const { authorizations } = session; if (!authorizations.find((authorization) => authorization.rules.find((rule) => rule.name === authorizationName))) { throw new InvalidAuthorization(`No authorization '${authorizationName || ""}' found for your account.`); } } checkSubscription(session, minSubscriptionType) { const userSubscriptionType = session.profile?.subscriptionType || null; if (userSubscriptionType === null || userSubscriptionType <= minSubscriptionType) { const message = `Invalid subscription ${userSubscriptionType || "n/a"}, required at least ${minSubscriptionType}`; throw new InvalidSubscription(message); } } setHost(host) { this.host = host; setCurrentServer(host); } setApiToken(token) { setApiToken(token); } setRefreshToken(refreshToken) { setRefreshToken(refreshToken); } setRefreshTenantId(refreshTenantId) { console.warn("Use of `setRefreshTenantId` is deprecated, use `setRefreshDomainName` instead."); setRefreshTenantId(refreshTenantId); getApiClient().setRefreshTenantId(refreshTenantId); } setRefreshDomainName(domainName) { setRefreshDomainName(domainName); getApiClient().setRefreshDomainName(domainName); } setRequestTimeout(requestTimeout) { setRequestTimeout(requestTimeout); getApiClient().setRequestTimeout(requestTimeout); } forceRefreshToken() { return getApiClient().forceRefreshToken(); } setIsMobile(mobile) { this.mobile = mobile; } getHost() { return this.host || undefined; } getSession() { return this.session || undefined; } getFirstName() { if (!this.session || !this.session.profile) { return ""; } return this.session.profile.firstName; } getLastName() { if (!this.session || !this.session.profile) { return ""; } return this.session.profile.lastName; } setClientId(clientId) { this.clientId = clientId; setApiClientId(this.clientId); } getName() { return `${this.getFirstName()} ${this.getLastName()}`; } _getHttpUserUuidHeaders(uuid) { return { ...(getFetchOptions()?.headers || {}), "X-User-UUID": uuid, }; } setHttpUserUuidHeader(uuid) { logger.info("Setting http header user uuid", { uuid }); if (!uuid) { logger.warn("attempting to set a null value to user uuid header"); return; } const headers = this._getHttpUserUuidHeaders(uuid); setFetchOptions({ ...getFetchOptions(), headers, }); } async checkHttpUserUuidHeader(uuid) { logger.info("Checking user uuid http header", { uuid }); if (!uuid) { return; } const headers = this._getHttpUserUuidHeaders(uuid); try { const response = await getApiClient().client.head("auth/0.1/status", null, headers, (r) => r); const allowsUserUuidHeader = response.headers .get("access-control-allow-headers") ?.includes("X-User-UUID"); if (this.mobile && !allowsUserUuidHeader) { throw new Error("Server does not allow user UUID header on mobile"); } // If the previous request went well, it means that the header is accepted this.setHttpUserUuidHeader(uuid); this.usingEdgeServer = true; logger.info("Setting usingEdgeServer value to TRUE", { requestHeaders: headers, responseHeaders: response.headers, allowsUserUuidHeader, }); } catch (e) { this.usingEdgeServer = false; logger.info("Setting usingEdgeServer to FALSE", { justification: e, }); } if (typeof this.onSetUsingEdgeServer === "function") { logger.info("calling onSetUsingEdgeServer", { usingEdgeServer: this.usingEdgeServer, }); this.onSetUsingEdgeServer(this.usingEdgeServer); } } async _onAuthenticated(rawSession) { if (this.authenticated && this.session) { return this.session; } const session = rawSession; if (!session) { return null; } if (this.usingEdgeServer) { this.setHttpUserUuidHeader(session.uuid); } else if (typeof this.usingEdgeServer === "undefined") { await this.checkHttpUserUuidHeader(session.uuid); } setApiToken(session.token); if (session.refreshToken) { setRefreshToken(session.refreshToken); } try { const [profile, { wazo_version: engineVersion }] = await Promise.all([ getApiClient().confd.getUser(session.uuid), getApiClient().confd.getInfos(), ]); session.engineVersion = engineVersion; session.profile = profile; this.checkAuthorizations(session, this.authorizationName); if (this.minSubscriptionType !== null) { this.checkSubscription(session, +this.minSubscriptionType); } } catch (e) { logger.error("on authenticated error", e); // Destroy tokens when validation fails if (this.clientId) { await getApiClient().auth.deleteRefreshToken(this.clientId); } if (session) { await getApiClient().auth.logOut(session.token); } throw e; } try { const lineIds = session.profile?.lines .filter((line) => !line.endpointSccp) .map((line) => String(line.id)); const sipLines = await getApiClient().confd.getUserLinesSip(session.uuid, lineIds); session.profile.sipLines = sipLines.filter((line) => line instanceof SipLine); } catch (e) { // When an user has only a sccp line, getSipLines return a 404 } this.authenticated = true; Workano.Websocket.open(this.host, session); this.session = session; return session; } } if (!global.workanoAuthInstance) { global.workanoAuthInstance = new Auth(); } // @ts-ignore: Circular definition of import alias 'default'. export default global.workanoAuthInstance;