LoginSignup
10
11

More than 5 years have passed since last update.

サーバーレス(HTML on S3)にGoogle認証付きS3アップローダーを作る

Last updated at Posted at 2017-11-14

更新(2017/11/18)

  • S3にあげるたびに更新が起こるのを阻止
  • HTML5のvalidationを使う
  • アップロードが完了した場合内容を画面上部に表示

やりたいこと

  • S3上にHTMLとJSファイルをおいて静的webページとして公開
  • webページに入力した内容をcsvにまとめてS3に出力
  • 認証はgoogleのOpenID Connectを使用(roleをつけておく)
  • バケットポリシーでIP制限をかけておく

S3でwebページ公開に関して

以下のサイトを参考にして公開
S3で静的ウェブサイトをホスティングしてみる
バケットポリシーも作っておく

googleのOpen Connect周り

以下のサイトを参考にクライアントID+認証周り作成
Googleの「OpenID Connect」を利用する為の「クライアントID」の取得方法

スクリーンショット 2017-11-13 18.23.27.png
*承認済みのJavaScript生成元を設定せずにしばらくはまっていました...

Googleと連携するroleの作成

AWSのコンソール画面で
IAM > role > roleの作成
と進み、ウェブIDのタブを選択してプロバイダーにGoogleを選択
AudienceにはクライアントIDを入力してpolicyのattachなどを行う
スクリーンショット 2017-11-15 16.49.52.png

例で用いたpolicyはS3の特定のフォルダにのみアクセス可能なものにしました

policy.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "s3:PutObject",
                "s3:PutObjectAcl"
            ],
            "Resource": [
                "arn:aws:s3:::BUCKET_NAME/output/*"
            ],
            "Effect": "Allow"
        },
        {
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::BUCKET_NAME"
            ],
            "Effect": "Allow",
            "Condition": {
                "StringEquals": {
                    "s3:prefix": "output"
                }
            }
        }
    ]
}

いよいよJavascript作成

フォルダ構成

  • BUCKET
    • output
    • HTML
      • index.html
      • favicon.ico
      • logic.js

ログイン周り

以下のURLを参考にJavascriptを作成(ログイン箇所に関しては例をそのまま使用)
ブラウザの JavaScript
Web Federated Identity Examples

index.html
<!DOCTYPE html>
<html>
<head>
    <link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
    <title>AWS SDK for JavaScript - Sample Application</title>
    <meta charset="utf-8"/>
    <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://sdk.amazonaws.com/js/aws-sdk-2.1.12.min.js"></script>
</head>

<body>
<div id="results" role="alert"></div>
<nav class="navbar navbar-default">
    <div class="container">
        <!-- 2.ヘッダ情報 -->
        <div class="navbar-header">
            <a class="navbar-brand">S3アップローダー</a>
        </div>
    </div>
</nav>


<div class="container" id="form">
    <form>
        <div class="form-group" id="name">
            <label>名前</label>
            <input type="text" name="name" class="form-control" placeholder="申請 太郎" required>
        </div>
        <div class="form-group" id="account_flg">
            <label>アカウント</label>
            <select name="account_flg" class="form-control">
                <option value="不要">不要</option>
                <option value="必要">必要</option>
            </select>
        </div>
        ...
        <button type="button" onclick="upload_csv();" class="btn btn-primary">申請登録</button>
    </form>
</div>
<span
        id="login"
        class="g-signin"
        data-height="short"
        data-callback="loginToGoogle"
        data-cookiepolicy="single_host_origin"
        data-requestvisibleactions="http://schemas.google.com/AddActivity"
        data-scope="https://www.googleapis.com/auth/plus.login">
</span>
<script>
    //以下ほぼサンプルのコピペ
    //s3 = null;  s3をアップローダーでも使いたいので後々グローバル変数で指定しています
    var clientID = '************.apps.googleusercontent.com'; // Google client ID
    var roleArn = 'arn:aws:iam::************:role/ROLE_NAME';
    document.getElementById('login').setAttribute('data-clientid', clientID);
    function loginToGoogle(response) {
        if (!response.error) {
            AWS.config.credentials = new AWS.WebIdentityCredentials({
                RoleArn: roleArn,
                WebIdentityToken: response.id_token
            });
            s3 = new AWS.S3();
            console.log('You are now logged in.');
        } else {
            console.log('There was a problem logging you in.');
        }
    }
    (function () {
        var po = document.createElement('script');
        po.type = 'text/javascript';
        po.async = true;
        po.src = 'https://apis.google.com/js/client:plusone.js';
        var s = document.getElementsByTagName('script')[0];
        s.parentNode.insertBefore(po, s);
    })();
</script>
<script src="logic.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
</body>
</html>

アップローダー周り

logic.js
//ここに必要な項目のIDを書いておく
csv_array = [
    'name',
    'account_flg'
]

//メインのところ(どのcsvを作成するか)
function upload_csv() {
   //連続で投稿する場合に結果画面を一度空欄にする
    document.getElementById('results').innerHTML = "";
    //inputタグを指定してvalidation確認
    var validity_list = document.getElementById("form").getElementsByTagName("div");
    for (var i = 0, l = validity_list.length; i < l; i++) {
        var validity_zone = validity_list[i].getElementsByTagName("input")[0];
        if (!validity_zone) {
            continue;
        }
        if (!validity_zone.checkValidity()) {
            alert(''+validity_list[i].getElementsByTagName("label")[0].innerHTML + ''+ validity_zone.validationMessage);
            return;
        }
    }
    //成功・失敗検知用
    success_flg = true;
    upload('account', make_csv(csv_array));
    //終わった後上部に移動して結果をalertで通知
   $("html,body").animate({scrollTop: 0}, 'fast');
    if (success_flg) {
        alert('Success upload');
    } else {
        alert("Fail upload ...");
    }
    //画面更新を防ぐ
    return false;
}

//配列情報から変数(csv)にヘッダー情報と内容を入れる関数
function make_csv(array) {
    var csv = '';
    for (i in array) {
        var block = document.getElementById(array[i]);
        csv += block.getElementsByTagName("label")[0].innerHTML + ',';
    }
    csv = csv.slice(0,-1);
    csv += '\n';
    for (i in array) {
        var block = document.getElementById(array[i]);
        if (block.getElementsByTagName("select")[0]){
            var num = block.getElementsByTagName("select")[0].selectedIndex;
            csv += block.getElementsByTagName("select")[0].options[num].value + ',';
        } else {
            csv += block.getElementsByTagName("input")[0].value + ',';
        }
    }
    csv = csv.slice(0,-1);
    return csv;
}

//出来上がったcsvファイルをアップロードする関数
function upload(service_name, body) {
    var results = document.getElementById('results');
    var params = {
        Bucket: 'BUCKET_NAME',
        Key: 'output/' + service_name + '_' + timestamp() + '.csv', //名前が被らないようにtimestam付
        ContentType: 'csv',
        Body: body
    };
    s3.putObject(params, function (err, data) {
        if (err) {
            success_flg = false;
        }
        //実行結果を画面上部に表示
        results.setAttribute("class", !err ? 'alert alert-success':'alert alert-danger' );
        if (!err){
            results.innerHTML = results.innerHTML + "<strong>【"+service_name+"】</strong>";
        }else{
            results.innerHTML = results.innerHTML + "<strong>ERROR【"+service_name+"】</strong>";
        }
    });
}

//タイムスタンプ作成用関数
function timestamp() {
    var d = new Date();
    var year = d.getFullYear();
    var month = d.getMonth() + 1;
    var day = d.getDate();
    var hour = ( d.getHours() < 10 ) ? '0' + d.getHours() : d.getHours();
    var min = ( d.getMinutes() < 10 ) ? '0' + d.getMinutes() : d.getMinutes();
    return '' + year + month + day + hour + min;
}

終わりに

IP制限をかけておけば簡単に社内サービスを作成できます
単にローカルファイルを上げるだけであれば、htnl上でファイル取得をしてuploadの関数を使えばできます
サーバーレス素晴らしい

10
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
11