diff --git a/apps/emqx_management/src/emqx_mgmt_api_app.erl b/apps/emqx_management/src/emqx_mgmt_api_app.erl index 53ad01485..dfce3cf30 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_app.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_app.erl @@ -91,10 +91,11 @@ fields(app) -> """They are useful for accessing public data anonymously,""" """and are used to associate API requests.""", example => <<"MzAyMjk3ODMwMDk0NjIzOTUxNjcwNzQ0NzQ3MTE2NDYyMDI">>})}, - {expired_at, hoconsc:mk(emqx_schema:rfc3339_system_time(), + {expired_at, hoconsc:mk(hoconsc:union([undefined, emqx_schema:rfc3339_system_time()]), #{desc => "No longer valid datetime", example => <<"2021-12-05T02:01:34.186Z">>, - nullable => true + nullable => true, + default => undefined })}, {created_at, hoconsc:mk(emqx_schema:rfc3339_system_time(), #{desc => "ApiKey create datetime", @@ -136,9 +137,15 @@ api_key(post, #{body := App}) -> #{ <<"name">> := Name, <<"desc">> := Desc0, - <<"expired_at">> := ExpiredAt, <<"enable">> := Enable } = App, + %% undefined is never expired + ExpiredAt0 = maps:get(<<"expired_at">>, App, <<"undefined">>), + ExpiredAt = + case ExpiredAt0 of + <<"undefined">> -> undefined; + _ -> ExpiredAt0 + end, Desc = unicode:characters_to_binary(Desc0, unicode), case emqx_mgmt_auth:create(Name, Enable, ExpiredAt, Desc) of {ok, NewApp} -> {200, format(NewApp)}; @@ -164,8 +171,13 @@ api_key_by_name(put, #{bindings := #{name := Name}, body := Body}) -> {error, not_found} -> {404, <<"NOT_FOUND">>} end. -format(App = #{expired_at := ExpiredAt, created_at := CreateAt}) -> +format(App = #{expired_at := ExpiredAt0, created_at := CreateAt}) -> + ExpiredAt = + case ExpiredAt0 of + undefined -> <<"undefined">>; + _ -> list_to_binary(calendar:system_time_to_rfc3339(ExpiredAt0)) + end, App#{ - expired_at => list_to_binary(calendar:system_time_to_rfc3339(ExpiredAt)), + expired_at => ExpiredAt, created_at => list_to_binary(calendar:system_time_to_rfc3339(CreateAt)) }. diff --git a/apps/emqx_management/src/emqx_mgmt_auth.erl b/apps/emqx_management/src/emqx_mgmt_auth.erl index ae6b0820d..512ec6b0f 100644 --- a/apps/emqx_management/src/emqx_mgmt_auth.erl +++ b/apps/emqx_management/src/emqx_mgmt_auth.erl @@ -37,7 +37,7 @@ api_secret_hash = <<>> :: binary() | '_', enable = true :: boolean() | '_', desc = <<>> :: binary() | '_', - expired_at = 0 :: integer() | '_', + expired_at = 0 :: integer() | undefined | '_', created_at = 0 :: integer() | '_' }). diff --git a/apps/emqx_management/test/emqx_mgmt_auth_api_SUITE.erl b/apps/emqx_management/test/emqx_mgmt_auth_api_SUITE.erl index 185ad5343..73d4ad566 100644 --- a/apps/emqx_management/test/emqx_mgmt_auth_api_SUITE.erl +++ b/apps/emqx_management/test/emqx_mgmt_auth_api_SUITE.erl @@ -23,7 +23,7 @@ all() -> [{group, parallel}, {group, sequence}]. suite() -> [{timetrap, {minutes, 1}}]. groups() -> [ - {parallel, [parallel], [t_create, t_update, t_delete, t_authorize]}, + {parallel, [parallel], [t_create, t_update, t_delete, t_authorize, t_create_unexpired_app]}, {sequence, [], [t_create_failed]} ]. @@ -137,7 +137,15 @@ t_authorize(_Config) -> }, ?assertMatch({ok, #{<<"api_key">> := _, <<"enable">> := true}}, update_app(Name, Expired)), ?assertEqual(Unauthorized, emqx_mgmt_api_test_util:request_api(get, BanPath, BasicHeader)), + ok. +t_create_unexpired_app(_Config) -> + Name1 = <<"EMQX-UNEXPIRED-API-KEY-1">>, + Name2 = <<"EMQX-UNEXPIRED-API-KEY-2">>, + {ok, Create1} = create_unexpired_app(Name1, #{}), + ?assertMatch(#{<<"expired_at">> := <<"undefined">>}, Create1), + {ok, Create2} = create_unexpired_app(Name2, #{expired_at => <<"undefined">>}), + ?assertMatch(#{<<"expired_at">> := <<"undefined">>}, Create2), ok. @@ -170,6 +178,15 @@ create_app(Name) -> Error -> Error end. +create_unexpired_app(Name, Params) -> + AuthHeader = emqx_mgmt_api_test_util:auth_header_(), + Path = emqx_mgmt_api_test_util:api_path(["api_key"]), + App = maps:merge(#{name => Name, desc => <<"Note"/utf8>>, enable => true}, Params), + case emqx_mgmt_api_test_util:request_api(post, Path, "", AuthHeader, App) of + {ok, Res} -> {ok, emqx_json:decode(Res, [return_maps])}; + Error -> Error + end. + delete_app(Name) -> DeletePath = emqx_mgmt_api_test_util:api_path(["api_key", Name]), emqx_mgmt_api_test_util:request_api(delete, DeletePath).