From 73d98ed56fe58522f52d1235ddc6b55474fa6202 Mon Sep 17 00:00:00 2001 From: absc Date: Thu, 15 Aug 2024 20:27:02 +0000 Subject: [PATCH] Move the defines in their own file. At the same time, simplify a bunch of APIs. --- dudeswave/src/Makefile | 2 + dudeswave/src/dudeswave_app.erl | 9 +- dudeswave/src/dudeswave_auth.erl | 127 ++++++++--------------- dudeswave/src/dudeswave_auth_handler.erl | 15 +-- dudeswave/src/dudeswave_user_handler.erl | 16 +-- storage/src/Makefile | 2 +- 6 files changed, 58 insertions(+), 113 deletions(-) diff --git a/dudeswave/src/Makefile b/dudeswave/src/Makefile index 7378aae..4a86db2 100644 --- a/dudeswave/src/Makefile +++ b/dudeswave/src/Makefile @@ -3,6 +3,8 @@ ERLC?= erlc -server +ERLOPTS= -I ../../ + OBJS= dudeswave.beam dudeswave_app.beam OBJS+= dudeswave_supervisor.beam dudeswave_handler.beam OBJS+= dudeswave_user_handler.beam dudeswave_auth.beam diff --git a/dudeswave/src/dudeswave_app.erl b/dudeswave/src/dudeswave_app.erl index e181ff9..dfd29e0 100644 --- a/dudeswave/src/dudeswave_app.erl +++ b/dudeswave/src/dudeswave_app.erl @@ -18,9 +18,7 @@ -export([bootstrap/3, start/2, stop/1]). --define(APPBUCK, dudeswave). --define(USERSBUCK, dudes). --define(COOKIESBUCK, cookies). +-include_lib("dudeswave/include/defines.hrl"). start(_Type, StartArgs) -> crypto:rand_seed(), @@ -40,9 +38,8 @@ start(_Type, StartArgs) -> Dispatch = cowboy_router:compile([ {'_', [ - {"/api/v1/user", dudeswave_user_handler, #{bucket => ?USERSBUCK, - cookies => ?COOKIESBUCK}}, - {"/", dudeswave_handler, #{bucket => ?APPBUCK}} + {"/api/v1/user", dudeswave_user_handler, #{}}, + {"/", dudeswave_handler, #{}} ]} ]), diff --git a/dudeswave/src/dudeswave_auth.erl b/dudeswave/src/dudeswave_auth.erl index 8b5e357..f1f8953 100644 --- a/dudeswave/src/dudeswave_auth.erl +++ b/dudeswave/src/dudeswave_auth.erl @@ -21,13 +21,11 @@ Here lives all the functions needed to create, update and delete users from the dudeswave database. """. --define(RANDBYTES, 32). --define(DEFVALIDITY, 365). - +-include_lib("dudeswave/include/defines.hrl"). -include_lib("storage/include/storage.hrl"). --export([authenticate/3, authenticate/4, details/2, new/4, - update/5, delete/2, logout/3]). +-export([authenticate/2, details/1, new/3, + update/4, delete/1, logout/2]). -doc """ Verify a session with an existing cookie. @@ -35,24 +33,27 @@ Verify a session with an existing cookie. Spec: ``` --spec authenticate(User, Cookie, Bucket) -> true | false | {error, Reason} when +-spec authenticate(User, Auth) -> true | false | {true, Cookie, Validity} | {error, Reason} when User :: binary(), - Cookie :: binary(), - Bucket :: atom(), + Auth :: {cookie, binary()} | {password, binary()}, Reason :: term(). ``` -Check against che `Bucket` table, containing the list of current cookies, -if `Cookie` is still valid to authenticate `User` session. +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, Cookie, Bucket) -> true | false | {error, Reason} when +-spec authenticate(User, Auth) -> true | false | {true, Cookie, Validity} | {error, Reason} when User :: binary(), + Auth :: {cookie, binary()} | {password, binary()}, Cookie :: binary(), - Bucket :: atom(), + Validity :: pos_integer(), Reason :: term(). -authenticate(User, Cookie, Bucket) -> - case storage:read(Bucket, Cookie) of +authenticate(User, {cookie, Cookie}) -> + case storage:read(?COOKIESBUCK, Cookie) of {ok, [R]} -> CurTime = calendar:now_to_universal_time(erlang:timestamp()), CookieTime = R#object.value, @@ -68,43 +69,10 @@ authenticate(User, Cookie, Bucket) -> end; {ok, []} -> false; {error, _} -> {error, service_unavailable} - end. + end; --doc """ -Authenticate a user and return a new cookie for the new session. - -Spec: - -``` --spec authenticate(User, Password, Cookies, Bucket) -> {true, Cookie, Validity} | - false | {error, Reason} when - User :: binary(), - Password :: binary(), - Cookies :: atom(), - Bucket :: atom(), - Cookie :: binary(), - Validity :: non_neg_integer(), - Reason :: term(). -``` - -Authenticate `User` with `Password`, reading the user's details from `Bucket`. - -If the authentication is successful, a new cookie is generated and stored in -the `Cookies` bucket. The cookie is returned to the caller in a tuple `{true, Cookie}`, -otherwise `false` is returned and the authentication is denied. -""". --spec authenticate(User, Password, Cookies, Bucket) -> {true, Cookie, Validity} | - false | {error, Reason} when - User :: binary(), - Password :: binary(), - Cookies :: atom(), - Bucket :: atom(), - Cookie :: binary(), - Validity :: non_neg_integer(), - Reason :: term(). - -authenticate(User, Password, Cookies, Bucket) -> - case storage:read(Bucket, User) of +authenticate(User, {password, Password}) -> + case storage:read(?USERSBUCK, User) of {ok, [R]} -> Validity = case application:get_env(cookie_validity) of {ok, Value} -> @@ -120,7 +88,8 @@ authenticate(User, Password, Cookies, Bucket) -> if Auth =:= Hash -> Cookie = base64:encode(rand:bytes(64)), - case storage:write(Cookies, <>, Validity, [{user, User}]) of + case storage:write(?COOKIESBUCK, <>, + Validity, [{user, User}]) of ok -> {true, Cookie, Validity}; {error, Reason} -> {error, Reason} end; @@ -136,26 +105,24 @@ Close an existing session Spec: ``` --spec logout(User, Cookie, Bucket) -> ok | {error, Reason} when +-spec logout(User, Cookie) -> ok | {error, Reason} when User :: binary(), Cookie :: binary(), - Bucket :: atom(), Reason :: term(). ``` Invalidate and delete `Cookie` associated with `User` from the system. """. --spec logout(User, Cookie, Bucket) -> ok | {error, Reason} when +-spec logout(User, Cookie) -> ok | {error, Reason} when User :: binary(), Cookie :: binary(), - Bucket :: atom(), Reason :: term(). -logout(User, Cookie, Bucket) -> - case storage:read(Bucket, Cookie) of +logout(User, Cookie) -> + case storage:read(?COOKIESBUCK, Cookie) of {ok, [R]} -> {user, User} = lists:keyfind(user, 1, R#object.metadata), - storage:delete(Bucket, Cookie); + storage:delete(?COOKIESBUCK, Cookie); {ok, []} -> {error, not_found}; {error, Reason} -> @@ -168,21 +135,19 @@ Return user details. Spec: ``` --spec details(User, Bucket) -> Value | {error, Reason} when +-spec details(User) -> Value | {error, Reason} when User :: binary(), - Bucket :: atom(), Value :: term(), Reason :: term(). ``` """. --spec details(User, Bucket) -> Value | {error, Reason} when +-spec details(User) -> Value | {error, Reason} when User :: binary(), - Bucket :: atom(), Value :: term(), Reason :: term(). -details(User, Bucket) -> - case storage:read(Bucket, User) of +details(User) -> + case storage:read(?USERSBUCK, User) of {ok, [R]} -> R#object.value; {error, Reason} -> {error, Reason}; {ok, []} -> {error, not_found} @@ -194,35 +159,33 @@ Create a new user. Spec: ``` --spec new(User, Password, Email, Bucket) -> ok | {error, Reason} when +-spec new(User, Password, Email) -> ok | {error, Reason} when User :: binary(), Password :: binary(), Email :: binary(), - Bucket :: atom(), Reason :: term(). ``` -The `User` is added to `Bucket`, that is, the users bucket for the application. +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 `status` of `waiting_confirmation`, based on the application settings, the confirmation method may vary. """. --spec new(User, Password, Email, Bucket) -> ok | {error, Reason} when +-spec new(User, Password, Email) -> ok | {error, Reason} when User :: binary(), Password :: binary(), Email :: binary(), - Bucket :: atom(), Reason :: term(). -new(User, Password, Email, Bucket) -> +new(User, Password, Email) -> Salt = rand:bytes(?RANDBYTES), Hash = crypto:hash(sha256, <>), Data = #{<<"email">> => Email}, Metadata = [{salt, Salt}, {hash, Hash}, {status, waiting_confirmation}], - storage:write(Bucket, User, Data, Metadata). + storage:write(?USERSBUCK, User, Data, Metadata). -doc """ Update user's details @@ -230,12 +193,11 @@ Update user's details Spec: ``` --spec update(User, Name, Email, Desc, Bucket) -> ok | {error, Reason} when +-spec update(User, Name, Email, Desc) -> ok | {error, Reason} when User :: binary(), Name :: binary(), Email :: binary(), Desc :: binary(), - Bucket :: atom(), Reason :: term(). ``` @@ -243,16 +205,15 @@ 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, Bucket) -> ok | {error, Reason} when +-spec update(User, Name, Email, Desc) -> ok | {error, Reason} when User :: binary(), Name :: binary(), Email :: binary(), Desc :: binary(), - Bucket :: atom(), Reason :: term(). -update(User, Name, Email, Desc, Bucket) -> - {ok, CurData, Metadata} = case storage:read(Bucket, User) of +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} @@ -261,25 +222,23 @@ update(User, Name, Email, Desc, Bucket) -> Data = CurData#{<<"email">> => Email, <<"name">> => Name, <<"description">> => Desc}, - storage:write(Bucket, User, Data, Metadata). + storage:write(?USERSBUCK, User, Data, Metadata). -doc """ Delete an existing user from the database. ``` --spec delete(User, Bucket) -> ok | {error, Reason} when +-spec delete(User) -> ok | {error, Reason} when User :: binary(), - Bucket :: atom(), Reason :: term(). ``` """. --spec delete(User, Bucket) -> ok | {error, Reason} when +-spec delete(User) -> ok | {error, Reason} when User :: binary(), - Bucket :: atom(), Reason :: term(). -delete(User, Bucket) -> +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(Bucket, User). \ No newline at end of file + storage:delete(?USERSBUCK, User). \ No newline at end of file diff --git a/dudeswave/src/dudeswave_auth_handler.erl b/dudeswave/src/dudeswave_auth_handler.erl index 8a8608d..6466308 100644 --- a/dudeswave/src/dudeswave_auth_handler.erl +++ b/dudeswave/src/dudeswave_auth_handler.erl @@ -96,9 +96,8 @@ forbidden(Req, State) -> _ -> #{dudeauth := Auth, dudename := User} = cowboy_req:match_cookies([dudeauth, dudename], Req), - {ok, Bucket} = maps:find(cookies, State), - case dudeswave_auth:authenticate(User, Auth, Bucket) of + case dudeswave_auth:authenticate(User, Auth) of {error, service_unavailable} -> exit(service_unavailable); true -> {false, Req, State}; false -> {true, Req, State} @@ -115,15 +114,13 @@ content_types_accepted(Req, State) -> resource_exists(Req, State) -> #{dudename := User} = cowboy_req:match_cookies([dudename], Req), - {ok, Bucket} = maps:find(bucket, State), - case dudeswave_auth:details(User, Bucket) of + case dudeswave_auth:details(User) of [] -> {false, Req, State}; {error, Reason} -> exit(Reason); _ -> NewState = State#{ - bucket => Bucket, user_exists => true }, {true, Req, NewState} @@ -139,11 +136,10 @@ is_conflict(Req, State) -> {true, Req, State}. allow_missing_post(Req, State) -> {false, Req, State}. delete_resource(Req, State) -> - {ok, Bucket} = maps:find(bucket, State), #{dudename := User, dudeauth := Auth} = cowboy_req:match_cookies([dudename, dudeauth], Req), - case dudeswave_auth:logout(User, Auth, Bucket) of + case dudeswave_auth:logout(User, Auth) of ok -> Req0 = cowboy_req:set_resp_cookie(<<"dudeauth">>, Auth, Req, #{max_age => 0}), @@ -160,13 +156,10 @@ delete_completed(Req, State) -> {false, Req, State}. % login(Req, State) -> - {ok, Cookies} = maps:find(cookies, State), - {ok, Bucket} = maps:find(bucket, State), - {ok, Data, Req0} = cowboy_req:read_body(Req), #{<<"user">> := User, <<"password">> := Pass} = json:decode(Data), - case dudeswave_auth:authenticate(User, Pass, Cookies, Bucket) of + case dudeswave_auth:authenticate(User, Pass) of {true, Cookie, Validity} -> Req1 = cowboy_req:set_resp_cookie(<<"dudeauth">>, Cookie, Req0, #{max_age => Validity}), diff --git a/dudeswave/src/dudeswave_user_handler.erl b/dudeswave/src/dudeswave_user_handler.erl index 3c86d92..257b248 100644 --- a/dudeswave/src/dudeswave_user_handler.erl +++ b/dudeswave/src/dudeswave_user_handler.erl @@ -147,9 +147,8 @@ forbidden(Req, State) -> _ -> #{dudeauth := Auth, dudename := User} = cowboy_req:match_cookies([dudeauth, dudename], Req), - {ok, Bucket} = maps:find(cookies, State), - case dudeswave_auth:authenticate(User, Auth, Bucket) of + case dudeswave_auth:authenticate(User, Auth) of {error, service_unavailable} -> {true, Req, State}; true -> @@ -180,14 +179,12 @@ content_types_accepted(Req, State) -> resource_exists(Req, State) -> #{dudename := User} = cowboy_req:match_cookies([dudename], Req), - {ok, Bucket} = maps:find(bucket, State), - case dudeswave_auth:details(User, Bucket) of + case dudeswave_auth:details(User) of [] -> {false, Req, State}; {error, _} -> {false, Req, State}; Details -> NewState = State#{ - bucket => Bucket, details => Details, user_exists => true, request => cowboy_req:method(Req) @@ -205,10 +202,9 @@ is_conflict(Req, State) -> {false, Req, State}. allow_missing_post(Req, State) -> {false, Req, State}. delete_resource(Req, State) -> - {ok, Bucket} = maps:find(bucket, State), #{dudename := User} = cowboy_req:match_cookies([dudename], Req), - case dudeswave_auth:delete(User, Bucket) of + case dudeswave_auth:delete(User) of ok -> {true, Req, State}; {error, _} -> {false, Req, State} end. @@ -220,26 +216,24 @@ delete_completed(Req, State) -> {true, Req, State}. % create_user(Req, State) -> - {ok, Bucket} = maps:find(bucket, State), #{dudename := User} = cowboy_req:match_cookies([dudename], Req), {ok, Data, Req0} = cowboy_req:read_body(Req), #{<<"password">> := Pass, <<"email">> := Email} = json:decode(Data), - case dudeswave_auth:new(User, Pass, Email, Bucket) of + case dudeswave_auth:new(User, Pass, Email) of ok -> {true, Req0, []}; {error, _} -> {false, Req0, State} end. modify_user(Req, State) -> - {ok, Bucket} = maps:find(bucket, State), #{dudename := User} = cowboy_req:match_cookies([dudename], Req), {ok, Data, Req0} = cowboy_req:read_body(Req), #{<<"email">> := Email, <<"description">> := Desc, <<"name">> := Name} = json:decode(Data), - case dudeswave_auth:update(User, Name, Email, Desc, Bucket) of + case dudeswave_auth:update(User, Name, Email, Desc) of ok -> {true, Req0, []}; {error, _} -> {false, Req0, State} end. diff --git a/storage/src/Makefile b/storage/src/Makefile index fb62b6c..265ec8f 100644 --- a/storage/src/Makefile +++ b/storage/src/Makefile @@ -3,7 +3,7 @@ ERLC?= erlc -server -ERLOPTS+= -I ../include +ERLOPTS+= -I ../../ OBJS= storage.beam storage_app.beam