Skip to content

Commit 73aebfa

Browse files
committed
adds string interpolation for custom field mappings
this allows to mix and match fragments of existing fields (identity response from provider) into new values (e.g. pick the leading part of email and use as username: `{{/^(.+)@/::email}}`)
1 parent 2bd8feb commit 73aebfa

File tree

1 file changed

+58
-29
lines changed

1 file changed

+58
-29
lines changed

app/custom-oauth/server/custom_oauth_server.js

Lines changed: 58 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,50 @@ const BeforeUpdateOrCreateUserFromExternalService = [];
1919

2020
const IDENTITY_PROPNAME_FILTER = /\./;
2121

22+
const getNestedValue = (propertyPath, source) =>
23+
propertyPath.split('.').reduce((prev, curr) => (prev ? prev[curr] : undefined), source);
24+
25+
// /^(.+)@/::email
26+
const REGEXP_FROM_FORMULA = /^\/((?!\/::).*)\/::(.+)/;
27+
const getRegexpMatch = (formula, data) => {
28+
const regexAndPath = REGEXP_FROM_FORMULA.exec(formula);
29+
if (!regexAndPath) {
30+
return getNestedValue(formula, data);
31+
}
32+
if (regexAndPath.length !== 3) {
33+
throw new Error(`expected array of length 3, got ${ regexAndPath.length }`);
34+
}
35+
36+
const [, regexString, path] = regexAndPath;
37+
const nestedValue = getNestedValue(path, data);
38+
try {
39+
const regex = new RegExp(regexString);
40+
const matches = regex.exec(nestedValue);
41+
42+
// regexp does not match nested value
43+
if (!matches) {
44+
return undefined;
45+
}
46+
47+
// we only support regular expressions with a single capture group
48+
const [, value] = matches;
49+
50+
// this could mean we return `undefined` (e.g. when capture group is empty)
51+
return value;
52+
} catch (error) {
53+
throw new Meteor.Error('CustomOAuth: Failed to extract identity value', error.message);
54+
}
55+
};
56+
57+
const templateStringRegex = /{{((?:(?!}}).)+)}}/g;
58+
const fromTemplate = (template, data) => {
59+
if (!templateStringRegex.test(template)) {
60+
return getNestedValue(template, data);
61+
}
62+
63+
return template.replace(templateStringRegex, (fullMatch, match) => getRegexpMatch(match, data));
64+
};
65+
2266
export class CustomOAuth {
2367
constructor(name, options) {
2468
logger.debug('Init CustomOAuth', name, options);
@@ -331,55 +375,40 @@ export class CustomOAuth {
331375
}
332376

333377
getUsername(data) {
334-
let username = '';
378+
const value = fromTemplate(this.usernameField, data);
335379

336-
username = this.usernameField.split('.').reduce(function(prev, curr) {
337-
return prev ? prev[curr] : undefined;
338-
}, data);
339-
340-
if (!username) {
380+
if (!value) {
341381
throw new Meteor.Error('field_not_found', `Username field "${ this.usernameField }" not found in data`, data);
342382
}
343-
return username;
383+
return value;
344384
}
345385

346386
getEmail(data) {
347-
let username = '';
348-
349-
username = this.emailField.split('.').reduce(function(prev, curr) {
350-
return prev ? prev[curr] : undefined;
351-
}, data);
387+
const value = fromTemplate(this.emailField, data);
352388

353-
if (!username) {
354-
throw new Meteor.Error('field_not_found', `Username field "${ this.emailField }" not found in data`, data);
389+
if (!value) {
390+
throw new Meteor.Error('field_not_found', `Email field "${ this.emailField }" not found in data`, data);
355391
}
356-
return username;
392+
return value;
357393
}
358394

359395
getCustomName(data) {
360-
let customName = '';
396+
const value = fromTemplate(this.nameField, data);
361397

362-
customName = this.nameField.split('.').reduce(function(prev, curr) {
363-
return prev ? prev[curr] : undefined;
364-
}, data);
365-
366-
if (!customName) {
398+
if (!value) {
367399
return this.getName(data);
368400
}
369401

370-
return customName;
402+
return value;
371403
}
372404

373405
getAvatarUrl(data) {
374-
const avatarUrl = this.avatarField.split('.').reduce(function(prev, curr) {
375-
return prev ? prev[curr] : undefined;
376-
}, data);
406+
const value = fromTemplate(this.avatarField, data);
377407

378-
if (!avatarUrl) {
379-
logger.debug(`Avatar field "${ this.avatarField }" not found in data`, data);
408+
if (!value) {
409+
throw new Meteor.Error('field_not_found', `Avatar field "${ this.avatarField }" not found in data`, data);
380410
}
381-
382-
return avatarUrl;
411+
return value;
383412
}
384413

385414
getName(identity) {

0 commit comments

Comments
 (0)