Просмотр исходного кода

Merge pull request #7749 from DDDHuang/fix_some_api

fix: create banned & dashboard default user name
DDDHuang 3 лет назад
Родитель
Сommit
2ea66ebcee

+ 150 - 0
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: """登录失败。用户名或密码错误"""
+        }
+    }
+
+}

+ 35 - 16
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], "")).
 

+ 144 - 107
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.

+ 0 - 2
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;

+ 188 - 0
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.
+

+ 3 - 1
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.
 

+ 2 - 3
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).