src/modules/identity-api.js
import IdentityCookie from './identity-cookie.js';
import IdentityProperty from './identity-property.js';
import IdentityUser from './identity-user.js';
import IdentityCrossApp from './identity-crossapp.js';
import {checkIsLocal} from '../helpers/local';
/**
* Authentication API calls
*/
class IdentityAPI {
/**
* @param {Object} [options={}] - Options
* @param {IdentityServer} [options.server] - Server instance api methods are called on.
* @param {IdentityConfig} [options.config] - Identity config object
* @param {Boolean} useCookie - Enable Identity Cookie module
*/
constructor({
server,
config,
useCookie = false,
} = {}) {
/**
* Save local Identity Server instance
*
* @type {IdentityServer}
*/
this._server = server;
/**
* @type {IdentityCookie}
*/
this._cookieHelpers = new IdentityCookie(config);
this._cookieHelpers.setEnabled(useCookie);
/**
* Check if environment proxies the api calls to fix issues with openidm endpoints
* @type {boolean}
*/
this._local = checkIsLocal();
const aws = location.origin.endsWith('id.nbc.com-sdk.s3-website-us-east-1.amazonaws.com');
/**
* Class variable that defines if API calls should be proxied
*
* @type {boolean}
*/
this._proxied = (this._local||aws);
}
/**
* Login user using email address and password
*
* @param {string} email - User email address
* @param {string} password - User password
* @param {Object} options - Additional options
* @param {Object} options.headers - additional headers to add to the call
* @return {Object} - IDM response
*/
loginByEmail(email, password, options = {headers: {}}) {
const b64Password = btoa(encodeURIComponent(password).replace(/%([0-9A-F]{2})/g, function(match, p1) {
return String.fromCharCode(parseInt(p1, 16));
}));
return this._server.call('api/2020-10/profile/email/session', {
method: 'GET',
headers: {
'X-IDM-Password': `=?UTF-8?B?${b64Password}?=`,
'X-IDM-Username': email,
...options.headers,
},
});
}
/**
* Register user by email address and password
*
* @param {Object} user - User properties
* @param {string} user.mail - User email address
* @param {string} user.password - User password
* @param {string} [user.userName=user.mail] - User name
* @param {string} [user.givenName] - User given name
* @param {string} [user.sn] - User surname
* @param {number} [user.termsAndConditionsAgreementDate] - Unix timestamp at which terms where agreed to
* @param {string} [user.facebookId] - User Facebook id
* @param {Object} options - Additional options
* @param {Object} options.headers - additional headers to add to the call
* @return {Promise} - Promise of IDM response
*/
registerByEmail(user, options = {headers: {}}) {
if (!user.userName) {
user.userName = user.mail;
}
return this._server.call('api/2020-10/profile/email/register', {
method: 'POST',
headers: options.headers,
body: user,
}).then((response) => {
if (response.result.code === 200) {
return this.loginByEmail(user.mail, user.password, {headers: {
'vppa_re_opt_in': false,
...options.headers,
}});
}
return response;
});
}
/**
* Register external library user
*
* @param {string} idToken - Auth token
* @param {string} type - External library type
* @param {Object} options - Additional options
* @param {Object} options.headers - additional headers to add to the call
* @return {Promise}
*/
externalRegister(idToken, type, options = {headers: {}}) {
const headers = {
...options.headers,
};
switch (type) {
case 'facebook':
headers.access_token = idToken;
break;
default:
headers.id_token = idToken;
}
return this._server.call(`api/2020-10/profile/${type}/register`, {
method: 'POST',
headers,
});
}
/**
* Get external library user session
*
* @param {string} idToken - Auth token
* @param {string} type - External library type
* @param {Object} options - Additional options
* @param {Object} options.headers - additional headers to add to the call
* @return {Promise}
*/
externalSession(idToken, type, options = {headers: {}}) {
const headers = {
...options.headers,
};
switch (type) {
case 'facebook':
headers.access_token = idToken;
break;
default:
headers.id_token = idToken;
}
return this._server.call(`api/2020-10/profile/${type}/session`, {
method: 'GET',
headers,
});
}
/**
* Get user info by token
* @param {string} token - IDM session token
* @param {Object} userData - existing user data
* @param {Object} options - additional options
* @param {Object} options.headers - additional headers to add to the call
* @return {Promise} Promise of userinfo
*/
getUserInfo(token, userData = null, options = {headers: {}}) {
const appendProps = (data) => {
// Get any userinfo from the cookie
let savedProps = {};
const userId = data._id;
if (!userId) return data;
if (this._cookieHelpers.getCookie('properties', {userId})) {
savedProps = JSON.parse(this._cookieHelpers.getCookie('properties', {userId}));
}
return Object.assign(data, savedProps);
};
return new Promise((resolve, reject) => {
if (userData) {
resolve(appendProps(userData));
} else {
// Get userinfo from the server
this._server.call('api/2020-10/profile', {
method: 'GET',
headers: {
session_token: token,
...options.headers,
},
}).then((response) => {
if (response.result.code === 200 && response.profile) {
resolve(appendProps(response.profile));
} else {
reject(response.result);
}
}).catch((error) => reject(error));
}
});
}
/**
* Update user profile properties
*
* @param {string} token - IDM session token
* @param {number} revisionId Profile revision id
* @param {string} userId - IDM user id
* @param {Object} properties - User profile properties to change
* @param {Object} options - Additional options
* @param {Object} options.headers - additional headers to add to the call
* @return {Promise} - Promise of user info
*/
updateProfile(token, revisionId, userId = null, properties, options = {headers: {}}) {
return new Promise((resolve, reject) => {
const serverUpdates = [];
const cookieUpdates = {};
const {standardMap} = IdentityProperty;
const {serverKeyMap} = IdentityUser;
Object.keys(properties).forEach((key) => {
const obj = {
field: serverKeyMap[key] || key,
value: properties[key],
};
// Only push properties that are in the standardMap
// Save all others to a cookie
if (Object.keys(standardMap).includes(key)) {
obj.operation = 'replace';
serverUpdates.push(obj);
} else {
if (obj.field === 'mail') return;
cookieUpdates[obj.field] = obj.value;
}
});
// Update profile cookie
if (Object.keys(cookieUpdates).length > 0) {
const existingCookie = this._cookieHelpers.getCookie('properties', {userId});
const savedProps = (existingCookie) ? JSON.parse(existingCookie) : {};
const newProps = Object.assign(savedProps, cookieUpdates);
const cookieValue = JSON.stringify(newProps);
this._cookieHelpers.setCookie('properties', cookieValue, {userId});
}
if (serverUpdates.length > 0) {
return this._server.call('api/2020-10/profile', {
method: 'PATCH',
headers: {
'session_token': token,
'X-IDM-Update-Rev': revisionId,
...options.headers,
},
body: serverUpdates,
}).then(resolve, reject);
}
return resolve({status: 'Success'});
});
}
/**
* Unauthenticate user
*
* @param {string} token - IDM session token
* @param {function} nativeController - native flag, defaults to false
* @param {Object} options - Additional options
* @param {Object} options.headers - additional headers to add to the call
* @return {Promise<Identity, Error>} - Promise of unauthenticated Identity
*/
unauthenticate(token, nativeController, options = {headers: {}}) {
if (nativeController) {
const crossAppBridge = new IdentityCrossApp(nativeController);
crossAppBridge.init().then(() => {
if (window.nativeWrapper.os === 'iOS') {
// Remove session from keychain
crossAppBridge.remove(token);
} else if (window.nativeWrapper.os === 'Android') {
// Disable Android auto sign in
nativeController.sendCommand('DisableAutoSignIn', {});
}
});
}
return this._server.call('openam/json/realms/root/sessions/?_action=logout', {
method: 'POST',
headers: {
iplanetdirectorypro: token,
...options.headers,
},
});
}
}
export default IdentityAPI;