From 57360de94db0e73b51542cbaccf5b8bc100deeaf Mon Sep 17 00:00:00 2001 From: DDDHuang <44492639+DDDHuang@users.noreply.github.com> Date: Mon, 25 Apr 2022 18:27:01 +0800 Subject: [PATCH] fix: dashboard users api format & i18n & response code --- .../i18n/emqx_dashboard_api_i18n.conf | 143 ++++++++++ .../src/emqx_dashboard_admin.erl | 2 +- .../emqx_dashboard/src/emqx_dashboard_api.erl | 244 ++++++++++-------- .../src/emqx_dashboard_schema.erl | 2 - 4 files changed, 281 insertions(+), 110 deletions(-) create mode 100644 apps/emqx_dashboard/i18n/emqx_dashboard_api_i18n.conf diff --git a/apps/emqx_dashboard/i18n/emqx_dashboard_api_i18n.conf b/apps/emqx_dashboard/i18n/emqx_dashboard_api_i18n.conf new file mode 100644 index 000000000..205483e61 --- /dev/null +++ b/apps/emqx_dashboard/i18n/emqx_dashboard_api_i18n.conf @@ -0,0 +1,143 @@ +emqx_dashboard_api { + + token { + desc { + en: """Dashboard Auth Token""" + zh: """Dashboard 认证 Token""" + } + } + + username { + desc { + en: """Dashboard Username""" + zh: """Dashboard 用户名""" + } + } + + user_description { + desc { + en: """Dashboard User Description""" + zh: """Dashboard 用户描述""" + } + } + + password { + desc { + en: """Dashboard Password""" + zh: """Dashboard 密码""" + } + } + + license { + desc { + en: """EMQX License. Community or enterprise""" + zh: """EMQX 许可。开源版本 或者企业版""" + } + } + + version { + desc { + en: """EMQX Version""" + zh: """EMQX 版本""" + } + } + + login_api { + desc { + en: """Dashboard Auth. Get Token""" + zh: """Dashboard 认证。获取 Token""" + } + } + + login_success { + desc { + en: """Dashboard Auth. Success""" + zh: """Dashboard 认证。成功""" + } + } + + login_failed401 { + desc { + en: """Login failed. Bad username or password""" + zh: """登录失败。用户名或密码错误""" + } + } + + logout_api { + desc { + en: """Dashboard user logout""" + zh: """Dashboard 用户登出""" + } + } + + list_users_api { + desc { + en: """Dashboard list users""" + zh: """Dashboard 用户列表""" + } + } + + create_user_api { + desc { + en: """Create dashboard user""" + zh: """创建 Dashboard 用户""" + } + } + + create_user_api_success { + desc { + en: """Create dashboard user success""" + zh: """创建 Dashboard 用户成功""" + } + } + + update_user_api { + desc { + en: """Update dashboard user description""" + zh: """更新 Dashboard 用户描述""" + } + } + + update_user_api200 { + desc { + en: """Update dashboard user success""" + zh: """更新 Dashboard 用户成功""" + } + } + + delete_user_api { + desc { + en: """Delete dashboard user""" + zh: """删除 Dashboard 用户""" + } + } + + users_api404 { + desc { + en: """Dashboard user not found""" + zh: """Dashboard 用户不存在""" + } + } + + change_pwd_api { + desc { + en: """Change dashboard user password""" + zh: """更改 Dashboard 用户密码""" + } + } + + old_pwd { + desc { + en: """Old password""" + zh: """旧密码""" + } + } + + new_pwd { + desc { + en: """New password""" + zh: """新密码""" + } + } + +} diff --git a/apps/emqx_dashboard/src/emqx_dashboard_admin.erl b/apps/emqx_dashboard/src/emqx_dashboard_admin.erl index 188b5411d..d5e908dd6 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard_admin.erl +++ b/apps/emqx_dashboard/src/emqx_dashboard_admin.erl @@ -206,7 +206,7 @@ all_users() -> description => Desc } end, ets:tab2list(?ADMIN)). - +-spec(return({atomic | aborted, term()}) -> {ok, term()} | {error, Reason :: binary()}). return({atomic, Result}) -> {ok, Result}; return({aborted, Reason}) -> diff --git a/apps/emqx_dashboard/src/emqx_dashboard_api.erl b/apps/emqx_dashboard/src/emqx_dashboard_api.erl index b7eb0c35f..adbf73f53 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard_api.erl +++ b/apps/emqx_dashboard/src/emqx_dashboard_api.erl @@ -19,17 +19,41 @@ -behaviour(minirest_api). -include("emqx_dashboard.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). +-include_lib("emqx/include/logger.hrl"). -include_lib("typerefl/include/types.hrl"). --import(hoconsc, [mk/2, ref/2, array/1, enum/1]). --export([api_spec/0, fields/1, paths/0, schema/1, namespace/0]). --export([login/2, logout/2, users/2, user/2, change_pwd/2]). +-import(hoconsc, [ + mk/2, + % ref/2, + array/1, + enum/1 + ]). + +-export([ + api_spec/0, + fields/1, + paths/0, + schema/1, + namespace/0 + ]). + +-export([ + login/2, + logout/2, + users/2, + user/2, + change_pwd/2 + ]). -define(EMPTY(V), (V == undefined orelse V == <<>>)). --define(ERROR_USERNAME_OR_PWD, 'ERROR_USERNAME_OR_PWD'). --define(USER_NOT_FOUND_BODY, #{ code => <<"USER_NOT_FOUND">> - , message => <<"User not found">>}). +-define(WRONG_USERNAME_OR_PWD, 'WRONG_USERNAME_OR_PWD'). +-define(WRONG_TOKEN_OR_USERNAME, 'WRONG_TOKEN_OR_USERNAME'). +-define(USER_NOT_FOUND, 'USER_NOT_FOUND'). +-define(ERROR_PWD_NOT_MATCH, 'ERROR_PWD_NOT_MATCH'). +-define(NOT_ALLOWED, 'NOT_ALLOWED'). +-define(BAD_REQUEST, 'BAD_REQUEST'). namespace() -> "dashboard". @@ -48,43 +72,26 @@ schema("/login") -> 'operationId' => login, post => #{ tags => [<<"dashboard">>], - desc => <<"Dashboard Auth">>, + desc => ?DESC(login_api), summary => <<"Dashboard Auth">>, - 'requestBody' => [ - {username, mk(binary(), - #{desc => <<"The User for which to create the token.">>, - 'maxLength' => 100, example => <<"admin">>})}, - {password, mk(binary(), - #{desc => "password", example => "public"})} - ], + 'requestBody' => fields([username, password]), responses => #{ - 200 => [ - {token, mk(string(), #{desc => <<"JWT Token">>})}, - {license, [{edition, - mk(enum([community, enterprise]), #{desc => <<"license">>, - example => "community"})}]}, - {version, mk(string(), #{desc => <<"version">>, example => <<"5.0.0">>})} - ], - 401 => [ - {code, mk(string(), #{example => 'ERROR_USERNAME_OR_PWD'})}, - {message, mk(string(), #{example => "Unauthorized"})} - ] + 200 => fields([token, version, license]), + 401 => response_schema(401) }, security => [] - }}; + } + }; schema("/logout") -> #{ 'operationId' => logout, post => #{ tags => [<<"dashboard">>], - desc => <<"Dashboard User logout">>, - 'requestBody' => [ - {username, mk(binary(), - #{desc => <<"The User for which to create the token.">>, - 'maxLength' => 100, example => <<"admin">>})} - ], + desc => ?DESC(logout_api), + 'requestBody' => fields([username]), responses => #{ - 204 => <<"Dashboard logout successfully">> + 204 => <<"Dashboard logout successfully">>, + 401 => response_schema(401) } } }; @@ -93,22 +100,18 @@ schema("/users") -> 'operationId' => users, get => #{ tags => [<<"dashboard">>], - desc => <<"Get dashboard users list">>, + desc => ?DESC(list_users_api), responses => #{ - 200 => mk( array(ref(?MODULE, user)) - , #{desc => "User lists"}) + 200 => mk(array(fields([username, description])), + #{desc => ?DESC(list_users_api)}) } }, post => #{ tags => [<<"dashboard">>], - desc => <<"Create dashboard users">>, - 'requestBody' => fields(user_password), + desc => ?DESC(create_user_api), + 'requestBody' => fields([username, password, description]), responses => #{ - 200 => mk( ref(?MODULE, user) - , #{desc => <<"Create User successfully">>}), - 400 => [{code, mk(string(), #{example => 'CREATE_FAIL'})}, - {message, mk(string(), #{example => "Create user failed"})} - ] + 200 => fields([username, description]) } } }; @@ -118,36 +121,23 @@ schema("/users/:username") -> 'operationId' => user, put => #{ tags => [<<"dashboard">>], - desc => <<"Update dashboard users">>, - parameters => [{username, mk(binary(), - #{in => path, example => <<"admin">>})}], - 'requestBody' => [ - { description - , mk(binary(), - #{desc => <<"User description">>, example => <<"administrator">>})} - ], + desc => ?DESC(update_user_api), + parameters => fields([username_in_path]), + 'requestBody' => fields([description]), responses => #{ - 200 => mk( ref(?MODULE, user) - , #{desc => <<"Update User successfully">>}), - 400 => [ - {code, mk(string(), #{example => 'UPDATE_FAIL'})}, - {message, mk(string(), #{example => "Update Failed unknown"})} - ], - 404 => emqx_dashboard_swagger:error_codes(['USER_NOT_FOUND'], <<"User Not Found">>) + 200 => fields([username, description]), + 404 => response_schema(404) } }, delete => #{ tags => [<<"dashboard">>], - desc => <<"Delete dashboard users">>, - parameters => [{username, mk(binary(), - #{in => path, example => <<"admin">>})}], + desc => ?DESC(delete_user_api), + parameters => fields([username_in_path]), responses => #{ 204 => <<"Delete User successfully">>, - 400 => [ - {code, mk(string(), #{example => 'CANNOT_DELETE_ADMIN'})}, - {message, mk(string(), #{example => "CANNOT DELETE ADMIN"})} - ], - 404 => emqx_dashboard_swagger:error_codes(['USER_NOT_FOUND'], <<"User Not Found">>) + 400 => emqx_dashboard_swagger:error_codes( + [?BAD_REQUEST, ?NOT_ALLOWED], ?DESC(login_failed_response400)), + 404 => response_schema(404) } } }; @@ -156,76 +146,105 @@ schema("/users/:username/change_pwd") -> 'operationId' => change_pwd, put => #{ tags => [<<"dashboard">>], - desc => <<"Update dashboard users password">>, - parameters => [{username, mk(binary(), - #{in => path, required => true, example => <<"admin">>})}], - 'requestBody' => [ - {old_pwd, mk(binary(), #{required => true})}, - {new_pwd, mk(binary(), #{required => true})} - ], + desc => ?DESC(change_pwd_api), + parameters => fields([username_in_path]), + 'requestBody' => fields([old_pwd, new_pwd]), responses => #{ 204 => <<"Update user password successfully">>, - 400 => [ - {code, mk(string(), #{example => 'UPDATE_FAIL'})}, - {message, mk(string(), #{example => "Failed Reason"})} - ] + 401 => emqx_dashboard_swagger:error_codes( + [?WRONG_USERNAME_OR_PWD, ?ERROR_PWD_NOT_MATCH], ?DESC(login_failed401)), + 404 => response_schema(404), + 400 => emqx_dashboard_swagger:error_codes( + [?BAD_REQUEST], ?DESC(login_failed_response400)) } } }. -fields(user) -> - [ - {description, - mk(binary(), - #{desc => <<"User description">>, example => "administrator"})}, - {username, - mk(binary(), - #{desc => <<"username">>, example => "emqx"})} - ]; -fields(user_password) -> - fields(user) ++ - [{password, mk(binary(), #{desc => "Password", example => <<"public">>})}]. +response_schema(401) -> + emqx_dashboard_swagger:error_codes([?WRONG_USERNAME_OR_PWD], ?DESC(login_failed401)); +response_schema(404) -> + emqx_dashboard_swagger:error_codes([?USER_NOT_FOUND], ?DESC(users_api404)). + +fields(List) -> + [field(Key) || Key <- List]. + +field(username) -> + {username, + mk(binary(), #{desc => ?DESC(username), 'maxLength' => 100, example => <<"admin">>})}; +field(username_in_path) -> + {username, + mk(binary(), #{desc => ?DESC(username), 'maxLength' => 100, example => <<"admin">>, + in => path, required => true})}; +field(password) -> + {password, + mk(binary(), #{desc => ?DESC(password), 'maxLength' => 100, example => <<"public">>})}; +field(description) -> + {description, + mk(binary(), #{desc => ?DESC(user_description), example => <<"administrator">>})}; +field(token) -> + {token, mk(binary(), #{desc => ?DESC(token)})}; +field(license) -> + {license, [ + {edition, mk(enum([community, enterprise]), + #{desc => ?DESC(license), example => community})}]}; +field(version) -> + {version, mk(string(), #{desc => ?DESC(version), example => <<"5.0.0">>})}; + +field(old_pwd) -> + {password, mk(binary(), #{desc => ?DESC(old_pwd)})}; + +field(new_pwd) -> + {password, mk(binary(), #{desc => ?DESC(new_pwd)})}. + +%% ------------------------------------------------------------------------------------------------- +%% API login(post, #{body := Params}) -> Username = maps:get(<<"username">>, Params), Password = maps:get(<<"password">>, Params), case emqx_dashboard_admin:sign_token(Username, Password) of {ok, Token} -> + ?SLOG(info, #{msg => "Dashboard login successfully", username => Username}), Version = iolist_to_binary(proplists:get_value(version, emqx_sys:info())), {200, #{token => Token, version => Version, license => #{edition => emqx_release:edition()} }}; - {error, _} -> - {401, #{code => ?ERROR_USERNAME_OR_PWD, message => <<"Auth filed">>}} + {error, R} -> + ?SLOG(info, #{msg => "Dashboard login failed", username => Username, reason => R}), + {401, ?WRONG_USERNAME_OR_PWD, <<"Auth filed">>} end. logout(_, #{body := #{<<"username">> := Username}, headers := #{<<"authorization">> := <<"Bearer ", Token/binary>>}}) -> case emqx_dashboard_admin:destroy_token_by_username(Username, Token) of ok -> + ?SLOG(info, #{msg => "Dashboard logout successfully", username => Username}), 204; _R -> - {401, 'BAD_TOKEN_OR_USERNAME', <<"Ensure your token & username">>} + ?SLOG(info, #{msg => "Dashboard logout failed.", username => Username}), + {401, ?WRONG_TOKEN_OR_USERNAME, <<"Ensure your token & username">>} end. users(get, _Request) -> {200, emqx_dashboard_admin:all_users()}; users(post, #{body := Params}) -> - Desc = maps:get(<<"description">>, Params), + Desc = maps:get(<<"description">>, Params, <<"">>), Username = maps:get(<<"username">>, Params), Password = maps:get(<<"password">>, Params), case ?EMPTY(Username) orelse ?EMPTY(Password) of true -> - {400, #{code => <<"CREATE_USER_FAIL">>, - message => <<"Username or password undefined">>}}; + {400, ?BAD_REQUEST, <<"Username or password undefined">>}; false -> case emqx_dashboard_admin:add_user(Username, Password, Desc) of {ok, Result} -> + ?SLOG(info, #{msg => "Create dashboard success", username => Username}), {200, Result}; {error, Reason} -> - {400, #{code => <<"CREATE_USER_FAIL">>, message => Reason}} + ?SLOG(info, #{msg => "Create dashboard failed", + username => Username, reason => Reason}), + {400, ?BAD_REQUEST, Reason} end end. @@ -234,20 +253,22 @@ user(put, #{bindings := #{username := Username}, body := Params}) -> case emqx_dashboard_admin:update_user(Username, Desc) of {ok, Result} -> {200, Result}; - {error, _Reason} -> - {404, ?USER_NOT_FOUND_BODY} + {error, Reason} -> + {404, ?USER_NOT_FOUND, Reason} end; user(delete, #{bindings := #{username := Username}}) -> case Username == emqx_dashboard_admin:default_username() of true -> - {400, #{code => <<"ACTION_NOT_ALLOWED">>, - message => <<"Cannot delete admin">>}}; + ?SLOG(info, #{msg => "Dashboard delete admin user failed", username => Username}), + Message = list_to_binary(io_lib:format("Cannot delete user ~p", [Username])), + {400, ?NOT_ALLOWED, Message}; false -> case emqx_dashboard_admin:remove_user(Username) of - {error, _Reason} -> - {404, ?USER_NOT_FOUND_BODY}; + {error, Reason} -> + {404, ?USER_NOT_FOUND, Reason}; {ok, _} -> + ?SLOG(info, #{msg => "Dashboard delete admin user", username => Username}), {204} end end. @@ -255,9 +276,18 @@ user(delete, #{bindings := #{username := Username}}) -> change_pwd(put, #{bindings := #{username := Username}, body := Params}) -> OldPwd = maps:get(<<"old_pwd">>, Params), NewPwd = maps:get(<<"new_pwd">>, Params), - case emqx_dashboard_admin:change_password(Username, OldPwd, NewPwd) of - {ok, _} -> - {204}; - {error, Reason} -> - {400, #{code => <<"CHANGE_PWD_FAIL">>, message => Reason}} + case ?EMPTY(OldPwd) orelse ?EMPTY(NewPwd) of + true -> + {400, ?BAD_REQUEST, <<"Old password or new password undefined">>}; + false -> + case emqx_dashboard_admin:change_password(Username, OldPwd, NewPwd) of + {ok, _} -> + {204}; + {error, <<"username_not_found">>} -> + {404, ?USER_NOT_FOUND, <<"User not found">>}; + {error, <<"password_error">>} -> + {401, ?ERROR_PWD_NOT_MATCH, <<"Old password not match">>}; + {error, Reason} -> + {400, ?BAD_REQUEST, Reason} + end end. diff --git a/apps/emqx_dashboard/src/emqx_dashboard_schema.erl b/apps/emqx_dashboard/src/emqx_dashboard_schema.erl index 404648b14..c2482f91b 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard_schema.erl +++ b/apps/emqx_dashboard/src/emqx_dashboard_schema.erl @@ -147,14 +147,12 @@ bind(desc) -> ?DESC(bind); bind(_) -> undefined. default_username(type) -> binary(); -default_username(default) -> "admin"; default_username(required) -> true; default_username(desc) -> ?DESC(default_username); default_username('readOnly') -> true; default_username(_) -> undefined. default_password(type) -> binary(); -default_password(default) -> "public"; default_password(required) -> true; default_password('readOnly') -> true; default_password(sensitive) -> true;