Login example in Erlang

Sample of login and some other REST requests in Erlang. In order to be able to parse JSON the file rfc4627.erl from https://github.com/tonyg/erlang-rfc4627 is needed. Please ensure that you respect the copyright of the erlang-rfc4627 library.

Save the code below in a file called demo.erl and put rfc4627.erl in the same directory. Start erl and write:


make:all([load]).
inets:start().
ssl:start().
S = demo:login("username", "password").
demo:get_accounts(S).
demo:get_account_info(S, 999999).

-module(demo).
-export([login/2,
        get_accounts/1,
        get_account_info/2]).

-define(service, "NEXTAPI").
-define(public_key_file, "NEXTAPI_TEST_public.pem").
-define(http_connect_timeout, 3000).
-define(http_request_timeout, 5000).
-define(next_version, 2).
-define(next_base_url, "https://api.test.nordnet.se/next").

-type session_info() :: string().

%%
%% Login to nExt.
%%
-spec login(string(), string()) -> session_info().
login(Username, Password) ->
    % Create the public key from the file.
    {ok, RSAPubPem} = file:read_file(?public_key_file),
    PemEntries = public_key:pem_decode(RSAPubPem),
    RSAPubKey = public_key:pem_entry_decode(hd(PemEntries)),

    % Create the blob
    Blob = list_to_binary(string:join([ binary_to_list(base64:encode(X)) ||
                X <- [Username, Password, create_timestamp()] ],":")),
    EncryptedBlob = base64:encode(public_key:encrypt_public(Blob, RSAPubKey)),

    {ok, LoginResult} = call_json_rest(post, "login", [{auth,binary_to_list(EncryptedBlob)},
            {service, ?service}]),
    proplists:get_value("session_key", LoginResult).

%%
%% Get the accounts of the user
%%
-spec get_accounts(session_info()) -> any().
get_accounts(SessionKey) ->
    {ok, Result} = call_json_rest(get, "accounts", [], SessionKey),
    Result.

%%
%% Get account info
%%
-spec get_account_info(session_info(), integer()) -> any().
get_account_info(SessionKey, AccNo) ->
    {ok, Result} = call_json_rest(get, "accounts/" ++ integer_to_list(AccNo), [], SessionKey),
    Result.

%%
%% Create a UNIX timestamp (in milliseconds).
%%
create_timestamp() ->
    {MegaSecs, Secs, MicroSecs} = erlang:now(),
    Ts = ((MegaSecs*1000000 + Secs)*1000000 + MicroSecs ) div 1000,
    integer_to_list(Ts).

%%
%% Call a REST service, asking for JSON format in the response.
%%
call_json_rest(Action, URL, Params) ->
    call_json_rest(Action, URL, Params, "").

%%
%% Call a REST service, asking for JSON format in the response.
%%
call_json_rest(Action, URL, Params, SessionKey) ->

    Data = string:join( [ atom_to_list(Key) ++ "=" ++
            edoc_lib:escape_uri(Value) ||
            {Key, Value} <- Params], "&"),
    case http_request(Action, create_url(URL), Data, "application/json", SessionKey) of
        {200, Body} ->
            case rfc4627:decode_noauto(Body) of
                {ok, {obj, JSON}, _} -> {ok, JSON};
                {ok, JSON,        _} -> {ok, JSON};
                {error, _} = Error   -> Error
            end;
        {Status, Body} when is_integer(Status) ->
            {error, {unacceptable_http_status, Status, Body}};
        {error, _} = Error ->
            Error
    end.

%%
%% Create an URL string
%%
create_url(Method) ->
    ?next_base_url ++ "/" ++ integer_to_list(?next_version) ++ "/" ++ Method.

%%
%% Do a HTTP call
%%
http_request(Action, URL, Data, ResponseType, SessionKey) ->
    HTTPOpts = [{timeout, ?http_request_timeout},
        {connect_timeout, ?http_connect_timeout}],
    try httpc:request(Action,
                    create_request_params(Action, URL, Data, create_headers(ResponseType, SessionKey)),
                    HTTPOpts, []) of
        {ok, {{ _, Status, _}, _, Body}} ->
            {Status, Body};
        {error, _} = Error ->
            Error
    catch _:Error ->
        {error, Error}
    end.

%%
%% Create headers. Use session key as Auth if needed.
%%
create_headers(ResponseType, SessionKey) ->
    [{"Accept", ResponseType}] ++
    case SessionKey of
        "" -> [];
        S -> [{"Authorization", "Basic " ++
                    binary_to_list(base64:encode(<<S/binary,":",S/binary>>))}]
    end.

%%
%% Request parameters differs on post.
%%
create_request_params(post, URL, Data, Headers) ->
    {URL, Headers, ["application/x-www-form-urlencoded;charset=UTF-8"], Data};
create_request_params(_, URL, Data, Headers) ->
    {URL ++ "?" ++ Data, Headers}.