Programming/node.js2017.02.02 11:23
Programming/node.js2016.12.27 23:04
NPM : https://github.com/voltrue2/in-app-purchase
var iap = require('in-app-purchase');

iap.config({
    verbose: true,
    //googlePublicKeyStrSandbox: "", //LIVE 먼저 실행하고 에러나면 SandBox 테스트실행함
    googlePublicKeyStrLive: "MII...............",
    googleAccToken: "6/h...........",
    googleRefToken: "9/d...........",
    googleClientID: "99.......kv.7dc.ap...ps.g....nt.com",
    googleClientSecret: "9.....4Qu"
});

iap.setup(function (error) {
    if (error) {
        // error hmm
    }
    var googleReceipt = {
        "data":'{"packageName":"kr.co.test","productId":"kr.co.test.is1","purchaseTime":155435321,
                 "purchaseState":0,"purchaseToken":"oksdf........"}', //{stringified data object}
        "signature": "qSVwa8E9z........."
    };
    iap.validate(iap.GOOGLE, googleReceipt, function (error, response) {
        if (error) {
            console.log(error);
        }
        if (iap.isValidated(response)) {
            var purchaseDataList = iap.getPurchaseData(response);
            console.log(purchaseDataList);
        }
    });
});


API 통신 로직 : node_modules\in-app-purchase\lib\google.js 참고

/** * Function to check subscription status in Google Play * @param {Object} data receipt data * @param {Function} cb callback function */ function checkSubscriptionStatus(data, cb) { data.service = constants.SERVICES.GOOGLE; if (!checkSubscriptionState) { return cb(null, data); } var packageName = data.packageName; var subscriptionID = data.productId; var purchaseToken = data.purchaseToken; var url = 'https://www.googleapis.com/androidpublisher/v2/applications/' + packageName + '/purchases/subscriptions/' + subscriptionID + '/tokens/' + purchaseToken; var state; var getSubInfo = function (next) { verbose.log(NAME, 'Get subscription info from', url); getSubscriptionInfo(url, function (error, response, body) { if (error || 'error' in body) { verbose.log(NAME, 'Failed to get subscription info from', url, error, body); state = constants.VALIDATION.FAILURE; // we must move on to validate() next(); return; }


1. googleClientID / googleClientSecret 생성하기

 1) 설정 => API 액세스 => 기존프로젝트 연결 또는 새프로젝트만들기

 

 2) Google 개발자 콘솔 들어가기 ( https://console.developers.google.com/apis/library?project=xxxx )

3) 사용자 인증 ( 프로젝트 확인 꼭!! ) oauth 생성 > 웹어플리케이션 > 리디렉션 URL ( https://developers.google.com/oauthplayground ) > clientId 및 clientSecret 확인



2. Access 및 Refresh Tokens 얻기

 1) https://developers.google.com/oauthplayground 이동 > 우측설정에서 CilentID와 Client secret 입력


2) Google Play Developer API v2 > 개발자 계정 확인 및 관리 허용 


3) Access Token (googleAccToken) / Refresh Token (googleRefToken)


※ Access Not Configured. Google Play Developer API has not been used in project

   => Google Play Android Developer API 사용으로 설정



※ The project id used to call the Google Play Developer API has not been linked in the Google Play Developer Console.

=> 설정 > API 액세스 > 프로젝트가 잘 연결되었는지 OAUTH 클라이언트 ID가 존재하는지 확인 할것



※  errors: [ [Object] ], code: 400, message: 'Invalid Value' } } 

   => 판매되는 상품에 따라서 lib/google.js 에서 url 정보를 purchases/subscriptions => purchases/products 

        변경해야되는 이슈 있음 해당 NPM 보니까 구독(subscriptions) 경우만 지원하므로
        자세한 내용은 아래 API 레퍼런스 참고 해서 수정 하거나 소스 참고해서 새로 짜는 방안도 있다.

        https://developers.google.com/android-publisher/api-ref/purchases/products
        https://developers.google.com/android-publisher/api-ref/purchases/subscriptions

         



Posted by 시니^^
Programming/node.js2016.12.27 20:33

NPM : https://github.com/voltrue2/in-app-purchase


var iap = require('in-app-purchase');
iap.config({
    //verbose: true,
    //googlePublicKeyStrSandbox: "", //LIVE 먼저 실행하고 에러나면 SandBox 테스트실행함
    googlePublicKeyStrLive: "MIIBIj............",
});


iap.setup(function (error) {
    if (error) {
        console.log(error);
    }
    var googleReceipt = {
        "data":'{"packageName":"kr.co.test","productId":"kr.co.test.is1","purchaseTime":155435321,
                 "purchaseState":0,"purchaseToken":"oksdf........"}', //{stringified data object}
        "signature": "qSVwa8E9z........."
    };
    iap.validate(iap.GOOGLE, googleReceipt, function (error, response) {
        if (error) {
            console.log(error);
        }
        console.log(iap.isValidated(response));
    });
});


유니티에서 받는 데이터 중  GoogleReceipt data 값 어떤 걸로 넣는지 찾는데 삽질 2시간 OTL....... 



Hash 검증 로직 : node_modules\in-app-purchase\lib\google.js 참고

function validatePublicKey(receipt, pkey, cb) {
    if (!receipt || !receipt.data) {
        return cb(new Error('missing receipt data'));
    }
    if (!pkey) {
        return cb(new Error('missing public key'));
    }
    if (typeof receipt.data !== 'string') {
        return cb(new Error('receipt.data must be a string'));
    }
    var validater = crypto.createVerify('SHA1');
    var valid;
    validater.update(receipt.data);
    try {
        valid = validater.verify(pkey, receipt.signature, 'base64');
    } catch (error) {
        return cb(error);
    }
    if (valid) {
        // validated successfully
        var data = JSON.parse(receipt.data);
        data.status = constants.VALIDATION.SUCCESS;
        return cb(null, data);
    }
    // failed to validate
    cb(new Error('failed to validate purchase'));
}


GooglePublicKey는 해당 어플리케이션 들어가서 확인 



Posted by 시니^^
Programming/node.js2016.12.22 11:01

https://github.com/expressjs/body-parser


A new body object containing the parsed data is populated on the request object after the middleware (i.e. req.body). This object will contain key-value pairs, where the value can be a string or array (when extended is false), or any type (when extended is true).


POST FORM (Content-Type:application/x-www-form-urlencoded)

$ curl --noproxy '*' -i -XPOST 'http://localhost/post_test' -d 'array[key1][key2]=233'
app.use(bodyParser.urlencoded({ extended: false }));
 // req.body : { 'array[key1][key2]': '233' }
app.use(bodyParser.urlencoded({ extended: true })); 
// req.body : { array: { key1: { key2: '233' } } }


Posted by 시니^^
Programming/node.js2016.12.20 14:28


Node.js 

const crypto = require("crypto");
const algo = 'aes-128-cbc';
const key = "security_key";
const iv = "iv_key";
const value = 'HelloWorld';
const keyBuffer = new Buffer(crypto.createHash('md5').update(key).digest('hex'),"hex");
const ivBuffer = new Buffer(crypto.createHash('md5').update(iv).digest('hex'),"hex");
const textBuffer = new Buffer(value);

let cipher = crypto.createCipheriv(algo, keyBuffer,ivBuffer); let encrypted = cipher.update(textBuffer); let encryptedFinal = cipher.final(); let encryptedText = encrypted.toString('base64') + encryptedFinal.toString('base64'); console.log(encryptedText); //xr046x2iGy4IDUHUm+p7lA==

let decipher = crypto.createDecipheriv(algo, keyBuffer,ivBuffer); decipher.setAutoPadding(true);//padding 처리가 언어별 달라서 확인필요 let decipheredContent = decipher.update(encryptedText,'base64','utf8'); decipheredContent += decipher.final('utf8'); console.log(decipheredContent); //HelloWorld


C# (Unity3D)

출처 : http://wyseburn.tistory.com/entry/AES-CBC-128BIT-PKCS5-%EC%95%94%EB%B3%B5%ED%98%B8%ED%99%94-1

using UnityEngine; using System; using System.Text; using System.IO; using System.Security.Cryptography; using System.Runtime.Remoting.Metadata.W3cXsd2001; public class Crypto { public static readonly string key = MD5Hash("security_key"); public static readonly string iv = MD5Hash("iv_key"); public static string MD5Hash(string str) { MD5 md5 = new MD5CryptoServiceProvider(); byte[] hash = md5.ComputeHash(Encoding.ASCII.GetBytes(str)); StringBuilder stringBuilder = new StringBuilder(); foreach (byte b in hash) { stringBuilder.AppendFormat("{0:x2}", b); } return stringBuilder.ToString(); } private static byte[] GetBytesFromHexString(string strInput) { byte[] bytArOutput = new byte[] { }; if ((!string.IsNullOrEmpty(strInput)) && strInput.Length % 2 == 0) { SoapHexBinary hexBinary = null; hexBinary = SoapHexBinary.Parse(strInput); bytArOutput = hexBinary.Value; } return bytArOutput; } //AES 암호화 public static string AES128Encrypt(string Input) { try { RijndaelManaged aes = new RijndaelManaged(); //aes.KeySize = 256; //AES256으로 사용시 aes.KeySize = 128; //AES128로 사용시 aes.BlockSize = 128; aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.PKCS7; aes.Key = GetBytesFromHexString(key); aes.IV = GetBytesFromHexString(iv); var encrypt = aes.CreateEncryptor(aes.Key, aes.IV); byte[] xBuff = null; using (var ms = new MemoryStream()) { using (var cs = new CryptoStream(ms, encrypt, CryptoStreamMode.Write)) { byte[] xXml = Encoding.UTF8.GetBytes(Input); cs.Write(xXml, 0, xXml.Length); } xBuff = ms.ToArray(); } string Output = Convert.ToBase64String(xBuff); return Output; } catch (Exception ex) { Debug.LogError(ex.Message); return ex.Message; } } //AES 복호화 public static string AES128Decrypt(string Input) { try { RijndaelManaged aes = new RijndaelManaged(); //aes.KeySize = 256; //AES256으로 사용시 aes.KeySize = 128; //AES128로 사용시 aes.BlockSize = 128; aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.PKCS7; aes.Key = GetBytesFromHexString(key); aes.IV = GetBytesFromHexString(iv); var decrypt = aes.CreateDecryptor(); byte[] xBuff = null; using (var ms = new MemoryStream()) { using (var cs = new CryptoStream(ms, decrypt, CryptoStreamMode.Write)) { byte[] xXml = Convert.FromBase64String(Input); cs.Write(xXml, 0, xXml.Length); } xBuff = ms.ToArray(); } string Output = Encoding.UTF8.GetString(xBuff); return Output; } catch (Exception ex) { Debug.LogError(ex.Message); return string.Empty; } } }


Posted by 시니^^
Programming/node.js2015.12.08 21:33

https://github.com/felixge/node-mysql


Mysql 모듈 보면 CreatePool 후 Query 방식이 두가지가 있다.


Pooling connections

1번안

var mysql = require('mysql');
var pool  = mysql.createPool({
  connectionLimit : 10,
  host            : 'example.org',
  user            : 'bob',
  password        : 'secret'
});

pool.query('SELECT 1 + 1 AS solution', function(err, rows, fields) {
  if (err) throw err;

  console.log('The solution is: ', rows[0].solution);
});

2번안

var mysql = require('mysql');
var pool  = mysql.createPool(...);

pool.getConnection(function(err, connection) {
  // Use the connection
  connection.query( 'SELECT something FROM sometable', function(err, rows) {
    // And done with the connection.
    connection.release();

    // Don't use the connection here, it has been returned to the pool.
  });
});


차이점을 2번의 경우에는 커넥션을 하나 지정해서 해당 커넥션으로해서 쿼리를 처리하고 마지막에 해당 커넥션을 다사용했다고

반환해주는 방식이다.


단순히 일반 SELECT 쿼리 위주의 API 개발일 경우 1번안이 편할 수 있지만 트랜젹센처리나 INSERT 후 Last Insert Id 값을 가져오거나 Found_rows 같은 처리된 Row의 정보를 가져와야 할때는 2번안으로해서 같은 커넥션으로 처리 되어야지만 제대로 된 값을 가져 올 수 있다.


그리고 2번안의 경우 사용 후에 connection.release 해주지 않을 경우 반환하지 않아서 connection limit가 걸릴 수 있다. 

Posted by 시니^^
Programming/node.js2015.06.25 12:55


node.js v.0.5부터 json 파일 require()로 가져 올 수 있다.

그런데... 형식에 조금만 벗어나도 에러가 발생하는데.. 형식은... 잘 맞추면 되는데요..

문제는....주석을 넣어도 에러가 난다 orz 

( 당연히 다른 라이브러리나 file 읽어서 하는 방법도 있지만 기본 기능을 그대로 쓰고 싶음...)

$ vi test.js
var testJson = require('./test.json');
console.log(testJson);

$ vi test.json
{
    //comment test
    "key1":"test1",
    "key2":"test2",
}
$ node test.js
module.js:485
    throw err;
          ^
SyntaxError: /data/webroot/test.json: Unexpected token /
    at Object.parse (native)
    at Object.Module._extensions..json (module.js:482:27)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Module.require (module.js:364:17)
    at require (module.js:380:17)
    at Object.<anonymous> (/data/webroot/test.js:1:78)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)


그래서 구글링 결과!!

http://stackoverflow.com/questions/244777/can-i-comment-a-json-file

{ //코멘트라는 키를 생성해버리면됨
   "_comment": "comment text goes here...",
   "glossary": {
{ //키명을 똑같이 해서 위에 설명을 넣어도될듯
  "api_host" : "The hostname of your API server. You may also specify the port.",
  "api_host" : "hodorhodor.com",

생각의 차이~!! 아래처럼 구분할 수있게 ㅎㅎ 어자피 해당 키는 안쓰면 되니까 ㅎㅎ

$ vi test.json
{
    "*****COMMENT*****" : "test comment",
    "key1":"test1",
    "*****COMMENT*****" : "test comment11",
    "key2":"test2",
    "*****COMMENT*****" : ""
}
$ node test.js
{ '*****COMMENT*****': '', key1: 'test1', key2: 'test2' }


Posted by 시니^^
Programming/node.js2015.06.24 16:45

 JSON Web Token (JWT) 

http://jwt.io/

- Restful API 설계과정에서 토큰 인증방식 고민하다가 보게된 JWT


구조는 아주 심플하고 간단함


1. 토큰 데이터의 구조

 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0MTIzMTIzNTQzNTM0NTY3ODkwIiwibmFtZSI6IkpvaDM0NTM0NTM0NW4gRG9lIiwiYWRtaW4iOnRydWV9.gUpPTlD6M3F264lbRyXa6lat7t1tqoP3MHOwFX1qies


1) 첫번째 인자 서명키 생성 방식에 대한 정보 (header) 

⇒ base64인코딩한 { "alg": "HS256",  "typ": "JWT" }

 : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9


2) 두번째 인자 실제 데이터 정보 (payload)

⇒ base64인코딩한 {  "sub": "1234123123543534567890", "name": "Joh345345345n Doe", "admin": true }

eyJzdWIiOiIxMjM0MTIzMTIzNTQzNTM0NTY3ODkwIiwibmFtZSI6IkpvaDM0NTM0NTM0NW4gRG9lIiwiYWRtaW4iOnRydWV9


3) 세번째 인자 데이터에 대한 무결성 / 변조 방지를 위한 HMAC 

⇒ HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),ServerkeyData)

gUpPTlD6M3F264lbRyXa6lat7t1tqoP3MHOwFX1qies


2. node.js 구현

https://github.com/auth0/node-jsonwebtoken

※ 기타 언어 라이브러리 참고 http://jwt.io/#libraries


1) npm 설치

$ npm install jsonwebtoken
jsonwebtoken@5.0.2 node_modules/jsonwebtoken
└── jws@3.0.0 (jwa@1.0.0, base64url@1.0.4)

2) jwt 토큰 생성 및 실행
var jwt      = require('jsonwebtoken');
var tokenKey = "TEST_KEY11"; //토큰키 서버에서 보관 중요
var payLoad  = {'uid':14554};
var token = jwt.sign(payLoad,tokenKey,{
    algorithm : 'HS256', //"HS256", "HS384", "HS512", "RS256", "RS384", "RS512" default SHA256
    expiresInMinutes : 1440 //expires in 24 hours
});
console.log("token : ", token);


$ node jwt_test.js token : eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjE0NTU0LCJpYXQiOjE0MzUxMzA4NzMsImV4cCI6MTQzNTIxNzI3M30.EWNUjnktCWxlqAAZW2bb0KCj5ftVjpDBocgv2OiypqM


2) jwt 토큰 디코딩

var jwt      = require('jsonwebtoken');
var tokenKey = "TEST_KEY11"; //토큰키 서버에서 보관 중요
var token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjE0NTU0LCJpYXQiOjE0MzUxMzA4NzMsImV4cCI6MTQzNTIxNzI3M30.EWNUjnktCWxlqAAZW2bb0KCj5ftVjpDBocgv2OiypqM';

//비동기처리
jwt.verify(token,tokenKey,function(err,decoded){
    console.log("sync : ", decoded);
});

//동기처리
try {
    var decoded = jwt.verify(token,tokenKey);
    console.log("async : ", decoded);
} catch(err){
    console.log(err);
}


$ node jwt_test_decoded.js
async :  { uid: 14554, iat: 1435130873, exp: 1435217273 }
sync :  { uid: 14554, iat: 1435130873, exp: 1435217273
//실행은 sync가 먼저 되었지만 비동기 방식이므로 더 뒤에 출력됨


참고사이트

https://auth0.com/blog/2014/01/07/angularjs-authentication-with-cookies-vs-token/

http://bcho.tistory.com/999

http://bcho.tistory.com/1000


Posted by 시니^^
Programming/node.js2015.03.25 20:14

restful api 만들려고 하는 데 페이지(URI)별 따로 모듈로 빼서 작업할 때 


routes module.exports 할때 매번 해당 모듈페이지에서 DB커넥션 작업 해야되나 고민하는데...


app.js에서 최초 한번 커넥션풀 유지해서 하는 방법에 대해서 어떻게 하나 예시 찾던 중 해결책 발견


참고사이트 : http://cwbuecheler.com/web/tutorials/2014/restful-web-app-node-express-mongodb/



var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
// Database
var mongo = require('mongoskin');
var db = mongo.db("mongodb://localhost:27017/nodetest2", {native_parser:true});
/ Make our db accessible to our router
app.use(function(req,res,next){
    req.db = db;
    next();
});
app.use('/', routes);
app.use('/users', users);

routes 위에다가 requset 정보에 db 커넥션풀 정보를 포함하게 정의하고 next 실행
그리고 각각의 routes 모듈에 보낼 수 있다
DB 커넥션풀 정보 필요없는 routes는 알아서 제외하면될 듯 



Posted by 시니^^
Programming/node.js2015.03.19 15:18

node.js에서 간략하게 IP기반으로 사용자 (위치) 지역 정보 데이터 분석용으로 GeoIP 라이브러리 있나 찾아봤음


역시 라이브러리 geoip-lite 라고 있음!!


Github : https://github.com/bluesmoon/node-geoip


1. 설치방법

$ npm install geoip-lite


2. 코드작성 및 사용법

$ vi geoip.js
=====================================
var geoip = require('geoip-lite');
var ip = "207.97.227.239";
var geo = geoip.lookup(ip);
console.log(geo);
====================================
$ node geoip.js 
{ range: [ 3479297920, 3479301339 ],
  country: 'US',
  region: 'TX',
  city: 'San Antonio',
  ll: [ 29.4889, -98.3987 ],
  metro: 641 }

※ ll 배열값에서 ll: [<latitude(위도)>, <longitude(경도)>] 순서임!!


3. GeoIP 정보 업데이트방법

※ github보면 geoip.startWatchingDataUpdate(); 함수로도 가능한데 아래 script로 하는게 더 낫은듯함

$ cd /usr/lib/node_modules/geoip-lite
$ npm run-script updatedb

> geoip-lite@1.1.5 updatedb /usr/lib/node_modules/geoip-lite
> node scripts/updatedb.js
Retrieving GeoIPCountryCSV.zip ... DONE
Extracting GeoIPCountryCSV.zip ... DONE
Processing Data (may take a moment) ...
Still working (96813) ... DONE
Retrieving GeoIPv6.csv ... DONE
Processing Data (may take a moment) ... DONE
Retrieving GeoLiteCity-latest.zip ... DONE
Extracting GeoLiteCity-latest.zip ... DONE
Processing Data (may take a moment) ...
Still working (122762) ...
Still working (246870) ...
Still working (368626) ...
Still working (490380) ...
Still working (612413) ...
Still working (734688) ...
Still working (855126) ...
Still working (977268) ...
Still working (1096307) ...
Still working (1217573) ...
Still working (1339167) ...
Still working (1459608) ...
Still working (1580033) ...
Still working (1700173) ...
Still working (1822030) ...
Still working (1943449) ... DONE
Retrieving GeoLiteCityv6.csv ... DONE
Processing Data (may take a moment) ... DONE
Successfully Updated Databases from MaxMind.


※ https://github.com/PaddeK/node-maxmind-db 추가 라이브러리(동일하게 MaxMind DB 기반이긴함) 

Posted by 시니^^

티스토리 툴바