ようへいの日々精進XP

よかろうもん

Amazon S3 + Cognito で簡易アンケートシステムを作る写経(Amazon S3 で HTML フォームを実装する写経)

やりたいこと

  • S3 で構成されたサイトに HTML フォームを実装し、アンケートページ的なものを提供したい
  • 最終的には S3 の前には CloudFront を挟む予定
  • 投稿の内容は S3 に JSON で保存する

図にすると以下のような感じ。

f:id:inokara:20170205224255p:plain

参考

qiita.com

ありがとうございます。ほぼ、上記ページの写経となります。

phiary.me

HTML フォームを JavaScript から扱う方法は上記のページが参考になりました。有難うございます。

検証環境

OS

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.11.6
BuildVersion:   15G1217

AWS CLI

$ aws --version
aws-cli/1.11.19 Python/2.7.10 Darwin/15.6.0 botocore/1.4.76

Data Bucket に未認証ユーザーが書き込めるような環境を AWS CLI で用意する

Cognito や IAM 等を色々と設定する必要があるけど、AWS CLI で全部準備する。

#
# 各環境変数に値を定義
#
_PROFILE=washino-profile
_REGION=ap-northeast-1
_DOMAIN_NAME=washi.inokara.tech
_IDENTITY_POOL_NAME=washino_identity
_S3_BUCKET_NAME=data-bucket

#
# identity pool を作成
#
_IDENTITY_POOL_ID=$(aws --profile ${_PROFILE} cognito-identity create-identity-pool --identity-pool-name ${_IDENTITY_POOL_NAME} --allow-unauthenticated-identities --query IdentityPoolId --output text)
echo ${_IDENTITY_POOL_ID}

#
# S3 バケットを作成
#
aws --profile ${_PROFILE} --region ${_REGION} s3 mb s3://${_S3_BUCKET_NAME}

#
# 作成したバケットに CORS を設定(これをやっとかんといかんばい)
#
cat << EOT >> cors.json
{
  "CORSRules": [
    {
      "AllowedOrigins": ["https://${_DOMAIN_NAME}"],
      "AllowedHeaders": ["*"],
      "AllowedMethods": ["PUT"]
    }
  ]
}
EOT
aws --profile ${_PROFILE} s3api put-bucket-cors --bucket ${_S3_BUCKET_NAME} --cors-configuration file://cors.json

#
# バケットポリシーを作成
#
cat << EOT >> s3PutAllowPolicy.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "S3PutAllowPolicy",
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:PutObjectAcl"
            ],
            "Resource": "arn:aws:s3:::${_S3_BUCKET_NAME}/*"
        }
    ]
}
EOT
_POLICY_ARN=$(aws --profile ${_PROFILE} iam create-policy --policy-name s3PutAllowPolicy --policy-document file://s3PutAllowPolicy.json --query Policy.Arn --output text)
echo ${_POLICY_ARN}

#
# identity pool に付与する IAM Role を作成
#
cat << EOT >> ${_IDENTITY_POOL_NAME}_authenticated.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "cognito-identity.amazonaws.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "cognito-identity.amazonaws.com:aud": "${_IDENTITY_POOL_ID}"
        },
        "ForAnyValue:StringLike": {
          "cognito-identity.amazonaws.com:amr": "authenticated"
        }
      }
    }
  ]
}
EOT
_IAM_ROLE_ARN=$(aws --profile ${_PROFILE} iam create-role --role-name cognito_${_IDENTITY_POOL_NAME}_authenticated --assume-role-policy-document file://${_IDENTITY_POOL_NAME}_authenticated.json --query Role.Arn --output text)
aws --profile ${_PROFILE} iam attach-role-policy --role-name cognito_${_IDENTITY_POOL_NAME}_authenticated --policy-arn ${_POLICY_ARN}

#
# identity pool に IAM Role を適用する
#
aws --profile ${_PROFILE} cognito-identity set-identity-pool-roles --identity-pool-id ${_IDENTITY_POOL_ID} --roles authenticated=${_IAM_ROLE_ARN}

#
# identity pool に付与する IAM Role を作成(未認証ユーザー用)
#
cat << EOT >> ${_IDENTITY_POOL_NAME}_unauthenticated.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "cognito-identity.amazonaws.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "cognito-identity.amazonaws.com:aud": "${_IDENTITY_POOL_ID}"
        },
        "ForAnyValue:StringLike": {
          "cognito-identity.amazonaws.com:amr": "unauthenticated"
        }
      }
    }
  ]
}
EOT
_IAM_ROLE_ARN=$(aws --profile ${_PROFILE} iam create-role --role-name cognito_${_IDENTITY_POOL_NAME}_unauthenticated --assume-role-policy-document file://${_IDENTITY_POOL_NAME}_unauthenticated.json --query Role.Arn --output text)
aws --profile ${_PROFILE} iam attach-role-policy --role-name cognito_${_IDENTITY_POOL_NAME}_unauthenticated --policy-arn ${_POLICY_ARN}

#
# identity pool に IAM Role を適用する
#
aws --profile ${_PROFILE} cognito-identity set-identity-pool-roles --identity-pool-id ${_IDENTITY_POOL_ID} --roles unauthenticated=${_IAM_ROLE_ARN}

#
# 後片付け(やらなくても良い)
#
rm -f cors.json s3PutAllowPolicy.json ${_IDENTITY_POOL_NAME}_authenticated.json ${_IDENTITY_POOL_NAME}_unauthenticated.json

あとは HTML と JavaScript でフォームを用意して HTML Bucket に放り込む

HTML はこんな感じ

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <script src="aws-sdk.min.js"></script>
    <script src="demo.js"></script>
    <title>雑なアンケート</title>
</head>
<body>
     <form name='orenoForm'>
         <table>
             <tr>
                 <th>お名前</th>
                 <td>
                     <input name="_name" type="text"  maxlength="50" value="" />
                 </td>
             </tr>
             <tr>
                 <th>メールアドレス</th>
                 <td>
                     <input name="_mail" type="email" maxlength="50" value="" />
                 </td>
             </tr>
             <tr>
                 <th>性別</th>
                 <td>
                     <input name="_sex" type="radio" value="男"/></input>
                     <input name="_sex" type="radio" value="女"/></input>
                     <input name="_sex" type="radio" value="未選択" checked="checked" />未選択</input>
                 </td>
             </tr>
             <tr>
                 <th>世の中に不満を</th>
                 <td>
                     <input name="_fuman" type="radio" value="感じる"/>感じる</input>
                     <input name="_fuman" type="radio" value="感じない"/>感じない</input>
                     <input name="_fuman" type="radio" value="未選択" checked="checked" />未選択</input>
                 </td>
             </tr>
             <tr>
                 <th>2017 年ホークスは</th>
                 <td>
                     <input name="_hawks" type="radio" value="優勝する"/>優勝する</input>
                     <input name="_hawks" type="radio" value="優勝しない"/>優勝しない</input>
                     <input name="_hawks" type="radio" value="未選択" checked="checked" />未選択</input>
                 </td>
             </tr>
             <tr>
                 <th>コメントをどうぞ</th>
                 <td>
                     <TEXTAREA name="_comment" cols="40" rows="6" name="comment"></TEXTAREA>
                 </td>
             </tr>
         </table>
         <input onClick="postMessage();" type="button" value="投稿"/>
     </form>
</body>
</html>

JavaScript はこんな感じ

//
// demo.js
//
var $aws_region = "ap-northeast-1";
var $aws_cognito_identity_pool_id = "ap-northeast-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxx"; // Cognito で作成した identity pool id を設定する
var $s3_prefix = "data/201702/";
var $s3_bucket = "xxxxxxxxxxxxxxxxxxxxxxxx";

AWS.config.region = $aws_region;
AWS.config.credentials = new AWS.CognitoIdentityCredentials({IdentityPoolId: $aws_cognito_identity_pool_id});
// AWS.config.credentials.get(function(err) {
//     if (!err) {
//         console.log("Cognito Identify Id: " + AWS.config.credentials.identityId);
//     }
// });

function postMessage() {
    var form = document.forms.orenoForm;
    var now = new Date();
    var obj = {"name": form._name.value,
               "mail": form._mail.value,
               "comment": form._comment.value,
               "sex": form._sex.value,
               "fuman": form._fuman.value,
               "hawks": form._hawks.value,
               "date": now.toLocaleString()};
    var s3 = new AWS.S3({params: {Bucket: $s3_bucket}});
    var blob = new Blob([JSON.stringify(obj, null, 2)], {type:'text/plain'});
    s3.putObject({Key: $s3_prefix + now.getTime() +".json", ContentType: "text/plain", Body: blob, ACL: "public-read"},
    function(err, data){
        if(data !== null){
            location.href="thanks.html";
        }
        else{
            alert("Post Failed: " + err.message);
        }
    });
}

Chrome で見ると…

なんかアンケートページっぽいですな。

f:id:inokara:20170205214951p:plain

アンケートに答えると…

以下のように S3 バケットに JSON データが保存される。

bash-3.2$ ls -tr *.json
1486298468151.json      1486298522074.json      1486298702117.json
bash-3.2$ cat 1486298702117.json | jq .
{
  "date": "2017/2/5 21:45:02",
  "hawks": "優勝する",
  "fuman": "感じない",
  "sex": "",
  "comment": "ああああ",
  "mail": "hanako@example.com",
  "name": "やまだはなこ"
}

集計とか

S3 の put イベントを拾って Lambda と組み合わせる例が紹介されているけど、とりあえずは Google SpreadSheet に結果を突っ込んでおけば良さそうということで、以下の記事のように Google SpreadSheet に突っ込んでおくことにする。

inokara.hateblo.jp

Elasticsearch とかに突っ込んでも面白そうだけど。

以上

メモでした。