Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | 1x 1x 1x 16x 16x 16x 16x 1x 1x 15x 9x 9x 9x 9x 9x 9x 6x 1x 1x 1x 5x 6x 16x 16x 13x 3x 4x 3x 3x 3x 3x 3x 4x 2x 2x 9x 9x 9x 9x 9x 9x 9x 8x 6x 6x 2x 2x 2x 2x 1x | /**
* Pre-Signup Lambda Trigger
*
* Handles three cases:
* 1. Social login (OIDC/OAuth via Google): auto-confirms user (IdP has already verified
* their identity) and links the external provider to any existing Cognito account with
* the same email to prevent duplicate accounts.
* 2. E2E test accounts (e2e-test-*@browsway.com): auto-confirms for automated testing.
* 3. Regular users: standard email confirmation required.
*/
const {
CognitoIdentityProviderClient,
ListUsersCommand,
AdminLinkProviderForUserCommand
} = require('@aws-sdk/client-cognito-identity-provider')
const cognitoClient = new CognitoIdentityProviderClient({})
exports.handler = async (event) => {
console.log('Pre-Signup event:', JSON.stringify(event, null, 2))
const { triggerSource } = event
const email = event.request.userAttributes.email
if (shouldBlockNonProdSignup(email)) {
console.log(`Blocked signup outside allowlist in non-production: ${email}`)
throw new Error('Sign-up is restricted in this environment')
}
// Social login: auto-confirm (user was already verified by the external OIDC provider)
// and attempt to link to an existing account with the same email to prevent duplicates.
if (triggerSource === 'PreSignUp_ExternalProvider') {
console.log(`Social sign-in detected (${event.userName}) - auto-confirming`)
event.response.autoConfirmUser = true
event.response.autoVerifyEmail = true
Eif (email) {
await linkToExistingAccount(event)
}
return event
}
// Auto-confirm E2E test accounts only
if (email && /^e2e-test-.+@browsway\.com$/.test(email)) {
console.log(`E2E test email detected: ${email} - auto-confirming user`)
event.response.autoConfirmUser = true
event.response.autoVerifyEmail = true
} else {
console.log(`Regular email: ${email} - requiring email confirmation`)
// Default behavior: user must confirm via email
}
return event
}
function shouldBlockNonProdSignup(email) {
const enforcementEnabled = process.env.ENFORCE_NON_PROD_SIGNUP_ALLOWLIST === 'true'
if (!enforcementEnabled || !email) {
return false
}
const allowlist = (process.env.NON_PROD_SIGNUP_ALLOWLIST || '')
.split(',')
.map((entry) => entry.trim().toLowerCase())
.filter(Boolean)
Iif (allowlist.length === 0) {
return false
}
const normalizedEmail = email.toLowerCase()
const atIndex = normalizedEmail.lastIndexOf('@')
const emailDomain = atIndex > -1 ? normalizedEmail.slice(atIndex + 1) : ''
return !allowlist.some((entry) => {
if (entry.includes('@')) {
return normalizedEmail === entry
}
return emailDomain === entry
})
}
/**
* Links an external provider identity to an existing Cognito user with the same email.
* This prevents duplicate accounts when a user signs up with email/password first,
* then later signs in with Google or Apple using the same email address.
*/
async function linkToExistingAccount(event) {
const email = event.request.userAttributes.email
// event.userPoolId is always present in Cognito trigger payloads — avoids a CDK
// circular dependency that would arise from passing it as an environment variable.
const userPoolId = event.userPoolId
// event.userName format: "Google_1234567890" or "SignInWithApple_abc123"
const separatorIndex = event.userName.indexOf('_')
const providerName = event.userName.substring(0, separatorIndex)
const providerUserId = event.userName.substring(separatorIndex + 1)
try {
const listResult = await cognitoClient.send(
new ListUsersCommand({
UserPoolId: userPoolId,
Filter: `email = "${email}"`,
Limit: 1
})
)
if (!listResult.Users || listResult.Users.length === 0) {
console.log(`No existing user found for email ${email} – new social signup`)
return
}
const existingUser = listResult.Users[0]
console.log(`Linking ${providerName} provider to existing user ${existingUser.Username}`)
await cognitoClient.send(
new AdminLinkProviderForUserCommand({
UserPoolId: userPoolId,
DestinationUser: {
ProviderName: 'Cognito',
ProviderAttributeName: 'Cognito_Subject',
ProviderAttributeValue: existingUser.Username
},
SourceUser: {
ProviderName: providerName,
ProviderAttributeName: 'Cognito_Subject',
ProviderAttributeValue: providerUserId
}
})
)
console.log(`Successfully linked ${providerName} to user ${existingUser.Username}`)
} catch (error) {
// Log but do not throw: if linking fails the sign-in still proceeds and creates a
// new account. The user can merge accounts manually if needed.
console.error('Account linking failed (non-blocking):', error)
}
}
|