RocketChat/Rocket.Chat

View on GitHub
apps/meteor/app/custom-oauth/server/transform_helpers.js

Summary

Maintainability
A
3 hrs
Test Coverage
import { isObject } from '../../../lib/utils/isObject';

export const normalizers = {
    // Set 'id' to '_id' for any sources that provide it
    _id(identity) {
        if (identity._id && !identity.id) {
            identity.id = identity._id;
        }
    },

    // Fix for Reddit
    redit(identity) {
        if (identity.result) {
            return identity.result;
        }
    },

    // Fix WordPress-like identities having 'ID' instead of 'id'
    wordpress(identity) {
        if (identity.ID && !identity.id) {
            identity.id = identity.ID;
        }
    },

    // Fix Auth0-like identities having 'user_id' instead of 'id'
    user_id(identity) {
        if (identity.user_id && !identity.id) {
            identity.id = identity.user_id;
        }
    },

    characterid(identity) {
        if (identity.CharacterID && !identity.id) {
            identity.id = identity.CharacterID;
        }
    },

    // Fix Dataporten having 'user.userid' instead of 'id'
    dataporten(identity) {
        if (identity.user && identity.user.userid && !identity.id) {
            if (identity.user.userid_sec && identity.user.userid_sec[0]) {
                identity.id = identity.user.userid_sec[0];
            } else {
                identity.id = identity.user.userid;
            }
            identity.email = identity.user.email;
        }
    },

    // Fix for Xenforo [BD]API plugin for 'user.user_id; instead of 'id'
    xenforo(identity) {
        if (identity.user && identity.user.user_id && !identity.id) {
            identity.id = identity.user.user_id;
            identity.email = identity.user.user_email;
        }
    },

    // Fix general 'phid' instead of 'id' from phabricator
    phabricator(identity) {
        if (identity.phid && !identity.id) {
            identity.id = identity.phid;
        }
    },

    // Fix Keycloak-like identities having 'sub' instead of 'id'
    kaycloak(identity) {
        if (identity.sub && !identity.id) {
            identity.id = identity.sub;
        }
    },

    // Fix OpenShift identities where id is in 'metadata' object
    openshift(identity) {
        if (!identity.id && identity.metadata && identity.metadata.uid) {
            identity.id = identity.metadata.uid;
            identity.name = identity.fullName;
        }
    },

    // Fix general 'userid' instead of 'id' from provider
    userid(identity) {
        if (identity.userid && !identity.id) {
            identity.id = identity.userid;
        }
    },

    // Fix Nextcloud provider
    nextcloud(identity) {
        if (!identity.id && identity.ocs && identity.ocs.data && identity.ocs.data.id) {
            identity.id = identity.ocs.data.id;
            identity.name = identity.ocs.data.displayname;
            identity.email = identity.ocs.data.email;
        }
    },

    // Fix when authenticating from a meteor app with 'emails' field
    meteor(identity) {
        if (!identity.email && identity.emails && Array.isArray(identity.emails) && identity.emails.length >= 1) {
            identity.email = identity.emails[0].address ? identity.emails[0].address : undefined;
        }
    },
};

const IDENTITY_PROPNAME_FILTER = /(\.)/g;
export const renameInvalidProperties = (input) => {
    if (Array.isArray(input)) {
        return input.map(renameInvalidProperties);
    }
    if (!isObject(input)) {
        return input;
    }

    return Object.entries(input).reduce(
        (result, [name, value]) => ({
            ...result,
            [name.replace(IDENTITY_PROPNAME_FILTER, '_')]: renameInvalidProperties(value),
        }),
        {},
    );
};

export const getNestedValue = (propertyPath, source) =>
    propertyPath.split('.').reduce((prev, curr) => (prev ? prev[curr] : undefined), source);

// /^(.+)@/::email
const REGEXP_FROM_FORMULA = /^\/((?!\/::).*)\/::(.+)/;
export const getRegexpMatch = (formula, data) => {
    const regexAndPath = REGEXP_FROM_FORMULA.exec(formula);
    if (!regexAndPath) {
        return getNestedValue(formula, data);
    }
    if (regexAndPath.length !== 3) {
        throw new Error(`expected array of length 3, got ${regexAndPath.length}`);
    }

    const [, regexString, path] = regexAndPath;
    const nestedValue = getNestedValue(path, data);
    const regex = new RegExp(regexString);
    const matches = regex.exec(nestedValue);

    // regexp does not match nested value
    if (!matches) {
        return undefined;
    }

    // we only support regular expressions with a single capture group
    const [, value] = matches;

    // this could mean we return `undefined` (e.g. when capture group is empty)
    return value;
};

const templateStringRegex = /{{((?:(?!}}).)+)}}/g;
export const fromTemplate = (template, data) => {
    if (!templateStringRegex.test(template)) {
        return getNestedValue(template, data);
    }

    return template.replace(templateStringRegex, (fullMatch, match) => getRegexpMatch(match, data));
};