diff --git a/cowlib/src/cow_base64url.beam b/cowlib/src/cow_base64url.beam deleted file mode 100644 index 7430dc8..0000000 Binary files a/cowlib/src/cow_base64url.beam and /dev/null differ diff --git a/cowlib/src/cow_cookie.beam b/cowlib/src/cow_cookie.beam deleted file mode 100644 index 59e2e2c..0000000 Binary files a/cowlib/src/cow_cookie.beam and /dev/null differ diff --git a/cowlib/src/cow_date.beam b/cowlib/src/cow_date.beam deleted file mode 100644 index fadd9db..0000000 Binary files a/cowlib/src/cow_date.beam and /dev/null differ diff --git a/cowlib/src/cow_hpack.beam b/cowlib/src/cow_hpack.beam deleted file mode 100644 index 6f585ae..0000000 Binary files a/cowlib/src/cow_hpack.beam and /dev/null differ diff --git a/dudeswave/src/Makefile b/dudeswave/src/Makefile deleted file mode 100644 index 8a14b81..0000000 --- a/dudeswave/src/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -.PHONY: all clean -.SUFFIXES: .erl .beam - -ERLC?= erlc -server - -ERLFLAGS= -I ../../ - -OBJS= dudeswave.beam dudeswave_app.beam -OBJS+= dudeswave_supervisor.beam dudeswave_handler.beam -OBJS+= dudeswave_user_handler.beam dudeswave_auth.beam -OBJS+= dudeswave_auth_handler.beam - -all: ${OBJS} - -.erl.beam: - ${ERLC} ${ERLOPTS} ${ERLFLAGS} $< - -clean: - rm -f *.beam - diff --git a/dudeswave/src/dudeswave_auth.erl b/dudeswave/src/dudeswave_auth.erl deleted file mode 100644 index e442efd..0000000 --- a/dudeswave/src/dudeswave_auth.erl +++ /dev/null @@ -1,408 +0,0 @@ -% -% Copyright (c) 2024 Andrea Biscuola -% -% Permission to use, copy, modify, and distribute this software for any -% purpose with or without fee is hereby granted, provided that the above -% copyright notice and this permission notice appear in all copies. -% -% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -% --module(dudeswave_auth). --moduledoc """ -Dudes users management module. - -Here lives all the functions for the APIs needed to create, update and delete -users from the dudeswave database. -""". - --include_lib("dudeswave/include/defines.hrl"). --include_lib("storage/include/storage.hrl"). - --export([authenticate/2, details/1, new/3, - update/4, delete/1, logout/2, auth_cookies/1, invalidate_cookies/1, - set_auth_cookies/4, read_login_data/1, read_new_user_data/1, - read_update_user_data/1]). - --doc """ -Verify a session with an existing cookie. - -Spec: - -``` --spec authenticate(User, Auth) -> true | false | {true, Cookie, Validity} | {error, Reason} when - User :: binary(), - Auth :: {cookie, binary()} | {password, binary()}, - Reason :: term(). -``` - -Authenticate a user with either an existing `Cookie` or by issuing a new one -after authenticating with `Password`. - -If `Cookie` is valid, the function returns `true`. If the authentication is denied -returns `false` -""". --spec authenticate(User, Auth) -> true | false | {true, Cookie, Validity} | {error, Reason} when - User :: binary(), - Auth :: {cookie, binary()} | {password, binary()}, - Cookie :: binary(), - Validity :: pos_integer(), - Reason :: term(). - -authenticate(User, {cookie, Cookie}) -> - case storage:read(?COOKIESBUCK, Cookie) of - {ok, [R]} -> - CurTime = calendar:now_to_universal_time(erlang:timestamp()), - CookieTime = R#object.value, - {user, CookieUser} = proplists:lookup(user, R#object.metadata), - - if - CookieTime >= CurTime -> - if - User =:= CookieUser -> true; - true -> false - end; - true -> false - end; - {ok, []} -> false; - {error, _} -> {error, service_unavailable} - end; - -authenticate(User, {password, Password}) -> - case storage:read(?USERSBUCK, User) of - {ok, [R]} -> - Validity = case application:get_env(cookie_validity) of - {ok, Value} -> - erlang:system_time(seconds) + Value * 86400; - undefined -> - erlang:system_time(seconds) + ?DEFVALIDITY * 86400 - end, - - {hash, Hash} = proplists:lookup(hash, R#object.metadata), - {salt, Salt} = proplists:lookup(salt, R#object.metadata), - {approved, Appr} = proplists:lookup(approved, R#object.metadata), - - Auth = crypto:hash(sha256, <>), - - if - Appr =/= true -> false; - Auth =:= Hash -> - Cookie = base64:encode(rand:bytes(64)), - case storage:write(?COOKIESBUCK, <>, - Validity, [{user, User}]) of - ok -> {true, Cookie, Validity}; - {error, Reason} -> {error, Reason} - end; - true -> false - end; - {ok, []} -> false; - {error, Reason} -> {error, Reason} - end. - --doc """ -Close an existing session - -Spec: - -``` --spec logout(User, Cookie) -> ok | {error, Reason} when - User :: binary(), - Cookie :: binary(), - Reason :: term(). -``` - -Invalidate and delete `Cookie` associated with `User` from the system. -""". --spec logout(User, Cookie) -> ok | {error, Reason} when - User :: binary(), - Cookie :: binary(), - Reason :: term(). - -logout(User, Cookie) -> - case storage:read(?COOKIESBUCK, Cookie) of - {ok, [R]} -> - {user, User} = proplists:lookup(user, R#object.metadata), - storage:delete(?COOKIESBUCK, Cookie); - {ok, []} -> - {error, not_found}; - {error, Reason} -> - {error, Reason} - end. - --doc """ -Return user details. - -Spec: - -``` --spec details(User) -> Value | {error, Reason} when - User :: binary(), - Value :: term(), - Reason :: term(). -``` -""". --spec details(User) -> Value | {error, Reason} when - User :: binary(), - Value :: term(), - Reason :: term(). - -details(User) -> - case storage:read(?USERSBUCK, User) of - {ok, [R]} -> R#object.value; - {error, Reason} -> {error, Reason}; - {ok, []} -> {error, not_found} - end. - --doc """ -Create a new user. - -Spec: - -``` --spec new(User, Password, Email) -> ok | {error, Reason} when - User :: binary(), - Password :: binary(), - Email :: binary(), - Reason :: term(). -``` - -The `User` is created, and stored in the application's users bucket -`Password` is salted and hashed with SHA256 before being stored. - -The new user is saved with a metadata `approved` of `false`, -""". --spec new(User, Password, Email) -> ok | {error, Reason} when - User :: binary(), - Password :: binary(), - Email :: binary(), - Reason :: term(). - -new(User, Password, Email) -> - Salt = rand:bytes(?RANDBYTES), - Hash = crypto:hash(sha256, <>), - - Data = #{<<"email">> => Email}, - Metadata = [{salt, Salt}, {hash, Hash}, {approved, false}], - - storage:write(?USERSBUCK, User, Data, Metadata). - --doc """ -Update user's details - -Spec: - -``` --spec update(User, Name, Email, Desc) -> ok | {error, Reason} when - User :: binary(), - Name :: binary(), - Email :: binary(), - Desc :: binary(), - Reason :: term(). -``` - -The details apart from `User` are updated. The username itself is immutable -and cannot be modified. All the other fields, excluding the e-mail, are the -ones that can be seen in the public page. -""". --spec update(User, Name, Email, Desc) -> ok | {error, Reason} when - User :: binary(), - Name :: binary(), - Email :: binary(), - Desc :: binary(), - Reason :: term(). - -update(User, Name, Email, Desc) -> - {ok, CurData, Metadata} = case storage:read(?USERSBUCK, User) of - {ok, [R]} -> - {ok, R#object.value, R#object.metadata}; - {error, Reason} -> {error, Reason} - end, - - Data = CurData#{<<"email">> => Email, <<"name">> => Name, - <<"description">> => Desc}, - - storage:write(?USERSBUCK, User, Data, Metadata). - --doc """ -Delete an existing user from the database. - -``` --spec delete(User) -> ok | {error, Reason} when - User :: binary(), - Reason :: term(). -``` -""". --spec delete(User) -> ok | {error, Reason} when - User :: binary(), - Reason :: term(). - -delete(User) -> - % We are missing the cleanup of the cookies - % here. For that, we need to add at least another - % API to the storage layer. - storage:delete(?USERSBUCK, User). - --doc """ -Get the authentication cookies from a cowboy request. - -Spec: - -``` --spec auth_cookies(Req) -> {User, Cookie} when - Req :: cowboy_req:req(), - User :: binary(), - Cookie :: binary(). -``` -""". --spec auth_cookies(Req) -> {User, Cookie} when - Req :: cowboy_req:req(), - User :: binary(), - Cookie :: binary(). - -auth_cookies(Req) -> - #{?DUDEAUTH := Cookie, ?DUDENAME := User} = cowboy_req:match_cookies([?DUDEAUTH, - ?DUDENAME], Req), - - {User, Cookie}. - --doc """ -Invalidate the cookies in the passed request. - -Spec: - -``` --spec invalidate_cookies(Req) -> Req0 when - Req :: cowboy_req:req(), - Req0 :: cowboy_req:req(). -``` - -A new request `Req0` is returned to the caller with the cookies zeroed and -completely invalidated. -""". --spec invalidate_cookies(Req) -> Req0 when - Req :: cowboy_req:req(), - Req0 :: cowboy_req:req(). - -invalidate_cookies(Req) -> - Req0 = cowboy_req:set_resp_cookie(<<"?DUDEAUTH">>, <<"">>, Req, - #{max_age => 0}), - Req1 = cowboy_req:set_resp_cookie(<<"?DUDENAME">>, <<"">>, Req0, - #{max_age => 0}), - - Req1. - --doc """ -Set the authentication cookies for the provided client request - -Spec: - -``` --spec set_auth_cookies(Req, User, Cookie, Validity) -> Req0 when - Req :: cowboy_req:req(), - User :: binary(), - Cookie :: binary(), - Validity :: pos_integer(), - Req0 :: cowboy_req:req(). -``` - -A new request object `Req0`is returned, with the user and auth cookies set. -""". --spec set_auth_cookies(Req, User, Cookie, Validity) -> Req0 when - Req :: cowboy_req:req(), - User :: binary(), - Cookie :: binary(), - Validity :: pos_integer(), - Req0 :: cowboy_req:req(). - -set_auth_cookies(Req, User, Cookie, Validity) -> - Req0 = cowboy_req:set_resp_cookie(<<"?DUDEAUTH">>, Cookie, Req, - #{max_age => Validity}), - Req1 = cowboy_req:set_resp_cookie(<<"?DUDENAME">>, User, Req0, - #{max_age => Validity}), - - Req1. - --doc """ -Spec: - -``` --spec read_login_data(Req) -> {User, Pass, Req0} when - Req :: cowboy_req:req(), - User :: binary(), - Pass :: binary(), - Req0 :: cowboy_req:req(). -``` -Read the login details from the `Req` body and return `User` and `Password`. -""". --spec read_login_data(Req) -> {User, Pass, Req0} when - Req :: cowboy_req:req(), - User :: binary(), - Pass :: binary(), - Req0 :: cowboy_req:req(). - -read_login_data(Req) -> - {ok, Data, Req0} = cowboy_req:read_body(Req), - #{<<"user">> := User, <<"password">> := Pass} = json:decode(Data), - - {User, Pass, Req0}. - --doc """ -Read new registration informations from the request - -Spec: - -``` --spec read_new_user_data(Req) -> {User, Pass, Email Req0} when - Req :: cowboy_req:req(), - User :: binary(), - Pass :: binary(), - Email :: binary(), - Req0 :: cowboy_req:req(). -``` -""". --spec read_new_user_data(Req) -> {User, Pass, Email, Req0} when - Req :: cowboy_req:req(), - User :: binary(), - Pass :: binary(), - Email :: binary(), - Req0 :: cowboy_req:req(). - -read_new_user_data(Req) -> - {ok, Data, Req0} = cowboy_req:read_body(Req), - #{<<"user">> := User, <<"password">> := Pass, - <<"email">> := Email} = json:decode(Data), - - {User, Pass, Email, Req0}. - --doc """ -Update user informations. - -Spec: - -``` --spec read_update_user_data(Req) -> {Email, Desc, Name, Req0} when - Req :: cowboy_req:req(), - Email :: binary(), - Desc :: binary(), - Name :: binary(), - Req0 :: cowboy_req:req(). -``` -""". --spec read_update_user_data(Req) -> {Email, Desc, Name, Req0} when - Req :: cowboy_req:req(), - Email :: binary(), - Desc :: binary(), - Name :: binary(), - Req0 :: cowboy_req:req(). - -read_update_user_data(Req) -> - {ok, Data, Req0} = cowboy_req:read_body(Req), - #{<<"email">> := Email, <<"description">> := Desc, - <<"name">> := Name} = json:decode(Data), - - {Email, Desc, Name, Req0}. \ No newline at end of file diff --git a/dudeswave_backend/src/dudeswave_backend.erl b/dudeswave_backend/src/dudeswave_backend.erl index 399df0b..e2a4879 100644 --- a/dudeswave_backend/src/dudeswave_backend.erl +++ b/dudeswave_backend/src/dudeswave_backend.erl @@ -23,6 +23,10 @@ % Module callbacks -export([init/1, handle_call/3, handle_cast/2, terminate/2]). +% Public API exports +-export([auth/3, logout/2, user_details/1, new_user/3, + update_user/4, delete_user/1]). + % % Startup functions % @@ -35,11 +39,86 @@ init([]) -> {ok, 0}. +% +% Public APIs +% + +auth(cookie, User, Cookie) -> + gen_server:call({local, ?MODULE}, {cookie, User, Cookie}); + +auth(password, User, Password) -> + gen_server:call({local, ?MODULE}, {password, User, Password}). + +logout(User, Cookie) -> + gen_server:call({local, ?MODULE}, {logout, User, Cookie}). + +user_details(User) -> + gen_server:call({local, ?MODULE}, {user_details, User}). + +new_user(User, Password, Email) -> + gen_server:call({local, ?MODULE}, {new_user, User, Password, Email}). + +update_user(User, Name, Email, Desc) -> + gen_server:call({local, ?MODULE}, {update_user, User, Name, Email, Desc}). + +delete_user(User) -> + gen_server:call({local, ?MODULE}, {delete_user, User}). + % % Callbacks % -handle_call(_Msg, _From, State) -> {noreply, State}. +handle_call({cookie, User, Cookie}, _From, State) -> + case dudeswave_backend_auth:authenticate(cookie, User, Cookie) of + true -> + {reply, true, State}; + false -> + {reply, false, State}; + {error, Reason} -> + {reply, {error, Reason}, State} + end; + +handle_call({password, User, Password}, _From, State) -> + case dudeswave_backend_auth:authenticate(password, User, Password) of + {true, Cookie, Validity} -> + {reply, {true, Cookie, Validity}, State}; + false -> + {reply, false, State}; + {error, Reason} -> + {reply, {error, Reason}, State} + end; + +handle_call({logout, User, Cookie}, _From, State) -> + case dudeswave_backend_auth:logout(User, Cookie) of + ok -> {reply, ok, State}; + {error, Reason} -> {reply, {error, Reason}, State} + end; + +handle_call({user_details, User}, _From, State) -> + case dudeswave_backend_user:details(User) of + {error, not_found} -> {reply, not_found, State}; + {error, Reason} -> {reply, {error, Reason}, State}; + Val -> {reply, Val, State} + end; + +handle_call({new_user, User, Password, Email}, _From, State) -> + case dudeswave_backend_user:new(User, Password, Email) of + ok -> {reply, ok, State}; + {error, Reason} -> {reply, {error, Reason}, State} + end; + +handle_call({update_user, User, Name, Email, Desc}, _From, State) -> + case dudeswave_backend_user:update(User, Name, Email, Desc) of + ok -> {reply, ok, State}; + {error, Reason} -> {reply, {error, Reason}, State} + end; + +handle_call({delete_user, User}, _From, State) -> + case dudeswave_backend_user:delete(User) of + ok -> {reply, ok, State}; + {error, Reason} -> {reply, {error, Reason}, State} + end. + handle_cast(_Msg, State) -> {noreply, State}. diff --git a/dudeswave_backend/src/dudeswave_backend_auth.erl b/dudeswave_backend/src/dudeswave_backend_auth.erl index 4164fec..c6b563a 100644 --- a/dudeswave_backend/src/dudeswave_backend_auth.erl +++ b/dudeswave_backend/src/dudeswave_backend_auth.erl @@ -20,10 +20,10 @@ Dudes authentication module Here lives all the functions for the APIs needed to handle users authentication. """. --include_lib("dudeswave/include/defines.hrl"). +-include_lib("dudeswave_backend/include/defines.hrl"). -include_lib("storage/include/storage.hrl"). --export([authenticate/2, logout/2]). +-export([authenticate/3, logout/2]). -doc """ Verify a session with an existing cookie. @@ -31,9 +31,12 @@ Verify a session with an existing cookie. Spec: ``` --spec authenticate(User, Auth) -> true | false | {true, Cookie, Validity} | {error, Reason} when +-spec authenticate(Type, User, Auth) -> true | false | {true, Cookie, Validity} | {error, Reason} when + Type :: cookie | password, User :: binary(), Auth :: {cookie, binary()} | {password, binary()}, + Cookie :: binary(), + Validity :: pos_integer(), Reason :: term(). ``` @@ -43,14 +46,15 @@ after authenticating with `Password`. If `Cookie` is valid, the function returns `true`. If the authentication is denied returns `false` """. --spec authenticate(User, Auth) -> true | false | {true, Cookie, Validity} | {error, Reason} when +-spec authenticate(Type, User, Auth) -> true | false | {true, Cookie, Validity} | {error, Reason} when + Type :: cookie | password, User :: binary(), Auth :: {cookie, binary()} | {password, binary()}, Cookie :: binary(), Validity :: pos_integer(), Reason :: term(). -authenticate(User, {cookie, Cookie}) -> +authenticate(cookie, User, Cookie) -> case storage:read(?COOKIESBUCK, Cookie) of {ok, [R]} -> CurTime = calendar:now_to_universal_time(erlang:timestamp()), @@ -69,7 +73,7 @@ authenticate(User, {cookie, Cookie}) -> {error, _} -> {error, service_unavailable} end; -authenticate(User, {password, Password}) -> +authenticate(password, User, Password) -> case storage:read(?USERSBUCK, User) of {ok, [R]} -> Validity = case application:get_env(cookie_validity) of diff --git a/dudeswave_backend/src/dudeswave_backend_user.erl b/dudeswave_backend/src/dudeswave_backend_user.erl index 699f642..907a561 100644 --- a/dudeswave_backend/src/dudeswave_backend_user.erl +++ b/dudeswave_backend/src/dudeswave_backend_user.erl @@ -15,7 +15,7 @@ % -module(dudeswave_backend_user). --include_lib("dudeswave/include/defines.hrl"). +-include_lib("dudeswave_backend/include/defines.hrl"). -include_lib("storage/include/storage.hrl"). -export([details/1, new/3, update/4, delete/1]). diff --git a/dudeswave/Makefile b/dudeswave_web/Makefile similarity index 100% rename from dudeswave/Makefile rename to dudeswave_web/Makefile diff --git a/dudeswave/ebin/dudeswave.app b/dudeswave_web/ebin/dudeswave_web.app similarity index 54% rename from dudeswave/ebin/dudeswave.app rename to dudeswave_web/ebin/dudeswave_web.app index 3eb1cc7..2101f05 100644 --- a/dudeswave/ebin/dudeswave.app +++ b/dudeswave_web/ebin/dudeswave_web.app @@ -1,12 +1,12 @@ -{application,dudeswave, +{application,dudeswave_web, [{description,"The dudeswave web experience"}, {vsn,"1.0.0"}, - {modules,[dudeswave,dudeswave_app,dudeswave_handler, - dudeswave_user_handler,dudeswave_supervisor, - dudeswave_auth,dudeswave_auth_handler]}, + {modules,[dudeswave_web,dudeswave_web_app,dudeswave_web_handler, + dudeswave_web_user_handler,dudeswave_web_supervisor, + dudeswave_web_auth_handler]}, {registered,[]}, {applications,[kernel,stdlib,erts,cowboy,ranch]}, - {mod,{dudeswave_app,[]}}, + {mod,{dudeswave_web_app,[]}}, {env, [ {ip,"127.0.0.1"}, {port,8080} diff --git a/dudeswave_web/src/Makefile b/dudeswave_web/src/Makefile new file mode 100644 index 0000000..75e14ed --- /dev/null +++ b/dudeswave_web/src/Makefile @@ -0,0 +1,19 @@ +.PHONY: all clean +.SUFFIXES: .erl .beam + +ERLC?= erlc -server + +ERLFLAGS= -I ../../ + +OBJS= dudeswave_web.beam dudeswave_web_app.beam +OBJS+= dudeswave_web_supervisor.beam dudeswave_web_handler.beam +OBJS+= dudeswave_web_user_handler.beam dudeswave_web_common.beam + +all: ${OBJS} + +.erl.beam: + ${ERLC} ${ERLOPTS} ${ERLFLAGS} $< + +clean: + rm -f *.beam + diff --git a/dudeswave/src/dudeswave.erl b/dudeswave_web/src/dudeswave_web.erl similarity index 98% rename from dudeswave/src/dudeswave.erl rename to dudeswave_web/src/dudeswave_web.erl index 9893223..ad16985 100644 --- a/dudeswave/src/dudeswave.erl +++ b/dudeswave_web/src/dudeswave_web.erl @@ -13,7 +13,7 @@ % ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF % OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. % --module(dudeswave). +-module(dudeswave_web). -behaviour(gen_server). diff --git a/dudeswave/src/dudeswave_app.erl b/dudeswave_web/src/dudeswave_web_app.erl similarity index 97% rename from dudeswave/src/dudeswave_app.erl rename to dudeswave_web/src/dudeswave_web_app.erl index 5a264cd..06f502d 100644 --- a/dudeswave/src/dudeswave_app.erl +++ b/dudeswave_web/src/dudeswave_web_app.erl @@ -13,14 +13,12 @@ % ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF % OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. % --module(dudeswave_app). +-module(dudeswave_web_app). -behaviour(application). -export([bootstrap/3, start/2, stop/1]). start(_Type, StartArgs) -> - crypto:rand_seed(), - {ok, Addr} = case application:get_env(ip) of {ok, AddrConf} -> inet:parse_address(AddrConf); undefined -> undefined diff --git a/dudeswave/src/dudeswave_auth_handler.erl b/dudeswave_web/src/dudeswave_web_auth_handler.erl similarity index 58% rename from dudeswave/src/dudeswave_auth_handler.erl rename to dudeswave_web/src/dudeswave_web_auth_handler.erl index bd77f7e..ed59839 100644 --- a/dudeswave/src/dudeswave_auth_handler.erl +++ b/dudeswave_web/src/dudeswave_web_auth_handler.erl @@ -94,9 +94,9 @@ forbidden(Req, State) -> <<"POST">> -> {false, Req, State}; _ -> - {User, Auth} = dudeswave_auth:auth_cookies(Req), + {User, Auth} = dudeswave_web_common:auth_cookies(Req), - case dudeswave_auth:authenticate(User, {cookie, Auth}) of + case dudeswave_backend:auth(cookie, User, Cookie) of {error, service_unavailable} -> exit(service_unavailable); true -> {false, Req, State}; false -> {true, Req, State} @@ -112,9 +112,9 @@ content_types_accepted(Req, State) -> end. resource_exists(Req, State) -> - {User, _} = dudeswave_auth:auth_cookies(Req), + {User, _} = dudeswave_web_common:auth_cookies(Req), - case dudeswave_auth:details(User) of + case dudeswave_backend:user_details(User) of [] -> {false, Req, State}; {error, Reason} -> @@ -135,11 +135,11 @@ is_conflict(Req, State) -> {true, Req, State}. allow_missing_post(Req, State) -> {false, Req, State}. delete_resource(Req, State) -> - {User, Auth} = dudeswave_auth:auth_cookies(Req), + {User, Auth} = dudeswave_web_common:auth_cookies(Req), - case dudeswave_auth:logout(User, Auth) of + case dudeswave_backend:logout(User, Auth) of ok -> - {true, dudeswave_auth:invalidate_cookies(Req), State}; + {true, invalidate_cookies(Req), State}; {error, _} -> {false, Req, State} end. @@ -151,11 +151,11 @@ delete_completed(Req, State) -> {false, Req, State}. % login(Req, State) -> - {User, Pass, Req0} = dudeswave_auth:read_login_data(Req), + {User, Pass, Req0} = read_login_data(Req), - case dudeswave_auth:authenticate(User, {password, Pass}) of + case dudeswave_backend:auth(password, User, Pass) of {true, Cookie, Validity} -> - {true, dudeswave_auth:set_auth_cookies(Req, User, Cookie, Validity), State}; + {true, set_auth_cookies(Req, User, Cookie, Validity), State}; false -> {false, Req0, State}; {error, _} -> @@ -170,3 +170,88 @@ logout(Req, State) -> {ok, Req, State}. % terminate(_Reason, _Req, _State) -> ok. + +% +% Private functions +% + +-doc """ +Invalidate the cookies in the passed request. + +Spec: + +``` +-spec invalidate_cookies(Req) -> Req0 when + Req :: cowboy_req:req(), + Req0 :: cowboy_req:req(). +``` + +A new request `Req0` is returned to the caller with the cookies zeroed and +completely invalidated. +""". +-spec invalidate_cookies(Req) -> Req0 when + Req :: cowboy_req:req(), + Req0 :: cowboy_req:req(). + +invalidate_cookies(Req) -> + Req0 = cowboy_req:set_resp_cookie(<<"?DUDEAUTH">>, <<"">>, Req, + #{max_age => 0}), + Req1 = cowboy_req:set_resp_cookie(<<"?DUDENAME">>, <<"">>, Req0, + #{max_age => 0}), + + Req1. + +-doc """ +Spec: + +``` +-spec read_login_data(Req) -> {User, Pass, Req0} when + Req :: cowboy_req:req(), + User :: binary(), + Pass :: binary(), + Req0 :: cowboy_req:req(). +``` +Read the login details from the `Req` body and return `User` and `Password`. +""". +-spec read_login_data(Req) -> {User, Pass, Req0} when + Req :: cowboy_req:req(), + User :: binary(), + Pass :: binary(), + Req0 :: cowboy_req:req(). + +read_login_data(Req) -> + {ok, Data, Req0} = cowboy_req:read_body(Req), + #{<<"user">> := User, <<"password">> := Pass} = json:decode(Data), + + {User, Pass, Req0}. + +-doc """ +Set the authentication cookies for the provided client request + +Spec: + +``` +-spec set_auth_cookies(Req, User, Cookie, Validity) -> Req0 when + Req :: cowboy_req:req(), + User :: binary(), + Cookie :: binary(), + Validity :: pos_integer(), + Req0 :: cowboy_req:req(). +``` + +A new request object `Req0`is returned, with the user and auth cookies set. +""". +-spec set_auth_cookies(Req, User, Cookie, Validity) -> Req0 when + Req :: cowboy_req:req(), + User :: binary(), + Cookie :: binary(), + Validity :: pos_integer(), + Req0 :: cowboy_req:req(). + +set_auth_cookies(Req, User, Cookie, Validity) -> + Req0 = cowboy_req:set_resp_cookie(<<"?DUDEAUTH">>, Cookie, Req, + #{max_age => Validity}), + Req1 = cowboy_req:set_resp_cookie(<<"?DUDENAME">>, User, Req0, + #{max_age => Validity}), + + Req1. diff --git a/dudeswave/include/defines.hrl b/dudeswave_web/src/dudeswave_web_common.erl similarity index 58% rename from dudeswave/include/defines.hrl rename to dudeswave_web/src/dudeswave_web_common.erl index 48c12ba..7762d6e 100644 --- a/dudeswave/include/defines.hrl +++ b/dudeswave_web/src/dudeswave_web_common.erl @@ -13,11 +13,29 @@ % ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF % OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. % +-module(dudeswave_web_common). --define(APPBUCK, dudeswave). --define(USERSBUCK, dudes). --define(COOKIESBUCK, cookies). --define(RANDBYTES, 32). --define(DEFVALIDITY, 365). --define(DUDENAME, "dudename"). --define(DUDEAUTH, "dudeauth"). +-export([auth_cookies/1]). + +-doc """ +Get the authentication cookies from a cowboy request. + +Spec: + +``` +-spec auth_cookies(Req) -> {User, Cookie} when + Req :: cowboy_req:req(), + User :: binary(), + Cookie :: binary(). +``` +""". +-spec auth_cookies(Req) -> {User, Cookie} when + Req :: cowboy_req:req(), + User :: binary(), + Cookie :: binary(). + +auth_cookies(Req) -> + #{?DUDEAUTH := Cookie} = cowboy_req:match_cookies([?DUDEAUTH], Req), + #{?DUDENAME := User} = cowboy_req:match_cookies([?DUDENAME], Req), + + {User, Cookie}. diff --git a/dudeswave/src/dudeswave_handler.erl b/dudeswave_web/src/dudeswave_web_handler.erl similarity index 98% rename from dudeswave/src/dudeswave_handler.erl rename to dudeswave_web/src/dudeswave_web_handler.erl index 9d8ece1..a4f16d6 100644 --- a/dudeswave/src/dudeswave_handler.erl +++ b/dudeswave_web/src/dudeswave_web_handler.erl @@ -13,7 +13,7 @@ % ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF % OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. % --module(dudeswave_handler). +-module(dudeswave_web_handler). -moduledoc """ The dudeswave GET handler. diff --git a/dudeswave/src/dudeswave_supervisor.erl b/dudeswave_web/src/dudeswave_web_supervisor.erl similarity index 94% rename from dudeswave/src/dudeswave_supervisor.erl rename to dudeswave_web/src/dudeswave_web_supervisor.erl index dad62ce..869a491 100644 --- a/dudeswave/src/dudeswave_supervisor.erl +++ b/dudeswave_web/src/dudeswave_web_supervisor.erl @@ -13,7 +13,7 @@ % ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF % OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. % --module(dudeswave_supervisor). +-module(dudeswave_web_supervisor). -behaviour(supervisor). -export([start/0, diff --git a/dudeswave/src/dudeswave_user_handler.erl b/dudeswave_web/src/dudeswave_web_user_handler.erl similarity index 72% rename from dudeswave/src/dudeswave_user_handler.erl rename to dudeswave_web/src/dudeswave_web_user_handler.erl index 3c6b623..da011dc 100644 --- a/dudeswave/src/dudeswave_user_handler.erl +++ b/dudeswave_web/src/dudeswave_web_user_handler.erl @@ -13,7 +13,7 @@ % ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF % OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. % --module(dudeswave_user_handler). +-module(dudeswave_web_user_handler). -moduledoc """ JSON API to manage users. @@ -138,9 +138,9 @@ forbidden(Req, State) -> <<"PUT">> -> {false, Req, State}; _ -> - {User, Auth} = dudeswave_auth:auth_cookies(Req), + {User, Auth} = dudeswave_web_common:auth_cookies(Req), - case dudeswave_auth:authenticate(User, {cookie, Auth}) of + case dudeswave_backend:auth(cookie, User, Auth) of {error, service_unavailable} -> {true, Req, State}; true -> {false, Req, State}; false -> {true, Req, State} @@ -168,9 +168,9 @@ content_types_accepted(Req, State) -> end. resource_exists(Req, State) -> - {User, _} = dudeswave_auth:auth_cookies(Req), + {User, _} = dudeswave_web_common:auth_cookies(Req), - case dudeswave_auth:details(User) of + case dudeswave_backend:user_details(User) of [] -> {false, Req, State}; {error, _} -> @@ -194,9 +194,9 @@ is_conflict(Req, State) -> {false, Req, State}. allow_missing_post(Req, State) -> {false, Req, State}. delete_resource(Req, State) -> - {User, _} = dudeswave_auth:auth_cookies(Req), + {User, _} = dudeswave_web_common:auth_cookies(Req), - case dudeswave_auth:delete(User) of + case dudeswave_backend:delete_user(User) of ok -> {true, Req, State}; {error, _} -> {false, Req, State} end. @@ -208,31 +208,85 @@ delete_completed(Req, State) -> {true, Req, State}. % create_user(Req, State) -> - {User, Pass, Email, Req0} = dudeswave_auth:read_new_user_data(Req), + {User, Pass, Email, Req0} = read_new_user_data(Req), - case dudeswave_auth:new(User, Pass, Email) of + case dudeswave_backend:new_user(User, Pass, Email) of ok -> {true, Req0, []}; {error, _} -> {false, Req0, State} end. modify_user(Req, State) -> - {User, _} = dudeswave_auth:auth_cookies(Req), - {Email, Desc, Name, Req0} = dudeswave_auth:read_update_user_data(Req), + {User, _} = dudeswave_web_common:auth_cookies(Req), + {Email, Desc, Name, Req0} = read_update_user_data(Req), - case dudeswave_auth:update(User, Name, Email, Desc) of + case dudeswave_backend:update_user(User, Name, Email, Desc) of ok -> {true, Req0, []}; {error, _} -> {false, Req0, State} end. user_details(Req, State) -> - {User, _} = dudeswave_auth:auth_cookies(Req), + {User, _} = dudeswave_web_common:auth_cookies(Req), #{details := Details} = State, Data = Details#{user => User}, {iolist_to_binary(json:encode(Data)), Req, State}. % -% gen_server callbacks +% Private functions % -terminate(_Reason, _Req, _State) -> ok. +-doc """ +Update user informations. + +Spec: + +``` +-spec read_update_user_data(Req) -> {Email, Desc, Name, Req0} when + Req :: cowboy_req:req(), + Email :: binary(), + Desc :: binary(), + Name :: binary(), + Req0 :: cowboy_req:req(). +``` +""". +-spec read_update_user_data(Req) -> {Email, Desc, Name, Req0} when + Req :: cowboy_req:req(), + Email :: binary(), + Desc :: binary(), + Name :: binary(), + Req0 :: cowboy_req:req(). + +read_update_user_data(Req) -> + {ok, Data, Req0} = cowboy_req:read_body(Req), + #{<<"email">> := Email, <<"description">> := Desc, + <<"name">> := Name} = json:decode(Data), + + {Email, Desc, Name, Req0}. + +-doc """ +Read new registration informations from the request + +Spec: + +``` +-spec read_new_user_data(Req) -> {User, Pass, Email Req0} when + Req :: cowboy_req:req(), + User :: binary(), + Pass :: binary(), + Email :: binary(), + Req0 :: cowboy_req:req(). +``` +""". +-spec read_new_user_data(Req) -> {User, Pass, Email, Req0} when + Req :: cowboy_req:req(), + User :: binary(), + Pass :: binary(), + Email :: binary(), + Req0 :: cowboy_req:req(). + +read_new_user_data(Req) -> + {ok, Data, Req0} = cowboy_req:read_body(Req), + #{<<"user">> := User, <<"password">> := Pass, + <<"email">> := Email} = json:decode(Data), + + {User, Pass, Email, Req0}.