Compare commits

...

6 Commits

Author SHA1 Message Date
absc ea339a1024 Updated che back-end to use the new multi-host calls.
The listeners update will come later.
2024-09-15 13:44:10 +00:00
absc b1c4ab8e16 Add support for multi-host to the authentication module. 2024-09-15 13:23:38 +00:00
absc f51847fe97 Handle multi-host users.
Also, put the details in the value field. We reserve the metadata field
only for searches where relevant.
2024-09-15 13:17:19 +00:00
absc 9e100b0317 Also the users process does not depend on ranch and cowboy. 2024-09-15 12:45:56 +00:00
absc c3731bf0f3 The back-end does not depend from ranch and cowboy. 2024-09-15 12:44:10 +00:00
absc 9fd60a2dfa The main includes are from the back-end. 2024-09-15 12:41:47 +00:00
6 changed files with 96 additions and 91 deletions

View File

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

View File

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

View File

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

View File

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

View File

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