feat: forgot/reset password API (variant A). Fixes EventHub/EventHubBack#57
CI / test (push) Successful in 7m20s
CI / deploy-ift (push) Successful in 6m9s
CI / e2e-ift (push) Successful in 1m28s
CI / deploy-stage (push) Successful in 2m34s
CI / e2e-stage (push) Successful in 1m11s

This commit is contained in:
2026-07-24 20:12:53 +03:00
parent e3013075cc
commit 61816e15b1
16 changed files with 592 additions and 5 deletions
+4 -1
View File
@@ -1,8 +1,11 @@
-module(logic_email).
-export([send_verification_email/2, send_specialist_invite/2]).
-export([send_verification_email/2, send_specialist_invite/2, send_password_reset/2]).
send_verification_email(Email, Token) ->
io:format("Sending verification email to ~s with token ~s~n", [Email, Token]).
send_specialist_invite(Email, Token) ->
io:format("Sending specialist invite email to ~s with token ~s~n", [Email, Token]).
send_password_reset(Email, Token) ->
io:format("Sending password reset email to ~s with token ~s~n", [Email, Token]).
+61
View File
@@ -0,0 +1,61 @@
-module(logic_password_reset).
-include("records.hrl").
-export([request_reset/1, reset_password/2]).
-define(MIN_PASSWORD_LEN, 8).
%%%-------------------------------------------------------------------
%%% @doc Запрос сброса пароля. Всегда `{ok, sent}` — без enumeration.
%%% Письмо (stub) уходит только для `active` пользователей.
%%% @end
%%%-------------------------------------------------------------------
-spec request_reset(Email :: binary()) -> {ok, sent}.
request_reset(Email) when is_binary(Email) ->
case core_user:get_by_email(Email) of
{ok, #user{id = UserId, status = active, email = UserEmail}} ->
{ok, Token, _Expires} = core_password_reset:create_token(UserId),
logic_email:send_password_reset(UserEmail, Token),
{ok, sent};
_ ->
{ok, sent}
end;
request_reset(_) ->
{ok, sent}.
%%%-------------------------------------------------------------------
%%% @doc Установка нового пароля по токену. Отзывает refresh-сессии user.
%%% @end
%%%-------------------------------------------------------------------
-spec reset_password(Token :: binary(), Password :: binary()) ->
ok | {error, expired | not_found | invalid_password | password_hash_failed | user_not_found | forbidden}.
reset_password(Token, Password)
when is_binary(Token), is_binary(Password), byte_size(Password) >= ?MIN_PASSWORD_LEN ->
case core_password_reset:verify_token(Token) of
{ok, UserId} ->
case core_user:get_by_id(UserId) of
{ok, #user{status = active}} ->
case logic_auth:hash_password(Password) of
{ok, NewHash} ->
case core_user:update(UserId, [{password_hash, NewHash}]) of
{ok, _} ->
core_password_reset:delete_token(Token),
core_auth_session:revoke_all_for_subject(UserId, user),
ok;
{error, not_found} ->
{error, user_not_found};
{error, _} = Err ->
Err
end;
{error, _} ->
{error, password_hash_failed}
end;
{ok, #user{}} ->
{error, forbidden};
{error, not_found} ->
{error, user_not_found}
end;
{error, _} = Err ->
Err
end;
reset_password(_, _) ->
{error, invalid_password}.