Amazon Cognito のローカル開発用エミュレータを作ろうとした話

自分が関わっているサービスで、認証に Amazon Cognito を使っています。

Amazon Cognito(ウェブ/モバイルアプリのユーザー管理)| AWS

Cognito は公式でエミュレータ的なものは提供されていないようなので、ローカル環境では開発用の Cognito に繋いでいます。

実物の Cognito を使う場合、メールをモックサーバに飛ばせないので誤送信の可能性があったり、ユーザーの追加が面倒だったりと、個人的に不便を感じていました。

そのため、Cognito の動きを再現するエミュレータを作りました。

今回は自分が必要な最低限の機能だけ実装しています。足りない機能は適宜追加するつもりです。

とりあえず以下の機能を実装しました。

  • 公開鍵の取得
  • 認証
  • ユーザー作成

エンドポイント

Cognito のエンドポイントは恐らく以下の2つだけです。

1. GET https://cognito-idp.{region}.amazonaws.com/{user-pool-id}/.well-known/jwks.json
2. POST https://cognito-idp.{region}.amazonaws.com/
  1. は公開鍵の取得、2. はそれ以外の操作(認証やユーザーの作成、削除)に使います。

公開鍵の取得

  1. のエンドポイントを叩くと、以下のような json が返ってきます。
{  
  "keys": [  
    {  
      "alg": "RS256",  
      "e": "XXXX",  
      "kid": "xxxxkid",  
      "kty": "RSA",  
      "n": "xxxx...",  
      "use": "sig"  
    },
    ...
  ]  
}

RSA で暗号化しているようなので、まずは鍵を作成します。以下を参考にしました。

jwt-goを用いた、GolangでのJWTの発行、認証について - Qiita
GolangでJWT(JSON Web Token)を取り扱いたいと思い、jwt-goの使い方を調べました。 JWTについての説明は他の有益な解説サイトがあるので、そちらを参考にして下さい。 早速実装の説明に入ります。 Rou...
Encode Private Key getting Error: asn1: structure error: tags don’t match
When i try to parses the encode private key with sample private key -----BEGIN OPENSSH PRIVATE KEY-----b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn

ターミナルで以下を実行します。

$ ssh-keygen -t rsa -f key.pem -m pem  
$ ssh-keygen -f key.pem.pub -e -m pkcs8 > key.pem.pub.pkcs8

key.pem が秘密鍵、key.pem.pub.pkcs8 が公開鍵です。

key.pem.pub.pkcs8を以下のように読み込んで整形します。

import (
"encoding/base64"
"encoding/binary"
"encoding/json"
"io/ioutil"
"github.com/dgrijalva/jwt-go"
)
const jwkKeyID = "hoge"
type jwkKey struct {
Alg string `json:"alg"`
E string `json:"e"`
Kid string `json:"kid"`
Kty string `json:"kty"`
N string `json:"n"`
Use string `json:"use"`
}
func getPublicKey() (string, error) {
verifyBytes, err := ioutil.ReadFile("./key.pem.pub.pkcs8")
if err != nil {
return "", err
}
verifyKey, err := jwt.ParseRSAPublicKeyFromPEM(verifyBytes)
if err != nil {
return "", err
}
encodedN := base64.RawURLEncoding.EncodeToString(verifyKey.N.Bytes())
bytesE := make([]byte, 4)
binary.BigEndian.PutUint32(bytesE, uint32(verifyKey.E))
encodedE := base64.RawURLEncoding.EncodeToString(bytesE)
pubKey, err := json.Marshal(jwkKey{
Alg: jwt.SigningMethodRS256.Alg(),
E: encodedE,
Kid: jwkKeyID,
Kty: "RSA",
N: encodedN,
Use: "sig",
})
if err != nil {
return "", err
}
return string(pubKey), nil
}

kid は鍵を識別するための ID ですが、後述する token の header にセットした値と一致していれば何でも良いです。

整形した鍵を json で返すエンドポイントを作成します。

import (
"fmt"
"log"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
func main() {
pubKeyString, err := getPublicKey()
if err != nil {
panic(err)
}
mux := chi.NewRouter()
mux.Use(middleware.Logger)
mux.Get("/{userPoolID}/.well-known/jwks.json", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(fmt.Sprintf(`{"keys":[%s]}`, pubKeyString)))
})
log.Println(http.ListenAndServe(":80", mux))
}

恐らく実際はuserPoolID ごとに異なる鍵を返していると思いますが、面倒なので全ての ID で同じ 鍵を返しています。

これで公開鍵の取得エンドポイントは完成です。

ユーザー情報

実際にどんな感じで保存しているかは分かりませんが、ここでは json 形式でファイルに保存するようにしました。

{
"user-pool-id-hoge": {
"username-hoge": {
"uuid": "208b377c-e86e-3f1d-a23c-cdab84d86901",
"password": "password",
"username": "username-hoge",
"email": "email@example.com",
"email_verified": true
}
}
}
import (
"encoding/json"
"fmt"
"sync"
)
const poolFileName = "pool.json"
func main() {
data, err := ioutil.ReadFile(poolFileName)
if err != nil {
panic(err)
}
poolMap := make(map[UserPoolID]UserMap)
err = json.Unmarshal(data, &poolMap)
if err != nil {
panic(err)
}
userPool = UserPool{
poolMap: poolMap,
mutex: &sync.Mutex{},
}
...
}
type User struct {
UUID string `json:"uuid"`
Password string `json:"password"`
Username Username `json:"username"`
Email string `json:"email"`
EmailVerified bool `json:"email_verified"`
}
type Username string
type UserMap map[Username]*User
type UserPoolID string
type UserPool struct {
poolMap map[UserPoolID]UserMap
mutex *sync.Mutex
}
func (pool UserPool) GetUser(userPoolID UserPoolID, username Username) (*User, bool) {
pool.mutex.Lock()
defer pool.mutex.Unlock()
return pool.getUser(userPoolID, username)
}
func (pool UserPool) getUser(userPoolID UserPoolID, username Username) (*User, bool) {
uMap, ok := pool.poolMap[userPoolID]
if !ok {
return nil, false
}
u, ok := uMap[username]
if !ok {
return nil, false
}
return u, true
}
func (pool UserPool) CreateUser(userPoolID UserPoolID, user User) error {
pool.mutex.Lock()
defer pool.mutex.Unlock()
uMap, ok := pool.poolMap[userPoolID]
if !ok {
uMap = make(UserMap)
pool.poolMap[userPoolID] = uMap
}
if _, ok := uMap[user.Username]; ok {
return fmt.Errorf("already exist")
}
uMap[user.Username] = &user
return pool.updateFile()
}
func (pool UserPool) updateFile() error {
file, err := os.Create(poolFileName)
if err != nil {
return err
}
defer file.Close()
content, err := json.Marshal(pool.poolMap)
if err != nil {
return err
}
_, err = file.Write(content)
return err
}
func (pool UserPool) DeleteUser(userPoolID UserPoolID, username Username) error {
pool.mutex.Lock()
defer pool.mutex.Unlock()
if _, exist := pool.getUser(userPoolID, username); exist {
delete(pool.poolMap[userPoolID], username)
}
return pool.updateFile()
}
view raw cognito_user_pool.go hosted with ❤ by GitHub

main 実行時にpool.json を読み込み、map で保持します。map にユーザーを追加、削除する度にファイルを更新しています。パフォーマンスは悪そうですが、そこまで気にしてないので良しとしています。

ユーザー情報を拡張する場合は構造体のフィールドを追加するなどして対応できると思います。

ルーティング

公開鍵の取得以外は全て同じエンドポイントですが、リクエストの識別にはヘッダを使用しています。

X-Amz-Target というキーにAWSCognitoIdentityProviderService.AdminInitiateAuth のような形式でセットされているので、これを使います。

import (
"fmt"
"io/ioutil"
"log"
"net/http"
"strings"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
)
func main() {
...
mux.Post("/", func(w http.ResponseWriter, r *http.Request) {
b, err := ioutil.ReadAll(r.Body)
if err != nil {
render.JSON(w, r, http.StatusInternalServerError)
return
}
defer r.Body.Close()
out, err := route(r.Header.Get("X-Amz-Target"), b)
if err != nil {
log.Println(err)
render.JSON(w, r, http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
w.Write(out)
})
log.Println(http.ListenAndServe(":80", mux))
}
func route(xAmzTarget string, body []byte) ([]byte, error) {
targets := strings.Split(xAmzTarget, ".")
if len(targets) < 2 {
return nil, fmt.Errorf("invalid header")
}
switch targets[1] {
case "AdminInitiateAuth":
return adminInitiateAuth(body)
case "AdminCreateUser":
return adminCreateUser(body)
...
}
return nil, fmt.Errorf("invalid operation name")
}
view raw cognito_route.go hosted with ❤ by GitHub

認証

公開鍵と同じように秘密鍵を読み込みます。clientID は環境変数から読み取ります。

import (
"crypto/rsa"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"time"
"github.com/aws/aws-sdk-go/service/cognitoidentityprovider"
"github.com/dgrijalva/jwt-go"
)
var signKey *rsa.PrivateKey
var clientID ClientID
type ClientID string
func main() {
...
signKey, err = getPrivateKey()
if err != nil {
panic(err)
}
clientID = ClientID(os.Getenv("CLIENT_ID"))
...
}
func getPrivateKey() (*rsa.PrivateKey, error) {
signBytes, err := ioutil.ReadFile("./key.pem")
if err != nil {
return nil, err
}
return jwt.ParseRSAPrivateKeyFromPEM(signBytes)
}
func (u User) ToToken(userPoolID UserPoolID) *jwt.Token {
token := jwt.New(jwt.SigningMethodRS256)
token.Header["kid"] = jwkKeyID
claims := token.Claims.(jwt.MapClaims)
claims["iat"] = time.Now().Unix()
claims["exp"] = time.Now().Add(time.Hour * 24 * 365 * 10).Unix()
claims["sub"] = u.UUID
claims["email"] = u.Email
return token
}
func adminInitiateAuth(body []byte) ([]byte, error) {
in := cognitoidentityprovider.AdminInitiateAuthInput{}
err := json.Unmarshal(body, &in)
if err != nil {
return nil, err
}
// TODO: まだ ADMIN_NO_SRP_AUTH だけ
if in.AuthFlow == nil || *in.AuthFlow != cognitoidentityprovider.AuthFlowTypeAdminNoSrpAuth {
return nil, fmt.Errorf("invalid auth flow")
}
var cID ClientID
if in.ClientId != nil {
cID = ClientID(*in.ClientId)
}
if cID != clientID {
return nil, fmt.Errorf("invalid client id")
}
var userPoolID UserPoolID
if in.UserPoolId != nil {
userPoolID = UserPoolID(*in.UserPoolId)
}
var username Username
if u, ok := in.AuthParameters["USERNAME"]; ok && u != nil {
username = Username(*u)
}
u, ok := userPool.GetUser(
userPoolID,
username,
)
if !ok {
return nil, fmt.Errorf("user not found")
}
if !u.EmailVerified {
return nil, fmt.Errorf("email not verified")
}
var password string
if p, ok := in.AuthParameters["PASSWORD"]; ok && p != nil {
password = *p
}
if u.Password != password {
return nil, fmt.Errorf("password not match")
}
tokenString, err := u.ToToken(userPoolID).SignedString(signKey)
if err != nil {
return nil, err
}
return json.Marshal(cognitoidentityprovider.AdminInitiateAuthOutput{
AuthenticationResult: &cognitoidentityprovider.AuthenticationResultType{
AccessToken: &[]string{tokenString}[0],
},
})
}

requestBody はgithub.com/aws/aws-sdk-go/service/cognitoidentityprovider の構造体を使ってそのままパースできます。

claims には最低限の情報をセットしていますが、実際に使うには足りないと思うので、以下を参考に必要に応じて情報を追加します。

Using the ID Token - Amazon Cognito
Using the ID Token

読み込んだ秘密鍵で署名し、トークン文字列に変換します。ここではAccessToken にセットしていますが、使い方によってはIdToken 等にセットするかもしれません。

ユーザー作成

import (
"encoding/json"
"fmt"
"math/rand"
"github.com/aws/aws-sdk-go/service/cognitoidentityprovider"
"github.com/gofrs/uuid"
)
func adminCreateUser(body []byte) ([]byte, error) {
in := cognitoidentityprovider.AdminCreateUserInput{}
err := json.Unmarshal(body, &in)
if err != nil {
return nil, err
}
var userPoolID UserPoolID
if in.UserPoolId != nil {
userPoolID = UserPoolID(*in.UserPoolId)
}
var username Username
if in.Username != nil {
username = Username(*in.Username)
}
if in.MessageAction != nil && *in.MessageAction == "RESEND" {
if _, exist := userPool.GetUser(userPoolID, username); !exist {
return nil, fmt.Errorf("user not found")
}
// TODO: パスワード変更して通知メール再送信
return json.Marshal(cognitoidentityprovider.AdminCreateUserOutput{})
}
var email string
for _, attr := range in.UserAttributes {
if attr.Name == nil || attr.Value == nil {
continue
}
if *attr.Name == "email" {
email = *attr.Value
}
}
// TODO: email_verified が true ならパスワードメール送信, false なら検証メール送信
// 常にtrueとして扱っている
if email == "" {
return nil, fmt.Errorf("invalid email")
}
id, err := uuid.NewV4()
if err != nil {
return nil, err
}
pass, err := makeRandomStr(8)
if err != nil {
return nil, err
}
idString := id.String()
user := User{
UUID: idString,
Password: pass,
Username: username,
Email: email,
EmailVerified: true,
}
err = userPool.CreateUser(userPoolID, user)
if err != nil {
return nil, err
}
return json.Marshal(cognitoidentityprovider.AdminCreateUserOutput{
User: &cognitoidentityprovider.UserType{
Attributes: []*cognitoidentityprovider.AttributeType{
{
Name: &[]string{"sub"}[0],
Value: &[]string{idString}[0],
},
},
},
})
}
func makeRandomStr(digit uint32) (string, error) {
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@&%/:;,."
b := make([]byte, digit)
if _, err := rand.Read(b); err != nil {
return "", err
}
var result string
for _, v := range b {
result += string(letters[int(v)%len(letters)])
}
return result, nil
}

全て検証済みのユーザーとして作成しています。メール送信部分は省いていますが、そのうち追加予定です。

ランダム文字列生成は以下を参考にしました。

golang でランダム文字列生成 - Qiita
Go では乱数を返すライブラリはあれど、ランダム文字列を返すライブラリがない(少なくとも今の所は)。というわけで、パスワードなどでランダムな文字列を生成したい場合は自分で書く必要がある。 ちなみに乱数を生成するには math/ra...

アプリ側の設定

Cognito のクライアント生成時に、特定の環境変数を設定している場合はそちらに繋ぐように変更します。

var endpoint *string
cognitoEndpoint = os.Getenv("COGNITO_ENDPOINT")
if cognitoEndpoint != "" {
endpoint = aws.String(cognitoEndpoint)
}
client := cognitoidentityprovider.New(
session.Must(session.NewSessionWithOptions(session.Options{
Config: aws.Config{Endpoint: endpoint},
SharedConfigState: session.SharedConfigEnable,
})),
)
Configuring the AWS SDK for Go - AWS SDK for Go
Configure the |sdk-go| to specify which credentials to use and to which AWS Region to send requests.

まとめ

すべてまとめると以下になります。コードが散らかってるのでもう少し整理したいところです…

汎用的なものが作れたら Docker Hub に上げようかなと思っていましたが、中々難しそうです。

package main
import (
"crypto/rsa"
"encoding/base64"
"encoding/binary"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"math/rand"
"net/http"
"os"
"strings"
"sync"
"time"
"github.com/aws/aws-sdk-go/service/cognitoidentityprovider"
"github.com/dgrijalva/jwt-go"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/render"
"github.com/gofrs/uuid"
)
var userPool UserPool
var clientID ClientID
var signKey *rsa.PrivateKey
const jwkKeyID = "hoge"
const poolFileName = "pool.json"
func main() {
data, err := ioutil.ReadFile(poolFileName)
if err != nil {
panic(err)
}
poolMap := make(map[UserPoolID]UserMap)
err = json.Unmarshal(data, &poolMap)
if err != nil {
panic(err)
}
userPool = UserPool{
poolMap: poolMap,
mutex: &sync.Mutex{},
}
clientID = ClientID(os.Getenv("CLIENT_ID"))
signKey, err = getPrivateKey()
if err != nil {
panic(err)
}
pubKeyString, err := getPublicKey()
if err != nil {
panic(err)
}
mux := chi.NewRouter()
mux.Use(middleware.Logger)
mux.Get("/{userPoolID}/.well-known/jwks.json", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(fmt.Sprintf(`{"keys":[%s]}`, pubKeyString)))
})
mux.Post("/", func(w http.ResponseWriter, r *http.Request) {
b, err := ioutil.ReadAll(r.Body)
if err != nil {
render.JSON(w, r, http.StatusInternalServerError)
return
}
defer r.Body.Close()
out, err := route(r.Header.Get("X-Amz-Target"), b)
if err != nil {
log.Println(err)
render.JSON(w, r, http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
w.Write(out)
})
log.Println(http.ListenAndServe(":80", mux))
}
type User struct {
UUID string `json:"uuid"`
Password string `json:"password"`
Username Username `json:"username"`
Email string `json:"email"`
EmailVerified bool `json:"email_verified"`
}
func (u User) ToToken(userPoolID UserPoolID) *jwt.Token {
token := jwt.New(jwt.SigningMethodRS256)
token.Header["kid"] = jwkKeyID
claims := token.Claims.(jwt.MapClaims)
claims["iat"] = time.Now().Unix()
claims["exp"] = time.Now().Add(time.Hour * 24 * 365 * 10).Unix()
claims["sub"] = u.UUID
claims["email"] = u.Email
return token
}
type ClientID string
type Username string
type UserMap map[Username]*User
type UserPoolID string
type UserPool struct {
poolMap map[UserPoolID]UserMap
mutex *sync.Mutex
}
func (pool UserPool) GetUser(userPoolID UserPoolID, username Username) (*User, bool) {
pool.mutex.Lock()
defer pool.mutex.Unlock()
return pool.getUser(userPoolID, username)
}
func (pool UserPool) getUser(userPoolID UserPoolID, username Username) (*User, bool) {
uMap, ok := pool.poolMap[userPoolID]
if !ok {
return nil, false
}
u, ok := uMap[username]
if !ok {
return nil, false
}
return u, true
}
func (pool UserPool) CreateUser(userPoolID UserPoolID, user User) error {
pool.mutex.Lock()
defer pool.mutex.Unlock()
uMap, ok := pool.poolMap[userPoolID]
if !ok {
uMap = make(UserMap)
pool.poolMap[userPoolID] = uMap
}
if _, ok := uMap[user.Username]; ok {
return fmt.Errorf("already exist")
}
uMap[user.Username] = &user
return pool.updateFile()
}
func (pool UserPool) updateFile() error {
file, err := os.Create(poolFileName)
if err != nil {
return err
}
defer file.Close()
content, err := json.Marshal(pool.poolMap)
if err != nil {
return err
}
_, err = file.Write(content)
return err
}
func (pool UserPool) DeleteUser(userPoolID UserPoolID, username Username) error {
pool.mutex.Lock()
defer pool.mutex.Unlock()
if _, exist := pool.getUser(userPoolID, username); exist {
delete(pool.poolMap[userPoolID], username)
}
return pool.updateFile()
}
type jwkKey struct {
Alg string `json:"alg"`
E string `json:"e"`
Kid string `json:"kid"`
Kty string `json:"kty"`
N string `json:"n"`
Use string `json:"use"`
}
func getPublicKey() (string, error) {
verifyBytes, err := ioutil.ReadFile("./key.pem.pub.pkcs8")
if err != nil {
return "", err
}
verifyKey, err := jwt.ParseRSAPublicKeyFromPEM(verifyBytes)
if err != nil {
return "", err
}
encodedN := base64.RawURLEncoding.EncodeToString(verifyKey.N.Bytes())
bytesE := make([]byte, 4)
binary.BigEndian.PutUint32(bytesE, uint32(verifyKey.E))
encodedE := base64.RawURLEncoding.EncodeToString(bytesE)
pubKey, err := json.Marshal(jwkKey{
Alg: jwt.SigningMethodRS256.Alg(),
E: encodedE,
Kid: jwkKeyID,
Kty: "RSA",
N: encodedN,
Use: "sig",
})
if err != nil {
return "", err
}
return string(pubKey), nil
}
func getPrivateKey() (*rsa.PrivateKey, error) {
signBytes, err := ioutil.ReadFile("./key.pem")
if err != nil {
return nil, err
}
return jwt.ParseRSAPrivateKeyFromPEM(signBytes)
}
func route(xAmzTarget string, body []byte) ([]byte, error) {
log.Println(xAmzTarget)
targets := strings.Split(xAmzTarget, ".")
if len(targets) < 2 {
return nil, fmt.Errorf("invalid header")
}
switch targets[1] {
case "AdminInitiateAuth":
return adminInitiateAuth(body)
case "AdminCreateUser":
return adminCreateUser(body)
}
return nil, fmt.Errorf("invalid operation name")
}
func adminInitiateAuth(body []byte) ([]byte, error) {
in := cognitoidentityprovider.AdminInitiateAuthInput{}
err := json.Unmarshal(body, &in)
if err != nil {
return nil, err
}
// TODO: まだ ADMIN_NO_SRP_AUTH だけ
if in.AuthFlow == nil || *in.AuthFlow != cognitoidentityprovider.AuthFlowTypeAdminNoSrpAuth {
return nil, fmt.Errorf("invalid auth flow")
}
var cID ClientID
if in.ClientId != nil {
cID = ClientID(*in.ClientId)
}
if cID != clientID {
return nil, fmt.Errorf("invalid client id")
}
var userPoolID UserPoolID
if in.UserPoolId != nil {
userPoolID = UserPoolID(*in.UserPoolId)
}
var username Username
if u, ok := in.AuthParameters["USERNAME"]; ok && u != nil {
username = Username(*u)
}
u, ok := userPool.GetUser(
userPoolID,
username,
)
if !ok {
return nil, fmt.Errorf("user not found")
}
if !u.EmailVerified {
return nil, fmt.Errorf("email not verified")
}
var password string
if p, ok := in.AuthParameters["PASSWORD"]; ok && p != nil {
password = *p
}
if u.Password != password {
return nil, fmt.Errorf("password not match")
}
tokenString, err := u.ToToken(userPoolID).SignedString(signKey)
if err != nil {
return nil, err
}
return json.Marshal(cognitoidentityprovider.AdminInitiateAuthOutput{
AuthenticationResult: &cognitoidentityprovider.AuthenticationResultType{
AccessToken: &[]string{tokenString}[0],
},
})
}
func adminCreateUser(body []byte) ([]byte, error) {
in := cognitoidentityprovider.AdminCreateUserInput{}
err := json.Unmarshal(body, &in)
if err != nil {
return nil, err
}
var userPoolID UserPoolID
if in.UserPoolId != nil {
userPoolID = UserPoolID(*in.UserPoolId)
}
var username Username
if in.Username != nil {
username = Username(*in.Username)
}
if in.MessageAction != nil && *in.MessageAction == "RESEND" {
if _, exist := userPool.GetUser(userPoolID, username); !exist {
return nil, fmt.Errorf("user not found")
}
// TODO: パスワード変更して通知メール再送信
return json.Marshal(cognitoidentityprovider.AdminCreateUserOutput{})
}
var email string
for _, attr := range in.UserAttributes {
if attr.Name == nil || attr.Value == nil {
continue
}
if *attr.Name == "email" {
email = *attr.Value
}
}
// TODO: email_verified が true ならパスワードメール送信, false なら検証メール送信
// 常にtrueとして扱っている
if email == "" {
return nil, fmt.Errorf("invalid email")
}
id, err := uuid.NewV4()
if err != nil {
return nil, err
}
pass, err := makeRandomStr(8)
if err != nil {
return nil, err
}
idString := id.String()
user := User{
UUID: idString,
Password: pass,
Username: username,
Email: email,
EmailVerified: true,
}
err = userPool.CreateUser(userPoolID, user)
if err != nil {
return nil, err
}
return json.Marshal(cognitoidentityprovider.AdminCreateUserOutput{
User: &cognitoidentityprovider.UserType{
Attributes: []*cognitoidentityprovider.AttributeType{
{
Name: &[]string{"sub"}[0],
Value: &[]string{idString}[0],
},
},
},
})
}
func makeRandomStr(digit uint32) (string, error) {
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@&%/:;,."
b := make([]byte, digit)
if _, err := rand.Read(b); err != nil {
return "", err
}
var result string
for _, v := range b {
result += string(letters[int(v)%len(letters)])
}
return result, nil
}
view raw cognito_mock.go hosted with ❤ by GitHub