kod-temp
Version:
Workano Communications SDK
410 lines (409 loc) • 14.5 kB
JavaScript
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;