Compare commits

..

No commits in common. "ea339a10248ca1a72e5f644415cff1a022e95c91" and "5f14a4f6cb698d4a59e063e5bdf8d4403785b375" have entirely different histories.

6 changed files with 91 additions and 96 deletions

View File

@ -5,6 +5,6 @@
dudeswave_backend_supervisor, dudeswave_backend_supervisor,
dudeswave_backend_auth,dudeswave_backend_user]}, dudeswave_backend_auth,dudeswave_backend_user]},
{registered,[]}, {registered,[]},
{applications,[kernel,stdlib,erts]}, {applications,[kernel,stdlib,erts,cowboy,ranch]},
{mod,{dudeswave_backend_app,[]}}, {mod,{dudeswave_backend_app,[]}},
{start_phases,[]}]}. {start_phases,[]}]}.

View File

@ -24,8 +24,8 @@
-export([init/1, handle_call/3, handle_cast/2, terminate/2]). -export([init/1, handle_call/3, handle_cast/2, terminate/2]).
% Public API exports % Public API exports
-export([auth/4, logout/3, user_details/2, new_user/4, -export([auth/3, logout/2, user_details/1, new_user/3,
update_user/5, delete_user/2]). update_user/4, delete_user/1]).
% %
% Startup functions % Startup functions
@ -47,33 +47,33 @@ init([]) ->
% Users management % Users management
% %
auth(cookie, User, Host, Cookie) -> auth(cookie, User, Cookie) ->
gen_server:call({local, ?MODULE}, {cookie, User, Host, Cookie}); gen_server:call({local, ?MODULE}, {cookie, User, Cookie});
auth(password, User, Host, Password) -> auth(password, User, Password) ->
gen_server:call({local, ?MODULE}, {password, User, Host, Password}). gen_server:call({local, ?MODULE}, {password, User, Password}).
logout(User, Host, Cookie) -> logout(User, Cookie) ->
gen_server:call({local, ?MODULE}, {logout, User, Host, Cookie}). gen_server:call({local, ?MODULE}, {logout, User, Cookie}).
user_details(User, Host) -> user_details(User) ->
gen_server:call({local, ?MODULE}, {user_details, User, Host}). gen_server:call({local, ?MODULE}, {user_details, User}).
new_user(User, Host, Password, Email) -> new_user(User, Password, Email) ->
gen_server:call({local, ?MODULE}, {new_user, User, Host, Password, Email}). gen_server:call({local, ?MODULE}, {new_user, User, Password, Email}).
update_user(User, Host, Name, Email, Desc) -> update_user(User, Name, Email, Desc) ->
gen_server:call({local, ?MODULE}, {update_user, User, Host, Name, Email, Desc}). gen_server:call({local, ?MODULE}, {update_user, User, Name, Email, Desc}).
delete_user(User, Host) -> delete_user(User) ->
gen_server:call({local, ?MODULE}, {delete_user, User, Host}). gen_server:call({local, ?MODULE}, {delete_user, User}).
% %
% Callbacks % Callbacks
% %
handle_call({cookie, User, Host, Cookie}, _From, State) -> handle_call({cookie, User, Cookie}, _From, State) ->
case dudeswave_users:authenticate(cookie, User, Host, Cookie) of case dudeswave_users:authenticate(cookie, User, Cookie) of
true -> true ->
{reply, true, State}; {reply, true, State};
false -> false ->
@ -82,8 +82,8 @@ handle_call({cookie, User, Host, Cookie}, _From, State) ->
{reply, {error, Reason}, State} {reply, {error, Reason}, State}
end; end;
handle_call({password, User, Host, Password}, _From, State) -> handle_call({password, User, Password}, _From, State) ->
case dudeswave_users:authenticate(password, User, Host, Password) of case dudeswave_users:authenticate(password, User, Password) of
{true, Cookie, Validity} -> {true, Cookie, Validity} ->
{reply, {true, Cookie, Validity}, State}; {reply, {true, Cookie, Validity}, State};
false -> false ->
@ -92,33 +92,33 @@ handle_call({password, User, Host, Password}, _From, State) ->
{reply, {error, Reason}, State} {reply, {error, Reason}, State}
end; end;
handle_call({logout, User, Host, Cookie}, _From, State) -> handle_call({logout, User, Cookie}, _From, State) ->
case dudeswave_users:logout(User, Host, Cookie) of case dudeswave_users:logout(User, Cookie) of
ok -> {reply, ok, State}; ok -> {reply, ok, State};
{error, Reason} -> {reply, {error, Reason}, State} {error, Reason} -> {reply, {error, Reason}, State}
end; end;
handle_call({user_details, User, Host}, _From, State) -> handle_call({user_details, User}, _From, State) ->
case dudeswave_users:details(User, Host) of case dudeswave_users:details(User) of
{error, not_found} -> {reply, not_found, State}; {error, not_found} -> {reply, not_found, State};
{error, Reason} -> {reply, {error, Reason}, State}; {error, Reason} -> {reply, {error, Reason}, State};
Val -> {reply, Val, State} Val -> {reply, Val, State}
end; end;
handle_call({new_user, User, Host, Password, Email}, _From, State) -> handle_call({new_user, User, Password, Email}, _From, State) ->
case dudeswave_users:new(User, Host, Password, Email) of case dudeswave_users:new(User, Password, Email) of
ok -> {reply, ok, State}; ok -> {reply, ok, State};
{error, Reason} -> {reply, {error, Reason}, State} {error, Reason} -> {reply, {error, Reason}, State}
end; end;
handle_call({update_user, User, Host, Name, Email, Desc}, _From, State) -> handle_call({update_user, User, Name, Email, Desc}, _From, State) ->
case dudeswave_users:update(User, Host, Name, Email, Desc) of case dudeswave_users:update(User, Name, Email, Desc) of
ok -> {reply, ok, State}; ok -> {reply, ok, State};
{error, Reason} -> {reply, {error, Reason}, State} {error, Reason} -> {reply, {error, Reason}, State}
end; end;
handle_call({delete_user, User, Host}, _From, State) -> handle_call({delete_user, User}, _From, State) ->
case dudeswave_users:delete(User, Host) of case dudeswave_users:delete(User) of
ok -> {reply, ok, State}; ok -> {reply, ok, State};
{error, Reason} -> {reply, {error, Reason}, State} {error, Reason} -> {reply, {error, Reason}, State}
end. end.

View File

@ -5,6 +5,6 @@
dudeswave_users_supervisor, dudeswave_users_supervisor,
dudeswave_users_auth,dudeswave_users_user]}, dudeswave_users_auth,dudeswave_users_user]},
{registered,[]}, {registered,[]},
{applications,[kernel,stdlib,erts]}, {applications,[kernel,stdlib,erts,cowboy,ranch]},
{mod,{dudeswave_users_app,[]}}, {mod,{dudeswave_users_app,[]}},
{start_phases,[]}]}. {start_phases,[]}]}.

View File

@ -0,0 +1,23 @@
%
% Copyright (c) 2024 Andrea Biscuola <a@abiscuola.com>
%
% 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.
%
-define(APPBUCK, dudeswave).
-define(USERSBUCK, dudes).
-define(COOKIESBUCK, cookies).
-define(RANDBYTES, 32).
-define(DEFVALIDITY, 365).
-define(DUDENAME, "dudename").
-define(DUDEAUTH, "dudeauth").

View File

@ -20,10 +20,10 @@ Dudes authentication module
Here lives all the functions for the APIs needed to handle users authentication. Here lives all the functions for the APIs needed to handle users authentication.
""". """.
-include_lib("dudeswave_backend/include/defines.hrl"). -include_lib("dudeswave_users/include/defines.hrl").
-include_lib("storage/include/storage.hrl"). -include_lib("storage/include/storage.hrl").
-export([authenticate/4, logout/3]). -export([authenticate/3, logout/2]).
-doc """ -doc """
Verify a session with an existing cookie. Verify a session with an existing cookie.
@ -31,10 +31,9 @@ Verify a session with an existing cookie.
Spec: Spec:
``` ```
-spec authenticate(Type, User, Host, 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, Type :: cookie | password,
User :: binary(), User :: binary(),
Host :: binary(),
Auth :: {cookie, binary()} | {password, binary()}, Auth :: {cookie, binary()} | {password, binary()},
Cookie :: binary(), Cookie :: binary(),
Validity :: pos_integer(), Validity :: pos_integer(),
@ -47,18 +46,15 @@ after authenticating with `Password`.
If `Cookie` is valid, the function returns `true`. If the authentication is denied If `Cookie` is valid, the function returns `true`. If the authentication is denied
returns `false` returns `false`
""". """.
-spec authenticate(Type, User, Host, 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, Type :: cookie | password,
User :: binary(), User :: binary(),
Host :: binary(),
Auth :: {cookie, binary()} | {password, binary()}, Auth :: {cookie, binary()} | {password, binary()},
Cookie :: binary(), Cookie :: binary(),
Validity :: pos_integer(), Validity :: pos_integer(),
Reason :: term(). Reason :: term().
authenticate(cookie, User, Host, Cookie) -> authenticate(cookie, User, Cookie) ->
ComplUser = <<User/binary, "@", Host/binary>>,
case storage:read(?COOKIESBUCK, Cookie) of case storage:read(?COOKIESBUCK, Cookie) of
{ok, [R]} -> {ok, [R]} ->
CurTime = calendar:now_to_universal_time(erlang:timestamp()), CurTime = calendar:now_to_universal_time(erlang:timestamp()),
@ -68,7 +64,7 @@ authenticate(cookie, User, Host, Cookie) ->
if if
CookieTime >= CurTime -> CookieTime >= CurTime ->
if if
ComplUser =:= CookieUser -> true; User =:= CookieUser -> true;
true -> false true -> false
end; end;
true -> false true -> false
@ -77,10 +73,8 @@ authenticate(cookie, User, Host, Cookie) ->
{error, _} -> {error, service_unavailable} {error, _} -> {error, service_unavailable}
end; end;
authenticate(password, User, Host, Password) -> authenticate(password, User, Password) ->
ComplUser = <<User/binary, "@", Host/binary>>, case storage:read(?USERSBUCK, User) of
case storage:read(?USERSBUCK, ComplUser) of
{ok, [R]} -> {ok, [R]} ->
Validity = case application:get_env(cookie_validity) of Validity = case application:get_env(cookie_validity) of
{ok, Value} -> {ok, Value} ->
@ -89,8 +83,8 @@ authenticate(password, User, Host, Password) ->
erlang:system_time(seconds) + ?DEFVALIDITY * 86400 erlang:system_time(seconds) + ?DEFVALIDITY * 86400
end, end,
{hash, Hash} = proplists:lookup(hash, R#object.value), {hash, Hash} = proplists:lookup(hash, R#object.metadata),
{salt, Salt} = proplists:lookup(salt, R#object.value), {salt, Salt} = proplists:lookup(salt, R#object.metadata),
{approved, Appr} = proplists:lookup(approved, R#object.metadata), {approved, Appr} = proplists:lookup(approved, R#object.metadata),
Auth = crypto:hash(sha256, <<Password/binary, Salt/binary>>), Auth = crypto:hash(sha256, <<Password/binary, Salt/binary>>),
@ -100,7 +94,7 @@ authenticate(password, User, Host, Password) ->
Auth =:= Hash -> Auth =:= Hash ->
Cookie = base64:encode(rand:bytes(64)), Cookie = base64:encode(rand:bytes(64)),
case storage:write(?COOKIESBUCK, <<Cookie/binary>>, case storage:write(?COOKIESBUCK, <<Cookie/binary>>,
Validity, [{user, ComplUser}]) of Validity, [{user, User}]) of
ok -> {true, Cookie, Validity}; ok -> {true, Cookie, Validity};
{error, Reason} -> {error, Reason} {error, Reason} -> {error, Reason}
end; end;
@ -116,27 +110,23 @@ Close an existing session
Spec: Spec:
``` ```
-spec logout(User, Host, Cookie) -> ok | {error, Reason} when -spec logout(User, Cookie) -> ok | {error, Reason} when
User :: binary(), User :: binary(),
Host :: binary(),
Cookie :: binary(), Cookie :: binary(),
Reason :: term(). Reason :: term().
``` ```
Invalidate and delete `Cookie` associated with `User` from the system. Invalidate and delete `Cookie` associated with `User` from the system.
""". """.
-spec logout(User, Host, Cookie) -> ok | {error, Reason} when -spec logout(User, Cookie) -> ok | {error, Reason} when
User :: binary(), User :: binary(),
Host :: binary(),
Cookie :: binary(), Cookie :: binary(),
Reason :: term(). Reason :: term().
logout(User, Host, Cookie) -> logout(User, Cookie) ->
ComplUser = <<User/binary, "@", Host/binary>>,
case storage:read(?COOKIESBUCK, Cookie) of case storage:read(?COOKIESBUCK, Cookie) of
{ok, [R]} -> {ok, [R]} ->
{user, ComplUser} = proplists:lookup(user, R#object.metadata), {user, User} = proplists:lookup(user, R#object.metadata),
storage:delete(?COOKIESBUCK, Cookie); storage:delete(?COOKIESBUCK, Cookie);
{ok, []} -> {ok, []} ->
{error, not_found}; {error, not_found};

View File

@ -15,10 +15,10 @@
% %
-module(dudeswave_users_user). -module(dudeswave_users_user).
-include_lib("dudeswave_backend/include/defines.hrl"). -include_lib("dudeswave_users/include/defines.hrl").
-include_lib("storage/include/storage.hrl"). -include_lib("storage/include/storage.hrl").
-export([details/2, new/4, update/5, delete/1]). -export([details/1, new/3, update/4, delete/1]).
-doc """ -doc """
Return user details. Return user details.
@ -26,30 +26,22 @@ Return user details.
Spec: Spec:
``` ```
-spec details(User, Host) -> {ok, Value} | {error, Reason} when -spec details(User) -> Value | {error, Reason} when
User :: binary(), User :: binary(),
Host :: binary(),
Value :: term(), Value :: term(),
Reason :: term(). Reason :: term().
``` ```
""". """.
-spec details(User, Host) -> {ok, Value} | {error, Reason} when -spec details(User) -> Value | {error, Reason} when
User :: binary(), User :: binary(),
Host :: binary(),
Value :: term(), Value :: term(),
Reason :: term(). Reason :: term().
details(User, Host) -> details(User) ->
ComplUser = <<User/binary, "@", Host/binary>>, case storage:read(?USERSBUCK, User) of
{ok, [R]} -> R#object.value;
case storage:read(?USERSBUCK, ComplUser) of {error, Reason} -> {error, Reason};
{ok, [R]} -> {ok, []} -> {error, not_found}
{details, Details} = proplists:lookup(details, R#object.value),
{ok, Details};
{error, Reason} ->
{error, Reason};
{ok, []} ->
{error, not_found}
end. end.
-doc """ -doc """
@ -58,9 +50,8 @@ Create a new user.
Spec: Spec:
``` ```
-spec new(User, Host, Password, Email) -> ok | {error, Reason} when -spec new(User, Password, Email) -> ok | {error, Reason} when
User :: binary(), User :: binary(),
Host :: binary(),
Password :: binary(), Password :: binary(),
Email :: binary(), Email :: binary(),
Reason :: term(). Reason :: term().
@ -71,23 +62,20 @@ The `User` is created, and stored in the application's users bucket
The new user is saved with a metadata `approved` of `false`, The new user is saved with a metadata `approved` of `false`,
""". """.
-spec new(User, Host, Password, Email) -> ok | {error, Reason} when -spec new(User, Password, Email) -> ok | {error, Reason} when
User :: binary(), User :: binary(),
Host :: binary(),
Password :: binary(), Password :: binary(),
Email :: binary(), Email :: binary(),
Reason :: term(). Reason :: term().
new(User, Host, Password, Email) -> new(User, Password, Email) ->
ComplUser = <<User/binary, "@", Host/binary>>,
Salt = rand:bytes(?RANDBYTES), Salt = rand:bytes(?RANDBYTES),
Hash = crypto:hash(sha256, <<Password/binary, Salt/binary>>), Hash = crypto:hash(sha256, <<Password/binary, Salt/binary>>),
Data = [{details, #{<<"email">> => Email}}, {salt, Salt}, {hash, Hash}], Data = #{<<"email">> => Email},
Metadata = [{approved, false}], Metadata = [{salt, Salt}, {hash, Hash}, {approved, false}],
storage:write(?USERSBUCK, ComplUser, Data, Metadata). storage:write(?USERSBUCK, User, Data, Metadata).
-doc """ -doc """
Update user's details Update user's details
@ -95,9 +83,8 @@ Update user's details
Spec: Spec:
``` ```
-spec update(User, Host, Name, Email, Desc) -> ok | {error, Reason} when -spec update(User, Name, Email, Desc) -> ok | {error, Reason} when
User :: binary(), User :: binary(),
Host :: binary(),
Name :: binary(), Name :: binary(),
Email :: binary(), Email :: binary(),
Desc :: binary(), Desc :: binary(),
@ -108,29 +95,24 @@ 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 and cannot be modified. All the other fields, excluding the e-mail, are the
ones that can be seen in the public page. ones that can be seen in the public page.
""". """.
-spec update(User, Host, Name, Email, Desc) -> ok | {error, Reason} when -spec update(User, Name, Email, Desc) -> ok | {error, Reason} when
User :: binary(), User :: binary(),
Host :: binary(),
Name :: binary(), Name :: binary(),
Email :: binary(), Email :: binary(),
Desc :: binary(), Desc :: binary(),
Reason :: term(). Reason :: term().
update(User, Host, Name, Email, Desc) -> update(User, Name, Email, Desc) ->
ComplUser = <<User/binary, "@", Host/binary>>, {ok, CurData, Metadata} = case storage:read(?USERSBUCK, User) of
{ok, Value, Metadata} = case storage:read(?USERSBUCK, ComplUser) of
{ok, [R]} -> {ok, [R]} ->
{ok, R#object.value, R#object.metadata}; {ok, R#object.value, R#object.metadata};
{error, Reason} -> {error, Reason} {error, Reason} -> {error, Reason}
end, end,
{details, CurDets} = proplists:lookup(details, Value), Data = CurData#{<<"email">> => Email, <<"name">> => Name,
NewDets = CurDets#{<<"email">> => Email, <<"name">> => Name,
<<"description">> => Desc}, <<"description">> => Desc},
NewData = lists:keyreplace(details, 1, Value, {details, NewDets}),
storage:write(?USERSBUCK, ComplUser, NewData, Metadata). storage:write(?USERSBUCK, User, Data, Metadata).
-doc """ -doc """
Delete an existing user from the database. Delete an existing user from the database.