-
-
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); | |
}; |
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
@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.