-module(eventhub_auth). -export([ generate_user_token/2, generate_admin_token/2, verify_user_token/1, verify_admin_token/1, authenticate_user_request/3, authenticate_admin_request/3, generate_refresh_token/1 ]). %% ========== КОНФИГУРАЦИЯ СЕКРЕТОВ ========== -spec get_user_secret() -> binary(). get_user_secret() -> case application:get_env(eventhub, jwt_secret) of {ok, Secret} when is_binary(Secret) -> Secret; undefined -> get_user_secret_from_env() end. get_user_secret_from_env() -> case os:getenv("JWT_SECRET") of false -> <<"user-secret-key-32-bytes-minimum!">>; S -> list_to_binary(S) end. -spec get_admin_secret() -> binary(). get_admin_secret() -> case application:get_env(eventhub, admin_jwt_secret) of {ok, Secret} when is_binary(Secret) -> Secret; undefined -> get_admin_secret_from_env() end. get_admin_secret_from_env() -> case os:getenv("ADMIN_JWT_SECRET") of false -> <<"admin-secret-key-32-bytes-minimum!">>; S -> list_to_binary(S) end. -spec get_user_jwk() -> jose_jwk:key(). get_user_jwk() -> jose_jwk:from_oct(get_user_secret()). -spec get_admin_jwk() -> jose_jwk:key(). get_admin_jwk() -> jose_jwk:from_oct(get_admin_secret()). %% ========== ГЕНЕРАЦИЯ ТОКЕНОВ ========== -spec generate_user_token(UserId :: binary(), Role :: binary()) -> binary(). generate_user_token(UserId, Role) -> generate_token(get_user_jwk(), UserId, Role, <<"user">>). -spec generate_admin_token(UserId :: binary(), Role :: binary()) -> binary(). generate_admin_token(UserId, Role) -> generate_token(get_admin_jwk(), UserId, Role, <<"admin">>). generate_token(JWK, UserId, Role, Audience) -> ExpTime = erlang:system_time(second) + 86400, Claims = #{ <<"user_id">> => UserId, <<"role">> => Role, <<"aud">> => Audience, <<"exp">> => ExpTime, <<"iat">> => erlang:system_time(second) }, JWT = jose_jwt:sign(JWK, #{<<"alg">> => <<"HS256">>}, Claims), {_, Token} = jose_jws:compact(JWT), Token. %% ========== ПРОВЕРКА ТОКЕНОВ ========== -spec verify_user_token(Token :: binary()) -> {ok, UserId :: binary(), Role :: binary()} | {error, atom()}. verify_user_token(Token) -> verify_token(get_user_jwk(), Token, <<"user">>). -spec verify_admin_token(Token :: binary()) -> {ok, UserId :: binary(), Role :: binary()} | {error, atom()}. verify_admin_token(Token) -> verify_token(get_admin_jwk(), Token, <<"admin">>). verify_token(JWK, Token, ExpectedAud) -> try case jose_jwt:verify(JWK, Token) of {true, {jose_jwt, Claims}, _} -> validate_claims(Claims, ExpectedAud); {true, Claims, _} when is_map(Claims) -> validate_claims(Claims, ExpectedAud); _ -> {error, invalid_signature} end catch _:_ -> {error, invalid_token} end. validate_claims(Claims, ExpectedAud) -> case maps:find(<<"aud">>, Claims) of {ok, ExpectedAud} -> case maps:find(<<"exp">>, Claims) of {ok, Exp} when is_integer(Exp) -> Now = erlang:system_time(second), if Exp > Now -> UserId = maps:get(<<"user_id">>, Claims, undefined), Role = maps:get(<<"role">>, Claims, <<"user">>), {ok, UserId, Role}; true -> {error, expired} end; {ok, _Exp} -> {error, expired}; _ -> {error, no_expiration} end; {ok, _} -> {error, invalid_audience}; error -> {error, missing_audience} end. %% ========== АУТЕНТИФИКАЦИЯ ЗАПРОСА ========== -spec authenticate_user_request(Req :: cowboy_req:req(), Email :: binary(), Password :: binary()) -> {ok, Token :: binary(), User :: map()} | {error, atom()}. authenticate_user_request(_Req, Email, Password) -> case logic_auth:authenticate_user(Email, Password) of {ok, User} -> UserId = maps:get(id, User), Role = maps:get(role, User, <<"user">>), Token = generate_user_token(UserId, Role), {ok, Token, User}; Error -> Error end. -spec authenticate_admin_request(Req :: cowboy_req:req(), Email :: binary(), Password :: binary()) -> {ok, Token :: binary(), User :: map()} | {error, atom()}. authenticate_admin_request(_Req, Email, Password) -> case logic_auth:authenticate_admin(Email, Password) of {ok, AdminMap} -> Role = maps:get(role, AdminMap, <<"admin">>), case admin_utils:is_admin(Role) of true -> AdminId = maps:get(id, AdminMap), Token = generate_admin_token(AdminId, Role), {ok, Token, AdminMap}; false -> {error, insufficient_permissions} end; Error -> Error end. %% ========== REFRESH TOKEN ========== -spec generate_refresh_token(UserId :: binary()) -> {binary(), calendar:datetime()}. generate_refresh_token(_UserId) -> RefreshToken = base64:encode(crypto:strong_rand_bytes(32)), Now = calendar:universal_time(), ExpiresAt = calendar:gregorian_seconds_to_datetime( calendar:datetime_to_gregorian_seconds(Now) + 30 * 24 * 3600 ), {RefreshToken, ExpiresAt}.