Skip to content

Instantly share code, notes, and snippets.

@dnys1
Created April 26, 2023 17:51
Show Gist options
  • Save dnys1/a4ecd81d3415538c4e0f009fbc88052a to your computer and use it in GitHub Desktop.
Save dnys1/a4ecd81d3415538c4e0f009fbc88052a to your computer and use it in GitHub Desktop.
import { PreSignUpTriggerHandler } from "aws-lambda";
import { CognitoIdentityProvider } from "@aws-sdk/client-cognito-identity-provider";
const CLIENT = new CognitoIdentityProvider({});
export const handler: PreSignUpTriggerHandler = async (event) => {
console.log(`Got event: ${JSON.stringify(event, null, 2)}`);
const {
triggerSource,
userPoolId,
userName,
request: {
userAttributes: { email },
},
} = event;
const listResp = await listUsersByEmail({
userPoolId,
email,
});
const usersForEmail = listResp.Users || [];
console.log(`Users for ${email}: ${JSON.stringify(usersForEmail, null, 2)}`);
if (triggerSource !== 'PreSignUp_ExternalProvider' || usersForEmail.length === 0) {
event.response = {
autoConfirmUser: true,
autoVerifyEmail: true,
autoVerifyPhone: false,
}
return event;
}
const existingUser = usersForEmail[0];
const existingUsername = usersForEmail[0].Username!;
const [providerName, providerUserId] = userName.split("_");
console.log(`Found existing native account: ${JSON.stringify(existingUser, null, 2)}`);
console.log(`Linking external account "${userName}" into native account "${existingUsername}"`);
const linkedAccounts = await adminLinkUserAccounts({
username: existingUsername,
userPoolId,
providerName,
providerUserId,
});
console.log(`Linked accounts: ${JSON.stringify(linkedAccounts, null, 2)}`);
event.response = {
autoConfirmUser: true,
autoVerifyEmail: true,
autoVerifyPhone: false,
}
return event;
};
export const listUsersByEmail = async ({
userPoolId,
email,
}: {
userPoolId: string;
email: string;
}) => {
return CLIENT.listUsers({
UserPoolId: userPoolId,
Filter: `email = "${email}"`,
});
};
export const adminLinkUserAccounts = async ({
username,
userPoolId,
providerName,
providerUserId,
}: {
username: string;
userPoolId: string;
providerName: string;
providerUserId: string;
}) => {
return CLIENT.adminLinkProviderForUser({
DestinationUser: {
ProviderAttributeValue: username,
ProviderName: "Cognito",
},
SourceUser: {
ProviderAttributeName: "Cognito_Subject",
ProviderAttributeValue: providerUserId,
ProviderName: providerName,
},
UserPoolId: userPoolId,
});
};
import { IdentityPool, UserPoolAuthenticationProvider } from '@aws-cdk/aws-cognito-identitypool-alpha';
import { App, CfnOutput, RemovalPolicy, Stack, StackProps } from 'aws-cdk-lib';
import { ProviderAttribute, UserPool, UserPoolIdentityProviderAmazon, UserPoolIdentityProviderGoogle, UserPoolIdentityProviderOidc } from 'aws-cdk-lib/aws-cognito';
import { Policy, PolicyStatement } from 'aws-cdk-lib/aws-iam';
import { Runtime } from 'aws-cdk-lib/aws-lambda';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import { Secret } from 'aws-cdk-lib/aws-secretsmanager';
import { Construct } from 'constructs';
export class MyStack extends Stack {
constructor(scope: Construct, id: string, props: StackProps = {}) {
super(scope, id, props);
const preSignUp = new NodejsFunction(this, 'pre-signup', {
runtime: Runtime.NODEJS_18_X,
});
const userPool = new UserPool(this, 'UserPool', {
removalPolicy: RemovalPolicy.DESTROY,
selfSignUpEnabled: true,
lambdaTriggers: {
preSignUp,
}
});
const policy = new Policy(this, "PreSignUpPolicy", {
statements: [
new PolicyStatement({
resources: [userPool.userPoolArn],
actions: [
"cognito-idp:ListUsers",
"cognito-idp:AdminLinkProviderForUser",
"cognito-idp:AdminCreateUser",
"cognito-idp:AdminSetUserPassword",
]
})
]
});
preSignUp.role!.attachInlinePolicy(policy);
const amazonCredentials = Secret.fromSecretNameV2(
this,
'AmazonCredentials',
'hosted-ui-merge/amazon',
);
const amazonProvider = new UserPoolIdentityProviderAmazon(
this,
'AmazonProvider',
{
userPool,
attributeMapping: {
email: ProviderAttribute.AMAZON_EMAIL,
givenName: ProviderAttribute.AMAZON_NAME,
},
clientId: amazonCredentials.secretValueFromJson('clientId').toString(),
clientSecret: amazonCredentials.secretValueFromJson('clientSecret').toString(),
scopes: ['profile', 'email', 'openid'],
},
);
const googleCredentials = Secret.fromSecretNameV2(
this,
'GoogleCredentials',
'hosted-ui-merge/google',
);
const googleProvider = new UserPoolIdentityProviderGoogle(
this,
'GoogleProvider',
{
userPool,
attributeMapping: {
email: ProviderAttribute.GOOGLE_EMAIL,
givenName: ProviderAttribute.GOOGLE_GIVEN_NAME,
familyName: ProviderAttribute.GOOGLE_FAMILY_NAME,
phoneNumber: ProviderAttribute.GOOGLE_PHONE_NUMBERS,
},
clientId: googleCredentials.secretValueFromJson('clientId').toString(),
clientSecretValue: googleCredentials
.secretValueFromJson('clientSecret'),
scopes: ['profile', 'email', 'openid'],
},
);
const microsoftCredentials = Secret.fromSecretNameV2(
this,
'MicrosoftCredentials',
'hosted-ui-merge/microsoft',
);
const microsoftProvider = new UserPoolIdentityProviderOidc(
this,
'MicrosoftProvider',
{
userPool,
name: 'Microsoft',
issuerUrl: microsoftCredentials.secretValueFromJson('issuerUrl').toString(),
attributeMapping: {
email: ProviderAttribute.other('email'),
preferredUsername: ProviderAttribute.other('preferred_username'),
},
clientId: microsoftCredentials.secretValueFromJson('clientId').toString(),
clientSecret: microsoftCredentials.secretValueFromJson('clientSecret').toString(),
scopes: ['profile', 'email', 'openid'],
},
);
userPool.registerIdentityProvider(amazonProvider);
userPool.registerIdentityProvider(googleProvider);
userPool.registerIdentityProvider(microsoftProvider);
const userPoolClient = userPool.addClient('UserPoolClient', {
authFlows: {
userSrp: true,
},
oAuth: {
flows: {
authorizationCodeGrant: true,
},
callbackUrls: [
"http://localhost:3000/",
"myapp://"
],
logoutUrls: [
"http://localhost:3000/",
"myapp://"
],
},
});
const domain = userPool.addDomain('Domain', {
cognitoDomain: {
domainPrefix: 'hosted-ui-merge',
},
});
const identityPool = new IdentityPool(this, 'IdentityPool', {
allowUnauthenticatedIdentities: true,
authenticationProviders: {
userPools: [
new UserPoolAuthenticationProvider({
userPool,
userPoolClient,
})
]
}
});
new CfnOutput(this, "UserPoolId", {
value: userPool.userPoolId,
});
new CfnOutput(this, "UserPoolClientId", {
value: userPoolClient.userPoolClientId,
});
new CfnOutput(this, "UserPool.HostedUI.Domain", {
value: domain.baseUrl(),
});
new CfnOutput(this, "IdentityPoolId", {
value: identityPool.identityPoolId,
});
}
}
const app = new App();
new MyStack(app, 'hosted-ui-merge', {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
}
});
app.synth();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment