aboutsummaryrefslogtreecommitdiff
path: root/server/src/jchat_http_auth.erl
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/jchat_http_auth.erl')
-rw-r--r--server/src/jchat_http_auth.erl155
1 files changed, 155 insertions, 0 deletions
diff --git a/server/src/jchat_http_auth.erl b/server/src/jchat_http_auth.erl
new file mode 100644
index 0000000..b59fa4c
--- /dev/null
+++ b/server/src/jchat_http_auth.erl
@@ -0,0 +1,155 @@
+-module(jchat_http_auth).
+
+-export([init/2]).
+
+-include("jchat.hrl").
+
+init(Req0, State) ->
+ Method = cowboy_req:method(Req0),
+ Path = cowboy_req:path(Req0),
+ handle_request(Method, Path, Req0, State).
+
+%% Handle registration endpoint
+handle_request(<<"POST">>, <<"/auth/register">>, Req0, State) ->
+ case cowboy_req:read_body(Req0) of
+ {ok, Body, Req1} ->
+ process_registration(Body, Req1, State);
+ {more, _Data, Req1} ->
+ reply_error(413, <<"requestTooLarge">>, <<"Request body too large">>, Req1, State)
+ end;
+
+%% Handle login endpoint
+handle_request(<<"POST">>, <<"/auth/login">>, Req0, State) ->
+ case cowboy_req:read_body(Req0) of
+ {ok, Body, Req1} ->
+ process_login(Body, Req1, State);
+ {more, _Data, Req1} ->
+ reply_error(413, <<"requestTooLarge">>, <<"Request body too large">>, Req1, State)
+ end;
+
+%% Handle logout endpoint
+handle_request(<<"POST">>, <<"/auth/logout">>, Req0, State) ->
+ % For JWT-based auth, logout is mainly client-side
+ % But we can implement token blacklisting here in the future
+ Req1 = cowboy_req:reply(200, #{
+ <<"content-type">> => <<"application/json; charset=utf-8">>,
+ <<"access-control-allow-origin">> => <<"*">>
+ }, jchat_utils:json_encode(#{<<"success">> => true}), Req0),
+ {ok, Req1, State};
+
+%% Handle token validation endpoint
+handle_request(<<"GET">>, <<"/auth/me">>, Req0, State) ->
+ case jchat_auth:authenticate_request(Req0) of
+ {ok, AuthContext} ->
+ User = maps:get(user, AuthContext),
+ UserJson = #{
+ <<"id">> => User#user.id,
+ <<"email">> => User#user.email,
+ <<"displayName">> => User#user.display_name,
+ <<"createdAt">> => User#user.created_at,
+ <<"lastLoginAt">> => User#user.last_login_at,
+ <<"isActive">> => User#user.is_active
+ },
+ Req1 = cowboy_req:reply(200, #{
+ <<"content-type">> => <<"application/json; charset=utf-8">>,
+ <<"access-control-allow-origin">> => <<"*">>
+ }, jchat_utils:json_encode(#{<<"user">> => UserJson}), Req0),
+ {ok, Req1, State};
+ {error, Error} ->
+ Status = maps:get(status, Error, 401),
+ Type = maps:get(type, Error, <<"unauthorized">>),
+ Detail = maps:get(detail, Error, <<"Authentication required">>),
+ reply_error(Status, Type, Detail, Req0, State)
+ end;
+
+%% Handle OPTIONS requests for CORS
+handle_request(<<"OPTIONS">>, _Path, Req0, State) ->
+ Req1 = cowboy_req:reply(200, #{
+ <<"access-control-allow-origin">> => <<"*">>,
+ <<"access-control-allow-methods">> => <<"POST, GET, OPTIONS">>,
+ <<"access-control-allow-headers">> => <<"content-type, authorization">>,
+ <<"access-control-max-age">> => <<"86400">>
+ }, <<>>, Req0),
+ {ok, Req1, State};
+
+%% Handle unsupported methods/paths
+handle_request(_Method, _Path, Req0, State) ->
+ reply_error(404, <<"notFound">>, <<"Endpoint not found">>, Req0, State).
+
+%% Process user registration
+process_registration(Body, Req0, State) ->
+ case jchat_utils:json_decode(Body) of
+ {ok, Data} ->
+ Email = maps:get(<<"email">>, Data, undefined),
+ Password = maps:get(<<"password">>, Data, undefined),
+ DisplayName = maps:get(<<"displayName">>, Data, undefined),
+
+ case {Email, Password, DisplayName} of
+ {undefined, _, _} ->
+ reply_error(400, <<"invalidArguments">>, <<"Email is required">>, Req0, State);
+ {_, undefined, _} ->
+ reply_error(400, <<"invalidArguments">>, <<"Password is required">>, Req0, State);
+ {_, _, undefined} ->
+ reply_error(400, <<"invalidArguments">>, <<"Display name is required">>, Req0, State);
+ {_, _, _} ->
+ case jchat_auth:register_user(Email, Password, DisplayName) of
+ {ok, Result} ->
+ Req1 = cowboy_req:reply(201, #{
+ <<"content-type">> => <<"application/json; charset=utf-8">>,
+ <<"access-control-allow-origin">> => <<"*">>
+ }, jchat_utils:json_encode(Result), Req0),
+ {ok, Req1, State};
+ {error, Error} ->
+ Status = maps:get(status, Error, 400),
+ Type = maps:get(type, Error, <<"registrationFailed">>),
+ Detail = maps:get(detail, Error, <<"Registration failed">>),
+ reply_error(Status, Type, Detail, Req0, State)
+ end
+ end;
+ {error, invalid_json} ->
+ reply_error(400, <<"invalidJSON">>, <<"Request body must be valid JSON">>, Req0, State)
+ end.
+
+%% Process user login
+process_login(Body, Req0, State) ->
+ case jchat_utils:json_decode(Body) of
+ {ok, Data} ->
+ Email = maps:get(<<"email">>, Data, undefined),
+ Password = maps:get(<<"password">>, Data, undefined),
+
+ case {Email, Password} of
+ {undefined, _} ->
+ reply_error(400, <<"invalidArguments">>, <<"Email is required">>, Req0, State);
+ {_, undefined} ->
+ reply_error(400, <<"invalidArguments">>, <<"Password is required">>, Req0, State);
+ {_, _} ->
+ case jchat_auth:login_user(Email, Password) of
+ {ok, Result} ->
+ Req1 = cowboy_req:reply(200, #{
+ <<"content-type">> => <<"application/json; charset=utf-8">>,
+ <<"access-control-allow-origin">> => <<"*">>
+ }, jchat_utils:json_encode(Result), Req0),
+ {ok, Req1, State};
+ {error, Error} ->
+ Status = maps:get(status, Error, 401),
+ Type = maps:get(type, Error, <<"loginFailed">>),
+ Detail = maps:get(detail, Error, <<"Login failed">>),
+ reply_error(Status, Type, Detail, Req0, State)
+ end
+ end;
+ {error, invalid_json} ->
+ reply_error(400, <<"invalidJSON">>, <<"Request body must be valid JSON">>, Req0, State)
+ end.
+
+%% Helper function to send error responses
+reply_error(Status, Type, Detail, Req0, State) ->
+ ErrorResponse = #{
+ <<"type">> => Type,
+ <<"detail">> => Detail,
+ <<"status">> => Status
+ },
+ Req1 = cowboy_req:reply(Status, #{
+ <<"content-type">> => <<"application/json; charset=utf-8">>,
+ <<"access-control-allow-origin">> => <<"*">>
+ }, jchat_utils:json_encode(ErrorResponse), Req0),
+ {ok, Req1, State}.