Amazon API Gateway,lambda,cognitoで認証して、Datasets参照・保存

あくまでもサンプルです。このままは使えないかと
組み合わせれば、なんとかなりそうなパターンを整理

サーバー上でcoginitoを使ってますが、
セッションエラーになるので基本はクライアントサイドで実行します。

#やりたいこと

* 未認証のRequestは、coginto.identityIdを返却
* 外部サービス認証のIDでcoginto.identityIdを参照
* Authロールにcredentialを更新
* 外部サービス認証時に認証情報をDatasetに保存
* coginto.identityIdからDatasetを参照


今回は後に共通処理としてまとめれる用に、ひとつの`exports.handler`でまとめてある(手抜き)
appIdは本来、認証をするためのパラメータ(id,pass)とか

####実行環境

* Amazon API Gateway
* Lambda (Node.js runtime)
* Cognito

####npm
```
var _ = require('lodash');
var Q = require("q");
var AWS = require('aws-sdk');
```

AWSはlambdaで実行するのに必要ではないけども、ローカル開発用
Qはlambdaのnodejsが`0.10.36`なので
[lambda current-supported-versions](http://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html)


# 未認証のRequestは、coginto.identityIdを返却
とりあえずGETでidentityIdを送る前提

```
var identityId = null;

var createUser = function(data){
  var d = Q.defer();
  AWS.config.credentials = new AWS.CognitoIdentityCredentials({
    IdentityId: identityId,
    IdentityPoolId: IDENTITY_POOLID
  });
  AWS.config.credentials.get(function(err) {
    if (err) {
      d.resolve({success: false});
      return;
    }
    identityId = AWS.config.credentials.identityId;
    d.resolve({
      identityId: identityId
    });
  });
  return d.promise;
}
```

# 外部サービス認証のIDでcoginto.identityIdを参照

`my.app`はcognitoで設定したもの
本来はここで独自認証の処理を行い、userIDやtokenをLoginに登録
setTimeoutは認証の代わり

TokenはAuthenticated RoleでのAWSの接続に利用(今回はつかっていない)

```
var authUser = function(data){
  var d = Q.defer();

  //ここで外部認証処理...
  setTimeout(function(){

    //結果を保持する
    var params = {
      IdentityPoolId: IDENTITY_POOLID,
      IdentityId: identityId,
      Logins: {
        'my.app': data.user
      }
    };
    cognitoidentity.getOpenIdTokenForDeveloperIdentity(params, function(err, res){
      if(err){
        d.resolve({success: false});
        return;
      }
      identityId = res.IdentityId;
      d.resolve(res);
    });

  }, 500);
  return d.promise;
};
```

# Authロールにcredentialを更新
getOpenIdTokenForDeveloperIdentityで取得したTokenでcredentialを更新することで、ロールを切り変える

```
cognitoidentity.getOpenIdTokenForDeveloperIdentity(params, function(err, res){
  if(err){
    return;
  }
      
  //credentialを更新
  AWS.config.credentials = new AWS.WebIdentityCredentials({
  	RoleArn: "arn:aws:iam:xxxxxxx:role/Cognito_myAppAuth_Role",
   	WebIdentityToken: res.Token
  });

  var dynamodb = new AWS.DynamoDB();
  dynamodb.scan({
  	TableName: 'xxxxxx'
  },function(err,data){
    //tableを取得
  });
});
```

# coginto.identityIdからDatasetを参照
ここはそのまま

```
var cognitosync = new AWS.CognitoSync();

var listData = function(data){
  var d = Q.defer();
  if(!identityId){
    d.resolve({
      success: false
    });
    return d.promise;
  }

  cognitosync.listRecords({
    DatasetName: COGNITO_DATASET_NAME,
    IdentityId: identityId,
    IdentityPoolId: IDENTITY_POOLID
  }, function(err, res){
    if (err) {
      d.resolve({success: false});
      return;
    }
    console.log(parseData(res.Records));
    d.resolve(res); 
  });
  return d.promise;
};
```

# 外部サービス認証時に認証情報をDatasetに保存

Recordに外部接続情報等を保持

```
var addData = function(data) {
  var d = Q.defer();
  
  if(!identityId){
    d.resolve({
      success: false
    });
    return d.promise;
  }
  
  var params = {
    DatasetName: COGNITO_DATASET_NAME,
    IdentityId: identityId,
    IdentityPoolId: IDENTITY_POOLID,
    SyncSessionToken: data.SyncSessionToken,
    RecordPatches: [{
        Key: 'USER_ID',
        Op: 'replace',
        SyncCount: data.DatasetSyncCount,
        Value: 'aaaaaaaaaaaaa'
    }]
  };
  cognitosync.updateRecords(params, function(err, data) {
    if(err){
      d.resolve({success: false});
      return;
    }
    d.resolve(parseData(data.Records)); 
  });
  return d.promise;
}
```

#実行結果

###identityIdなし
https://xxxxxxx.execute-api.ap-northeast-1.amazonaws.com/production/cognito

``` response
{
 	identityId: "ap-northeast-1:2b967234-8a98-4dbf-945f-ba9581981a06"
}
```

###identityIdあり
https://xxxxxxx.execute-api.ap-northeast-1.amazonaws.com/production/cognito?identityId=ap-northeast-1:2b967234-8a98-4dbf-945f-ba9581981a06

``` response
{
	identityId: "ap-northeast-1:2b967234-8a98-4dbf-945f-ba9581981a06",
	userdata: {
		USER_ID: "aaaaaaaaaaaaa"
	}
}
```

###外部の認証情報(appId)を渡す
https://xxxxxxx.execute-api.ap-northeast-1.amazonaws.com/production/cognito?appId=user1

```
{
	identityId: "ap-northeast-1:dc94f109-993a-457e-b40e-86042a1fbe67",
	userdata: {
		USER_ID: "aaaaaaaaaaaaa"
	}
}
```

### identityId, appId の優先度
appIdが優先される。
Identity poolに既にある場合は`Linked logins：DISABLED`となる

# 全文
アカウントはダミー

``` index.js
/**
 * cognito test
 *
 */
"use strict";

var _ = require('lodash');
var Q = require("q");
var AWS = require('aws-sdk');
AWS.config.region = 'ap-northeast-1';
var IDENTITY_POOLID = 'ap-northeast-1:XXXXXXXXX';
var COGNITO_DATASET_NAME = 'userData';

var cognitoidentity = new AWS.CognitoIdentity();
var cognitosync = new AWS.CognitoSync();
var identityId = null; 


/**
 * 認証情報がない場合はUnauthenticated Userを作成
 */
var createUser = function(data){
  var d = Q.defer();
  AWS.config.credentials = new AWS.CognitoIdentityCredentials({
    IdentityId: identityId,
    IdentityPoolId: IDENTITY_POOLID
  });
  AWS.config.credentials.get(function(err) {
    if (err) {
      d.resolve({success: false});
      return;
    }
    identityId = AWS.config.credentials.identityId;
    d.resolve({
      identityId: identityId
    });
  });
  return d.promise;
}


/**
 * Developer Authenticated 認証
 */
var authUser = function(data){
  var d = Q.defer();

  //ここで外部認証処理...
  setTimeout(function(){

    //結果を保持する
    var params = {
      IdentityPoolId: IDENTITY_POOLID,
      IdentityId: identityId,
      Logins: {
        'my.app': data.user
      }
    };
    cognitoidentity.getOpenIdTokenForDeveloperIdentity(params, function(err, res){
      if(err){
        d.resolve({success: false});
        return;
      }
      identityId = res.IdentityId;
      d.resolve(res);
    });

  }, 500);
  return d.promise;
};


/**
 * List store Data
 */
var listData = function(data){

  var d = Q.defer();
  if(!identityId){
    d.resolve({
      success: false
    });
    return d.promise;
  }

  cognitosync.listRecords({
    DatasetName: COGNITO_DATASET_NAME,
    IdentityId: identityId,
    IdentityPoolId: IDENTITY_POOLID
  }, function(err, res){
    if (err) {
      d.resolve({success: false});
      return;
    }
    console.log(parseData(res.Records));
    d.resolve(res); 
  });
  return d.promise;
};


/**
 * add store Data
 */
var addData = function(data) {
  var d = Q.defer();
  
  if(!identityId){
    d.resolve({
      success: false
    });
    return d.promise;
  }
  
  var params = {
    DatasetName: COGNITO_DATASET_NAME,
    IdentityId: identityId,
    IdentityPoolId: IDENTITY_POOLID,
    SyncSessionToken: data.SyncSessionToken,
    RecordPatches: [{
        Key: 'USER_ID',
        Op: 'replace',
        SyncCount: data.DatasetSyncCount,
        Value: 'aaaaaaaaaaaaa'
    }]
  };
  cognitosync.updateRecords(params, function(err, data) {
    if(err){
      d.resolve({success: false});
      return;
    }
    d.resolve(parseData(data.Records)); 
  });
  return d.promise;
}

/**
 * Records を Object に変換
 */
var parseData= function(rec){
  var obj = {};
  _.each(rec, function(r){
    obj[r.Key] = r.Value;
  });
  return obj;
};

exports.handler = function (e, context) {
  var query = e.queryParameters || {};
  
  //認証情報ない
  if(!query.identityId && !query.appId){
    createUser().done(function(res){
      context.succeed(res);
    });
    return;
  }

  //appIdがない
  if(!query.appId){
    identityId = query.identityId;
    Q.when()
    .then(createUser)
    .then(listData)
    .then(addData)
    .done(function(res){
      context.succeed({
        identityId:identityId,
        userdata:res
      });
    });
    return;
  }

  if(query.identityId) {
    identityId = query.identityId;
  }

  Q.when({
    user: query.appId
  })
  .then(authUser)
  .then(listData)
  .then(addData)
  .done(function(res){
    context.succeed({
      identityId:identityId,
      userdata:res
    });
  });
};
```

#参考リンク
* http://mobile.awsblog.com/post/Tx2323YHCM0I7OO/Understanding-Amazon-Cognito-Authentication-Part-4-Enhanced-Flow
* http://qiita.com/imaifactory/items/b66cdf91118e3be6fe17