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..bd97b5b41 --- /dev/null +++ b/apps/emqx_dashboard/i18n/emqx_dashboard_api_i18n.conf @@ -0,0 +1,150 @@ +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: """新密码""" + } + } + + login_failed_response400 { + desc { + en: """Login failed. Bad username or password""" + zh: """登录失败。用户名或密码错误""" + } + } + +} diff --git a/apps/emqx_dashboard/src/emqx_dashboard_admin.erl b/apps/emqx_dashboard/src/emqx_dashboard_admin.erl index 140622c67..958e1beae 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard_admin.erl +++ b/apps/emqx_dashboard/src/emqx_dashboard_admin.erl @@ -45,7 +45,9 @@ , verify_hash/2 ]). --export([add_default_user/0]). +-export([ add_default_user/0 + , default_username/0 + ]). -type emqx_admin() :: #?ADMIN{}. @@ -104,18 +106,24 @@ add_user_(Username, Password, Desc) -> mnesia:write(Admin), #{username => Username, description => Desc}; [_] -> - mnesia:abort(<<"Username Already Exist">>) + mnesia:abort(<<"username_already_exist">>) end. -spec(remove_user(binary()) -> {ok, any()} | {error, any()}). remove_user(Username) when is_binary(Username) -> Trans = fun() -> case lookup_user(Username) of - [] -> mnesia:abort(<<"Username Not Found">>); + [] -> mnesia:abort(<<"username_not_found">>); _ -> mnesia:delete({?ADMIN, Username}) end end, - return(mria:transaction(?DASHBOARD_SHARD, Trans)). + case return(mria:transaction(?DASHBOARD_SHARD, Trans)) of + {ok, Result} -> + _ = emqx_dashboard_token:destroy_by_username(Username), + {ok, Result}; + {error, Reason} -> + {error, Reason} + end. -spec(update_user(binary(), binary()) -> {ok, map()} | {error, term()}). update_user(Username, Desc) when is_binary(Username) -> @@ -142,7 +150,7 @@ sha256(SaltBin, Password) -> update_user_(Username, Desc) -> case mnesia:wread({?ADMIN, Username}) of [] -> - mnesia:abort(<<"Username Not Found">>); + mnesia:abort(<<"username_not_found">>); [Admin] -> mnesia:write(Admin#?ADMIN{description = Desc}), #{username => Username, description => Desc} @@ -158,20 +166,28 @@ change_password(Username, Password) when is_binary(Username), is_binary(Password change_password_hash(Username, hash(Password)). change_password_hash(Username, PasswordHash) -> - update_pwd(Username, fun(User) -> - User#?ADMIN{pwdhash = PasswordHash} - end). + ChangePWD = + fun(User) -> + User#?ADMIN{pwdhash = PasswordHash} + end, + case update_pwd(Username, ChangePWD) of + {ok, Result} -> + _ = emqx_dashboard_token:destroy_by_username(Username), + {ok, Result}; + {error, Reason} -> {error, Reason} + end. update_pwd(Username, Fun) -> - Trans = fun() -> - User = - case lookup_user(Username) of + Trans = + fun() -> + User = + case lookup_user(Username) of [Admin] -> Admin; [] -> - mnesia:abort(<<"Username Not Found">>) - end, - mnesia:write(Fun(User)) - end, + mnesia:abort(<<"username_not_found">>) + end, + mnesia:write(Fun(User)) + end, return(mria:transaction(?DASHBOARD_SHARD, Trans)). @@ -190,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}) -> @@ -240,6 +256,9 @@ destroy_token_by_username(Username, Token) -> add_default_user() -> add_default_user(binenv(default_username), binenv(default_password)). +default_username() -> + binenv(default_username). + binenv(Key) -> iolist_to_binary(emqx_conf:get([dashboard, Key], "")). diff --git a/apps/emqx_dashboard/src/emqx_dashboard_api.erl b/apps/emqx_dashboard/src/emqx_dashboard_api.erl index f8d5a4b4e..f6f1174f6 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard_api.erl +++ b/apps/emqx_dashboard/src/emqx_dashboard_api.erl @@ -19,17 +19,40 @@ -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, + 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 +71,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 +99,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(hoconsc:ref(user)), + #{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 +120,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 +145,107 @@ 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)) } } }. +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(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">>})}]. + fields([username, description]); +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) -> + {old_pwd, mk(binary(), #{desc => ?DESC(old_pwd)})}; + +field(new_pwd) -> + {new_pwd, 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,30 +254,47 @@ 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 == <<"admin">> of + 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. change_pwd(put, #{bindings := #{username := Username}, body := Params}) -> + LogMeta = #{msg => "Dashboard change password", username => Username}, 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 -> + ?SLOG(error, LogMeta#{result => failed, reason => "password undefined or empty"}), + {400, ?BAD_REQUEST, <<"Old password or new password undefined">>}; + false -> + case emqx_dashboard_admin:change_password(Username, OldPwd, NewPwd) of + {ok, _} -> + ?SLOG(info, LogMeta#{result => success}), + {204}; + {error, <<"username_not_found">>} -> + ?SLOG(error, LogMeta#{result => failed, reason => "username not found"}), + {404, ?USER_NOT_FOUND, <<"User not found">>}; + {error, <<"password_error">>} -> + ?SLOG(error, LogMeta#{result => failed, reason => "error old pwd"}), + {401, ?ERROR_PWD_NOT_MATCH, <<"Old password not match">>}; + {error, Reason} -> + ?SLOG(error, LogMeta#{result => failed, reason => Reason}), + {400, ?BAD_REQUEST, Reason} + end end. diff --git a/apps/emqx_dashboard/src/emqx_dashboard_cli.erl b/apps/emqx_dashboard/src/emqx_dashboard_cli.erl index 63d48c36d..675f015a4 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard_cli.erl +++ b/apps/emqx_dashboard/src/emqx_dashboard_cli.erl @@ -31,8 +31,6 @@ admins(["add", Username, Password, Desc]) -> case emqx_dashboard_admin:add_user(bin(Username), bin(Password), bin(Desc)) of {ok, _} -> emqx_ctl:print("ok~n"); - {error, already_existed} -> - emqx_ctl:print("Error: already existed~n"); {error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason]) end; diff --git a/apps/emqx_dashboard/test/emqx_dashboard_admin_SUITE.erl b/apps/emqx_dashboard/test/emqx_dashboard_admin_SUITE.erl new file mode 100644 index 000000000..23a92cb18 --- /dev/null +++ b/apps/emqx_dashboard/test/emqx_dashboard_admin_SUITE.erl @@ -0,0 +1,188 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2022 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- +-module(emqx_dashboard_admin_SUITE). + +-compile(nowarn_export_all). +-compile(export_all). + +-include("emqx_dashboard.hrl"). +-include_lib("emqx/include/http_api.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +all() -> + emqx_common_test_helpers:all(?MODULE). + +init_per_suite(Config) -> + mria:start(), + application:load(emqx_dashboard), + emqx_common_test_helpers:start_apps([emqx_conf, emqx_dashboard], fun set_special_configs/1), + Config. + +set_special_configs(emqx_dashboard) -> + emqx_dashboard_api_test_helpers:set_default_config(), + ok; +set_special_configs(_) -> + ok. + +end_per_suite(Config) -> + end_suite(), + Config. + +end_per_testcase(_, _Config) -> + All = emqx_dashboard_admin:all_users(), + [emqx_dashboard_admin:remove_user(Name) || #{username := Name} <- All]. + +end_suite() -> + application:unload(emqx_management), + emqx_common_test_helpers:stop_apps([emqx_dashboard]). + +t_check_user(_) -> + Username = <<"admin1">>, + Password = <<"public">>, + BadUsername = <<"admin_bad">>, + BadPassword = <<"public_bad">>, + EmptyUsername = <<>>, + EmptyPassword = <<>>, + {ok, _} = emqx_dashboard_admin:add_user(Username, Password, <<"desc">>), + ok = emqx_dashboard_admin:check(Username, Password), + {error, <<"password_error">>} = emqx_dashboard_admin:check(Username, BadPassword), + {error, <<"username_not_found">>} = emqx_dashboard_admin:check(BadUsername, Password), + {error, <<"username_not_found">>} = emqx_dashboard_admin:check(BadUsername, BadPassword), + {error, <<"username_not_found">>} = emqx_dashboard_admin:check(EmptyUsername, Password), + {error, <<"password_error">>} = emqx_dashboard_admin:check(Username, EmptyPassword), + {error, <<"username_not_provided">>} = emqx_dashboard_admin:check(undefined, Password), + {error, <<"password_not_provided">>} = emqx_dashboard_admin:check(Username, undefined), + ok. + +t_add_user(_) -> + AddUser = <<"add_user">>, + AddPassword = <<"add_password">>, + AddDescription = <<"add_description">>, + + BadAddUser = <<"***add_user_bad">>, + + %% add success. not return password + {ok, NewUser} = emqx_dashboard_admin:add_user(AddUser, AddPassword, AddDescription), + AddUser = maps:get(username, NewUser), + AddDescription = maps:get(description, NewUser), + false = maps:is_key(password, NewUser), + + %% add again + {error, <<"username_already_exist">>} = + emqx_dashboard_admin:add_user(AddUser, AddPassword, AddDescription), + + %% add bad username + BadNameError = + <<"Bad Username. Only upper and lower case letters, numbers and underscores are supported">>, + {error, BadNameError} = emqx_dashboard_admin:add_user(BadAddUser, AddPassword, AddDescription), + ok. + +t_lookup_user(_) -> + LookupUser = <<"lookup_user">>, + LookupPassword = <<"lookup_password">>, + LookupDescription = <<"lookup_description">>, + + BadLookupUser = <<"***lookup_user_bad">>, + + {ok, _} = + emqx_dashboard_admin:add_user(LookupUser, LookupPassword, LookupDescription), + %% lookup success. not return password + [#emqx_admin{username = LookupUser, description = LookupDescription}] = + emqx_dashboard_admin:lookup_user(LookupUser), + + [] = emqx_dashboard_admin:lookup_user(BadLookupUser), + ok. + +t_all_users(_) -> + Username = <<"admin_all">>, + Password = <<"public">>, + {ok, _} = emqx_dashboard_admin:add_user(Username, Password, <<"desc">>), + All = emqx_dashboard_admin:all_users(), + ?assert(erlang:length(All) >= 1), + ok. + +t_delete_user(_) -> + DeleteUser = <<"delete_user">>, + DeletePassword = <<"delete_password">>, + DeleteDescription = <<"delete_description">>, + + DeleteBadUser = <<"delete_user_bad">>, + + {ok, _NewUser} = + emqx_dashboard_admin:add_user(DeleteUser, DeletePassword, DeleteDescription), + {ok, ok} = emqx_dashboard_admin:remove_user(DeleteUser), + %% remove again + {error, <<"username_not_found">>} = emqx_dashboard_admin:remove_user(DeleteUser), + {error, <<"username_not_found">>} = emqx_dashboard_admin:remove_user(DeleteBadUser), + ok. + +t_update_user(_) -> + UpdateUser = <<"update_user">>, + UpdatePassword = <<"update_password">>, + UpdateDescription = <<"update_description">>, + + NewDesc = <<"new_description">>, + + BadUpdateUser = <<"update_user_bad">>, + + {ok, _} = emqx_dashboard_admin:add_user(UpdateUser, UpdatePassword, UpdateDescription), + {ok, NewUserInfo} = + emqx_dashboard_admin:update_user(UpdateUser, NewDesc), + UpdateUser = maps:get(username, NewUserInfo), + NewDesc = maps:get(description, NewUserInfo), + + {error,<<"username_not_found">>} = emqx_dashboard_admin:update_user(BadUpdateUser, NewDesc), + ok. + +t_change_password(_) -> + User = <<"change_user">>, + OldPassword = <<"change_password">>, + Description = <<"change_description">>, + + NewPassword = <<"new_password">>, + + BadChangeUser = <<"change_user_bad">>, + + {ok, _} = emqx_dashboard_admin:add_user(User, OldPassword, Description), + + {ok, ok} = emqx_dashboard_admin:change_password(User, OldPassword, NewPassword), + %% change pwd again + {error,<<"password_error">>} = + emqx_dashboard_admin:change_password(User, OldPassword, NewPassword), + + {error, <<"username_not_found">>} = + emqx_dashboard_admin:change_password(BadChangeUser, OldPassword, NewPassword), + ok. + +t_clean_token(_) -> + Username = <<"admin_token">>, + Password = <<"public">>, + NewPassword = <<"public1">>, + {ok, _} = emqx_dashboard_admin:add_user(Username, Password, <<"desc">>), + {ok, Token} = emqx_dashboard_admin:sign_token(Username, Password), + ok = emqx_dashboard_admin:verify_token(Token), + %% change password + {ok, _} = emqx_dashboard_admin:change_password(Username, Password, NewPassword), + timer:sleep(5), + {error, not_found} = emqx_dashboard_admin:verify_token(Token), + %% remove user + {ok, Token2} = emqx_dashboard_admin:sign_token(Username, NewPassword), + ok = emqx_dashboard_admin:verify_token(Token2), + {ok, _} = emqx_dashboard_admin:remove_user(Username), + timer:sleep(5), + {error, not_found} = emqx_dashboard_admin:verify_token(Token2), + ok. + diff --git a/apps/emqx_management/src/emqx_mgmt_api_banned.erl b/apps/emqx_management/src/emqx_mgmt_api_banned.erl index fc9ece871..0fe8d56f7 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_banned.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_banned.erl @@ -159,7 +159,9 @@ banned(post, #{body := Body}) -> Ban -> case emqx_banned:create(Ban) of {ok, Banned} -> {200, format(Banned)}; - {error, {already_exist, Old}} -> {400, 'ALREADY_EXISTS', format(Old)} + {error, {already_exist, Old}} -> + OldBannedFormat = emqx_json:encode(format(Old)), + {400, 'ALREADY_EXISTS', OldBannedFormat} end end. diff --git a/scripts/merge-i18n.escript b/scripts/merge-i18n.escript index 13a7df27b..e32e82d0f 100755 --- a/scripts/merge-i18n.escript +++ b/scripts/merge-i18n.escript @@ -3,8 +3,7 @@ -mode(compile). main(_) -> - {ok, BaseConf} = file:read_file("apps/emqx_dashboard/i18n/emqx_dashboard_i18n.conf"), - + BaseConf = <<"">>, Cfgs = get_all_cfgs("apps/"), Conf = [merge(BaseConf, Cfgs), io_lib:nl() @@ -23,7 +22,7 @@ merge(BaseConf, Cfgs) -> end, BaseConf, Cfgs). get_all_cfgs(Root) -> - Apps = filelib:wildcard("*", Root) -- ["emqx_machine", "emqx_dashboard"], + Apps = filelib:wildcard("*", Root) -- ["emqx_machine"], Dirs = [filename:join([Root, App]) || App <- Apps], lists:foldl(fun get_cfgs/2, [], Dirs).