Skip to content

Commit

Permalink
Merge pull request #10 from janetechinc/sjb/sso-user-migration
Browse files Browse the repository at this point in the history
feat(cognito): verify SSO users and migrate
  • Loading branch information
samueljamesbliss authored Oct 16, 2023
2 parents 14aef24 + 1b2e606 commit ffe00a8
Show file tree
Hide file tree
Showing 7 changed files with 2,183 additions and 49 deletions.
31 changes: 31 additions & 0 deletions examples/javascript/src/lib/jane-service.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,36 @@ const verifyCredentials = async (data, token) => {
return result
}

/** ----- GET SSO USER ATTRIBUTES ----- */

const verifySSOUser = async (data, token) => {
const response = await apiService.post(
`${COGNITO_API}/verify_sso_user`,
data,
token
)

const result = {
errorMessage: "",
user: response.body?.user,
}

switch (response.statusCode) {
case 200:
break
case 404:
result.errorMessage = "User not found"
break
default:
result.errorMessage = buildErrorMessage(
"Error verifying SSO user",
response
)
}

return result
}

/** ----- VALIDATE USER ----- */


Expand Down Expand Up @@ -171,5 +201,6 @@ export default {
userExists,
ensureExternalUserExists,
verifyCredentials,
verifySSOUser,
validateUser
}
12 changes: 12 additions & 0 deletions examples/javascript/src/lib/utils.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,15 @@ export const mapUserAttributes = (userAttributes) => {

return userData
}

export const addAreaCodeToPhone = (phone) => {
let partial = phone.startsWith("+") ? phone.substring(1) : phone

// Missing + and country code, 2223334444
if (phone.length === 10) {
return `+1${partial}`
}

// If was already correct, just return the +
return `+${partial}`
}
13 changes: 1 addition & 12 deletions examples/javascript/src/migration-lambda/index.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { addAreaCodeToPhone } from '../lib/utils.mjs';
import Jane from "../lib/jane-service.mjs";
import apiService from "../lib/api-service.mjs";

Expand Down Expand Up @@ -96,15 +97,3 @@ export const handler = async (event) => {

return event;
};

const addAreaCodeToPhone = (phone) => {
let partial = phone.startsWith("+") ? phone.substring(1) : phone;

// Missing + and country code, 2223334444
if (phone.length === 10) {
return `+1${partial}`;
}

// If was already correct, just return the +
return `+${partial}`;
};
94 changes: 91 additions & 3 deletions examples/javascript/src/post-confirmation-lambda/index.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { mapUserAttributes } from '../lib/utils.mjs';
import Jane from '../lib/jane-service.mjs';
import apiService from '../lib/api-service.mjs';
import { addAreaCodeToPhone, mapUserAttributes } from "../lib/utils.mjs"
import Jane from "../lib/jane-service.mjs"
import apiService from "../lib/api-service.mjs"
import {
AdminUpdateUserAttributesCommand,
CognitoIdentityProviderClient,
} from "@aws-sdk/client-cognito-identity-provider"

/**
* Possible trigger sources:
Expand All @@ -25,6 +29,8 @@ export const handler = async (event) => {
);
return event;
}

event = await handleUserMigration(event);

const { success, errorMessage } = await Jane.createUser({
pool_id: event.userPoolId,
Expand All @@ -41,3 +47,85 @@ export const handler = async (event) => {

return event;
};
/* Cognito SSO flows do not go through our migration handler
instead we handle those migrations here, after signup.
If a user is signing up via sso, we check for a Jane SSO user
associated with this client and use that users data for the migration */
const handleUserMigration = async (event) => {
let userIdentities;
try {
userIdentities = JSON.parse(event.request.userAttributes.identities);
} catch (err) {
console.error("userIdentities unable to parse", err);
return event;
}

const userGoogleIdentity = userIdentities.find(
(i) => i.providerType === "Google"
);
if (!userGoogleIdentity) {
return event;
}

const { errorMessage, user } = await Jane.verifySSOUser({
email: event.request.userAttributes.email,
userAttributes: event.request.userAttributes,
appClientId: event.callerContext.clientId,
});
if (errorMessage === "User not found") {
// Jane user for this client was not found, continue normal sign up
return event;
} else if (errorMessage || !user) {
// something went wrong, continue normal sign up and log error
console.error(`failed to retrieve data for migration: ${errorMessage}`);
return event;
}
const attributes = {};
const { first_name, last_name, phone, birth_date } = user;

const attributesToUpdate = [];
first_name &&
(attributes.given_name = first_name) &&
attributesToUpdate.push({
Name: "given_name",
Value: first_name,
});
last_name &&
(attributes.family_name = last_name) &&
attributesToUpdate.push({
Name: "family_name",
Value: last_name,
});
phone &&
(attributes.phone_number = addAreaCodeToPhone(phone)) &&
attributesToUpdate.push({
Name: "phone_number",
Value: addAreaCodeToPhone(phone),
});
birth_date &&
(attributes.birthdate = birth_date) &&
attributesToUpdate.push({
Name: "birthdate",
Value: birth_date,
});
const cognitoIdServiceProvider = new CognitoIdentityProviderClient({
region: "us-east-1",
});
const command = new AdminUpdateUserAttributesCommand({
UserAttributes: attributesToUpdate,
UserPoolId: event.userPoolId,
Username: event.userName,
});
await cognitoIdServiceProvider
.send(command)
.then((data) => console.log("Cognito user updated!", data))
.catch((err) => {
console.error("Cognito Attribute Update Unsuccessful", err);
});

event.request.userAttributes = {
...event.request.userAttributes,
...attributes,
};
return event;
};
14 changes: 14 additions & 0 deletions examples/javascript/src/pre-signup-lambda/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,20 @@ export const handler = async (event) => {
}

if (userExists) {
// Caveat, flow is "wrong" because SSO is sinup/sign in is the same thing, so it calls signup when
// we actually want is signin

// Eg:
// Here we should check event.triggerSource === 'PreSignUp_ExternalProvider'
// User should be confirmed (just like a migration)
// No message to user since it already had an account before (just like a migration)
// And move flow along to Post-Confirmation, where it can finish the migration
if (event.triggerSource === 'PreSignUp_ExternalProvider') {
event.response.autoConfirmUser = true
event.response.autoVerifyEmail = true

return event
}
throw Error('User already exists, please log in')
}

Expand Down
Loading

0 comments on commit ffe00a8

Please sign in to comment.