Skip to content

Instantly share code, notes, and snippets.

@viralsavaniIM
Last active November 21, 2022 19:44
Show Gist options
  • Save viralsavaniIM/97eaa4b5f1ccd56f8c64f1215f4779cd to your computer and use it in GitHub Desktop.
Save viralsavaniIM/97eaa4b5f1ccd56f8c64f1215f4779cd to your computer and use it in GitHub Desktop.
Facebank OAuth Integration Guide: AWS Cognito Sign Up Trigger with Custom Error
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2021 Facebank, Inc. //
// All rights reserved. //
// //
// Redistribution and use in source and binary forms, with or without //
// modification, are permitted provided that the following conditions are //
// met: //
// //
// Redistributions of source code must retain the above copyright //
// notice, this list of conditions and the following disclaimer. //
// Redistributions in bytecode form must reproduce the above copyright //
// notice, this list of conditions and the following disclaimer in //
// the documentation and/or other materials provided with the //
// distribution. //
// //
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS //
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT //
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR //
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT //
// HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, //
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, //
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS //
// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND //
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR //
// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE //
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH //
// DAMAGE. //
///////////////////////////////////////////////////////////////////////////////
const AWS = require("aws-sdk");
const cognitoIdp = new AWS.CognitoIdentityServiceProvider();
/**
* The name you gave the Facebank OIDC provider when conneting it in the Cognito
*/
const FACEBANK_COGNITO_PROVIDER_NAME = "FacebankApp";
/**
* The prefix that is added as message to the error when incoming federated user
* already exists as native cognito user
*/
const EXTERNAL_IDENTITY_LINK_TOKEN_PREFIX = "CONFIRM_IDENTITY_LINK_";
/**
* Name of the trigger when sign up lambda was invoked for federated users
*/
const EXTERNAL_AUTHENTICATION_PROVIDER_TRIGGER_SOURCE =
"PreSignUp_ExternalProvider";
/**
* Get all the users matching @param email
* {@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CognitoIdentityServiceProvider.html#listUsers-property}
* @param {String} userPoolId Cognito pool id
* @param {String} email
*/
async function listUsersByEmail(userPoolId, email) {
const params = {
UserPoolId: userPoolId,
Filter: `email = "${email}"`,
};
return new Promise((resolve, reject) => {
cognitoIdp.listUsers(params, (err, data) => {
if (err) {
reject(err);
return;
}
resolve(data);
});
});
}
function convertUtf8ToBase64(payload) {
return Buffer.from(payload, 'utf8').toString('base64');
}
exports.handler = async (event, context, callback) => {
console.log(`Event - \n ${JSON.stringify(event, null, 2)}`);
try {
const triggerSource = event.triggerSource;
const userPoolId = event.userPoolId;
const userName = event.userName;
const userEmail = event.request.userAttributes.email;
if (triggerSource === EXTERNAL_AUTHENTICATION_PROVIDER_TRIGGER_SOURCE) {
// example userName: facebankapp_412639q6-c1df-4cp9-9425-09uweob1d96b7
const [providerNameValue, providerUserId] = userName.split("_");
if (FACEBANK_COGNITO_PROVIDER_NAME.toLowerCase() === providerNameValue) {
const usersFilteredByEmail = await listUsersByEmail(
userPoolId,
userEmail
);
if (
usersFilteredByEmail.Users &&
usersFilteredByEmail.Users.length > 0
) {
// user already has cognito account
const attributes = usersFilteredByEmail.Users[0].Attributes;
// Only link the user if link doesn't already exists
/**
* 1. Get the User Attributes
* 2. Filter out the attribute named "identities"
* 3. Parse the "identities" value from JSON string to valid JSON
* 4. Filter out all the identities which matches `FACEBANK_COGNITO_PROVIDER_NAME`
* 5. Check if user exists
*/
let nativeUserExists = true;
const userIdentity = attributes.filter(
(attribute) => attribute.Name === "identities"
);
if (userIdentity.length > 0) {
// this does not handle the case if user has multiple idenity from different federated providers
const identityJSON = JSON.parse(userIdentity[0].Value);
const faceBankIdentityEntry = identityJSON.filter(
(identity) =>
identity.providerName === FACEBANK_COGNITO_PROVIDER_NAME
);
if (faceBankIdentityEntry.length !== 0) {
nativeUserExists = false;
}
}
const encodedUsername = convertUtf8ToBase64(userName);
if (nativeUserExists) {
return callback(Error(`${EXTERNAL_IDENTITY_LINK_TOKEN_PREFIX}${encodedUsername}`), event);
}
}
}
}
return callback(null, event);
} catch (error) {
return callback(error, event);
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment