🐴
Cognitoへのサインアップをトリガに動くLambdaをGoで書く
そもそも何がやりたかったか
Cognitoへのユーザの追加時に、ユーザが所属するテナントを判断して該当のCognitoグループに所属させるといったことを実現したかったです。
これを実現するため、以下のような設計を行いました。
前提として、ユーザが所属するテナントは予めアプリケーションのDBに保存されているものとします。(管理者ユーザはメールアドレスや所属テナントなど入力してDBに保存し、同時にユーザを招待するようなイメージ)
やったこと
Serverless Framework + Goを使って実装しました。
また、前提として、Cognitoはすでに作成済みのものを使いました。
1.serverless.ymlの記述
ベースとして、Serverless Frameworkのaws-go-mod
を使用しました。
(以下コマンドでテンプレートを生成できます。)
sls create --template aws-go-mod --path cognito-sign-up
こちらで生成されたベースに対して、以下のようにserverless.ymlを変更します。
(本記事と関係ない部分の記述は省略しています。)
provider:
iam:
role:
statements:
- Effect: "Allow"
Action:
- "cognito-idp:*"
Resource:
- "*"
functions:
sign-up:
handler: bootstrap
events:
- cognitoUserPool:
pool: ${self:custom.cognito.${self:provider.stage}.poolName}
trigger: PostConfirmation # サインアップ後
existing: true
vpc:
securityGroupIds:
- !Ref CognitoSignInLambdaSecurityGroup
subnetIds: ${self:custom.vpc.${self:provider.stage}.subnetIds}
以下、補足です。
- 関数のトリガとなるイベントとして、
cognitoUserPool
のPostConfirmation
を設定します。 - 既存のCognitoを使う時は、
existing: true
を設定する必要があります。 - VPC内のDBにアクセスするために、
vpc
の項目を設定していますが、DBへのアクセスが不要であれば設定も不要です。
2.Goの関数の記述
以下、長々とコードを貼り付けますがポイントは以下2点です。
ちなみに、コードの箇所は本記事とは関係ない部分は省略しているので、そのまま貼り付けただけでは動かないかもしれません。
引数と戻り値の型
-
引数
-
aws-lambda-go/events
のCognitoEventUserPoolsPostConfirmation
を受け取れます。 - コードでも記載しているように、このリクエストから、Cognitoのユーザ名やemailを取得できます。
-
-
戻り値
- 引数で渡されたrequestと、errorがあればそれを返却します。
公式ドキュメントの該当箇所は以下です。
レスポンスは5秒以内に返却する必要がある
以下の公式ドキュメントの重要な考慮事項
に記載の通り、レスポンスは5秒以内に返却する必要があり、しかもこの値は変更することができません。
仮に5秒以上かかる処理を実行した場合、ログインに失敗する形となります。
import (
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider"
)
func Handler(request events.CognitoEventUserPoolsPostConfirmation) (events.CognitoEventUserPoolsPostConfirmation, error) {
// サインインしたユーザの情報を取得
userName := request.UserName
email := request.Request.UserAttributes["email"]
// DB接続
db, err := sql.Open("postgres", "host="+dbHost+" dbname="+dbName+" user="+dbUser+" password="+dbPass)
if err != nil {
log.Panicln("DB Connection Error: ", err)
}
defer db.Close()
// ユーザが所属しているテナントを取得
query := `SELECT "company"."companyCode" FROM "user" INNER JOIN "company" ON "user"."companyId" = "company"."id" WHERE "user"."email" = $1`
rows, err := db.Query(query, email)
if err != nil {
log.Panicln("DB Query Error: ", err)
}
defer rows.Close()
var companyCodes []string
for rows.Next() {
var code string
err := rows.Scan(&code)
if err != nil {
log.Panicln("Row Scan Error: ", err)
}
companyCodes = append(companyCodes, code)
}
// ユーザを各テナントのCognitoグループに追加
cfg, _ := config.LoadDefaultConfig(context.TODO()) // 外部リソース(~/.aws/config など)からコンフィグ情報 (aws.Config) を生成
client := cognitoidentityprovider.NewFromConfig(cfg)
for i := 0; i < len(companyCodes); i++ {
_, err := client.AdminAddUserToGroup(context.TODO(), &cognitoidentityprovider.AdminAddUserToGroupInput{
GroupName: aws.String(companyCodes[i]),
Username: aws.String(userName),
UserPoolId: aws.String(cognitoUserPoolId),
})
if err != nil {
log.Panicln("AdminAddUserToGroup Error: ", err)
}
}
return request, nil
}
Discussion