Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cognito): verify SSO users and migrate #11

Merged
merged 1 commit into from
Oct 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 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,47 @@ const verifyCredentials = async (data, token) => {
return result
}

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

const verifySSOUser = async (data, token) => {
// user_attributes.identities is already a stringified object and
// unfortunately if you call JSON.stringify on it, the object breaks
// on the backend when it attempts to re-parse it.
// The solution here is to parse identities before stringifying it.
const parsedData = {
...data,
user_attributes: {
...data.user_attributes,
identities: JSON.parse(data.user_attributes.identities)
}
}
const response = await apiService.post(
`${COGNITO_API}/verify_sso_user`,
parsedData,
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 +212,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}`;
};
104 changes: 97 additions & 7 deletions examples/javascript/src/post-confirmation-lambda/index.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { mapUserAttributes } from '../lib/utils.mjs';
import { addAreaCodeToPhone, mapUserAttributes } from '../lib/utils.mjs';
import Jane from '../lib/jane-service.mjs';
import apiService from '../lib/api-service.mjs';

Expand Down Expand Up @@ -26,12 +26,17 @@ export const handler = async (event) => {
return event;
}

const { success, errorMessage } = await Jane.createUser({
pool_id: event.userPoolId,
external_id: event.userName,
app_client_id: event.callerContext.clientId,
...mapUserAttributes(event.request.userAttributes),
}, token);
event = await handleUserMigration(event, token);

const { success, errorMessage } = await Jane.createUser(
{
pool_id: event.userPoolId,
external_id: event.userName,
app_client_id: event.callerContext.clientId,
...mapUserAttributes(event.request.userAttributes),
},
token
);

if (!success) {
throw new Error(`User creation was not successful: ${errorMessage}`);
Expand All @@ -41,3 +46,88 @@ 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, token) => {
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,
user_attributes: event.request.userAttributes,
app_client_id: event.callerContext.clientId,
},
token
);
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