2013년 8월 27일 화요일

node.js 를 위한 vim 환경 설정.

tmux를 쓰다보니 vim 환경 설정 셋을 문득 만들어 보고 싶었다.

1. 일단 vim 먼저 받고

sudo apt-get install vim

2. manage your runtimepath 관리용 pathgen 설치

mkdir -p ~/.vim/autoload ~/.vim/bundle; \
curl -Sso ~/.vim/autoload/pathogen.vim \
    https://raw.github.com/tpope/vim-pathogen/master/autoload/pathogen.vim

3. ~/.vimrc 없을테니 아래와 같이 생성. tab/indent 간격은 2에 탭 대신 space로 쓰기 위해 expandtab 추가

execute pathogen#infect()
syntax on
filetype plugin indent on
set tabstop=2
set shiftwidth=2
set softtabstop=2
set expandtab

4. 아무 js 나 vim 으로 열어서 color 적용여부 확인

5. sensible plugin 설치

git clone git://github.com/tpope/vim-sensible.git ~/.vim/bundle/vim-sensible

6. node bundle 설치

git clone https://github.com/moll/vim-node.git ~/.vim/bundle/node

7. coffeescript bundle 설치

git clone https://github.com/kchmck/vim-coffee-script.git ~/.vim/bundle/vim-coffee-script/

8. zen code 보다 더 좋은 Emmet 설치 - ctrl+y+, 로 활성화

git clone http://github.com/mattn/emmet-vim.git ~/.vim/bundle/emmet-vim

9. less 도 활성화

git clone https://github.com/groenewege/vim-less ~/.vim/bundle/vim-less

뭐 여기까지 하면 되려나?
기왕 만든거 한방팩 추가
https://gist.github.com/acidsound/6346222

터미널 열고
curl https://gist.github.com/acidsound/6346222/raw/81f10ede441502f4b797189b2784bc81c6e514bd/set_vim.sh | /bin/sh

요거 한번 붙여주면 알아서 설치한다.

2013년 8월 25일 일요일

Meteor 에서 외부 Application으로 DDP 인증 (2) : SRP 교환

DDP로 인증을 하려면 네트웍상에 평문으로 보내지 않기 위해
SRP(Secure Remote Password) 라는 프로토콜로 변환한다.

https://github.com/meteor/meteor/tree/devel/packages/srp

package.js 를 보면 biginteger 와 sha256 을 사용한다.

구현해야할 부분은 https://github.com/meteor/meteor/blob/devel/packages/accounts-password/password_client.js#L10
이 곳 참조.

password 를 가지고 다음과 같이.

    s = new srp.Client password
    request = s.startExchange()
    if typeof selector == 'string'
      selector =
        if ~selector.indexof("@")
        then username: selector
        else email: selector
    request.user = selector

beginPasswordExchange 를 시작하는 부분의 인자는 request 자체를 사용.
request 내용은 A를 키로 하는 SRP 암호문과 user를 키로하는 사용자명이 되겠다.

method 쏴준다. A에 srp로 암호화한 문자를 사용.


{ msg: 'method',
  method: 'beginPasswordExchange',
  params:
   [ { A: '439284960ce6bc7c163262b5629a467cd06bb982432615ec526a1ed204d2f76a1a254faaab6c5c9309e120d708e3e46fe58d8c5017aedb85efb9670cfcbf8a8b2dd996a6226f7be032bfedc23b1261093c0374e3e176fb374818f8a38b015517c8bfc3cc1326967760bdeaac09e154e115826022cdcea75f944d61a7e724721a',
       user: {
         username: "......."  /* 혹은  email: "...." */
       } } ],
  id: '1' }

형식으로 보내면 되겠다.
코드를 써보면

  if dataJSON.msg is 'ready'
    console.log '>> method beginPasswordExchange'
    s = new srp.Client password
    request = s.startExchange()
    if typeof selector is 'string'
      selector =
        if ~selector.indexOf("@")
        then email: selector
        else username: selector
    request.user = selector
    sendObject =
      msg: 'method',
      method: 'beginPasswordExchange',
      params: [request]
      id: (++nextid).toString()
    ws.send JSON.stringify sendObject

ready 이후에 이런식으로 써주면 되겠다. id는 1 넣으니 Malformed method invocation 오류가 나니 sequence 값을 계속 사용하고 있다.

돌려보니

>> method beginPasswordExchange
{ msg: 'updated', methods: [ '2' ] }
{ msg: 'result',
  id: '2',
  result:
   { identity: 'YzHuNjbsWJm5KvDce',
     salt: 'sre6wg5ENP2FFNc42',
     B: '6bd07e3539b211ad523e38509501becba435618e25d6bd3f55852311a4c8d5a49cc0b29ffa8ccc28d3122a12b6ee20fefa01fd313fbe92c4200156e7faa7c21e3a43b3949fc7feaba9843c858f3144d92994261e7a86f9154593ec795c7725bf73e8e4fa2febda2a4fcb8bb84aa248a2ad2bb1c31d13bdc1bf025e3deec813aa' } }

하지만 이게 전부는 아니다.
서버에 갔다와서 해쉬를 받아오는 부분이라 유저명만 체크한다고 봐도 된다. 암호는 결과 값을 기준으로 확인해야함.

login method를 쏴보자.

{
  "msg":"method",
  "method":"login",
  "params":[
    {
      "srp":{
        "M":"e3e18d009a23a2f560e8eb157b6eca9d686dc4f95e2f0381ba9a8fb8dd4fa9de"
      }
    }
  ],
  "id":"2"
}

분석해보니 이런 내용이 websocket 나왔었다.
일단 여기까지 별 어려움 없이 잘 왔다.

어짜피 한 password 방식일때인 경우만 분석해봤는데
다른 경우도 크게 다르지 않을 거라고 생각한다.

전체 소스는 gist(https://gist.github.com/acidsound/6330360)에 올려놓겠다.

nextid = 0
connected = false;
ws = require 'ws'
## srp library from https://github.com/meteor/meteor/tree/devel/packages/srp
srp = require './lib/srp.js'
isLogin = false

## id/pass
selector = '<ID>'
password = '<PASS>'
s = new srp.Client password

ws = new ws 'ws://localhost:3000/websocket'
ws.on 'open', ->
  console.log "open socket"
  ws.send JSON.stringify
    msg: 'connect'
    version: 'pre1'
    support: ['pre1']
ws.on 'message', (data, flags)->
  dataJSON = JSON.parse data
  console.log dataJSON
  if dataJSON.msg is 'connected'
    connected = true
    ws.send JSON.stringify
      msg: 'sub'
      id: (++nextid).toString()
      name: 'meteor.loginServiceConfiguration'
  if dataJSON.msg is 'ready'
    if not isLogin
      console.log '>> method beginPasswordExchange'
      request = s.startExchange()
      if typeof selector is 'string'
        selector =
          if ~selector.indexOf("@")
          then email: selector
          else username: selector
      request.user = selector
      sendObject =
        msg: 'method'
        method: 'beginPasswordExchange'
        params: [request]
        id: (++nextid).toString()
      ws.send JSON.stringify sendObject
  if dataJSON.msg is 'result'
    if not isLogin
      console.log ">> checked username"
      if !!dataJSON.error
        console.log ">> username fail"
      else
        response = s.respondToChallenge dataJSON.result
        sendObject =
          msg: 'method'
          method: 'login'
          params: [
            srp: response
          ]
          id: (++nextid).toString()
        console.log JSON.stringify sendObject
        ws.send JSON.stringify sendObject
        isLogin = true
    else
      ## after login
      if !!dataJSON.error
        console.log ">>>> incorrect password"
      else
        if s.verifyConfirmation(HAMK:dataJSON.result.HAMK)
          console.log ">>>> login success"
        else
          console.log ">>>> server is cheating!"




2013년 8월 13일 화요일

Meteor 에서 외부 Application으로 DDP 인증.

http://stackoverflow.com/questions/16729992/authenticating-with-meteor-via-ddp-and-srp

Basic Auth로 websocket frame을 떠봤더니

o
1
오전 1:47:09
a["{\"server_id\":\"X43fG44wD7iJNCdoq\"}"]
42
오전 1:47:09
["{\"msg\":\"connect\",\"version\":\"pre1\",\"support\":[\"pre1\"]}"]
69
오전 1:47:09
["{\"msg\":\"sub\",\"id\":\"8jW8AaNcWf2iRhXWC\",\"name\":\"meteor.loginServiceConfiguration\",\"params\":[]}"]
110
오전 1:47:09
a["{\"msg\":\"connected\",\"session\":\"ZddaS7aatWT4i8nAR\"}"]
62
오전 1:47:09
a["{\"msg\":\"added\",\"collection\":\"users\",\"id\":\"aHC7AHZXbnhs7Efs9\",\"fields\":{\"username\":\"mone\",\"profile\":{\"photoSmall\":\"/image/tempImage/member_photo_placeholder.png\"}}}"]
192
오전 1:47:09
a["{\"msg\":\"added\",\"collection\":\"users\",\"id\":\"HuL3adLrtPKMoFWMK\",\"fields\":{\"profile\":{\"photoSmall\":\"/image/tempImage/member_photo_placeholder.png\"},\"username\":\"admin\"}}"]
193
오전 1:47:09
a["{\"msg\":\"added\",\"collection\":\"users\",\"id\":\"MZqYAk6C9kYao3ATz\",\"fields\":{\"profile\":{\"photoSmall\":\"/image/tempImage/member_photo_placeholder.png\"},\"username\":\"spectrum\"}}"]
196
오전 1:47:09
a["{\"msg\":\"ready\",\"subs\":[\"8jW8AaNcWf2iRhXWC\"]}"]
----------------------------------------------------------------------

여기까지 로그인 전

----------------------------------------------------------------------
"{\"msg\":\"method\",\"method\":\"login\",\"params\":[{\"srp\":{\"M\":\" \"}}],\"id\":\"2\"}"]
158
오전 1:48:22
a["{\"msg\":\"changed\",\"collection\":\"users\",\"id\":\"MZqYAk6C9kYao3ATz\",\"fields\":{\"emails\":[{\"address\":\"spectrick@gmail.com\",\"verified\":false}]}}"]
163
오전 1:48:22
a["{\"msg\":\"ready\",\"subs\":[\"8jW8AaNcWf2iRhXWC\"]}"]
57
오전 1:48:22
a["{\"msg\":\"updated\",\"methods\":[\"2\"]}"]
46
오전 1:48:22
a["{\"msg\":\"result\",\"id\":\"2\",\"result\":{\"token\":\"oGyFPfZM3c95yXw7M\",\"id\":\"MZqYAk6C9kYao3ATz\",\"HAMK\":\"b372e317d359b294cd9c5a4ca39c8f5523c310288661eba731191e2b4614ef17\"}}"]
190
오전 1:48:22
h

뭐 대략 이렇게 나오는데
a는 응답일테고

순서대로 보면
1. Connect 요구
{ msg: 'connect',
  version: 'pre1',
  support: [ 'pre1' ] }
이건 간단한데
{msg: "connected", session: "4vXhahuK5GTNzcC4S"}
이런 식으로 응답이 오면 오케이

2. 인증설정요구
{ msg: 'sub',
  id: 'xaiCo33DPqaEJhkbc',   /* id는 UUID. 아무거나 넣어도 된다. */
  name: 'meteor.loginServiceConfiguration',
  params: [] }

잠시 삼천포
UUID 생성은

Meteor.uuid = function () {
  var HEX_DIGITS = "0123456789abcdef";
  var s = [];
  for (var i = 0; i < 36; i++) {
    s[i] = Random.choice(HEX_DIGITS);
  }
  s[14] = "4";
  s[19] = HEX_DIGITS.substr((parseInt(s[19],16) & 0x3) | 0x8, 1);
  s[8] = s[13] = s[18] = s[23] = "-";

  var uuid = s.join("");
  return uuid;
}

Random.choice = function (arrayOrString) {
  var index = Math.floor(Random.fraction() * arrayOrString.length);
  if (typeof arrayOrString === "string")
    return arrayOrString.substr(index, 1);
  else
    return arrayOrString[index];
}

이런 식이다.
아무튼 sub 즉 subscribe 메시지로 meteor.loginServiceConfiguration 이란 이름의 collection을 구독 요청하면

{ server_id: 'X43fG44wD7iJNCdoq' }
{ msg: 'connected', session: 'EzRf563xHiK7J2FDo' }
{ msg: 'added',
  collection: 'users',
  id: 'aHC7AHZXbnhs7Efs9',
  fields: 
   { username: 'mone',
     profile: { photoSmall: '/image/tempImage/member_photo_placeholder.png' } } }
{ msg: 'added',
  collection: 'users',
  id: 'HuL3adLrtPKMoFWMK',
  fields: 
   { profile: { photoSmall: '/image/tempImage/member_photo_placeholder.png' },
     username: 'admin' } }
{ msg: 'added',
  collection: 'users',
  id: 'MZqYAk6C9kYao3ATz',
  fields: 
   { profile: { photoSmall: '/image/tempImage/member_photo_placeholder.png' },
     username: 'spectrum' } }
{ msg: 'ready', subs: [ '1' ] }

유저명이 나오고, 'ready'를 받으면 끝. atmosphere.meteor.com 은 user 명을 보내지 않더라. 뭔가 설정이 있을 듯.


보내는게 무엇인지 살펴보니
{ msg: 'method',
  method: 'login',
  params: [ { srp: 
    "M":"HASHHASHHASHHASHHASHHASHHASHHASHHASHHASH"
  } ],
  id: '2' }
이렇다.
참고로 atmosphere는

{ msg: 'method',
  method: 'login',
  params: 
   [ 'atm',
     '0.1.0',
     { srp: 
      "M":"HASHHASHHASHHASHHASHHASHHASHHASHHASHHASH"
     } ],
  id: '2' }

뭔가 조금 더 있다.

요걸 보내면 된다는 말인데
M에 사용자계정과 암호를 잘 어떻게 해서 응답이 돌아오면 되겠다.

M에 해당하는 부분은 SRP라는 것으로 암호화되어있는데
이걸 사용하면 된다.
로직은 그냥 소스 읽자.

nextid = 0
connected = false;
ws = require 'ws'
ws = new ws 'ws://localhost:3000/websocket'
ws.on 'open', ->
  console.log "open socket"
  ws.send JSON.stringify
    msg: 'connect'
    version: 'pre1'
    support: ['pre1']
ws.on 'message', (data, flags)->
  dataJSON = JSON.parse data
  console.log dataJSON
  if dataJSON.msg is 'connected'
    connected = true
    ws.send JSON.stringify
      msg: 'sub'
      id: (++nextid).toString()
      name: 'meteor.loginServiceConfiguration'
  if dataJSON.msg is 'ready'
    ws.send JSON.stringify
      msg: 'method'
      method: 'login',
      params: [
        srp:
          M: "*이 부분에 SRP로 인코딩"
      ]
      id: '2'

해봤는데 잘 안된다.
사실 이 부분이 핵심인데 Basic auth / OAuth 도 다 다를것이다.
OAuth를 쓰면 어짜피 웹으로 왔다갔다 해야해서 모바일 등에서 쓰기 곤란할 듯.
그럼 로그인은 웹에서 하고 인증된 후에 재로그인이 가능한지 확인해보자.

{ msg: 'method',
  method: 'login',
  params: [ { resume: 'MvzqjyYD9bCs3rrcb' } ],
  id: '1' }

resume 값을 기억하고 있으면 되는가보다 리프레쉬 했는데 같은 값을 반환한다.

open socket
{ server_id: 'X43fG44wD7iJNCdoq' }
{ msg: 'connected', session: 'ggEMcek5WmGD9vJSG' }
{ msg: 'added',
  collection: 'users',
  id: 'aHC7AHZXbnhs7Efs9',
  fields: 
   { username: 'mone',
     profile: { photoSmall: '/image/tempImage/member_photo_placeholder.png' } } }
{ msg: 'added',
  collection: 'users',
  id: 'HuL3adLrtPKMoFWMK',
  fields: 
   { profile: { photoSmall: '/image/tempImage/member_photo_placeholder.png' },
     username: 'admin' } }
{ msg: 'added',
  collection: 'users',
  id: 'MZqYAk6C9kYao3ATz',
  fields: 
   { profile: { photoSmall: '/image/tempImage/member_photo_placeholder.png' },
     username: 'spectrum' } }
{ msg: 'ready', subs: [ '1' ] }
{ msg: 'changed',
  collection: 'users',
  id: 'MZqYAk6C9kYao3ATz',
  fields: { emails: [ [Object] ] } }
{ msg: 'ready', subs: [ '1' ] }
{ msg: 'updated', methods: [ '11' ] }
{ msg: 'result',
  id: '11',
  result: { token: 'MvzqjyYD9bCs3rrcb', id: 'MZqYAk6C9kYao3ATz' } }
{ msg: 'result',
  id: '11',
  result: { token: 'MvzqjyYD9bCs3rrcb', id: 'MZqYAk6C9kYao3ATz' } }
{ msg: 'updated', methods: [ '11' ] }


음 token 이 어짜피 있어서 의미없나?
암튼 전체 소스
nextid = 0
connected = false;
ws = require 'ws'
ws = new ws 'ws://localhost:3000/websocket'
ws.on 'open', ->
  console.log "open socket"
  ws.send JSON.stringify
    msg: 'connect'
    version: 'pre1'
    support: ['pre1']
ws.on 'message', (data, flags)->
  dataJSON = JSON.parse data
  console.log dataJSON
  if dataJSON.msg is 'connected'
    connected = true
    ws.send JSON.stringify
      msg: 'sub'
      id: (++nextid).toString()
      name: 'meteor.loginServiceConfiguration'
  if dataJSON.msg is 'ready'
    ws.send JSON.stringify
      msg: 'method'
      method: 'login',
      params: [
        resume: 'MvzqjyYD9bCs3rrcb'
      ]
      id: '1'