Help us understand the problem. What is going on with this article?

S3 + Lambda + Cognitoを使って、簡単お問い合わせシステム構築

More than 3 years have passed since last update.

S3とLambdaとCognitoを使って、簡単でセキュアなお問い合わせフォームシステムを作ってみます。

記事執筆当時、Cognitoがバージニア/アイルランドのみ利用可能だったため、今回はS3/Lambda/Congito全部バージニア(us-east-1)に揃えています(S3とLambdaはリージョン揃える必要があります)。2015年9月以降、東京リージョンでもCognitoは利用可能となっています。

全体像

S3上にホスティングしているお問い合わせフォームから投稿すると、S3上にJSON形式で内容がuploadされます。ファイルがuploadされると、Lambdaがイベントフックして内容をGmailに送信する構造です。
お問い合わせ数の少ないサイトでは、これで十分でしょう。

SQLインジェクションとは無縁ですし、負荷も気にしなくて良いので、選択肢としてはありかなと思います。

0 準備

0-1 : S3バケットの作成

ホスティング用のS3バケットを作成します。仮にバケット名をxxx.example.comとし、Static Web HostingをONにします。

1 Cognito

フォーム投稿ボタンを押した時に、データをS3に時限でupload可能なIAM Roleを発行してもらうため、AWS CognitoのSecurity Token Serviceを使います。

1-1: Cognitoで identity pool作成

CognitoトップからCreate identity poolをします。

Identity Pool Name: 適当なプール名を入れてください
Enable Access to Unauthenticated Identities: 認証なしで、時限IAM Roleを発行します。Amazon/Facebook/Twitterなどのアカウントを要求することも可能です。

cognito0

cognito1

identity poolが作成されたら、identity pool idをメモします。

cognitox

1-2: 自動作成したIAM RoleにS3 upload権限を追加

次に自動作成したIAM Roleにs3:PutObject / s3:PutObjectAcl権限を追加します。

cognito2

cognito3

1-3: S3バケットにCORS許可

JavaScriptでS3にアクセスするため、S3にCORS(Cross-Origin Resource Sharing)を許可します。

S3バケット(xxx.example.com) => Permissions => Edit CORS Configuration でCORSを設定します。

  • Allowedmethod: PUT(upload)のみ許可します
  • AllowedOrigin: お問い合わせフォームのあるドメインを指定(*にしてしまうと、どこからでもcognitoで発行したSecurity TokenでS3 putができるようになります。S3 put攻撃されたくないので、ここは制限します)
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>xxx.example.com.s3-website-us-east-1.amazonaws.com</AllowedOrigin>
        <AllowedMethod>PUT</AllowedMethod>
        <AllowedHeader>*</AllowedHeader>
    </CORSRule>
</CORSConfiguration>

以上で、お問い合わせフォーム(xxx.example.com)からのみ、時限でs3にファイルをputできるIAM Roleが発行可能になりました。

2 お問い合わせフォーム本体

次にお問い合わせフォーム本体をs3にuploadしましょう。
今回は、「問い合わせタイトル」「返信メールアドレス」「本文」の3項目を投稿させます。
重要なのは、IdentityPoolIdの欄に、先ほどメモったIDをコピペすることです。

aws-sdk.min.jsと以下のindex.htmlをS3バケットにuploadします。

index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" type="text/css" href="import.css">
    <script src="aws-sdk.min.js"></script>
    <title>投書画面</title>
    <script>
        var $id = function(id) { return document.getElementById(id); };
        AWS.config.region = "us-east-1";
        AWS.config.credentials = new AWS.CognitoIdentityCredentials({IdentityPoolId: "Cognitoで作成したIdentityPoolId"});
        AWS.config.credentials.get(function(err) {
            if (!err) {
                console.log("Cognito Identify Id: " + AWS.config.credentials.identityId);
            }
        });

        function uploadFile() {
            AWS.config.region = 'us-east-1';
            var s3BucketName = "xxx.example.com";
            var now = new Date();
            var obj = {"title":$id("title").value, "mail":$id("mail").value ,"contents":$id("contents").value, "date": now.toLocaleString()};
            var s3 = new AWS.S3({params: {Bucket: s3BucketName}});
            var blob = new Blob([JSON.stringify(obj, null, 2)], {type:'text/plain'});
            s3.putObject({Key: "uploads/" +now.getTime()+".txt", ContentType: "text/plain", Body: blob, ACL: "public-read"},
            function(err, data){
                if(data !== null){
                    alert("お問い合わせ完了致しました");
                }
                else{
                    alert("Upload Failed" + err.message);
                }
            });
        }
    </script>
</head>
<body>
    <div class="wrapper">
        <div id="postform">
            <form>
                <table>
                    <tr>
                        <th>件名</th>
                        <td>
                            <input id="title" type="text" name="title" class="titletext fontchange" maxlength="40" value="" />
                        </td>
                    </tr>
                    <tr>
                        <th>メールアドレス</th>
                        <td>
                            <input id="mail" type="email" name="mail" size="30" maxlength="50" />
                        </td>
                    </tr>
                    <tr>
                        <th>お問い合わせ内容</th>
                        <td>
                            <TEXTAREA id="contents" cols="40" rows="6" name="contents" class="fontchange"></TEXTAREA>
                        </td>
                    </tr>
                </table>
                <div id="button_area" class="clearfix">
                    <input onClick="uploadFile();" type="button" value="投稿" id="css_button" class="button_right" />
                </div>
            </form>
        </div>
        <! --loginform-->
    </div>
    <! --wrapper -->
</body>
</html>

3 Lambda

3-1 準備

以下2つのモジュールをDLします。

npm install aws-sdk
npm install nodemailer

以上をインストールすると以下の通りnode_modulesディレクトリ以下に保存されます。

[~] ls node_modules
aws-sdk nodemailer

後ほど、作成するindex.jsと共にzipにしてLambdaにuploadします。

3-2 Lambda function

以下の感じで作成します

index.js
console.log("Loading event")
var aws = require('aws-sdk');
var s3 = new aws.S3({apiVersion: '2006-03-01'});
var mailer = require('nodemailer');

var settings = {
    service: 'Gmail',
    auth: {
        user: '送信元Gmailアドレス',
        pass: 'Gmailパスワード',
        port: 25
    }
};

var smtp = mailer.createTransport(settings);

exports.handler = function(event, context) {
    console.log('Received event:', JSON.stringify(event, null, 2));
    var bucket = event.Records[0].s3.bucket.name;
    var key = event.Records[0].s3.object.key;
    s3.getObject({Bucket: bucket, Key: key},
        function(err, data) {
            if (err){
                context.done('error', 'error getting file' + err);
            } else {
                var message = JSON.parse(data.Body);
                var options = {
                    to : '送信先Gmailアドレス',
                    replyTo : message.mail,
                    subject: message.title,
                    text: ' ' + message.contents
                };
                smtp.sendMail(options, function(error, info){
                    if (error){
                        console.log('error:' + error);
                    } else {
                        console.log('Message sent:' + info.response);
                    }
                })
            }
        }
    );
};

以上をzipで固めてLambda functionにupします

zip -r s3_form.zip index.js node_modules

function名はS3mail_semdとします。(タイポ...)

3-3: 対象S3バケットのイベントフック作成

最後に対象S3バケットで、下図の通りEvent Notificationsを設定します。

hook

以上でひと通り完了です。

確認

こんな感じで投稿すると
mail

無事Gmailで受け取れました

reply

参考サイト

https://www.system-i-enter.com/blog/blog/2015/02/03/s3/
https://www.system-i-enter.com/blog/blog/2015/02/10/aws-lambda/
http://dev.classmethod.jp/cloud/cors-cross-origin-resource-sharing-cross-domain/
http://dev.classmethod.jp/etc/about-cors/

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした