implementing user authentication with bcrypt in ChicagoBoss.
see http://shanelogsdon.com/erlang/implementing-user-authentication-with-bcrypt-in-chicagoboss
implementing user authentication with bcrypt in ChicagoBoss.
see http://shanelogsdon.com/erlang/implementing-user-authentication-with-bcrypt-in-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}]}. |