Skip to content

Commit 48dbb51

Browse files
Merge pull request #11 from janetechinc/sjb/verify-sso-user-migration
2 parents fee65f9 + 6392f44 commit 48dbb51

File tree

7 files changed

+2200
-53
lines changed

7 files changed

+2200
-53
lines changed

examples/javascript/src/lib/jane-service.mjs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,47 @@ const verifyCredentials = async (data, token) => {
124124
return result
125125
}
126126

127+
/** ----- GET SSO USER ATTRIBUTES ----- */
128+
129+
const verifySSOUser = async (data, token) => {
130+
// user_attributes.identities is already a stringified object and
131+
// unfortunately if you call JSON.stringify on it, the object breaks
132+
// on the backend when it attempts to re-parse it.
133+
// The solution here is to parse identities before stringifying it.
134+
const parsedData = {
135+
...data,
136+
user_attributes: {
137+
...data.user_attributes,
138+
identities: JSON.parse(data.user_attributes.identities)
139+
}
140+
}
141+
const response = await apiService.post(
142+
`${COGNITO_API}/verify_sso_user`,
143+
parsedData,
144+
token
145+
)
146+
147+
const result = {
148+
errorMessage: "",
149+
user: response.body?.user,
150+
}
151+
152+
switch (response.statusCode) {
153+
case 200:
154+
break
155+
case 404:
156+
result.errorMessage = "User not found"
157+
break
158+
default:
159+
result.errorMessage = buildErrorMessage(
160+
"Error verifying SSO user",
161+
response
162+
)
163+
}
164+
165+
return result
166+
}
167+
127168
/** ----- VALIDATE USER ----- */
128169

129170

@@ -171,5 +212,6 @@ export default {
171212
userExists,
172213
ensureExternalUserExists,
173214
verifyCredentials,
215+
verifySSOUser,
174216
validateUser
175217
}

examples/javascript/src/lib/utils.mjs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,15 @@ export const mapUserAttributes = (userAttributes) => {
4444

4545
return userData
4646
}
47+
48+
export const addAreaCodeToPhone = (phone) => {
49+
let partial = phone.startsWith("+") ? phone.substring(1) : phone
50+
51+
// Missing + and country code, 2223334444
52+
if (phone.length === 10) {
53+
return `+1${partial}`
54+
}
55+
56+
// If was already correct, just return the +
57+
return `+${partial}`
58+
}

examples/javascript/src/migration-lambda/index.mjs

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { addAreaCodeToPhone } from '../lib/utils.mjs';
12
import Jane from "../lib/jane-service.mjs";
23
import apiService from "../lib/api-service.mjs";
34

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

9798
return event;
9899
};
99-
100-
const addAreaCodeToPhone = (phone) => {
101-
let partial = phone.startsWith("+") ? phone.substring(1) : phone;
102-
103-
// Missing + and country code, 2223334444
104-
if (phone.length === 10) {
105-
return `+1${partial}`;
106-
}
107-
108-
// If was already correct, just return the +
109-
return `+${partial}`;
110-
};

examples/javascript/src/post-confirmation-lambda/index.mjs

Lines changed: 97 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { mapUserAttributes } from '../lib/utils.mjs';
1+
import { addAreaCodeToPhone, mapUserAttributes } from '../lib/utils.mjs';
22
import Jane from '../lib/jane-service.mjs';
33
import apiService from '../lib/api-service.mjs';
44

@@ -26,12 +26,17 @@ export const handler = async (event) => {
2626
return event;
2727
}
2828

29-
const { success, errorMessage } = await Jane.createUser({
30-
pool_id: event.userPoolId,
31-
external_id: event.userName,
32-
app_client_id: event.callerContext.clientId,
33-
...mapUserAttributes(event.request.userAttributes),
34-
}, token);
29+
event = await handleUserMigration(event, token);
30+
31+
const { success, errorMessage } = await Jane.createUser(
32+
{
33+
pool_id: event.userPoolId,
34+
external_id: event.userName,
35+
app_client_id: event.callerContext.clientId,
36+
...mapUserAttributes(event.request.userAttributes),
37+
},
38+
token
39+
);
3540

3641
if (!success) {
3742
throw new Error(`User creation was not successful: ${errorMessage}`);
@@ -41,3 +46,88 @@ export const handler = async (event) => {
4146

4247
return event;
4348
};
49+
/* Cognito SSO flows do not go through our migration handler
50+
instead we handle those migrations here, after signup.
51+
If a user is signing up via sso, we check for a Jane SSO user
52+
associated with this client and use that users data for the migration */
53+
const handleUserMigration = async (event, token) => {
54+
let userIdentities;
55+
try {
56+
userIdentities = JSON.parse(event.request.userAttributes.identities);
57+
} catch (err) {
58+
console.error("userIdentities unable to parse", err);
59+
return event;
60+
}
61+
62+
const userGoogleIdentity = userIdentities.find(
63+
(i) => i.providerType === "Google"
64+
);
65+
if (!userGoogleIdentity) {
66+
return event;
67+
}
68+
69+
const { errorMessage, user } = await Jane.verifySSOUser(
70+
{
71+
email: event.request.userAttributes.email,
72+
user_attributes: event.request.userAttributes,
73+
app_client_id: event.callerContext.clientId,
74+
},
75+
token
76+
);
77+
if (errorMessage === "User not found") {
78+
// Jane user for this client was not found, continue normal sign up
79+
return event;
80+
} else if (errorMessage || !user) {
81+
// something went wrong, continue normal sign up and log error
82+
console.error(`failed to retrieve data for migration: ${errorMessage}`);
83+
return event;
84+
}
85+
const attributes = {};
86+
const { first_name, last_name, phone, birth_date } = user;
87+
88+
const attributesToUpdate = [];
89+
first_name &&
90+
(attributes.given_name = first_name) &&
91+
attributesToUpdate.push({
92+
Name: "given_name",
93+
Value: first_name,
94+
});
95+
last_name &&
96+
(attributes.family_name = last_name) &&
97+
attributesToUpdate.push({
98+
Name: "family_name",
99+
Value: last_name,
100+
});
101+
phone &&
102+
(attributes.phone_number = addAreaCodeToPhone(phone)) &&
103+
attributesToUpdate.push({
104+
Name: "phone_number",
105+
Value: addAreaCodeToPhone(phone),
106+
});
107+
birth_date &&
108+
(attributes.birthdate = birth_date) &&
109+
attributesToUpdate.push({
110+
Name: "birthdate",
111+
Value: birth_date,
112+
});
113+
const cognitoIdServiceProvider = new CognitoIdentityProviderClient({
114+
region: "us-east-1",
115+
});
116+
const command = new AdminUpdateUserAttributesCommand({
117+
UserAttributes: attributesToUpdate,
118+
UserPoolId: event.userPoolId,
119+
Username: event.userName,
120+
});
121+
await cognitoIdServiceProvider
122+
.send(command)
123+
.then((data) => console.log("Cognito user updated!", data))
124+
.catch((err) => {
125+
console.error("Cognito Attribute Update Unsuccessful", err);
126+
});
127+
128+
event.request.userAttributes = {
129+
...event.request.userAttributes,
130+
...attributes,
131+
};
132+
return event;
133+
};

examples/javascript/src/pre-signup-lambda/index.mjs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,20 @@ export const handler = async (event) => {
6060
}
6161

6262
if (userExists) {
63+
// Caveat, flow is "wrong" because SSO is sinup/sign in is the same thing, so it calls signup when
64+
// we actually want is signin
65+
66+
// Eg:
67+
// Here we should check event.triggerSource === 'PreSignUp_ExternalProvider'
68+
// User should be confirmed (just like a migration)
69+
// No message to user since it already had an account before (just like a migration)
70+
// And move flow along to Post-Confirmation, where it can finish the migration
71+
if (event.triggerSource === 'PreSignUp_ExternalProvider') {
72+
event.response.autoConfirmUser = true
73+
event.response.autoVerifyEmail = true
74+
75+
return event
76+
}
6377
throw Error('User already exists, please log in')
6478
}
6579

0 commit comments

Comments
 (0)