emqx/apps/emqx_dashboard/src/emqx_dashboard_api.erl

263 lines
9.6 KiB
Erlang

%%--------------------------------------------------------------------
%% 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_api).
-behaviour(minirest_api).
-include("emqx_dashboard.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]).
-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">>}).
namespace() -> "dashboard".
api_spec() ->
emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true, translate_body => true}).
paths() ->
[ "/login"
, "/logout"
, "/users"
, "/users/:username"
, "/users/:username/change_pwd"].
schema("/login") ->
#{
'operationId' => login,
post => #{
tags => [<<"dashboard">>],
description => <<"Dashboard Auth">>,
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"})}
],
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"})}
]
},
security => []
}};
schema("/logout") ->
#{
'operationId' => logout,
post => #{
tags => [<<"dashboard">>],
description => <<"Dashboard User logout">>,
'requestBody' => [
{username, mk(binary(),
#{desc => <<"The User for which to create the token.">>,
'maxLength' => 100, example => <<"admin">>})}
],
responses => #{
204 => <<"Dashboard logout successfully">>
}
}
};
schema("/users") ->
#{
'operationId' => users,
get => #{
tags => [<<"dashboard">>],
description => <<"Get dashboard users list">>,
responses => #{
200 => mk( array(ref(?MODULE, user))
, #{desc => "User lists"})
}
},
post => #{
tags => [<<"dashboard">>],
description => <<"Create dashboard users">>,
'requestBody' => fields(user_password),
responses => #{
200 => mk( ref(?MODULE, user)
, #{desc => <<"Create User successfully">>}),
400 => [{code, mk(string(), #{example => 'CREATE_FAIL'})},
{message, mk(string(), #{example => "Create user failed"})}
]
}
}
};
schema("/users/:username") ->
#{
'operationId' => user,
put => #{
tags => [<<"dashboard">>],
description => <<"Update dashboard users">>,
parameters => [{username, mk(binary(),
#{in => path, example => <<"admin">>})}],
'requestBody' => [
{ description
, mk(binary(),
#{desc => <<"User description">>, example => <<"administrator">>})}
],
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">>)
}
},
delete => #{
tags => [<<"dashboard">>],
description => <<"Delete dashboard users">>,
parameters => [{username, mk(binary(),
#{in => path, example => <<"admin">>})}],
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">>)
}
}
};
schema("/users/:username/change_pwd") ->
#{
'operationId' => change_pwd,
put => #{
tags => [<<"dashboard">>],
description => <<"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})}
],
responses => #{
204 => <<"Update user password successfully">>,
400 => [
{code, mk(string(), #{example => 'UPDATE_FAIL'})},
{message, mk(string(), #{example => "Failed Reason"})}
]
}
}
}.
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">>})}].
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} ->
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">>}}
end.
logout(_, #{body := #{<<"username">> := Username},
headers := #{<<"authorization">> := <<"Bearer ", Token/binary>>}}) ->
case emqx_dashboard_admin:destroy_token_by_username(Username, Token) of
ok ->
204;
_R ->
{401, 'BAD_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),
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">>}};
false ->
case emqx_dashboard_admin:add_user(Username, Password, Desc) of
{ok, Result} ->
{200, Result};
{error, Reason} ->
{400, #{code => <<"CREATE_USER_FAIL">>, message => Reason}}
end
end.
user(put, #{bindings := #{username := Username}, body := Params}) ->
Desc = maps:get(<<"description">>, Params),
case emqx_dashboard_admin:update_user(Username, Desc) of
{ok, Result} ->
{200, Result};
{error, _Reason} ->
{404, ?USER_NOT_FOUND_BODY}
end;
user(delete, #{bindings := #{username := Username}}) ->
case Username == <<"admin">> of
true ->
{400, #{code => <<"ACTION_NOT_ALLOWED">>,
message => <<"Cannot delete admin">>}};
false ->
case emqx_dashboard_admin:remove_user(Username) of
{error, _Reason} ->
{404, ?USER_NOT_FOUND_BODY};
{ok, _} ->
{204}
end
end.
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}}
end.