Skip to content

Instantly share code, notes, and snippets.

@slogsdon
Last active April 2, 2016 22:19
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save slogsdon/7226067 to your computer and use it in GitHub Desktop.
Save slogsdon/7226067 to your computer and use it in GitHub Desktop.
Erlang: User authentication with bcrypt and ChicagoBoss
{deps, [
{boss, ".*", {git, "git://github.com/evanmiller/ChicagoBoss.git", "HEAD"}},
{bcrypt, ".*", {git, "https://github.com/opscode/erlang-bcrypt.git", "HEAD"}}
]}.
{plugin_dir, ["priv/rebar"]}.
{plugins, [boss_plugin]}.
{eunit_compile_opts, [{src_dirs, ["src/test"]}]}.
{lib_dirs, ["./deps/elixir/lib"]}.
%% file: priv/init/module_10_bcrypt.erl
-module(module_10_bcrypt).
-export([init/0, stop/0]).
%% We need to manually start the bcrypt application.
%% @TODO: figure out how to get this to run via boss.config.
init() ->
%% Uncomment the following line if your CB app doesn't start crypto on its own
% crypto:start(),
bcrypt:start().
stop() ->
bcrypt:stop().
%% Comment the above and uncomment the following lines if your CB app doesn't start crypto on its own
% bcrypt:stop(),
% crypto:stop().
%% file: src/model/test_user.erl
-module(test_user, [Id, Email, Username, Password]).
-compile(export_all).
-define(SETEC_ASTRONOMY, "Too many secrets").
session_identifier() ->
mochihex:to_hex(erlang:md5(?SETEC_ASTRONOMY ++ Id)).
check_password(PasswordAttempt) ->
StoredPassword = erlang:binary_to_list(Password),
user_lib:compare_password(PasswordAttempt, StoredPassword).
set_login_cookies() ->
[ mochiweb_cookies:cookie("user_id", erlang:md5(Id), [{path, "/"}]),
mochiweb_cookies:cookie("session_id", session_identifier(), [{path, "/"}]) ].
%% file: src/lib/user_lib.erl
-module(user_lib).
-compile(export_all).
%% On success, returns {ok, Hash}.
hash_password(Password)->
{ok, Salt} = bcrypt:gen_salt(),
bcrypt:hashpw(Password, Salt).
%% Tests for presence and validity of session.
%% Forces login on failure.
require_login(Req) ->
case Req:cookie("user_id") of
undefined -> {redirect, "/user/login"};
Id ->
case boss_db:find(Id) of
undefined -> {redirect, "/user/login"};
TestUser ->
case TestUser:session_identifier() =:= Req:cookie("session_id") of
false -> {redirect, "/user/login"};
true -> {ok, TestUser}
end
end
end.
compare_password(PasswordAttempt, Password) ->
{ok, Password} =:= bcrypt:hashpw(PasswordAttempt, Password).
%% file: src/controller/test_user_controller.erl
-module(test_user_controller, [Req]).
-compile(export_all).
login('GET', []) ->
{ok, [{redirect, Req:header(referer)}]};
login('POST', []) ->
Username = Req:post_param("username"),
case boss_db:find(annie_user, [{username, Username}], [{limit, 1}]) of
[TestUser] ->
case TestUser:check_password(Req:post_param("password")) of
true ->
{redirect, proplists:get_value("redirect",
Req:post_params(), "/"), TestUser:set_login_cookies()};
false ->
{ok, [{error, "Password mismatch"}]}
end;
[] ->
{ok, [{error, "User not found"}]}
end.
register('GET', []) ->
{ok, []};
register('POST', []) ->
Email = Req:post_param("email"),
Username = Req:post_param("username"),
{ok, Password} = user_lib:hash_password(Req:post_param("password")),
TestUser = test_user:new(id, Email, Username, Password),
Result = TestUser:save(),
{ok, [Result]}.
%% file: src/controller/test_index_controller.erl
-module(test_index_controller, [Req]).
-compile(export_all).
%% Forces login if valid session is not present.
%% Called before all actions.
before_(_) ->
user_lib:require_login(Req).
%%
%% Index
%%
%% requires TestUser
%%
%% GET index/index
%%
index('GET', [], TestUser) ->
{ok, [{test_user, TestUser}]}.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment