-
-
Save onhate/67797decbb5a262786cea405f8bf29a1 to your computer and use it in GitHub Desktop.
import { PreSignUpTriggerEvent, PreSignUpTriggerHandler } from 'aws-lambda'; | |
import { CognitoIdentityServiceProvider } from 'aws-sdk'; | |
const cognito = new CognitoIdentityServiceProvider(); | |
const knownProviderNames = { | |
google: 'Google', | |
facebook: 'Facebook' | |
}; | |
const getProviderName = async (userPoolId: string, providerName: string) => { | |
if (knownProviderNames[providerName]) { | |
return knownProviderNames[providerName]; | |
} | |
const { Providers } = await cognito.listIdentityProviders({ UserPoolId: userPoolId }).promise(); | |
for (const provider of Providers) { | |
if (provider.ProviderName.toLowerCase() === providerName.toLowerCase()) { | |
return provider.ProviderName; | |
} | |
} | |
}; | |
const tryMergeUserAccounts = async (event: PreSignUpTriggerEvent) => { | |
const { userPoolId, userName } = event; | |
const { email } = event.request.userAttributes; | |
const [provider, ...providerValues] = userName.split('_'); | |
const providerValue = providerValues.join('_'); | |
// merge social provider with existing cognito user by email | |
if (provider.length > 0 && providerValue.length > 0) { | |
const [{ Users }, providerName] = await Promise.all([ | |
cognito | |
.listUsers({ | |
UserPoolId: userPoolId, | |
AttributesToGet: ['email'], | |
Filter: `email = "${email}"`, | |
Limit: 1 | |
}) | |
.promise(), | |
getProviderName(userPoolId, provider) | |
]); | |
if (providerName && Users.length > 0) { | |
for (const user of Users) { | |
await cognito | |
.adminLinkProviderForUser({ | |
UserPoolId: userPoolId, | |
DestinationUser: { | |
ProviderName: 'Cognito', | |
ProviderAttributeValue: user.Username | |
}, | |
SourceUser: { | |
ProviderName: providerName, | |
ProviderAttributeName: 'Cognito_Subject', | |
ProviderAttributeValue: providerValue | |
} | |
}) | |
.promise(); | |
} | |
// return true to indicate users were merged | |
return true; | |
} | |
} | |
return false; | |
}; | |
export const handler: PreSignUpTriggerHandler = async (event, _, callback) => { | |
// continue the flow only if did not link providers | |
const wereUsersMerged = await tryMergeUserAccounts(event); | |
return wereUsersMerged ? undefined : callback(null, event); | |
}; |
@campanagerald I show the user a friendly message based on this error asking for the user to login again because there was already another account found and it was merged. There's no way around it, trust me hehe
Thanks, @onhate!
Do you have an idea how to do it when the user login from an external provider first? it seems that Cognito doesn't link if the external user is already created.
@campanagerald I consider that a security flaw because only knowing your email I could take over you account, on the other hand merging an existing cognito account with an external provider is less risky because you could consider that the third party certifies the user is the owner of that email. The best UX flow in that case would be to have a section on "my account" when the user is authenticated to "create a password" that would then create an username/password account and merge them.
I consider that a security flow because only knowing your email I could take over you account
@onhate I agree with this if the pool is not setup to use email verification but if it is then the user would still have to prove they have access to the code (and thus the email account) before they could login correct?
I'm just wondering if there is some other case I'm not thinking of where it would be a security risk to allow linking of an existing social account with a new email/password account if email verification codes are enabled.
Hey, thanks for this snippet.
And I was facing some issues with SAM + TS.
Could you help me with that, if you have a similar setup ?
@johnny68 I have an updated version of this, will update later
@johnny68 updated to suport dynamic providers other than facebook and google
Don't use callback in an async handler, just return.
@onhate @Samrose-Ahmed @johnny68 @njeirath @coyksdev can any one provide code for js please
return wereUsersMerged ? undefined : callback(null, event);
It should always return event, otherwise user will not be logged-in after merging when using amplify
Thank you for this!
Hi, Thanks for this. It worked for me. However, it throws an error "Already found an entry for ...". How do you handle it? I also return undefined but still, it throws an error.