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

feat(authn http api): add test case and improve http api spec

zhouzb 4 лет назад
Родитель
Сommit
7febcb852a

+ 6 - 6
apps/emqx_authn/etc/emqx_authn.conf

@@ -1,11 +1,11 @@
 emqx_authn: {
 emqx_authn: {
     enable: false
     enable: false
     authenticators: [
     authenticators: [
-        {
-            name: "authenticator1"
-            mechanism: password-based
-            server_type: built-in-database
-            user_id_type: clientid
-        }
+        # {
+        #     name: "authenticator1"
+        #     mechanism: password-based
+        #     server_type: built-in-database
+        #     user_id_type: clientid
+        # }
     ]
     ]
 }
 }

+ 0 - 1
apps/emqx_authn/include/emqx_authn.hrl

@@ -26,7 +26,6 @@
         , provider :: module()
         , provider :: module()
         , config :: map()
         , config :: map()
         , state :: map()
         , state :: map()
-        , version :: binary()
         }).
         }).
 
 
 -record(chain,
 -record(chain,

+ 24 - 18
apps/emqx_authn/src/emqx_authn.erl

@@ -194,20 +194,25 @@ do_update_authenticator(ChainID, AuthenticatorID, #{name := NewName} = Config, C
                         false ->
                         false ->
                             case CreateWhenNotFound of
                             case CreateWhenNotFound of
                                 true ->
                                 true ->
-                                    case do_create_authenticator(ChainID, AuthenticatorID, Config) of
-                                        {ok, Authenticator} ->
-                                            NAuthenticators = Authenticators ++ [{AuthenticatorID, NewName, Authenticator}],
-                                            ok = mnesia:write(?CHAIN_TAB, Chain#chain{authenticators = NAuthenticators}, write),
-                                            {ok, serialize_authenticator(Authenticator)};
-                                        {error, Reason} ->
-                                            {error, Reason}
-                                    end;
+                                    case lists:keymember(NewName, 2, Authenticators) of
+                                        true ->
+                                            {error, name_has_be_used};
+                                        false ->
+                                            case do_create_authenticator(ChainID, AuthenticatorID, Config) of
+                                                {ok, Authenticator} ->
+                                                    NAuthenticators = Authenticators ++ [{AuthenticatorID, NewName, Authenticator}],
+                                                    ok = mnesia:write(?CHAIN_TAB, Chain#chain{authenticators = NAuthenticators}, write),
+                                                    {ok, serialize_authenticator(Authenticator)};
+                                                {error, Reason} ->
+                                                    {error, Reason}
+                                            end
+                                        end;
                                 false ->
                                 false ->
                                     {error, {not_found, {authenticator, AuthenticatorID}}}
                                     {error, {not_found, {authenticator, AuthenticatorID}}}
                             end;
                             end;
                         {value,
                         {value,
                          {_, _, #authenticator{provider = Provider,
                          {_, _, #authenticator{provider = Provider,
-                                               state    = #{version := Version} = State}},
+                                               state    = #{version := Version} = State} = Authenticator},
                          Others} ->
                          Others} ->
                             case lists:keymember(NewName, 2, Others) of
                             case lists:keymember(NewName, 2, Others) of
                                 true ->
                                 true ->
@@ -215,12 +220,12 @@ do_update_authenticator(ChainID, AuthenticatorID, #{name := NewName} = Config, C
                                 false ->
                                 false ->
                                     case (NewProvider = authenticator_provider(Config)) =:= Provider of
                                     case (NewProvider = authenticator_provider(Config)) =:= Provider of
                                         true ->
                                         true ->
-                                            Unique = {ChainID, AuthenticatorID, Version},
+                                            Unique = <<ChainID/binary, "/", AuthenticatorID/binary, ":", Version/binary>>,
                                             case Provider:update(Config#{'_unique' => Unique}, State) of
                                             case Provider:update(Config#{'_unique' => Unique}, State) of
                                                 {ok, NewState} ->
                                                 {ok, NewState} ->
-                                                    NewAuthenticator = #authenticator{name = NewName,
-                                                                                      config = Config,
-                                                                                      state = switch_version(NewState)},
+                                                    NewAuthenticator = Authenticator#authenticator{name = NewName,
+                                                                                                   config = Config,
+                                                                                                   state = switch_version(NewState)},
                                                     NewAuthenticators = replace_authenticator(AuthenticatorID, NewAuthenticator, Authenticators),
                                                     NewAuthenticators = replace_authenticator(AuthenticatorID, NewAuthenticator, Authenticators),
                                                     ok = mnesia:write(?CHAIN_TAB, Chain#chain{authenticators = NewAuthenticators}, write),
                                                     ok = mnesia:write(?CHAIN_TAB, Chain#chain{authenticators = NewAuthenticators}, write),
                                                     {ok, serialize_authenticator(NewAuthenticator)};
                                                     {ok, serialize_authenticator(NewAuthenticator)};
@@ -228,12 +233,13 @@ do_update_authenticator(ChainID, AuthenticatorID, #{name := NewName} = Config, C
                                                     {error, Reason}
                                                     {error, Reason}
                                             end;
                                             end;
                                         false ->
                                         false ->
-                                            case NewProvider:create(Config#{'_unique' => {ChainID, AuthenticatorID, Version}}) of
+                                            Unique = <<ChainID/binary, "/", AuthenticatorID/binary, ":", Version/binary>>,
+                                            case NewProvider:create(Config#{'_unique' => Unique}) of
                                                 {ok, NewState} ->
                                                 {ok, NewState} ->
-                                                    NewAuthenticator = #authenticator{name = NewName,
-                                                                                      provider = NewProvider,
-                                                                                      config = Config,
-                                                                                      state = switch_version(NewState)},
+                                                    NewAuthenticator = Authenticator#authenticator{name = NewName,
+                                                                                                   provider = NewProvider,
+                                                                                                   config = Config,
+                                                                                                   state = switch_version(NewState)},
                                                     NewAuthenticators = replace_authenticator(AuthenticatorID, NewAuthenticator, Authenticators),
                                                     NewAuthenticators = replace_authenticator(AuthenticatorID, NewAuthenticator, Authenticators),
                                                     ok = mnesia:write(?CHAIN_TAB, Chain#chain{authenticators = NewAuthenticators}, write),
                                                     ok = mnesia:write(?CHAIN_TAB, Chain#chain{authenticators = NewAuthenticators}, write),
                                                     _ = Provider:destroy(State),
                                                     _ = Provider:destroy(State),

+ 166 - 156
apps/emqx_authn/src/emqx_authn_api.erl

@@ -29,28 +29,16 @@
         , users2/2
         , users2/2
         ]).
         ]).
 
 
-api_spec() ->
-    {[ authenticators_api()
-     , authenticators_api2()
-     , position_api()
-     , import_users_api()
-     , users_api()
-     , users2_api()
-     ], definitions()}.
-
-authenticators_api() ->
-    Example1 = #{name => <<"example">>,
-                 mechanism => <<"password-based">>,
-                 config => #{
+-define(EXAMPLE_1, #{name => <<"example 1">>,
+                     mechanism => <<"password-based">>,
                      server_type => <<"built-in-example">>,
                      server_type => <<"built-in-example">>,
                      user_id_type => <<"username">>,
                      user_id_type => <<"username">>,
                      password_hash_algorithm => #{
                      password_hash_algorithm => #{
                          name => <<"sha256">>    
                          name => <<"sha256">>    
-                     }
-                 }},
-    Example2 = #{name => <<"example">>,
-                 mechanism => <<"password-based">>,
-                 config => #{
+                     }}).
+
+-define(EXAMPLE_2, #{name => <<"example 2">>,
+                     mechanism => <<"password-based">>,
                      server_type => <<"http-server">>,
                      server_type => <<"http-server">>,
                      method => <<"post">>,
                      method => <<"post">>,
                      url => <<"http://localhost:80/login">>,
                      url => <<"http://localhost:80/login">>,
@@ -60,19 +48,47 @@ authenticators_api() ->
                      form_data => #{
                      form_data => #{
                         <<"username">> => <<"${mqtt-username}">>,
                         <<"username">> => <<"${mqtt-username}">>,
                         <<"password">> => <<"${mqtt-password}">>
                         <<"password">> => <<"${mqtt-password}">>
-                     }
-                 }},
-    Example3 = #{name => <<"example">>,
-                 mechanism => <<"jwt">>,
-                 config => #{
+                     }}).
+
+-define(EXAMPLE_3, #{name => <<"example 3">>,
+                     mechanism => <<"jwt">>,
                      use_jwks => false,
                      use_jwks => false,
                      algorithm => <<"hmac-based">>,
                      algorithm => <<"hmac-based">>,
                      secret => <<"mysecret">>,
                      secret => <<"mysecret">>,
                      secret_base64_encoded => false,
                      secret_base64_encoded => false,
                      verify_claims => #{
                      verify_claims => #{
                          <<"username">> => <<"${mqtt-username}">>
                          <<"username">> => <<"${mqtt-username}">>
-                     }
-                 }},
+                     }}).
+
+-define(ERR_RESPONSE(Desc), #{description => Desc,
+                              content => #{
+                                  'application/json' => #{
+                                      schema => minirest:ref(<<"error">>),
+                                      examples => #{
+                                        example1 => #{
+                                            summary => <<"Not Found">>,
+                                            value => #{code => <<"NOT_FOUND">>, message => <<"Authenticator '67e4c9d3' does not exist">>}
+                                        },
+                                        example2 => #{
+                                            summary => <<"Conflict">>,
+                                            value => #{code => <<"ALREADY_EXISTS">>, message => <<"Name has be used">>}
+                                        },
+                                        example3 => #{
+                                            summary => <<"Bad Request 1">>,
+                                            value => #{code => <<"OUT_OF_RANGE">>, message => <<"Out of range">>}
+                                        }
+                                  }}}}).
+
+api_spec() ->
+    {[ authenticators_api()
+     , authenticators_api2()
+     , position_api()
+     , import_users_api()
+     , users_api()
+     , users2_api()
+     ], definitions()}.
+
+authenticators_api() ->
     Metadata = #{
     Metadata = #{
         post => #{
         post => #{
             description => "Create authenticator",
             description => "Create authenticator",
@@ -83,15 +99,15 @@ authenticators_api() ->
                         examples => #{
                         examples => #{
                             default => #{
                             default => #{
                                 summary => <<"Default">>,
                                 summary => <<"Default">>,
-                                value => emqx_json:encode(Example1)
+                                value => emqx_json:encode(?EXAMPLE_1)
                             },
                             },
                             http => #{
                             http => #{
                                 summary => <<"Authentication provided by HTTP Server">>,
                                 summary => <<"Authentication provided by HTTP Server">>,
-                                value => emqx_json:encode(Example2)
+                                value => emqx_json:encode(?EXAMPLE_2)
                             },
                             },
                             jwt => #{
                             jwt => #{
                                 summary => <<"JWT Authentication">>,
                                 summary => <<"JWT Authentication">>,
-                                value => emqx_json:encode(Example3)
+                                value => emqx_json:encode(?EXAMPLE_3)
                             }
                             }
                         }
                         }
                     }
                     }
@@ -102,10 +118,26 @@ authenticators_api() ->
                     description => <<"Created">>,
                     description => <<"Created">>,
                     content => #{
                     content => #{
                         'application/json' => #{
                         'application/json' => #{
-                            schema => minirest:ref(<<"returned_authenticator">>)
+                            schema => minirest:ref(<<"returned_authenticator">>),
+                            examples => #{
+                                example1 => #{
+                                    summary => <<"Example 1">>,
+                                    value => emqx_json:encode(maps:put(id, <<"example 1">>, ?EXAMPLE_1))
+                                },
+                                example2 => #{
+                                    summary => <<"Example 2">>,
+                                    value => emqx_json:encode(maps:put(id, <<"example 2">>, ?EXAMPLE_2))
+                                },
+                                example3 => #{
+                                    summary => <<"Example 3">>,
+                                    value => emqx_json:encode(maps:put(id, <<"example 3">>, ?EXAMPLE_3))
+                                }
+                            }
                         }
                         }
                     }
                     }
-                }
+                },
+                <<"400">> => ?ERR_RESPONSE(<<"Bad Request">>),
+                <<"409">> => ?ERR_RESPONSE(<<"Conflict">>)
             }
             }
         },
         },
         get => #{
         get => #{
@@ -118,6 +150,15 @@ authenticators_api() ->
                             schema => #{
                             schema => #{
                                 type => array,
                                 type => array,
                                 items => minirest:ref(<<"returned_authenticator">>)
                                 items => minirest:ref(<<"returned_authenticator">>)
+                            },
+                            examples => #{
+                                example1 => #{
+                                    summary => <<"Example 1">>,
+                                    value => emqx_json:encode([ maps:put(id, <<"example 1">>, ?EXAMPLE_1)
+                                                              , maps:put(id, <<"example 2">>, ?EXAMPLE_2)
+                                                              , maps:put(id, <<"example 3">>, ?EXAMPLE_3)
+                                                              ])
+                                }
                             }
                             }
                         }
                         }
                     }
                     }
@@ -146,18 +187,25 @@ authenticators_api2() ->
                     description => <<"OK">>,
                     description => <<"OK">>,
                     content => #{
                     content => #{
                         'application/json' => #{
                         'application/json' => #{
-                            schema => minirest:ref(<<"returned_authenticator">>)
+                            schema => minirest:ref(<<"returned_authenticator">>),
+                            examples => #{
+                                example1 => #{
+                                    summary => <<"Example 1">>,
+                                    value => emqx_json:encode(maps:put(id, <<"example 1">>, ?EXAMPLE_1))
+                                },
+                                example2 => #{
+                                    summary => <<"Example 2">>,
+                                    value => emqx_json:encode(maps:put(id, <<"example 2">>, ?EXAMPLE_2))
+                                },
+                                example3 => #{
+                                    summary => <<"Example 3">>,
+                                    value => emqx_json:encode(maps:put(id, <<"example 3">>, ?EXAMPLE_3))
+                                }
+                            }
                         }
                         }
                     }
                     }
                 },
                 },
-                <<"404">> => #{
-                    description => <<"Not Found">>,
-                    content => #{
-                        'application/json' => #{
-                            schema => minirest:ref(<<"error">>)
-                        }
-                    }
-                }
+                <<"404">> => ?ERR_RESPONSE(<<"Not Found">>)
             }
             }
         },
         },
         put => #{
         put => #{
@@ -180,6 +228,16 @@ authenticators_api2() ->
                                      , minirest:ref(<<"jwt">>)
                                      , minirest:ref(<<"jwt">>)
                                      , minirest:ref(<<"scram">>)
                                      , minirest:ref(<<"scram">>)
                                      ]   
                                      ]   
+                        },
+                        examples => #{
+                            example1 => #{
+                                summary => <<"Example 1">>,
+                                value => emqx_json:encode(?EXAMPLE_1)
+                            },
+                            example2 => #{
+                                summary => <<"Example 2">>,
+                                value => emqx_json:encode(?EXAMPLE_2)
+                            }
                         }
                         }
                     }
                     }
                 }
                 }
@@ -189,18 +247,27 @@ authenticators_api2() ->
                     description => <<"OK">>,
                     description => <<"OK">>,
                     content => #{
                     content => #{
                         'application/json' => #{
                         'application/json' => #{
-                            schema => minirest:ref(<<"returned_authenticator">>)
+                            schema => minirest:ref(<<"returned_authenticator">>),
+                            examples => #{
+                                example1 => #{
+                                    summary => <<"Example 1">>,
+                                    value => emqx_json:encode(maps:put(id, <<"example 1">>, ?EXAMPLE_1))
+                                },
+                                example2 => #{
+                                    summary => <<"Example 2">>,
+                                    value => emqx_json:encode(maps:put(id, <<"example 2">>, ?EXAMPLE_2))
+                                },
+                                example3 => #{
+                                    summary => <<"Example 3">>,
+                                    value => emqx_json:encode(maps:put(id, <<"example 3">>, ?EXAMPLE_3))
+                                }
+                            }
                         }
                         }
                     }
                     }
                 },
                 },
-                <<"404">> => #{
-                    description => <<"Not Found">>,
-                    content => #{
-                        'application/json' => #{
-                            schema => minirest:ref(<<"error">>)
-                        }
-                    }
-                }
+                <<"400">> => ?ERR_RESPONSE(<<"Bad Request">>),
+                <<"404">> => ?ERR_RESPONSE(<<"Not Found">>),
+                <<"409">> => ?ERR_RESPONSE(<<"Conflict">>)
             }
             }
         },
         },
         delete => #{
         delete => #{
@@ -219,14 +286,7 @@ authenticators_api2() ->
                 <<"204">> => #{
                 <<"204">> => #{
                     description => <<"No Content">>
                     description => <<"No Content">>
                 },
                 },
-                <<"404">> => #{
-                    description => <<"Not Found">>,
-                    content => #{
-                        'application/json' => #{
-                            schema => minirest:ref(<<"error">>)
-                        }
-                    }
-                }
+                <<"404">> => ?ERR_RESPONSE(<<"Not Found">>)
             }
             }
         }
         }
     },
     },
@@ -266,14 +326,8 @@ position_api() ->
                 <<"204">> => #{
                 <<"204">> => #{
                     description => <<"No Content">>
                     description => <<"No Content">>
                 },
                 },
-                <<"404">> => #{
-                    description => <<"Not Found">>,
-                    content => #{
-                        'application/json' => #{
-                            schema => minirest:ref(<<"error">>)
-                        }
-                    }
-                }
+                <<"400">> => ?ERR_RESPONSE(<<"Bad Request">>),
+                <<"404">> => ?ERR_RESPONSE(<<"Not Found">>)
             }
             }
         }
         }
     },
     },
@@ -312,22 +366,8 @@ import_users_api() ->
                 <<"204">> => #{
                 <<"204">> => #{
                     description => <<"No Content">>
                     description => <<"No Content">>
                 },
                 },
-                <<"400">> => #{
-                    description => <<"Bad Request">>,
-                    content => #{
-                        'application/json' => #{
-                            schema => minirest:ref(<<"error">>)
-                        }
-                    }
-                },
-                <<"404">> => #{
-                    description => <<"Not Found">>,
-                    content => #{
-                        'application/json' => #{
-                            schema => minirest:ref(<<"error">>)
-                        }
-                    }
-                }
+                <<"400">> => ?ERR_RESPONSE(<<"Bad Request">>),
+                <<"404">> => ?ERR_RESPONSE(<<"Not Found">>)
             }
             }
         }
         }
     },
     },
@@ -382,14 +422,8 @@ users_api() ->
                         }
                         }
                     }
                     }
                 },
                 },
-                <<"400">> => #{
-                    description => <<"Bad Request">>,
-                    content => #{
-                        'application/json' => #{
-                            schema => minirest:ref(<<"error">>)
-                        }
-                    }
-                }
+                <<"400">> => ?ERR_RESPONSE(<<"Bad Request">>),
+                <<"404">> => ?ERR_RESPONSE(<<"Not Found">>)
             }
             }
         },
         },
         get => #{
         get => #{
@@ -423,7 +457,8 @@ users_api() ->
                             }
                             }
                         }
                         }
                     }
                     }
-                }
+                },
+                <<"404">> => ?ERR_RESPONSE(<<"Not Found">>)
             }
             }
         }
         }
     },
     },
@@ -486,14 +521,8 @@ users2_api() ->
                         }
                         }
                     }
                     }
                 },
                 },
-                <<"404">> => #{
-                    description => <<"Not Found">>,
-                    content => #{
-                        'application/json' => #{
-                            schema => minirest:ref(<<"error">>)
-                        }
-                    }
-                }
+                <<"400">> => ?ERR_RESPONSE(<<"Bad Request">>),
+                <<"404">> => ?ERR_RESPONSE(<<"Not Found">>)
             }
             }
         },
         },
         get => #{
         get => #{
@@ -536,14 +565,7 @@ users2_api() ->
                         }
                         }
                     }
                     }
                 },
                 },
-                <<"404">> => #{
-                    description => <<"Not Found">>,
-                    content => #{
-                        'application/json' => #{
-                            schema => minirest:ref(<<"error">>)
-                        }
-                    }
-                }
+                <<"404">> => ?ERR_RESPONSE(<<"Not Found">>)
             }
             }
         },
         },
         delete => #{
         delete => #{
@@ -570,14 +592,7 @@ users2_api() ->
                 <<"204">> => #{
                 <<"204">> => #{
                     description => <<"No Content">>
                     description => <<"No Content">>
                 },
                 },
-                <<"404">> => #{
-                    description => <<"Not Found">>,
-                    content => #{
-                        'application/json' => #{
-                            schema => minirest:ref(<<"error">>)
-                        }
-                    }
-                }
+                <<"404">> => ?ERR_RESPONSE(<<"Not Found">>)
             }
             }
         }
         }
     },
     },
@@ -1007,47 +1022,43 @@ authenticators2(delete, Request) ->
 position(post, Request) ->
 position(post, Request) ->
     AuthenticatorID = cowboy_req:binding(id, Request),
     AuthenticatorID = cowboy_req:binding(id, Request),
     {ok, Body, _} = cowboy_req:read_body(Request),
     {ok, Body, _} = cowboy_req:read_body(Request),
-    case emqx_json:decode(Body, [return_maps]) of
-        #{<<"position">> := Position} when is_integer(Position) ->
-            case emqx_authn:move_authenticator_to_the_nth(?CHAIN, AuthenticatorID, Position) of
-                ok ->
-                    {204};
-                {error, Reason} ->
-                    serialize_error(Reason)
-            end;
-        _ ->
-            serialize_error({missing_parameter, position})
+    NBody = emqx_json:decode(Body, [return_maps]),
+    Config = hocon_schema:check_plain(emqx_authn_other_schema, #{<<"position">> => NBody},
+                                      #{nullable => true}, ["position"]),
+    #{position := #{position := Position}} = emqx_map_lib:unsafe_atom_key_map(Config),
+    case emqx_authn:move_authenticator_to_the_nth(?CHAIN, AuthenticatorID, Position) of
+        ok ->
+            {204};
+        {error, Reason} ->
+            serialize_error(Reason)
     end.
     end.
 
 
 import_users(post, Request) ->
 import_users(post, Request) ->
     AuthenticatorID = cowboy_req:binding(id, Request),
     AuthenticatorID = cowboy_req:binding(id, Request),
     {ok, Body, _} = cowboy_req:read_body(Request),
     {ok, Body, _} = cowboy_req:read_body(Request),
-    case emqx_json:decode(Body, [return_maps]) of
-        #{<<"filename">> := Filename} when is_binary(Filename) ->
-            case emqx_authn:import_users(?CHAIN, AuthenticatorID, Filename) of
-                ok ->
-                    {204};
-                {error, Reason} ->
-                    serialize_error(Reason)
-            end;
-        _ ->
-            serialize_error({missing_parameter, filename})
+    NBody = emqx_json:decode(Body, [return_maps]),
+    Config = hocon_schema:check_plain(emqx_authn_other_schema, #{<<"filename">> => NBody},
+                                      #{nullable => true}, ["filename"]),
+    #{filename := #{filename := Filename}} = emqx_map_lib:unsafe_atom_key_map(Config),
+    case emqx_authn:import_users(?CHAIN, AuthenticatorID, Filename) of
+        ok ->
+            {204};
+        {error, Reason} ->
+            serialize_error(Reason)
     end.
     end.
 
 
 users(post, Request) ->
 users(post, Request) ->
     AuthenticatorID = cowboy_req:binding(id, Request),
     AuthenticatorID = cowboy_req:binding(id, Request),
     {ok, Body, _} = cowboy_req:read_body(Request),
     {ok, Body, _} = cowboy_req:read_body(Request),
-    case emqx_json:decode(Body, [return_maps]) of
-        #{<<"user_id">> := _,
-          <<"password">> := _} = UserInfo ->
-            case emqx_authn:add_user(?CHAIN, AuthenticatorID, UserInfo) of
-                {ok, User} ->
-                    {201, User};
-                {error, Reason} ->
-                    serialize_error(Reason)
-            end;
-        _ ->
-            serialize_error({missing_parameter, user_id})
+    NBody = emqx_json:decode(Body, [return_maps]),
+    Config = hocon_schema:check_plain(emqx_authn_other_schema, #{<<"user_info">> => NBody},
+                                      #{nullable => true}, ["user_info"]),
+    #{user_info := UserInfo} = emqx_map_lib:unsafe_atom_key_map(Config),
+    case emqx_authn:add_user(?CHAIN, AuthenticatorID, UserInfo) of
+        {ok, User} ->
+            {201, User};
+        {error, Reason} ->
+            serialize_error(Reason)
     end;
     end;
 users(get, Request) ->
 users(get, Request) ->
     AuthenticatorID = cowboy_req:binding(id, Request),
     AuthenticatorID = cowboy_req:binding(id, Request),
@@ -1062,16 +1073,15 @@ users2(patch, Request) ->
     AuthenticatorID = cowboy_req:binding(id, Request),
     AuthenticatorID = cowboy_req:binding(id, Request),
     UserID = cowboy_req:binding(user_id, Request),
     UserID = cowboy_req:binding(user_id, Request),
     {ok, Body, _} = cowboy_req:read_body(Request),
     {ok, Body, _} = cowboy_req:read_body(Request),
-    case emqx_json:decode(Body, [return_maps]) of
-        #{<<"password">> := _} = UserInfo ->
-            case emqx_authn:add_user(?CHAIN, AuthenticatorID, UserID, UserInfo) of
-                {ok, User} ->
-                    {200, User};
-                {error, Reason} ->
-                    serialize_error(Reason)
-            end;
-        _ ->
-            serialize_error({missing_parameter, password})
+    NBody = emqx_json:decode(Body, [return_maps]),
+    Config = hocon_schema:check_plain(emqx_authn_other_schema, #{<<"new_user_info">> => NBody},
+                                      #{nullable => true}, ["new_user_info"]),
+    #{new_user_info := NewUserInfo} = emqx_map_lib:unsafe_atom_key_map(Config),
+    case emqx_authn:update_user(?CHAIN, AuthenticatorID, UserID, NewUserInfo) of
+        {ok, User} ->
+            {200, User};
+        {error, Reason} ->
+            serialize_error(Reason)
     end;
     end;
 users2(get, Request) ->
 users2(get, Request) ->
     AuthenticatorID = cowboy_req:binding(id, Request),
     AuthenticatorID = cowboy_req:binding(id, Request),
@@ -1106,6 +1116,6 @@ serialize_error({missing_parameter, Name}) ->
             message => list_to_binary(
             message => list_to_binary(
                 io_lib:format("The input parameter '~p' that is mandatory for processing this request is not supplied", [Name])
                 io_lib:format("The input parameter '~p' that is mandatory for processing this request is not supplied", [Name])
             )}};
             )}};
-serialize_error(_) ->
+serialize_error(Reason) ->
     {400, #{code => <<"BAD_REQUEST">>,
     {400, #{code => <<"BAD_REQUEST">>,
-            message => <<"Todo">>}}.
+            message => list_to_binary(io_lib:format("Todo: ~p", [Reason]))}}.

+ 3 - 3
apps/emqx_authn/src/enhanced_authn/emqx_enhanced_authn_scram_mnesia.erl

@@ -133,8 +133,8 @@ destroy(#{user_group := UserGroup}) ->
         end).
         end).
 
 
 %% TODO: binary to atom
 %% TODO: binary to atom
-add_user(#{<<"user_id">> := UserID,
-           <<"password">> := Password}, #{user_group := UserGroup} = State) ->
+add_user(#{user_id := UserID,
+           password := Password}, #{user_group := UserGroup} = State) ->
     trans(
     trans(
         fun() ->
         fun() ->
             case mnesia:read(?TAB, {UserGroup, UserID}, write) of
             case mnesia:read(?TAB, {UserGroup, UserID}, write) of
@@ -157,7 +157,7 @@ delete_user(UserID, #{user_group := UserGroup}) ->
             end
             end
         end).
         end).
 
 
-update_user(UserID, #{<<"password">> := Password},
+update_user(UserID, #{password := Password},
             #{user_group := UserGroup} = State) ->
             #{user_group := UserGroup} = State) ->
     trans(
     trans(
         fun() ->
         fun() ->

+ 3 - 3
apps/emqx_authn/src/simple_authn/emqx_authn_mnesia.erl

@@ -178,8 +178,8 @@ import_users(Filename0, State) ->
             {error, {unsupported_file_format, Extension}}
             {error, {unsupported_file_format, Extension}}
     end.
     end.
 
 
-add_user(#{<<"user_id">> := UserID,
-           <<"password">> := Password},
+add_user(#{user_id := UserID,
+           password := Password},
          #{user_group := UserGroup} = State) ->
          #{user_group := UserGroup} = State) ->
     trans(
     trans(
         fun() ->
         fun() ->
@@ -203,7 +203,7 @@ delete_user(UserID, #{user_group := UserGroup}) ->
             end
             end
         end).
         end).
 
 
-update_user(UserID, #{<<"password">> := Password},
+update_user(UserID, #{password := Password},
             #{user_group := UserGroup} = State) ->
             #{user_group := UserGroup} = State) ->
     trans(
     trans(
         fun() ->
         fun() ->

+ 58 - 0
apps/emqx_authn/src/simple_authn/emqx_authn_other_schema.erl

@@ -0,0 +1,58 @@
+%%--------------------------------------------------------------------
+%% Copyright (c) 2021 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_authn_other_schema).
+
+-include("emqx_authn.hrl").
+-include_lib("typerefl/include/types.hrl").
+
+-behaviour(hocon_schema).
+
+-export([ structs/0
+        , fields/1
+        ]).
+
+structs() -> [ "filename", "position", "user_info", "new_user_info"].
+
+fields("filename") ->
+    [ {filename, fun filename/1} ];
+fields("position") ->
+    [ {position, fun position/1} ];
+fields("user_info") ->
+    [ {user_id, fun user_id/1}
+    , {password, fun password/1}
+    ];
+fields("new_user_info") ->
+    [ {password, fun password/1}
+    ].
+
+filename(type) -> string();
+filename(nullable) -> false;
+filename(_) -> undefined.
+
+position(type) -> integer();
+position(validate) -> [fun (Position) -> Position > 0 end];
+position(nullable) -> false;
+position(_) -> undefined.
+
+user_id(type) -> binary();
+user_id(nullable) -> false;
+user_id(_) -> undefined.
+
+password(type) -> binary();
+password(nullable) -> false;
+password(_) -> undefined.
+

+ 36 - 26
apps/emqx_authn/test/emqx_authn_SUITE.erl

@@ -54,34 +54,44 @@ t_authenticator(_) ->
     AuthenticatorName1 = <<"myauthenticator1">>,
     AuthenticatorName1 = <<"myauthenticator1">>,
     AuthenticatorConfig1 = #{name => AuthenticatorName1,
     AuthenticatorConfig1 = #{name => AuthenticatorName1,
                              mechanism => 'password-based',
                              mechanism => 'password-based',
-                             config => #{
-                                 server_type => 'built-in-database',
-                                 user_id_type => username,
-                                 password_hash_algorithm => #{
-                                     name => sha256
-                                 }}},
-    ?assertEqual({ok, AuthenticatorConfig1}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig1)),
-    ?assertEqual({ok, AuthenticatorConfig1}, ?AUTH:lookup_authenticator(?CHAIN, AuthenticatorName1)),
-    ?assertEqual({ok, [AuthenticatorConfig1]}, ?AUTH:list_authenticators(?CHAIN)),
-    ?assertEqual({error, {already_exists, {authenticator, AuthenticatorName1}}}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig1)),
+                             server_type => 'built-in-database',
+                             user_id_type => username,
+                             password_hash_algorithm => #{
+                                 name => sha256
+                             }},
+    {ok, #{name := AuthenticatorName1, id := ID1}} = ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig1),
+    ?assertMatch({ok, #{name := AuthenticatorName1}}, ?AUTH:lookup_authenticator(?CHAIN, ID1)),
+    ?assertMatch({ok, [#{name := AuthenticatorName1}]}, ?AUTH:list_authenticators(?CHAIN)),
+    ?assertEqual({error, name_has_be_used}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig1)),
+
+    AuthenticatorConfig2 = #{name => AuthenticatorName1,
+                             mechanism => jwt,
+                             use_jwks => false,
+                             algorithm => 'hmac-based',
+                             secret => <<"abcdef">>,
+                             secret_base64_encoded => false,
+                             verify_claims => []},
+    {ok, #{name := AuthenticatorName1, id := ID1, mechanism := jwt}} = ?AUTH:update_authenticator(?CHAIN, ID1, AuthenticatorConfig2),
+
+    ID2 = <<"random">>,
+    ?assertEqual({error, {not_found, {authenticator, ID2}}}, ?AUTH:update_authenticator(?CHAIN, ID2, AuthenticatorConfig2)),
+    ?assertEqual({error, name_has_be_used}, ?AUTH:update_or_create_authenticator(?CHAIN, ID2, AuthenticatorConfig2)),
 
 
     AuthenticatorName2 = <<"myauthenticator2">>,
     AuthenticatorName2 = <<"myauthenticator2">>,
-    AuthenticatorConfig2 = AuthenticatorConfig1#{name => AuthenticatorName2},
-    ?assertEqual({ok, AuthenticatorConfig2}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig2)),
-    ?assertMatch({ok, #{id := ?CHAIN, authenticators := [AuthenticatorConfig1, AuthenticatorConfig2]}}, ?AUTH:lookup_chain(?CHAIN)),
-    ?assertEqual({ok, AuthenticatorConfig2}, ?AUTH:lookup_authenticator(?CHAIN, AuthenticatorName2)),
-    ?assertEqual({ok, [AuthenticatorConfig1, AuthenticatorConfig2]}, ?AUTH:list_authenticators(?CHAIN)),
-
-    ?assertEqual(ok, ?AUTH:move_authenticator_to_the_front(?CHAIN, AuthenticatorName2)),
-    ?assertEqual({ok, [AuthenticatorConfig2, AuthenticatorConfig1]}, ?AUTH:list_authenticators(?CHAIN)),
-    ?assertEqual(ok, ?AUTH:move_authenticator_to_the_end(?CHAIN, AuthenticatorName2)),
-    ?assertEqual({ok, [AuthenticatorConfig1, AuthenticatorConfig2]}, ?AUTH:list_authenticators(?CHAIN)),
-    ?assertEqual(ok, ?AUTH:move_authenticator_to_the_nth(?CHAIN, AuthenticatorName2, 1)),
-    ?assertEqual({ok, [AuthenticatorConfig2, AuthenticatorConfig1]}, ?AUTH:list_authenticators(?CHAIN)),
-    ?assertEqual({error, out_of_range}, ?AUTH:move_authenticator_to_the_nth(?CHAIN, AuthenticatorName2, 3)),
-    ?assertEqual({error, out_of_range}, ?AUTH:move_authenticator_to_the_nth(?CHAIN, AuthenticatorName2, 0)),
-    ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName1)),
-    ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName2)),
+    AuthenticatorConfig3 = AuthenticatorConfig2#{name => AuthenticatorName2},
+    {ok, #{name := AuthenticatorName2, id := ID2, secret := <<"abcdef">>}} = ?AUTH:update_or_create_authenticator(?CHAIN, ID2, AuthenticatorConfig3),
+    ?assertMatch({ok, #{name := AuthenticatorName2}}, ?AUTH:lookup_authenticator(?CHAIN, ID2)),
+    {ok, #{name := AuthenticatorName2, id := ID2, secret := <<"fedcba">>}} = ?AUTH:update_or_create_authenticator(?CHAIN, ID2, AuthenticatorConfig3#{secret := <<"fedcba">>}),
+
+    ?assertMatch({ok, #{id := ?CHAIN, authenticators := [#{name := AuthenticatorName1}, #{name := AuthenticatorName2}]}}, ?AUTH:lookup_chain(?CHAIN)),
+    ?assertMatch({ok, [#{name := AuthenticatorName1}, #{name := AuthenticatorName2}]}, ?AUTH:list_authenticators(?CHAIN)),
+
+    ?assertEqual(ok, ?AUTH:move_authenticator_to_the_nth(?CHAIN, ID2, 1)),
+    ?assertMatch({ok, [#{name := AuthenticatorName2}, #{name := AuthenticatorName1}]}, ?AUTH:list_authenticators(?CHAIN)),
+    ?assertEqual({error, out_of_range}, ?AUTH:move_authenticator_to_the_nth(?CHAIN, ID2, 3)),
+    ?assertEqual({error, out_of_range}, ?AUTH:move_authenticator_to_the_nth(?CHAIN, ID2, 0)),
+    ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID1)),
+    ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID2)),
     ?assertEqual({ok, []}, ?AUTH:list_authenticators(?CHAIN)),
     ?assertEqual({ok, []}, ?AUTH:list_authenticators(?CHAIN)),
     ok.
     ok.
 
 

+ 12 - 14
apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl

@@ -39,15 +39,14 @@ end_per_suite(_) ->
 
 
 t_jwt_authenticator(_) ->
 t_jwt_authenticator(_) ->
     AuthenticatorName = <<"myauthenticator">>,
     AuthenticatorName = <<"myauthenticator">>,
-    Config = #{use_jwks => false,
+    Config = #{name => AuthenticatorName,
+               mechanism => jwt,
+               use_jwks => false,
                algorithm => 'hmac-based',
                algorithm => 'hmac-based',
                secret => <<"abcdef">>,
                secret => <<"abcdef">>,
                secret_base64_encoded => false,
                secret_base64_encoded => false,
                verify_claims => []},
                verify_claims => []},
-    AuthenticatorConfig = #{name => AuthenticatorName,
-                            mechanism => jwt,
-                            config => Config},
-    ?assertEqual({ok, AuthenticatorConfig}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig)),
+    {ok, #{name := AuthenticatorName, id := ID}} = ?AUTH:create_authenticator(?CHAIN, Config),
 
 
     Payload = #{<<"username">> => <<"myuser">>},
     Payload = #{<<"username">> => <<"myuser">>},
     JWS = generate_jws('hmac-based', Payload, <<"abcdef">>),
     JWS = generate_jws('hmac-based', Payload, <<"abcdef">>),
@@ -62,11 +61,11 @@ t_jwt_authenticator(_) ->
     %% secret_base64_encoded
     %% secret_base64_encoded
     Config2 = Config#{secret => base64:encode(<<"abcdef">>),
     Config2 = Config#{secret => base64:encode(<<"abcdef">>),
                       secret_base64_encoded => true},
                       secret_base64_encoded => true},
-    ?assertMatch({ok, _}, ?AUTH:update_authenticator(?CHAIN, AuthenticatorName, Config2)),
+    ?assertMatch({ok, _}, ?AUTH:update_authenticator(?CHAIN, ID, Config2)),
     ?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo, ok)),
     ?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo, ok)),
 
 
     Config3 = Config#{verify_claims => [{<<"username">>, <<"${mqtt-username}">>}]},
     Config3 = Config#{verify_claims => [{<<"username">>, <<"${mqtt-username}">>}]},
-    ?assertMatch({ok, _}, ?AUTH:update_authenticator(?CHAIN, AuthenticatorName, Config3)),
+    ?assertMatch({ok, _}, ?AUTH:update_authenticator(?CHAIN, ID, Config3)),
     ?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo, ok)),
     ?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo, ok)),
     ?assertEqual({stop, {error, bad_username_or_password}}, ?AUTH:authenticate(ClientInfo#{username => <<"otheruser">>}, ok)),
     ?assertEqual({stop, {error, bad_username_or_password}}, ?AUTH:authenticate(ClientInfo#{username => <<"otheruser">>}, ok)),
 
 
@@ -109,7 +108,7 @@ t_jwt_authenticator(_) ->
     ClientInfo8 = ClientInfo#{password => JWS8},
     ClientInfo8 = ClientInfo#{password => JWS8},
     ?assertEqual({stop, {error, bad_username_or_password}}, ?AUTH:authenticate(ClientInfo8, ok)),
     ?assertEqual({stop, {error, bad_username_or_password}}, ?AUTH:authenticate(ClientInfo8, ok)),
 
 
-    ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName)),
+    ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID)),
     ok.
     ok.
 
 
 t_jwt_authenticator2(_) ->
 t_jwt_authenticator2(_) ->
@@ -117,14 +116,13 @@ t_jwt_authenticator2(_) ->
     PublicKey = list_to_binary(filename:join([Dir, "data/public_key.pem"])),
     PublicKey = list_to_binary(filename:join([Dir, "data/public_key.pem"])),
     PrivateKey = list_to_binary(filename:join([Dir, "data/private_key.pem"])),
     PrivateKey = list_to_binary(filename:join([Dir, "data/private_key.pem"])),
     AuthenticatorName = <<"myauthenticator">>,
     AuthenticatorName = <<"myauthenticator">>,
-    Config = #{use_jwks => false,
+    Config = #{name => AuthenticatorName,
+               mechanism => jwt,
+               use_jwks => false,
                algorithm => 'public-key',
                algorithm => 'public-key',
                certificate => PublicKey,
                certificate => PublicKey,
                verify_claims => []},
                verify_claims => []},
-    AuthenticatorConfig = #{name => AuthenticatorName,
-                            mechanism => jwt,
-                            config => Config},
-    ?assertEqual({ok, AuthenticatorConfig}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig)),
+    {ok, #{name := AuthenticatorName, id := ID}} = ?AUTH:create_authenticator(?CHAIN, Config),
 
 
     Payload = #{<<"username">> => <<"myuser">>},
     Payload = #{<<"username">> => <<"myuser">>},
     JWS = generate_jws('public-key', Payload, PrivateKey),
     JWS = generate_jws('public-key', Payload, PrivateKey),
@@ -133,7 +131,7 @@ t_jwt_authenticator2(_) ->
     ?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo, ok)),
     ?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo, ok)),
     ?assertEqual({stop, {error, not_authorized}}, ?AUTH:authenticate(ClientInfo#{password => <<"badpassword">>}, ok)),
     ?assertEqual({stop, {error, not_authorized}}, ?AUTH:authenticate(ClientInfo#{password => <<"badpassword">>}, ok)),
 
 
-    ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName)),
+    ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID)),
     ok.
     ok.
 
 
 generate_jws('hmac-based', Payload, Secret) ->
 generate_jws('hmac-based', Payload, Secret) ->

+ 45 - 49
apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl

@@ -41,18 +41,17 @@ t_mnesia_authenticator(_) ->
     AuthenticatorName = <<"myauthenticator">>,
     AuthenticatorName = <<"myauthenticator">>,
     AuthenticatorConfig = #{name => AuthenticatorName,
     AuthenticatorConfig = #{name => AuthenticatorName,
                             mechanism => 'password-based',
                             mechanism => 'password-based',
-                            config => #{
-                                server_type => 'built-in-database',
-                                user_id_type => username,
-                                password_hash_algorithm => #{
-                                    name => sha256
-                                }}},
-    ?assertEqual({ok, AuthenticatorConfig}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig)),
+                            server_type => 'built-in-database',
+                            user_id_type => username,
+                            password_hash_algorithm => #{
+                                name => sha256
+                            }},
+    {ok, #{name := AuthenticatorName, id := ID}} = ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig),
 
 
     UserInfo = #{<<"user_id">> => <<"myuser">>,
     UserInfo = #{<<"user_id">> => <<"myuser">>,
                  <<"password">> => <<"mypass">>},
                  <<"password">> => <<"mypass">>},
-    ?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:add_user(?CHAIN, AuthenticatorName, UserInfo)),
-    ?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:lookup_user(?CHAIN, AuthenticatorName, <<"myuser">>)),
+    ?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:add_user(?CHAIN, ID, UserInfo)),
+    ?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:lookup_user(?CHAIN, ID, <<"myuser">>)),
 
 
     ClientInfo = #{zone => external,
     ClientInfo = #{zone => external,
                    username => <<"myuser">>,
                    username => <<"myuser">>,
@@ -70,39 +69,38 @@ t_mnesia_authenticator(_) ->
     ?assertEqual({error, bad_username_or_password}, emqx_access_control:authenticate(ClientInfo3)),
     ?assertEqual({error, bad_username_or_password}, emqx_access_control:authenticate(ClientInfo3)),
 
 
     UserInfo2 = UserInfo#{<<"password">> => <<"mypass2">>},
     UserInfo2 = UserInfo#{<<"password">> => <<"mypass2">>},
-    ?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:update_user(?CHAIN, AuthenticatorName, <<"myuser">>, UserInfo2)),
+    ?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:update_user(?CHAIN, ID, <<"myuser">>, UserInfo2)),
     ClientInfo4 = ClientInfo#{password => <<"mypass2">>},
     ClientInfo4 = ClientInfo#{password => <<"mypass2">>},
     ?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo4, ok)),
     ?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo4, ok)),
 
 
-    ?assertEqual(ok, ?AUTH:delete_user(?CHAIN, AuthenticatorName, <<"myuser">>)),
-    ?assertEqual({error, not_found}, ?AUTH:lookup_user(?CHAIN, AuthenticatorName, <<"myuser">>)),
+    ?assertEqual(ok, ?AUTH:delete_user(?CHAIN, ID, <<"myuser">>)),
+    ?assertEqual({error, not_found}, ?AUTH:lookup_user(?CHAIN, ID, <<"myuser">>)),
 
 
-    ?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:add_user(?CHAIN, AuthenticatorName, UserInfo)),
-    ?assertMatch({ok, #{user_id := <<"myuser">>}}, ?AUTH:lookup_user(?CHAIN, AuthenticatorName, <<"myuser">>)),
-    ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName)),
+    ?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:add_user(?CHAIN, ID, UserInfo)),
+    ?assertMatch({ok, #{user_id := <<"myuser">>}}, ?AUTH:lookup_user(?CHAIN, ID, <<"myuser">>)),
+    ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID)),
 
 
-    ?assertEqual({ok, AuthenticatorConfig}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig)),
-    ?assertMatch({error, not_found}, ?AUTH:lookup_user(?CHAIN, AuthenticatorName, <<"myuser">>)),
-    ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName)),
+    {ok, #{name := AuthenticatorName, id := ID1}} = ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig),
+    ?assertMatch({error, not_found}, ?AUTH:lookup_user(?CHAIN, ID1, <<"myuser">>)),
+    ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID1)),
     ok.
     ok.
 
 
 t_import(_) ->
 t_import(_) ->
     AuthenticatorName = <<"myauthenticator">>,
     AuthenticatorName = <<"myauthenticator">>,
     AuthenticatorConfig = #{name => AuthenticatorName,
     AuthenticatorConfig = #{name => AuthenticatorName,
                             mechanism => 'password-based',
                             mechanism => 'password-based',
-                            config => #{
-                                server_type => 'built-in-database',
-                                user_id_type => username,
-                                password_hash_algorithm => #{
-                                    name => sha256
-                                }}},
-    ?assertEqual({ok, AuthenticatorConfig}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig)),
+                            server_type => 'built-in-database',
+                            user_id_type => username,
+                            password_hash_algorithm => #{
+                                name => sha256
+                            }},
+    {ok, #{name := AuthenticatorName, id := ID}} = ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig),
 
 
     Dir = code:lib_dir(emqx_authn, test),
     Dir = code:lib_dir(emqx_authn, test),
-    ?assertEqual(ok, ?AUTH:import_users(?CHAIN, AuthenticatorName, filename:join([Dir, "data/user-credentials.json"]))),
-    ?assertEqual(ok, ?AUTH:import_users(?CHAIN, AuthenticatorName, filename:join([Dir, "data/user-credentials.csv"]))),
-    ?assertMatch({ok, #{user_id := <<"myuser1">>}}, ?AUTH:lookup_user(?CHAIN, AuthenticatorName, <<"myuser1">>)),
-    ?assertMatch({ok, #{user_id := <<"myuser3">>}}, ?AUTH:lookup_user(?CHAIN, AuthenticatorName, <<"myuser3">>)),
+    ?assertEqual(ok, ?AUTH:import_users(?CHAIN, ID, filename:join([Dir, "data/user-credentials.json"]))),
+    ?assertEqual(ok, ?AUTH:import_users(?CHAIN, ID, filename:join([Dir, "data/user-credentials.csv"]))),
+    ?assertMatch({ok, #{user_id := <<"myuser1">>}}, ?AUTH:lookup_user(?CHAIN, ID, <<"myuser1">>)),
+    ?assertMatch({ok, #{user_id := <<"myuser3">>}}, ?AUTH:lookup_user(?CHAIN, ID, <<"myuser3">>)),
 
 
     ClientInfo1 = #{username => <<"myuser1">>,
     ClientInfo1 = #{username => <<"myuser1">>,
 			        password => <<"mypassword1">>},
 			        password => <<"mypassword1">>},
@@ -110,37 +108,35 @@ t_import(_) ->
     ClientInfo2 = ClientInfo1#{username => <<"myuser3">>,
     ClientInfo2 = ClientInfo1#{username => <<"myuser3">>,
                                password => <<"mypassword3">>},
                                password => <<"mypassword3">>},
     ?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo2, ok)),
     ?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo2, ok)),
-    ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName)),
+    ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID)),
     ok.
     ok.
 
 
 t_multi_mnesia_authenticator(_) ->
 t_multi_mnesia_authenticator(_) ->
     AuthenticatorName1 = <<"myauthenticator1">>,
     AuthenticatorName1 = <<"myauthenticator1">>,
     AuthenticatorConfig1 = #{name => AuthenticatorName1,
     AuthenticatorConfig1 = #{name => AuthenticatorName1,
                              mechanism => 'password-based',
                              mechanism => 'password-based',
-                             config => #{
-                                 server_type => 'built-in-database',
-                                 user_id_type => username,
-                                 password_hash_algorithm => #{
-                                     name => sha256
-                                 }}},
+                             server_type => 'built-in-database',
+                             user_id_type => username,
+                             password_hash_algorithm => #{
+                                 name => sha256
+                             }},
     AuthenticatorName2 = <<"myauthenticator2">>,
     AuthenticatorName2 = <<"myauthenticator2">>,
     AuthenticatorConfig2 = #{name => AuthenticatorName2,
     AuthenticatorConfig2 = #{name => AuthenticatorName2,
                              mechanism => 'password-based',
                              mechanism => 'password-based',
-                             config => #{
-                                 server_type => 'built-in-database',
-                                 user_id_type => clientid,
-                                 password_hash_algorithm => #{
-                                     name => sha256
-                                 }}},
-    ?assertEqual({ok, AuthenticatorConfig1}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig1)),
-    ?assertEqual({ok, AuthenticatorConfig2}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig2)),
+                             server_type => 'built-in-database',
+                             user_id_type => clientid,
+                             password_hash_algorithm => #{
+                                 name => sha256
+                             }},
+    {ok, #{name := AuthenticatorName1, id := ID1}} = ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig1),
+    {ok, #{name := AuthenticatorName2, id := ID2}} = ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig2),
 
 
     ?assertEqual({ok, #{user_id => <<"myuser">>}},
     ?assertEqual({ok, #{user_id => <<"myuser">>}},
-                 ?AUTH:add_user(?CHAIN, AuthenticatorName1,
+                 ?AUTH:add_user(?CHAIN, ID1,
                                 #{<<"user_id">> => <<"myuser">>,
                                 #{<<"user_id">> => <<"myuser">>,
                                   <<"password">> => <<"mypass1">>})),
                                   <<"password">> => <<"mypass1">>})),
     ?assertEqual({ok, #{user_id => <<"myclient">>}},
     ?assertEqual({ok, #{user_id => <<"myclient">>}},
-                 ?AUTH:add_user(?CHAIN, AuthenticatorName2,
+                 ?AUTH:add_user(?CHAIN, ID2,
                                 #{<<"user_id">> => <<"myclient">>,
                                 #{<<"user_id">> => <<"myclient">>,
                                   <<"password">> => <<"mypass2">>})),
                                   <<"password">> => <<"mypass2">>})),
 
 
@@ -148,12 +144,12 @@ t_multi_mnesia_authenticator(_) ->
                     clientid => <<"myclient">>,
                     clientid => <<"myclient">>,
 			        password => <<"mypass1">>},
 			        password => <<"mypass1">>},
     ?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo1, ok)),
     ?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo1, ok)),
-    ?assertEqual(ok, ?AUTH:move_authenticator_to_the_front(?CHAIN, AuthenticatorName2)),
+    ?assertEqual(ok, ?AUTH:move_authenticator_to_the_nth(?CHAIN, ID2, 1)),
 
 
     ?assertEqual({stop, {error, bad_username_or_password}}, ?AUTH:authenticate(ClientInfo1, ok)),
     ?assertEqual({stop, {error, bad_username_or_password}}, ?AUTH:authenticate(ClientInfo1, ok)),
     ClientInfo2 = ClientInfo1#{password => <<"mypass2">>},
     ClientInfo2 = ClientInfo1#{password => <<"mypass2">>},
     ?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo2, ok)),
     ?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo2, ok)),
 
 
-    ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName1)),
-    ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName2)),
+    ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID1)),
+    ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID2)),
     ok.
     ok.