diff options
author | Calvin Morrison <calvin@pobox.com> | 2025-09-03 21:15:36 -0400 |
---|---|---|
committer | Calvin Morrison <calvin@pobox.com> | 2025-09-03 21:15:36 -0400 |
commit | 49fa5aa2a127bdf8924d02bf77e5086b39c7a447 (patch) | |
tree | 61d86a7705dacc9fddccc29fa79d075d83ab8059 /server/src/jchat_http_auth.erl |
Diffstat (limited to 'server/src/jchat_http_auth.erl')
-rw-r--r-- | server/src/jchat_http_auth.erl | 155 |
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}. |