Files
kami-parse-server/src/Adapters/Auth/index.js

265 lines
7.5 KiB
JavaScript
Executable File

import loadAdapter from '../AdapterLoader';
import Parse from 'parse/node';
import AuthAdapter from './AuthAdapter';
const apple = require('./apple');
const digits = require('./twitter'); // digits tokens are validated by twitter
const facebook = require('./facebook');
import gcenter from './gcenter';
import github from './github';
const google = require('./google');
import gpgames from './gpgames';
import instagram from './instagram';
const janraincapture = require('./janraincapture');
const janrainengage = require('./janrainengage');
const keycloak = require('./keycloak');
const ldap = require('./ldap');
import line from './line';
import linkedin from './linkedin';
const meetup = require('./meetup');
import mfa from './mfa';
import microsoft from './microsoft';
import oauth2 from './oauth2';
const phantauth = require('./phantauth');
import qq from './qq';
import spotify from './spotify';
import twitter from './twitter';
const vkontakte = require('./vkontakte');
import wechat from './wechat';
import weibo from './weibo';
const anonymous = {
validateAuthData: () => {
return Promise.resolve();
},
validateAppId: () => {
return Promise.resolve();
},
};
const providers = {
apple,
gcenter,
gpgames,
facebook,
instagram,
linkedin,
meetup,
mfa,
google,
github,
twitter,
spotify,
anonymous,
digits,
janrainengage,
janraincapture,
line,
vkontakte,
qq,
wechat,
weibo,
phantauth,
microsoft,
keycloak,
ldap,
};
// Indexed auth policies
const authAdapterPolicies = {
default: true,
solo: true,
additional: true,
};
function authDataValidator(provider, adapter, appIds, options) {
return async function (authData, req, user, requestObject) {
if (appIds && typeof adapter.validateAppId === 'function') {
await Promise.resolve(adapter.validateAppId(appIds, authData, options, requestObject));
}
if (
adapter.policy &&
!authAdapterPolicies[adapter.policy] &&
typeof adapter.policy !== 'function'
) {
throw new Parse.Error(
Parse.Error.OTHER_CAUSE,
'AuthAdapter policy is not configured correctly. The value must be either "solo", "additional", "default" or undefined (will be handled as "default")'
);
}
if (typeof adapter.validateAuthData === 'function') {
return adapter.validateAuthData(authData, options, requestObject);
}
if (
typeof adapter.validateSetUp !== 'function' ||
typeof adapter.validateLogin !== 'function' ||
typeof adapter.validateUpdate !== 'function'
) {
throw new Parse.Error(
Parse.Error.OTHER_CAUSE,
'Adapter is not configured. Implement either validateAuthData or all of the following: validateSetUp, validateLogin and validateUpdate'
);
}
// When masterKey is detected, we should trigger a logged in user
const isLoggedIn =
(req.auth.user && user && req.auth.user.id === user.id) || (user && req.auth.isMaster);
let hasAuthDataConfigured = false;
if (user && user.get('authData') && user.get('authData')[provider]) {
hasAuthDataConfigured = true;
}
if (isLoggedIn) {
// User is updating their authData
if (hasAuthDataConfigured) {
return {
method: 'validateUpdate',
validator: () => adapter.validateUpdate(authData, options, requestObject),
};
}
// Set up if the user does not have the provider configured
return {
method: 'validateSetUp',
validator: () => adapter.validateSetUp(authData, options, requestObject),
};
}
// Not logged in and authData is configured on the user
if (hasAuthDataConfigured) {
return {
method: 'validateLogin',
validator: () => adapter.validateLogin(authData, options, requestObject),
};
}
// User not logged in and the provider is not set up, for example when a new user
// signs up or an existing user uses a new auth provider
return {
method: 'validateSetUp',
validator: () => adapter.validateSetUp(authData, options, requestObject),
};
};
}
function loadAuthAdapter(provider, authOptions) {
// providers are auth providers implemented by default
let defaultAdapter = providers[provider];
// authOptions can contain complete custom auth adapters or
// a default auth adapter like Facebook
const providerOptions = authOptions[provider];
if (
providerOptions &&
Object.prototype.hasOwnProperty.call(providerOptions, 'oauth2') &&
providerOptions['oauth2'] === true
) {
defaultAdapter = oauth2;
}
// Default provider not found and a custom auth provider was not provided
if (!defaultAdapter && !providerOptions) {
return;
}
const adapter =
defaultAdapter instanceof AuthAdapter ? defaultAdapter : Object.assign({}, defaultAdapter);
const keys = [
'validateAuthData',
'validateAppId',
'validateSetUp',
'validateLogin',
'validateUpdate',
'challenge',
'validateOptions',
'policy',
'afterFind',
];
const defaultAuthAdapter = new AuthAdapter();
keys.forEach(key => {
const existing = adapter?.[key];
if (
existing &&
typeof existing === 'function' &&
existing.toString() === defaultAuthAdapter[key].toString()
) {
adapter[key] = null;
}
});
const appIds = providerOptions ? providerOptions.appIds : undefined;
// Try the configuration methods
if (providerOptions) {
const optionalAdapter = loadAdapter(providerOptions, undefined, providerOptions);
if (optionalAdapter) {
keys.forEach(key => {
if (optionalAdapter[key]) {
adapter[key] = optionalAdapter[key];
}
});
}
}
if (adapter.validateOptions) {
adapter.validateOptions(providerOptions);
}
return { adapter, appIds, providerOptions };
}
module.exports = function (authOptions = {}, enableAnonymousUsers = true) {
let _enableAnonymousUsers = enableAnonymousUsers;
const setEnableAnonymousUsers = function (enable) {
_enableAnonymousUsers = enable;
};
// To handle the test cases on configuration
const getValidatorForProvider = function (provider) {
if (provider === 'anonymous' && !_enableAnonymousUsers) {
return { validator: undefined };
}
const authAdapter = loadAuthAdapter(provider, authOptions);
if (!authAdapter) { return; }
const { adapter, appIds, providerOptions } = authAdapter;
return { validator: authDataValidator(provider, adapter, appIds, providerOptions), adapter };
};
const runAfterFind = async (req, authData) => {
if (!authData) {
return;
}
const adapters = Object.keys(authData);
await Promise.all(
adapters.map(async provider => {
const authAdapter = getValidatorForProvider(provider);
if (!authAdapter) {
return;
}
const { adapter, providerOptions } = authAdapter;
const afterFind = adapter.afterFind;
if (afterFind && typeof afterFind === 'function') {
const requestObject = {
ip: req.config.ip,
user: req.auth.user,
master: req.auth.isMaster,
};
const result = afterFind.call(
adapter,
authData[provider],
providerOptions,
requestObject,
);
if (result) {
authData[provider] = result;
}
}
})
);
};
return Object.freeze({
getValidatorForProvider,
setEnableAnonymousUsers,
runAfterFind,
});
};
module.exports.loadAuthAdapter = loadAuthAdapter;