서버리스 설치 후 서버리스 프로젝트를 생성해 줍니다.
npx serverless create --template aws-nodejs --path beforSignUp
인라인 편집기를 사용하지 않는 이유는 무엇인가요?
Cognito와 GraphQL 서버인 백엔드에 접속하기 위해서 aws-sdk와 graphql-request 모듈을 설치하기 때문입니다.
GraphQL 요청은 모듈을 사용하지 않고 어떻게든 보낼 수 있는데, aws-sdk는 Cognito User Pool에 접속하기 위해서 필수입니다.
그래서 GraphQL도 그냥 모듈을 사용했습니다.
외부 모듈을 설치하려면 인라인 편집기는 사용할 수 없습니다.
yarn add aws-sdk graphql-request
serverless.yml
서버리스 프로젝트에는 serverless.yml 파일이 있습니다.
서버리스가 생성하고 배포할 AWS 서비스들을 설정하는 파일입니다.
양이 매우 많아보이는데 주석을 제거하면 별거 없습니다.
service: beforesignup
provider:
name: aws
runtime: nodejs12.x
region: ap-northeast-2
functions:
hello:
handler: handler.verifier
function 밑에 hello는 람다 함수 이름이 됩니다. 원하는 이름으로 바꿔주세요. 전 실수로 못바꿨네요.
함수 이름 아래 handler는 람다 함수가 실행할 함수를 나타냅니다. handler.verifier는 handler.js의 verifier 함수를 실행한다는 것을 의미합니다. 원래는 handler.hello인데 전 handler.verifier로 변경했습니다.
region은 한국에서 서비스한다면 ap-northeast-2(서울)이 좋겠죠.
handler.js
서버리스 프로젝트 생성시 serverless.js와 같이 생성되는 파일입니다.
handler.js 파일에 있다고 무조건 실행되는 것이 아니라, serverless.yml에 지정을 해 주어야 실행됩니다.
제가 임시로 사용하는 Pre-SignUp 함수입니다.
'use strict';
const { GraphQLClient } = require('graphql-request');
const AWS = require('aws-sdk');
module.exports.verifier = async (event, context, callback) => {
console.log("event.request: ", event.request);
const cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider({
apiVersion: '2016-04-19',
region: 'ap-northeast-2'
});
const graphQLClient = new GraphQLClient(process.env.ENDPOINT);
let firstName = "";
let lastName = "";
let name = ""
let picture = "";
if (event.request.userAttributes.hasOwnProperty("given_name")) {
firstName = event.request.userAttributes.given_name;
}
if (event.request.userAttributes.hasOwnProperty("family_name")) {
lastName = event.request.userAttributes.family_name;
}
if (event.request.userAttributes.hasOwnProperty("name")) {
name = event.request.userAttributes.name;
}
if (event.request.userAttributes.hasOwnProperty("picture")) {
picture = event.request.userAttributes.picture;
} else {
picture = process.env.DEFAULT_IMAGE
}
// Set the email as verified if it is in the request
if (event.request.userAttributes.hasOwnProperty("email")) {
const email = event.request.userAttributes.email;
const params = {
"Filter": `email = \"${email}\"`,
"Limit": 1,
"UserPoolId": process.env.USER_POOL_ID
};
const { Users } = await cognitoidentityserviceprovider.listUsers(params).promise()
console.log("cog_data", Users)
if(Users.length > 0) {
console.log("Cognito에 이미 동일한 이메일을 사용하는 사용자가 존재합니다.")
callback("EmailExistError_Cognito");
} else {
const userName = event.userName;
const createAccountMutation =
`mutation{
createAccount(userName: "${userName}", email: "${email}", firstName: "${firstName}", lastName: "${lastName}", name: "${name}", picture: "${picture}")
}`;
console.log("Prisma에 계정 생성 요청")
const { createAccount } = await graphQLClient.request(createAccountMutation);
console.log("Prisma에 계정 생성")
console.log(createAccount);
if(createAccount === "ExistEmailError") {
callback("EmailExistError_Prisma");
} else if(createAccount === "ExistUserNameError") {
callback("UserNameExistError_Prisma");
}
}
//event.response.autoVerifyEmail = true;
}
// Return to Amazon Cognito
callback(null, event);
};
하나 하나 알아봅시다.
먼저 매개값으로 전달받은 event.request 객체를 출력해 볼까요?
Cognito 소셜 로그인 추가 (실전) - 구글에서 속성을 매핑해 주었기 때문에 Cognito에 직접 가입한 경우나 구글을 통해서 가입한 경우나 event의 속성 이름은 동일합니다.
Cognito에 직접 가입한 경우
직접 가입할 때 성, 이름, 이메일, 패스워드만 입력받도록 코딩했기 때문에 전송된 정보가 더 적습니다.
event.request: {
userAttributes: {
last_name: '권',
first_name: '준동',
email: 'mark8125@naver.com'
},
validationData: null
}
구글을 통해서 가입한 경우
Cognito 소셜 로그인 추가 (실전) - 구글에서 인증 범위를 profile email openid로 설정해 주었기 때문에 더 많은 정보가 넘어옵니다.
특히 picture를 데이터베이스에 저장해 놓으면 사용자가 느끼기에 더 친숙할 것입니다.
event.request: {
userAttributes: {
email_verified: 'true',
'cognito:email_alias': '9wonjoondong@gmail.com',
name: '권준동',
'cognito:phone_number_alias': '',
given_name: '준동',
family_name: '권',
email: '9wonjoondong@gmail.com',
picture: 'https://lh6.googleusercontent.com/-tax6FzeL4HI/AAAAAAAAAAI/AAAAAAAAAAA/AAKWJJMy9-KUijHTV48EwH2hQI8NpEfxHw/s96-c/photo.jpg'
},
validationData: {}
}
Cognito User Pool 객체 얻기
const cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider({
apiVersion: '2016-04-19',
region: 'ap-northeast-2'
});
GraphQL 클라이언트 객체 얻기
ENDPOINT는 람다 환경변수로 정해 놓은 GrpahQL 백앤드 접속 경로입니다.
인스타그램 클론코딩에선 백앤드가 연결된 API 게이트웨이 주소로 설정했습니다.
const graphQLClient = new GraphQLClient(process.env.ENDPOINT);
evnet 객체에서 사용자 정보 추출
let firstName = "";
let lastName = "";
let name = ""
let picture = "";
if (event.request.userAttributes.hasOwnProperty("given_name")) {
firstName = event.request.userAttributes.given_name;
}
if (event.request.userAttributes.hasOwnProperty("family_name")) {
lastName = event.request.userAttributes.family_name;
}
if (event.request.userAttributes.hasOwnProperty("name")) {
name = event.request.userAttributes.name;
}
if (event.request.userAttributes.hasOwnProperty("picture")) {
picture = event.request.userAttributes.picture;
} else {
picture = process.env.DEFAULT_IMAGE
}
event 객체에 email 속성이 있다면,
if (event.request.userAttributes.hasOwnProperty("email")) {
...
}
event 객체에서 이메일 추출
const email = event.request.userAttributes.email;
Cognito User Pool 객체를 사용하여 해당 User Pool에 동일한 이메일을 사용하는 사용자가 있는지 확인
const params = {
"Filter": `email = \"${email}\"`,
"Limit": 1,
"UserPoolId": process.env.USER_POOL_ID
};
const { Users } = await cognitoidentityserviceprovider.listUsers(params).promise()
참조 : https://docs.aws.amazon.com/ko_kr/cognito/latest/developerguide/how-to-manage-user-accounts.html
백엔드에 계정 복제 요청
위에서 event.request만 출력했는데, userName은 request와 같은 레벨로 전송되는 속성입니다.
사용자가 로그인할 때 사용하는 ID라고 보면 됩니다.
그리고 mutation.createAccount GrapqQL 요청식은 GraqpQL API에 대한 이해가 필요합니다.
하지만 언뜻 보기에 GraphQL 서버인 백엔드에 event에서 추출한 사용자 정보를 사용하여 계정 생성을 요청하는 것 같지 않나요?
다음에 포스팅하겠지만 백엔드의 mutation.createAccount Resolver는 내부적으로 데이터베이스에서 동일한 이메일이 있는지를 체크합니다.
만약 동일한 userName이나 이메일을 사용하는 사용자가 있다면 "EmailExistError_Prisma" 또는 "UserNameExistError_Prisma" 문자열을 반환할 것입니다.
const userName = event.userName;
const createAccountMutation =
`mutation{
createAccount(userName: "${userName}", email: "${email}", firstName: "${firstName}", lastName: "${lastName}", name: "${name}", picture: "${picture}")
}`;
const { createAccount } = await graphQLClient.request(createAccountMutation);
데이터베이스에 동일한 userName이나 이메일이 있다면 오류 반환
if(createAccount === "ExistEmailError") {
callback("EmailExistError_Prisma");
} else if(createAccount === "ExistUserNameError") {
callback("UserNameExistError_Prisma");
}
이상없다면 다음 단계로 진행
callback(null, event);
참고 : AWS Lambda 핸들러 구조
배포
그냥 아래 명령어를 입력하면 AWS 람다가 생성됩니다.
그리고 Cognito에서 사전 가입 트리거로 선택해 주면 끝납니다.
npx serverless deploy
'AWS > Lambda' 카테고리의 다른 글
AWS Lambda 핸들러 구조 (0) | 2020.05.13 |
---|