refactor: `Service Configuration` async on server (#28813)

pull/28823/head
Kevin Aleman 3 years ago committed by GitHub
parent 806072839d
commit d941e032a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      apps/meteor/app/api/server/v1/settings.ts
  2. 8
      apps/meteor/app/apple/server/appleOauthRegisterService.ts
  3. 12
      apps/meteor/app/cas/server/cas_rocketchat.js
  4. 8
      apps/meteor/app/custom-oauth/client/custom_oauth_client.js
  5. 4
      apps/meteor/app/custom-oauth/server/custom_oauth_server.js
  6. 38
      apps/meteor/app/dolphin/lib/common.js
  7. 43
      apps/meteor/app/lib/server/functions/getAvatarSuggestionForUser.ts
  8. 2
      apps/meteor/app/lib/server/methods/refreshOAuthService.ts
  9. 2
      apps/meteor/app/lib/server/oauth/oauth.js
  10. 228
      apps/meteor/app/lib/server/startup/oAuthServicesUpdate.js
  11. 4
      apps/meteor/app/meteor-accounts-saml/client/saml_client.js
  12. 46
      apps/meteor/app/meteor-accounts-saml/server/lib/settings.ts
  13. 6
      apps/meteor/app/wordpress/lib/common.js
  14. 2
      apps/meteor/server/services/meteor/service.ts
  15. 2
      apps/meteor/server/startup/migrations/v267.ts

@ -70,8 +70,8 @@ API.v1.addRoute(
'settings.oauth',
{ authRequired: false },
{
get() {
const oAuthServicesEnabled = ServiceConfiguration.configurations.find({}, { fields: { secret: 0 } }).fetch();
async get() {
const oAuthServicesEnabled = await ServiceConfiguration.configurations.find({}, { fields: { secret: 0 } }).fetchAsync();
return API.v1.success({
services: oAuthServicesEnabled.map((service) => {
@ -213,9 +213,9 @@ API.v1.addRoute(
'service.configurations',
{ authRequired: false },
{
get() {
async get() {
return API.v1.success({
configurations: ServiceConfiguration.configurations.find({}, { fields: { secret: 0 } }).fetch(),
configurations: await ServiceConfiguration.configurations.find({}, { fields: { secret: 0 } }).fetchAsync(),
});
},
},

@ -27,16 +27,16 @@ settings.watchMultiple(
'Accounts_OAuth_Apple_iss',
'Accounts_OAuth_Apple_kid',
],
([enabled, clientId, serverSecret, iss, kid]) => {
async ([enabled, clientId, serverSecret, iss, kid]) => {
if (!enabled) {
return ServiceConfiguration.configurations.remove({
return ServiceConfiguration.configurations.removeAsync({
service: 'apple',
});
}
// if everything is empty but Apple login is enabled, don't show the login button
if (!clientId && !serverSecret && !iss && !kid) {
ServiceConfiguration.configurations.upsert(
await ServiceConfiguration.configurations.upsertAsync(
{
service: 'apple',
},
@ -72,7 +72,7 @@ settings.watchMultiple(
serverSecret as string,
);
ServiceConfiguration.configurations.upsert(
await ServiceConfiguration.configurations.upsertAsync(
{
service: 'apple',
},

@ -48,12 +48,12 @@ Meteor.startup(function () {
let timer;
function updateServices(/* record*/) {
async function updateServices(/* record*/) {
if (typeof timer !== 'undefined') {
Meteor.clearTimeout(timer);
}
timer = Meteor.setTimeout(function () {
timer = Meteor.setTimeout(async function () {
const data = {
// These will pe passed to 'node-cas' as options
enabled: settings.get('CAS_enabled'),
@ -71,14 +71,14 @@ function updateServices(/* record*/) {
// Either register or deregister the CAS login service based upon its configuration
if (data.enabled) {
logger.info('Enabling CAS login service');
ServiceConfiguration.configurations.upsert({ service: 'cas' }, { $set: data });
await ServiceConfiguration.configurations.upsertAsync({ service: 'cas' }, { $set: data });
} else {
logger.info('Disabling CAS login service');
ServiceConfiguration.configurations.remove({ service: 'cas' });
await ServiceConfiguration.configurations.removeAsync({ service: 'cas' });
}
}, 2000);
}
settings.watchByRegex(/^CAS_.+/, (key, value) => {
updateServices(value);
settings.watchByRegex(/^CAS_.+/, async (key, value) => {
await updateServices(value);
});

@ -59,7 +59,7 @@ export class CustomOAuth {
configureLogin() {
const loginWithService = `loginWith${capitalize(String(this.name || ''))}`;
Meteor[loginWithService] = (options, callback) => {
Meteor[loginWithService] = async (options, callback) => {
// support a callback without options
if (!callback && typeof options === 'function') {
callback = options;
@ -67,18 +67,18 @@ export class CustomOAuth {
}
const credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback);
this.requestCredential(options, credentialRequestCompleteCallback);
await this.requestCredential(options, credentialRequestCompleteCallback);
};
}
requestCredential(options, credentialRequestCompleteCallback) {
async requestCredential(options, credentialRequestCompleteCallback) {
// support both (options, callback) and (callback).
if (!credentialRequestCompleteCallback && typeof options === 'function') {
credentialRequestCompleteCallback = options;
options = {};
}
const config = ServiceConfiguration.configurations.findOne({ service: this.name });
const config = await ServiceConfiguration.configurations.findOneAsync({ service: this.name });
if (!config) {
if (credentialRequestCompleteCallback) {
credentialRequestCompleteCallback(new ServiceConfiguration.ConfigError());

@ -100,8 +100,8 @@ export class CustomOAuth {
}
}
getAccessToken(query) {
const config = ServiceConfiguration.configurations.findOne({ service: this.name });
async getAccessToken(query) {
const config = await ServiceConfiguration.configurations.findOneAsync({ service: this.name });
if (!config) {
throw new ServiceConfiguration.ConfigError();
}

@ -29,28 +29,28 @@ function DolphinOnCreateUser(options, user) {
}
if (Meteor.isServer) {
Meteor.startup(() =>
Meteor.startup(async () => {
settings.watch('Accounts_OAuth_Dolphin_URL', (value) => {
config.serverURL = value;
return Dolphin.configure(config);
}),
);
if (settings.get('Accounts_OAuth_Dolphin_URL')) {
const data = {
buttonLabelText: settings.get('Accounts_OAuth_Dolphin_button_label_text'),
buttonColor: settings.get('Accounts_OAuth_Dolphin_button_color'),
buttonLabelColor: settings.get('Accounts_OAuth_Dolphin_button_label_color'),
clientId: settings.get('Accounts_OAuth_Dolphin_id'),
secret: settings.get('Accounts_OAuth_Dolphin_secret'),
serverURL: settings.get('Accounts_OAuth_Dolphin_URL'),
loginStyle: settings.get('Accounts_OAuth_Dolphin_login_style'),
};
ServiceConfiguration.configurations.upsert({ service: 'dolphin' }, { $set: data });
}
callbacks.add('beforeCreateUser', DolphinOnCreateUser, callbacks.priority.HIGH, 'dolphin');
});
if (settings.get('Accounts_OAuth_Dolphin_URL')) {
const data = {
buttonLabelText: settings.get('Accounts_OAuth_Dolphin_button_label_text'),
buttonColor: settings.get('Accounts_OAuth_Dolphin_button_color'),
buttonLabelColor: settings.get('Accounts_OAuth_Dolphin_button_label_color'),
clientId: settings.get('Accounts_OAuth_Dolphin_id'),
secret: settings.get('Accounts_OAuth_Dolphin_secret'),
serverURL: settings.get('Accounts_OAuth_Dolphin_URL'),
loginStyle: settings.get('Accounts_OAuth_Dolphin_login_style'),
};
await ServiceConfiguration.configurations.upsertAsync({ service: 'dolphin' }, { $set: data });
}
callbacks.add('beforeCreateUser', DolphinOnCreateUser, callbacks.priority.HIGH, 'dolphin');
});
} else {
Meteor.startup(() =>
Tracker.autorun(function () {

@ -71,22 +71,33 @@ const avatarProviders = {
}
},
customOAuth(user: IUser) {
const avatars = [];
for (const service in user.services) {
if (user.services[service as keyof typeof user.services]._OAuthCustom) {
const services = ServiceConfiguration.configurations.find({ service }, { fields: { secret: 0 } }).fetch();
if (services.length > 0) {
if (user.services[service as keyof typeof user.services].avatarUrl) {
avatars.push({
service,
url: user.services[service as keyof typeof user.services].avatarUrl,
});
async customOAuth(user: IUser) {
const avatars: { service: string; url: string }[] = [];
if (!user.services) {
return avatars;
}
await Promise.all(
Object.keys(user.services).map(async (service) => {
if (!user.services) {
return;
}
if (user.services[service as keyof typeof user.services]._OAuthCustom) {
const services = await ServiceConfiguration.configurations.find({ service }, { fields: { secret: 0 } }).fetchAsync();
if (services.length > 0) {
if (user.services[service as keyof typeof user.services].avatarUrl) {
avatars.push({
service,
url: user.services[service as keyof typeof user.services].avatarUrl,
});
}
}
}
}
}
}),
);
return avatars;
},
@ -131,8 +142,8 @@ export async function getAvatarSuggestionForUser(
const avatars = [];
for (const avatarProvider of Object.values(avatarProviders)) {
const avatar = avatarProvider(user);
for await (const avatarProvider of Object.values(avatarProviders)) {
const avatar = await avatarProvider(user);
if (avatar) {
if (Array.isArray(avatar)) {
avatars.push(...avatar);

@ -29,7 +29,7 @@ Meteor.methods<ServerMethods>({
});
}
ServiceConfiguration.configurations.remove({});
await ServiceConfiguration.configurations.removeAsync({});
await Settings.update({ _id: /^(Accounts_OAuth_|SAML_|CAS_).+/ }, { $set: { _updatedAt: new Date() } }, { multi: true });
},

@ -35,7 +35,7 @@ Accounts.registerLoginHandler(async function (options) {
}
// Make sure we're configured
if (!ServiceConfiguration.configurations.findOne({ service: options.serviceName })) {
if (!(await ServiceConfiguration.configurations.findOneAsync({ service: options.serviceName }))) {
throw new ServiceConfiguration.ConfigError();
}

@ -1,4 +1,3 @@
import { Meteor } from 'meteor/meteor';
import { ServiceConfiguration } from 'meteor/service-configuration';
import _ from 'underscore';
@ -9,131 +8,130 @@ import { addOAuthService } from '../functions/addOAuthService';
const logger = new Logger('rocketchat:lib');
function _OAuthServicesUpdate() {
async function _OAuthServicesUpdate() {
const services = settings.getByRegexp(/^(Accounts_OAuth_|Accounts_OAuth_Custom-)[a-z0-9_]+$/i);
services
.filter(([, value]) => typeof value === 'boolean')
.forEach(([key, value]) => {
logger.debug({ oauth_updated: key });
let serviceName = key.replace('Accounts_OAuth_', '');
if (serviceName === 'Meteor') {
serviceName = 'meteor-developer';
}
const filteredServices = services.filter(([, value]) => typeof value === 'boolean');
for await (const [key, value] of filteredServices) {
logger.debug({ oauth_updated: key });
let serviceName = key.replace('Accounts_OAuth_', '');
if (serviceName === 'Meteor') {
serviceName = 'meteor-developer';
}
if (/Accounts_OAuth_Custom-/.test(key)) {
serviceName = key.replace('Accounts_OAuth_Custom-', '');
}
if (value === true) {
const data = {
clientId: settings.get(`${key}_id`),
secret: settings.get(`${key}_secret`),
};
if (/Accounts_OAuth_Custom-/.test(key)) {
serviceName = key.replace('Accounts_OAuth_Custom-', '');
data.custom = true;
data.clientId = settings.get(`${key}-id`);
data.secret = settings.get(`${key}-secret`);
data.serverURL = settings.get(`${key}-url`);
data.tokenPath = settings.get(`${key}-token_path`);
data.identityPath = settings.get(`${key}-identity_path`);
data.authorizePath = settings.get(`${key}-authorize_path`);
data.scope = settings.get(`${key}-scope`);
data.accessTokenParam = settings.get(`${key}-access_token_param`);
data.buttonLabelText = settings.get(`${key}-button_label_text`);
data.buttonLabelColor = settings.get(`${key}-button_label_color`);
data.loginStyle = settings.get(`${key}-login_style`);
data.buttonColor = settings.get(`${key}-button_color`);
data.tokenSentVia = settings.get(`${key}-token_sent_via`);
data.identityTokenSentVia = settings.get(`${key}-identity_token_sent_via`);
data.keyField = settings.get(`${key}-key_field`);
data.usernameField = settings.get(`${key}-username_field`);
data.emailField = settings.get(`${key}-email_field`);
data.nameField = settings.get(`${key}-name_field`);
data.avatarField = settings.get(`${key}-avatar_field`);
data.rolesClaim = settings.get(`${key}-roles_claim`);
data.groupsClaim = settings.get(`${key}-groups_claim`);
data.channelsMap = settings.get(`${key}-groups_channel_map`);
data.channelsAdmin = settings.get(`${key}-channels_admin`);
data.mergeUsers = settings.get(`${key}-merge_users`);
data.mapChannels = settings.get(`${key}-map_channels`);
data.mergeRoles = settings.get(`${key}-merge_roles`);
data.rolesToSync = settings.get(`${key}-roles_to_sync`);
data.showButton = settings.get(`${key}-show_button`);
new CustomOAuth(serviceName.toLowerCase(), {
serverURL: data.serverURL,
tokenPath: data.tokenPath,
identityPath: data.identityPath,
authorizePath: data.authorizePath,
scope: data.scope,
loginStyle: data.loginStyle,
tokenSentVia: data.tokenSentVia,
identityTokenSentVia: data.identityTokenSentVia,
keyField: data.keyField,
usernameField: data.usernameField,
emailField: data.emailField,
nameField: data.nameField,
avatarField: data.avatarField,
rolesClaim: data.rolesClaim,
groupsClaim: data.groupsClaim,
mapChannels: data.mapChannels,
channelsMap: data.channelsMap,
channelsAdmin: data.channelsAdmin,
mergeUsers: data.mergeUsers,
mergeRoles: data.mergeRoles,
rolesToSync: data.rolesToSync,
accessTokenParam: data.accessTokenParam,
showButton: data.showButton,
});
}
if (serviceName === 'Facebook') {
data.appId = data.clientId;
delete data.clientId;
}
if (serviceName === 'Twitter') {
data.consumerKey = data.clientId;
delete data.clientId;
}
if (value === true) {
const data = {
clientId: settings.get(`${key}_id`),
secret: settings.get(`${key}_secret`),
if (serviceName === 'Linkedin') {
data.clientConfig = {
requestPermissions: ['r_liteprofile', 'r_emailaddress'],
};
}
if (/Accounts_OAuth_Custom-/.test(key)) {
data.custom = true;
data.clientId = settings.get(`${key}-id`);
data.secret = settings.get(`${key}-secret`);
data.serverURL = settings.get(`${key}-url`);
data.tokenPath = settings.get(`${key}-token_path`);
data.identityPath = settings.get(`${key}-identity_path`);
data.authorizePath = settings.get(`${key}-authorize_path`);
data.scope = settings.get(`${key}-scope`);
data.accessTokenParam = settings.get(`${key}-access_token_param`);
data.buttonLabelText = settings.get(`${key}-button_label_text`);
data.buttonLabelColor = settings.get(`${key}-button_label_color`);
data.loginStyle = settings.get(`${key}-login_style`);
data.buttonColor = settings.get(`${key}-button_color`);
data.tokenSentVia = settings.get(`${key}-token_sent_via`);
data.identityTokenSentVia = settings.get(`${key}-identity_token_sent_via`);
data.keyField = settings.get(`${key}-key_field`);
data.usernameField = settings.get(`${key}-username_field`);
data.emailField = settings.get(`${key}-email_field`);
data.nameField = settings.get(`${key}-name_field`);
data.avatarField = settings.get(`${key}-avatar_field`);
data.rolesClaim = settings.get(`${key}-roles_claim`);
data.groupsClaim = settings.get(`${key}-groups_claim`);
data.channelsMap = settings.get(`${key}-groups_channel_map`);
data.channelsAdmin = settings.get(`${key}-channels_admin`);
data.mergeUsers = settings.get(`${key}-merge_users`);
data.mapChannels = settings.get(`${key}-map_channels`);
data.mergeRoles = settings.get(`${key}-merge_roles`);
data.rolesToSync = settings.get(`${key}-roles_to_sync`);
data.showButton = settings.get(`${key}-show_button`);
new CustomOAuth(serviceName.toLowerCase(), {
serverURL: data.serverURL,
tokenPath: data.tokenPath,
identityPath: data.identityPath,
authorizePath: data.authorizePath,
scope: data.scope,
loginStyle: data.loginStyle,
tokenSentVia: data.tokenSentVia,
identityTokenSentVia: data.identityTokenSentVia,
keyField: data.keyField,
usernameField: data.usernameField,
emailField: data.emailField,
nameField: data.nameField,
avatarField: data.avatarField,
rolesClaim: data.rolesClaim,
groupsClaim: data.groupsClaim,
mapChannels: data.mapChannels,
channelsMap: data.channelsMap,
channelsAdmin: data.channelsAdmin,
mergeUsers: data.mergeUsers,
mergeRoles: data.mergeRoles,
rolesToSync: data.rolesToSync,
accessTokenParam: data.accessTokenParam,
showButton: data.showButton,
});
}
if (serviceName === 'Facebook') {
data.appId = data.clientId;
delete data.clientId;
}
if (serviceName === 'Twitter') {
data.consumerKey = data.clientId;
delete data.clientId;
}
if (serviceName === 'Linkedin') {
data.clientConfig = {
requestPermissions: ['r_liteprofile', 'r_emailaddress'],
};
}
if (serviceName === 'Nextcloud') {
data.buttonLabelText = settings.get('Accounts_OAuth_Nextcloud_button_label_text');
data.buttonLabelColor = settings.get('Accounts_OAuth_Nextcloud_button_label_color');
data.buttonColor = settings.get('Accounts_OAuth_Nextcloud_button_color');
}
// If there's no data other than the service name, then put the service name in the data object so the operation won't fail
const keys = Object.keys(data).filter((key) => data[key] !== undefined);
if (!keys.length) {
data.service = serviceName.toLowerCase();
}
ServiceConfiguration.configurations.upsert(
{
service: serviceName.toLowerCase(),
},
{
$set: data,
},
);
} else {
ServiceConfiguration.configurations.remove({
service: serviceName.toLowerCase(),
});
if (serviceName === 'Nextcloud') {
data.buttonLabelText = settings.get('Accounts_OAuth_Nextcloud_button_label_text');
data.buttonLabelColor = settings.get('Accounts_OAuth_Nextcloud_button_label_color');
data.buttonColor = settings.get('Accounts_OAuth_Nextcloud_button_color');
}
// If there's no data other than the service name, then put the service name in the data object so the operation won't fail
const keys = Object.keys(data).filter((key) => data[key] !== undefined);
if (!keys.length) {
data.service = serviceName.toLowerCase();
}
});
await ServiceConfiguration.configurations.upsertAsync(
{
service: serviceName.toLowerCase(),
},
{
$set: data,
},
);
} else {
await ServiceConfiguration.configurations.removeAsync({
service: serviceName.toLowerCase(),
});
}
}
}
const OAuthServicesUpdate = _.debounce(Meteor.bindEnvironment(_OAuthServicesUpdate), 2000);
const OAuthServicesUpdate = _.debounce(_OAuthServicesUpdate, 2000);
function OAuthServicesRemove(_id) {
async function OAuthServicesRemove(_id) {
const serviceName = _id.replace('Accounts_OAuth_Custom-', '');
return ServiceConfiguration.configurations.remove({
return ServiceConfiguration.configurations.removeAsync({
service: serviceName.toLowerCase(),
});
}

@ -23,8 +23,8 @@ const logoutBehaviour = {
ONLY_RC: 'Local',
};
Meteor.logout = function (...args) {
const samlService = ServiceConfiguration.configurations.findOne({ service: 'saml' });
Meteor.logout = async function (...args) {
const samlService = await ServiceConfiguration.configurations.findOneAsync({ service: 'saml' });
if (samlService) {
const provider = samlService.clientConfig && samlService.clientConfig.provider;
if (provider) {

@ -101,7 +101,7 @@ const configureSamlService = function (samlConfigs: Record<string, any>): IServi
};
};
export const loadSamlServiceProviders = function (): void {
export const loadSamlServiceProviders = async function (): Promise<void> {
const serviceName = 'saml';
const services = settings.getByRegexp(/^(SAML_Custom_)[a-z]+$/i);
@ -109,27 +109,29 @@ export const loadSamlServiceProviders = function (): void {
return SAMLUtils.setServiceProvidersList([]);
}
const providers = services
.map(([key, value]) => {
if (value === true) {
const samlConfigs = getSamlConfigs(key);
SAMLUtils.log(key);
ServiceConfiguration.configurations.upsert(
{
service: serviceName.toLowerCase(),
},
{
$set: samlConfigs,
},
);
return configureSamlService(samlConfigs);
}
ServiceConfiguration.configurations.remove({
service: serviceName.toLowerCase(),
});
return false;
})
.filter((e) => e) as IServiceProviderOptions[];
const providers = (
await Promise.all(
services.map(async ([key, value]) => {
if (value === true) {
const samlConfigs = getSamlConfigs(key);
SAMLUtils.log(key);
await ServiceConfiguration.configurations.upsertAsync(
{
service: serviceName.toLowerCase(),
},
{
$set: samlConfigs,
},
);
return configureSamlService(samlConfigs);
}
await ServiceConfiguration.configurations.removeAsync({
service: serviceName.toLowerCase(),
});
return false;
}),
)
).filter((e) => e) as IServiceProviderOptions[];
SAMLUtils.setServiceProvidersList(providers);
};

@ -20,7 +20,7 @@ const config = {
const WordPress = new CustomOAuth('wordpress', config);
const fillSettings = _.debounce(
Meteor.bindEnvironment(() => {
Meteor.bindEnvironment(async () => {
config.serverURL = settings.get('API_Wordpress_URL');
if (!config.serverURL) {
if (config.serverURL === undefined) {
@ -74,7 +74,7 @@ const fillSettings = _.debounce(
if (Meteor.isServer) {
const enabled = settings.get('Accounts_OAuth_Wordpress');
if (enabled) {
ServiceConfiguration.configurations.upsert(
await ServiceConfiguration.configurations.upsertAsync(
{
service: 'wordpress',
},
@ -83,7 +83,7 @@ const fillSettings = _.debounce(
},
);
} else {
ServiceConfiguration.configurations.remove({
await ServiceConfiguration.configurations.removeAsync({
service: 'wordpress',
});
}

@ -256,7 +256,7 @@ export class MeteorService extends ServiceClassInternal implements IMeteor {
}
async getLoginServiceConfiguration(): Promise<any[]> {
return ServiceConfiguration.configurations.find({}, { fields: { secret: 0 } }).fetch();
return ServiceConfiguration.configurations.find({}, { fields: { secret: 0 } }).fetchAsync();
}
async callMethodWithToken(userId: string, token: string, method: string, args: any[]): Promise<void | any> {

@ -6,7 +6,7 @@ import { addMigration } from '../../lib/migrations';
addMigration({
version: 267,
async up() {
ServiceConfiguration.configurations.remove({
await ServiceConfiguration.configurations.removeAsync({
service: 'blockstack',
});

Loading…
Cancel
Save