2024-07-26 19:57:34 -04:00
|
|
|
%
|
|
|
|
% 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.
|
|
|
|
%
|
2024-08-04 07:54:46 -04:00
|
|
|
-module(dudeswave_user_handler).
|
2024-07-26 19:57:34 -04:00
|
|
|
-moduledoc """
|
2024-08-04 07:54:46 -04:00
|
|
|
JSON API to manage users.
|
2024-08-06 19:17:35 -04:00
|
|
|
|
2024-08-07 18:10:45 -04:00
|
|
|
The username should be passed as the last token in the request, like:
|
2024-08-06 19:17:35 -04:00
|
|
|
|
|
|
|
```
|
|
|
|
/user/foo
|
|
|
|
```
|
|
|
|
|
2024-08-07 18:10:45 -04:00
|
|
|
Where `foo` is the actual username.
|
2024-08-06 19:17:35 -04:00
|
|
|
|
|
|
|
The user parameter must be called `username` as this module expects it
|
|
|
|
in order to work properly. All the requests must be done with a valid
|
|
|
|
session cookie in order to work.
|
|
|
|
|
|
|
|
If the session is not valid, all the requests will return `403 Forbidden` to
|
|
|
|
the client. In case a technical problem occurs, `500 Internal Server Error`
|
|
|
|
is returned.
|
|
|
|
|
|
|
|
This module accepts four methods:
|
|
|
|
|
|
|
|
- GET /user/:username
|
|
|
|
Retrieve user's details. However, this call requires the user to have
|
|
|
|
a valid cookie set. Not suitable for a public page.
|
|
|
|
|
|
|
|
- POST /user/:username
|
|
|
|
Update user's details, like their name, description and whatnot.
|
|
|
|
|
|
|
|
- DELETE /user/:username
|
|
|
|
Remove a user forever. The data is delete immediately. However,
|
|
|
|
it's content is left up there. Probably a specific option will be added
|
|
|
|
later. This request does not have a body. The call deletes the user
|
|
|
|
immediately, however we return `202 Accepted` in case of success,
|
|
|
|
for the simple reason that we may make the call asynchronous
|
|
|
|
to remove additional content in background.
|
|
|
|
|
|
|
|
- PUT /user/:username
|
|
|
|
Register a user. The registration takes only three parameter: username,
|
|
|
|
password and e-mail. The e-mail is required if a confirmation message
|
|
|
|
is to be sent. The plan is to have a separate process handle this, so the
|
|
|
|
API just need to set the proper value for the user's `status` in it's metadata.
|
|
|
|
|
|
|
|
JSON APIs
|
|
|
|
|
|
|
|
GET /user/:username
|
|
|
|
|
|
|
|
```
|
|
|
|
{
|
2024-08-07 18:10:45 -04:00
|
|
|
"user" : "foo",
|
2024-08-06 19:17:35 -04:00
|
|
|
"email": "foo@example.com",
|
|
|
|
"description": "A wonderful user",
|
|
|
|
"name": "Fantastic Foo"
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Response codes:
|
|
|
|
|
|
|
|
- 200 OK (Success)
|
|
|
|
- 404 Not Found
|
|
|
|
|
|
|
|
PUT /user/:username
|
|
|
|
|
|
|
|
```
|
|
|
|
{
|
|
|
|
"email": "foo@example.com",
|
|
|
|
"password": "123456"
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Response codes:
|
|
|
|
|
|
|
|
- 201 Created
|
|
|
|
- 400 Bad Request
|
|
|
|
- 409 Conflict (User already exists)
|
|
|
|
|
|
|
|
POST /user/:username
|
|
|
|
|
|
|
|
```
|
|
|
|
{
|
|
|
|
"email": "foo@example.com",
|
|
|
|
"description": "A wonderful user",
|
|
|
|
"name": "Fantastic Foo"
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Response codes:
|
|
|
|
|
|
|
|
- 200 OK
|
|
|
|
- 400 Bad Request
|
|
|
|
- 404 Not Found
|
|
|
|
|
|
|
|
DELETE /user/:username
|
|
|
|
|
|
|
|
- 202 Accepted
|
|
|
|
- 404 Not Found
|
|
|
|
|
2024-07-26 19:57:34 -04:00
|
|
|
""".
|
|
|
|
|
|
|
|
-behaviour(cowboy_handler).
|
|
|
|
|
|
|
|
-export([init/2, terminate/3]).
|
|
|
|
|
|
|
|
%
|
|
|
|
% Callbacks exports
|
|
|
|
%
|
2024-08-06 19:17:35 -04:00
|
|
|
-export([allowed_methods/2, content_types_provided/2,
|
|
|
|
content_types_accepted/2, known_methods/2, is_authorized/2,
|
|
|
|
forbidden/2, resource_exists/2, is_conflict/2, previously_existed/2,
|
|
|
|
allow_missing_post/2, delete_resource/2, create_user/2, modify_user/2,
|
|
|
|
delete_completed/2, user_details/2]).
|
2024-07-26 19:57:34 -04:00
|
|
|
|
|
|
|
%
|
2024-08-06 19:17:35 -04:00
|
|
|
% Cowboy standard callbacks
|
2024-07-26 19:57:34 -04:00
|
|
|
%
|
|
|
|
|
|
|
|
init(Req, State) ->
|
|
|
|
{cowboy_rest, Req, State}.
|
|
|
|
|
2024-08-06 19:17:35 -04:00
|
|
|
known_methods(Req, State) ->
|
|
|
|
{[<<"POST">>, <<"PUT">>, <<"DELETE">>, <<"GET">>], Req, State}.
|
|
|
|
|
2024-07-26 19:57:34 -04:00
|
|
|
allowed_methods(Req, State) ->
|
2024-08-06 19:17:35 -04:00
|
|
|
{[<<"POST">>, <<"PUT">>, <<"DELETE">>, <<"GET">>], Req, State}.
|
|
|
|
|
|
|
|
is_authorized(Req, State) -> {true, Req, State}.
|
|
|
|
|
|
|
|
forbidden(Req, State) ->
|
|
|
|
case cowboy_req:method(Req) of
|
|
|
|
<<"PUT">> ->
|
|
|
|
{false, Req, State};
|
|
|
|
_ ->
|
|
|
|
#{dudeauth := Auth} = cowboy_req:match_cookies([dudeauth], Req),
|
|
|
|
{ok, Bucket} = maps:find(cookies, State),
|
|
|
|
User = cowboy_req:binding(username, Req),
|
|
|
|
|
|
|
|
case dudeswave_auth:authenticate({cookie, User, Auth}, Bucket) of
|
|
|
|
{error, service_unavailable} -> exit(service_unavailable);
|
|
|
|
true -> {false, Req, State};
|
|
|
|
false -> {true, Req, State}
|
|
|
|
end
|
|
|
|
end.
|
2024-07-26 19:57:34 -04:00
|
|
|
|
2024-08-06 19:17:35 -04:00
|
|
|
content_types_provided(Req, State) ->
|
|
|
|
case cowboy_req:method(Req) of
|
|
|
|
<<"PUT">> ->
|
|
|
|
{[{<<"application/json">>, create_user}], Req, State};
|
|
|
|
<<"POST">> ->
|
|
|
|
{[{<<"application/json">>, modify_user}], Req, State};
|
|
|
|
<<"DELETE">> ->
|
|
|
|
{[{<<"application/json">>, delete_user}], Req, State};
|
|
|
|
<<"GET">> ->
|
|
|
|
{[{<<"application/json">>, user_details}], Req, State}
|
|
|
|
end.
|
2024-07-26 19:57:34 -04:00
|
|
|
|
2024-08-06 19:17:35 -04:00
|
|
|
content_types_accepted(Req, State) ->
|
|
|
|
case cowboy_req:method(Req) of
|
|
|
|
<<"PUT">> ->
|
|
|
|
{[{<<"application/json">>, create_user}], Req, State};
|
|
|
|
<<"POST">> ->
|
|
|
|
{[{<<"application/json">>, modify_user}], Req, State}
|
|
|
|
end.
|
2024-07-26 19:57:34 -04:00
|
|
|
|
|
|
|
resource_exists(Req, State) ->
|
2024-08-06 19:17:35 -04:00
|
|
|
User = cowboy_req:binding(username, Req),
|
2024-07-26 19:57:34 -04:00
|
|
|
{ok, Bucket} = maps:find(bucket, State),
|
|
|
|
|
2024-08-06 19:17:35 -04:00
|
|
|
case dudeswave_auth:user_details(User, Bucket) of
|
|
|
|
[] -> {false, Req, State};
|
|
|
|
{error, Reason} -> exit(Reason);
|
|
|
|
Details ->
|
|
|
|
NewState = State#{
|
|
|
|
bucket => Bucket,
|
|
|
|
details => Details,
|
|
|
|
user_exists => true,
|
|
|
|
request => cowboy_req:method(Req)
|
|
|
|
},
|
|
|
|
{true, Req, NewState}
|
2024-07-26 19:57:34 -04:00
|
|
|
end.
|
|
|
|
|
2024-08-06 19:17:35 -04:00
|
|
|
previously_existed(Req, State) -> {false, Req, State}.
|
|
|
|
|
|
|
|
is_conflict(Req, #{user_exists := true, request := <<"PUT">>}) ->
|
|
|
|
{true, Req, []};
|
2024-07-26 19:57:34 -04:00
|
|
|
|
|
|
|
is_conflict(Req, State) -> {false, Req, State}.
|
|
|
|
|
2024-08-06 19:17:35 -04:00
|
|
|
allow_missing_post(Req, State) -> {false, Req, State}.
|
2024-08-02 19:13:47 -04:00
|
|
|
|
2024-08-06 19:17:35 -04:00
|
|
|
delete_resource(Req, State) ->
|
|
|
|
{ok, Bucket} = maps:find(bucket, State),
|
|
|
|
User = cowboy_req:binding(username, Req),
|
2024-08-02 19:13:47 -04:00
|
|
|
|
2024-08-06 19:17:35 -04:00
|
|
|
case dudeswave_auth:delete(User, Bucket) of
|
|
|
|
ok -> {true, Req, State};
|
|
|
|
{error, _} -> {false, Req, State}
|
|
|
|
end.
|
2024-07-26 19:57:34 -04:00
|
|
|
|
2024-08-06 19:17:35 -04:00
|
|
|
delete_completed(Req, State) -> {false, Req, State}.
|
2024-08-02 19:13:47 -04:00
|
|
|
|
2024-08-06 19:17:35 -04:00
|
|
|
%
|
|
|
|
% Custom callbacks
|
|
|
|
%
|
|
|
|
|
|
|
|
create_user(Req, State) ->
|
|
|
|
{ok, Bucket} = maps:find(bucket, State),
|
|
|
|
User = cowboy_req:binding(username, Req),
|
|
|
|
|
|
|
|
#{<<"password">> := Pass, <<"email">> := Email} = json:decode(cowboy_req:body(req)),
|
|
|
|
|
|
|
|
case dudeswave_auth:new_user(User, Pass, Email, Bucket) of
|
|
|
|
ok -> {true, Req, []};
|
|
|
|
{error, Reason} -> {false, Req, Reason}
|
|
|
|
end.
|
|
|
|
|
|
|
|
modify_user(Req, State) ->
|
|
|
|
{ok, Bucket} = maps:find(bucket, State),
|
|
|
|
User = cowboy_req:binding(username, Req),
|
|
|
|
|
|
|
|
#{<<"email">> := Email, <<"description">> := Desc,
|
|
|
|
<<"name">> := Name} = json:decode(cowboy_req:body(req)),
|
|
|
|
|
|
|
|
case dudeswave_auth:update(User, Name, Email, Desc, Bucket) of
|
|
|
|
ok -> {true, Req, []};
|
|
|
|
{error, Reason} -> {false, Req, Reason}
|
2024-07-26 19:57:34 -04:00
|
|
|
end.
|
|
|
|
|
2024-08-06 19:17:35 -04:00
|
|
|
user_details(Req, State) ->
|
|
|
|
#{details := Details} = State,
|
2024-08-07 18:10:45 -04:00
|
|
|
Data = Details#{user => cowboy_req:binding(username, Req)},
|
2024-08-06 19:17:35 -04:00
|
|
|
|
2024-08-07 18:10:45 -04:00
|
|
|
{iolist_to_binary(json:encode(Data)), Req, State}.
|
2024-08-06 19:17:35 -04:00
|
|
|
|
|
|
|
%
|
|
|
|
% gen_server callbacks
|
|
|
|
%
|
|
|
|
|
2024-07-26 19:57:34 -04:00
|
|
|
terminate(_Reason, _Req, _State) -> ok.
|