cumulocity-cypress
Version:
Cypress commands for Cumulocity IoT
224 lines (223 loc) • 7.19 kB
JavaScript
/// <reference types="cypress" />
import _ from "lodash";
import { BasicAuth, BearerAuth, } from "@c8y/client";
import { normalizeBaseUrl } from "./url";
import { get_i } from "./util";
export const C8yPactAuthObjectKeys = [
"userAlias",
"user",
"type",
];
/**
* Checks if the given object is a C8yAuthOptions.
*
* @param obj The object to check.
* @param options Options to check for additional properties.
* @returns True if the object is a C8yAuthOptions, false otherwise.
*/
export function isAuthOptions(obj) {
return (_.isObjectLike(obj) &&
(("user" in obj && "password" in obj) || "token" in obj));
}
// new function to convert C8yAuthOptions to IAuthentication
export function toC8yAuthentication(obj) {
if (!obj || !_.isObjectLike(obj)) {
return undefined;
}
if (_.get(obj, "getFetchOptions")) {
return obj;
}
if (!isAuthOptions(obj)) {
return undefined;
}
if (obj.token) {
return new BearerAuth(obj.token);
}
else if (obj.user && obj.password) {
return new BasicAuth({
user: obj.user,
password: obj.password,
tenant: obj.tenant,
});
}
return undefined;
}
// map from case insensitive auth type to C8yAuthOptionType
export function getAuthType(auth) {
const type = _.isString(auth)
? auth.toLowerCase()
: auth?.type?.toLowerCase();
if (type === "bearerauth") {
return "BearerAuth";
}
if (type === "basicauth") {
return "BasicAuth";
}
if (type === "cookieauth") {
return "CookieAuth";
}
return undefined;
}
export function hasAuthentication(client) {
if (!client)
return false;
const fetchClient = _.get(client, "_client.core") ?? _.get(client, "core") ?? client;
const getFetchOptionsFn = _.get(fetchClient, "getFetchOptions");
if (_.isFunction(getFetchOptionsFn)) {
const options = getFetchOptionsFn.apply(fetchClient);
if (!options)
return false;
if (get_i(options, "headers.X-XSRF-TOKEN"))
return true;
if (get_i(options, "headers.authorization"))
return true;
}
if (_.get(fetchClient, "_auth"))
return true;
return false;
}
export function toPactAuthObject(obj) {
return _.pick(obj, C8yPactAuthObjectKeys);
}
export function isPactAuthObject(obj) {
return (_.isObjectLike(obj) &&
("user" in obj || "token" in obj) &&
("userAlias" in obj || "type" in obj || "token" in obj) &&
Object.keys(obj).every((key) => ["token", ...C8yPactAuthObjectKeys].includes(key)));
}
export function normalizeAuthHeaders(headers) {
// required to fix inconsistencies between c8yclient and interceptions
// using lowercase and uppercase. fix here.
const xsrfTokenHeader = Object.keys(headers || {}).find((key) => key.toLowerCase() === "x-xsrf-token");
const authorizationHeader = Object.keys(headers || {}).find((key) => key.toLowerCase() === "authorization");
if (xsrfTokenHeader && xsrfTokenHeader !== "X-XSRF-TOKEN") {
headers["X-XSRF-TOKEN"] = headers[xsrfTokenHeader];
delete headers[xsrfTokenHeader];
}
if (authorizationHeader && authorizationHeader !== "Authorization") {
headers["Authorization"] = headers[authorizationHeader];
delete headers[authorizationHeader];
}
return headers;
}
export function getAuthOptionsFromEnv(env) {
if (env == null || !_.isObjectLike(env)) {
return undefined;
}
// check first environment variables
const jwtToken = env["C8Y_TOKEN"];
let tokenAuth = undefined;
try {
const authFromToken = getAuthOptionsFromJWT(jwtToken);
if (authFromToken) {
tokenAuth = authWithTenant(env, authFromToken);
}
}
catch {
// ignore errors from extractTokensFromJWT
// this is expected if the token is not a valid JWT
}
const user = env[`C8Y_USERNAME`] ?? env[`C8Y_USER`];
const password = env[`C8Y_PASSWORD`];
let basicAuth = undefined;
if (!_.isEmpty(user) && !_.isEmpty(password)) {
basicAuth = authWithTenant(env, {
user,
password,
});
}
if (!tokenAuth && !basicAuth) {
return undefined;
}
return { ...(basicAuth ?? {}), ...(tokenAuth ?? {}) };
}
export function authWithTenant(env, options) {
if (env == null || !_.isObjectLike(env)) {
return options;
}
const tenant = env[`C8Y_TENANT`];
if (tenant && !options?.tenant) {
_.extend(options, { tenant });
}
return options;
}
export function getAuthOptionsFromBasicAuthHeader(authHeader) {
if (!authHeader ||
!_.isString(authHeader) ||
!authHeader.startsWith("Basic ")) {
return undefined;
}
const base64Credentials = authHeader.slice("Basic ".length);
const credentials = decodeBase64(base64Credentials);
const components = credentials.split(":");
if (!components || components.length < 2) {
return undefined;
}
return { user: components[0], password: components.slice(1).join(":") };
}
/**
* Extracts the authentication options from a JWT token.
* @param jwtToken The JWT token to extract the authentication options from.
* @returns The extracted authentication options.
*/
export function getAuthOptionsFromJWT(jwtToken) {
try {
const payload = JSON.parse(atob(jwtToken.split(".")[1]));
// Remove all characters not valid in JWT tokens (base64url: A-Z, a-z, 0-9, -, _, .)
const cleanedToken = jwtToken?.replace(/[^A-Za-z0-9\-_.]/g, "");
return {
token: cleanedToken,
xsrfToken: payload.xsrfToken,
tenant: payload.ten,
user: payload.sub,
baseUrl: normalizeBaseUrl(payload.aud ?? payload.iss),
};
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to decode JWT token: ${message}`);
}
}
/**
* Extracts the tenant from the basic auth object.
* @param auth The basic auth object containing the user property.
* @returns The tenant or undefined if not found.
*/
export function tenantFromBasicAuth(auth) {
if (_.isString(auth)) {
auth = { user: auth };
}
if (!auth || !_.isObjectLike(auth) || !auth.user)
return undefined;
const components = auth.user.split("/");
if (!components ||
components.length < 2 ||
_.isEmpty(components[1]) ||
_.isEmpty(components[0]))
return undefined;
return components[0];
}
export function encodeBase64(str) {
if (!str)
return "";
let encoded;
if (typeof Buffer !== "undefined") {
encoded = Buffer.from(str).toString("base64");
}
else {
encoded = btoa(str);
}
return encoded;
}
export function decodeBase64(base64) {
if (!base64)
return "";
let decoded;
if (typeof Buffer !== "undefined") {
decoded = Buffer.from(base64, "base64").toString("utf-8");
}
else {
decoded = atob(base64);
}
return decoded;
}