1: -module(graphql_account_SUITE).
    2: 
    3: -include_lib("common_test/include/ct.hrl").
    4: -include_lib("eunit/include/eunit.hrl").
    5: 
    6: -compile([export_all, nowarn_export_all]).
    7: 
    8: -import(distributed_helper, [mim/0, require_rpc_nodes/1, rpc/4]).
    9: -import(graphql_helper, [execute/3, execute_auth/2, get_listener_port/1,
   10:                          get_listener_config/1, get_ok_value/2, get_err_msg/1]).
   11: 
   12: -define(NOT_EXISTING_JID, <<"unknown987@unknown">>).
   13: 
   14: suite() ->
   15:     require_rpc_nodes([mim]) ++ escalus:suite().
   16: 
   17: all() ->
   18:     [{group, user_account_handler},
   19:      {group, admin_account_handler}].
   20: 
   21: groups() ->
   22:     [{user_account_handler, [parallel], user_account_handler()},
   23:      {admin_account_handler, [], admin_account_handler()}].
   24: 
   25: user_account_handler() ->
   26:     [user_unregister,
   27:      user_change_password].
   28: 
   29: admin_account_handler() ->
   30:     [admin_list_users,
   31:      admin_count_users,
   32:      admin_get_active_users_number,
   33:      admin_check_password,
   34:      admin_check_password_hash,
   35:      admin_check_plain_password_hash,
   36:      admin_check_user,
   37:      admin_register_user,
   38:      admin_register_random_user,
   39:      admin_remove_non_existing_user,
   40:      admin_remove_existing_user,
   41:      admin_ban_user,
   42:      admin_change_user_password,
   43:      admin_list_old_users_domain,
   44:      admin_list_old_users_all,
   45:      admin_remove_old_users_domain,
   46:      admin_remove_old_users_all].
   47: 
   48: init_per_suite(Config) ->
   49:     Config1 = [{ctl_auth_mods, mongoose_helper:auth_modules()} | Config],
   50:     Config2 = escalus:init_per_suite(Config1),
   51:     dynamic_modules:save_modules(domain_helper:host_type(), Config2).
   52: 
   53: end_per_suite(Config) ->
   54:     dynamic_modules:restore_modules(Config),
   55:     escalus:end_per_suite(Config).
   56: 
   57: init_per_group(admin_account_handler, Config) ->
   58:     Mods = [{mod_last, config_parser_helper:default_mod_config(mod_last)}],
   59:     dynamic_modules:ensure_modules(domain_helper:host_type(), Mods),
   60:     dynamic_modules:ensure_modules(domain_helper:secondary_host_type(), Mods),
   61:     Config1 = escalus:create_users(Config, escalus:get_users([alice])),
   62:     graphql_helper:init_admin_handler(Config1);
   63: init_per_group(user_account_handler, Config) ->
   64:     [{schema_endpoint, user} | Config];
   65: init_per_group(_, Config) ->
   66:     Config.
   67: 
   68: end_per_group(admin_account_handler, Config) ->
   69:     escalus_fresh:clean(),
   70:     escalus:delete_users(Config, escalus:get_users([alice])),
   71:     dynamic_modules:restore_modules(Config);
   72: end_per_group(user_account_handler, _Config) ->
   73:     escalus_fresh:clean();
   74: end_per_group(_, _Config) ->
   75:     ok.
   76: 
   77: init_per_testcase(C, Config) when C =:= admin_list_old_users_all;
   78:                                   C =:= admin_list_old_users_domain;
   79:                                   C =:= admin_remove_old_users_all;
   80:                                   C =:= admin_remove_old_users_domain ->
   81:     {_, AuthMods} = lists:keyfind(ctl_auth_mods, 1, Config),
   82:     case lists:member(ejabberd_auth_ldap, AuthMods) of
   83:         true -> {skip, not_fully_supported_with_ldap};
   84:         false ->
   85:             Config1 = escalus:create_users(Config, escalus:get_users([alice, bob, alice_bis])),
   86:             escalus:init_per_testcase(C, Config1)
   87:     end;
   88: init_per_testcase(admin_register_user = C, Config) ->
   89:     Config1 = [{user, {<<"gql_admin_registration_test">>, domain_helper:domain()}} | Config],
   90:     escalus:init_per_testcase(C, Config1);
   91: init_per_testcase(admin_check_plain_password_hash = C, Config) ->
   92:     {_, AuthMods} = lists:keyfind(ctl_auth_mods, 1, Config),
   93:     case lists:member(ejabberd_auth_ldap, AuthMods) of
   94:         true ->
   95:             {skip, not_fully_supported_with_ldap};
   96:         false ->
   97:             AuthOpts = mongoose_helper:auth_opts_with_password_format(plain),
   98:             Config1 = mongoose_helper:backup_and_set_config_option(
   99:                         Config, {auth, domain_helper:host_type()}, AuthOpts),
  100:             Config2 = escalus:create_users(Config1, escalus:get_users([carol])),
  101:             escalus:init_per_testcase(C, Config2)
  102:     end;
  103: init_per_testcase(CaseName, Config) ->
  104:     escalus:init_per_testcase(CaseName, Config).
  105: 
  106: end_per_testcase(C, Config) when C =:= admin_list_old_users_all;
  107:                                  C =:= admin_list_old_users_domain;
  108:                                  C =:= admin_remove_old_users_all;
  109:                                  C =:= admin_remove_old_users_domain ->
  110:     escalus:delete_users(Config, escalus:get_users([alice, bob, alice_bis]));
  111: end_per_testcase(admin_register_user = C, Config) ->
  112:     {Username, Domain} = proplists:get_value(user, Config),
  113:     rpc(mim(), mongoose_account_api, unregister_user, [Username, Domain]),
  114:     escalus:end_per_testcase(C, Config);
  115: end_per_testcase(admin_check_plain_password_hash, Config) ->
  116:     mongoose_helper:restore_config(Config),
  117:     escalus:delete_users(Config, escalus:get_users([carol]));
  118: end_per_testcase(CaseName, Config) ->
  119:     escalus:end_per_testcase(CaseName, Config).
  120: 
  121: user_unregister(Config) ->
  122:     escalus:fresh_story_with_config(Config, [{alice, 1}], fun user_unregister_story/2).
  123: 
  124: user_unregister_story(Config, Alice) ->
  125:     Ep = ?config(schema_endpoint, Config),
  126:     Password = lists:last(escalus_users:get_usp(Config, alice)),
  127:     BinJID = escalus_client:full_jid(Alice),
  128:     Creds = {BinJID, Password},
  129:     Query = <<"mutation { account { unregister } }">>,
  130: 
  131:     Path = [data, account, unregister],
  132:     Resp = execute(Ep, #{query => Query}, Creds),
  133:     ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Resp), <<"successfully unregistered">>)),
  134:     % Ensure the user is removed
  135:     AllUsers = rpc(mim(), mongoose_account_api, list_users, [domain_helper:domain()]),
  136:     LAliceJID = jid:to_binary(jid:to_lower((jid:binary_to_bare(BinJID)))),
  137:     ?assertNot(lists:member(LAliceJID, AllUsers)).
  138:     
  139: user_change_password(Config) ->
  140:     escalus:fresh_story_with_config(Config, [{alice, 1}], fun user_change_password_story/2).
  141: 
  142: user_change_password_story(Config, Alice) ->
  143:     Ep = ?config(schema_endpoint, Config),
  144:     Password = lists:last(escalus_users:get_usp(Config, alice)),
  145:     Creds = {escalus_client:full_jid(Alice), Password},
  146:     % Set an empty password
  147:     Resp1 = execute(Ep, user_change_password_body(<<>>), Creds),
  148:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp1), <<"Empty password">>)),
  149:     % Set a correct password
  150:     Path = [data, account, changePassword],
  151:     Resp2 = execute(Ep, user_change_password_body(<<"kaczka">>), Creds),
  152:     ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Resp2), <<"Password changed">>)).
  153: 
  154: admin_list_users(Config) ->
  155:     % An unknown domain
  156:     Resp = execute_auth(list_users_body(<<"unknown-domain">>), Config),
  157:     ?assertEqual([], get_ok_value([data, account, listUsers], Resp)),
  158:     % A domain with users
  159:     Domain = domain_helper:domain(),
  160:     Username = jid:nameprep(escalus_users:get_username(Config, alice)),
  161:     JID = <<Username/binary, "@", Domain/binary>>,
  162:     Resp2 = execute_auth(list_users_body(Domain), Config),
  163:     Users = get_ok_value([data, account, listUsers], Resp2),
  164:     ?assert(lists:member(JID, Users)).
  165:     
  166: admin_count_users(Config) ->
  167:     % An unknown domain
  168:     Resp = execute_auth(count_users_body(<<"unknown-domain">>), Config),
  169:     ?assertEqual(0, get_ok_value([data, account, countUsers], Resp)),
  170:     % A domain with at least one user
  171:     Domain = domain_helper:domain(),
  172:     Resp2 = execute_auth(count_users_body(Domain), Config),
  173:     ?assert(0 < get_ok_value([data, account, countUsers], Resp2)).
  174: 
  175: admin_get_active_users_number(Config) ->
  176:     % Check non-existing domain
  177:     Resp = execute_auth(get_active_users_number_body(<<"unknown-domain">>, 5), Config),
  178:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp), <<"Cannot count">>)),
  179:     % Check an existing domain without active users
  180:     Resp2 = execute_auth(get_active_users_number_body(domain_helper:domain(), 5), Config),
  181:     ?assertEqual(0, get_ok_value([data, account, countActiveUsers], Resp2)).
  182: 
  183: admin_check_password(Config) ->
  184:     Password = lists:last(escalus_users:get_usp(Config, alice)),
  185:     BinJID = escalus_users:get_jid(Config, alice),
  186:     Path = [data, account, checkPassword],
  187:     % A correct password
  188:     Resp1 = execute_auth(check_password_body(BinJID, Password), Config),
  189:     ?assertMatch(#{<<"correct">> := true, <<"message">> := _}, get_ok_value(Path, Resp1)),
  190:     % An incorrect password
  191:     Resp2 = execute_auth(check_password_body(BinJID, <<"incorrect_pw">>), Config),
  192:     ?assertMatch(#{<<"correct">> := false, <<"message">> := _}, get_ok_value(Path, Resp2)),
  193:     % A non-existing user
  194:     Resp3 = execute_auth(check_password_body(?NOT_EXISTING_JID, Password), Config),
  195:     ?assertEqual(null, get_ok_value(Path, Resp3)).
  196: 
  197: admin_check_password_hash(Config) ->
  198:     UserSCRAM = escalus_users:get_jid(Config, alice),
  199:     EmptyHash = list_to_binary(get_md5(<<>>)),
  200:     Method = <<"md5">>,
  201:     % SCRAM password user
  202:     Resp1 = execute_auth(check_password_hash_body(UserSCRAM, EmptyHash, Method), Config),
  203:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp1), <<"SCRAM password">>)),
  204:     % A non-existing user
  205:     Resp2 = execute_auth(check_password_hash_body(?NOT_EXISTING_JID, EmptyHash, Method), Config),
  206:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp2), <<"not exist">>)).
  207: 
  208: admin_check_plain_password_hash(Config) ->
  209:     UserJID = escalus_users:get_jid(Config, carol),
  210:     Password = lists:last(escalus_users:get_usp(Config, carol)),
  211:     Method = <<"md5">>,
  212:     Hash = list_to_binary(get_md5(Password)),
  213:     WrongHash = list_to_binary(get_md5(<<"wrong password">>)),
  214:     Path = [data, account, checkPasswordHash],
  215:     % A correct hash
  216:     Resp = execute_auth(check_password_hash_body(UserJID, Hash, Method), Config),
  217:     ?assertMatch(#{<<"correct">> := true, <<"message">> := _}, get_ok_value(Path, Resp)),
  218:     % An incorrect hash
  219:     Resp2 = execute_auth(check_password_hash_body(UserJID, WrongHash, Method), Config),
  220:     ?assertMatch(#{<<"correct">> := false, <<"message">> := _}, get_ok_value(Path, Resp2)),
  221:     % A not-supported hash method
  222:     Resp3 = execute_auth(check_password_hash_body(UserJID, Hash, <<"a">>), Config),
  223:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp3), <<"not supported">>)).
  224: 
  225: admin_check_user(Config) ->
  226:     BinJID = escalus_users:get_jid(Config, alice),
  227:     Path = [data, account, checkUser],
  228:     % An existing user
  229:     Resp1 = execute_auth(check_user_body(BinJID), Config),
  230:     ?assertMatch(#{<<"exist">> := true, <<"message">> := _}, get_ok_value(Path, Resp1)),
  231:     % A non-existing user
  232:     Resp2 = execute_auth(check_user_body(?NOT_EXISTING_JID), Config),
  233:     ?assertMatch(#{<<"exist">> := false, <<"message">> := _}, get_ok_value(Path, Resp2)).
  234: 
  235: admin_register_user(Config) ->
  236:     Password = <<"my_password">>,
  237:     {Username, Domain} = proplists:get_value(user, Config),
  238:     Path = [data, account, registerUser, message],
  239:     % Register a new user
  240:     Resp1 = execute_auth(register_user_body(Domain, Username, Password), Config),
  241:     ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Resp1), <<"successfully registered">>)),
  242:     % Try to register a user with existing name
  243:     Resp2 = execute_auth(register_user_body(Domain, Username, Password), Config),
  244:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp2), <<"already registered">>)).
  245: 
  246: admin_register_random_user(Config) ->
  247:     Password = <<"my_password">>,
  248:     Domain = domain_helper:domain(),
  249:     Path = [data, account, registerUser],
  250:     % Register a new user
  251:     Resp1 = execute_auth(register_user_body(Domain, null, Password), Config),
  252:     #{<<"message">> := Msg, <<"jid">> := JID} = get_ok_value(Path, Resp1),
  253:     {Username, Server} = jid:to_lus(jid:from_binary(JID)),
  254: 
  255:     ?assertNotEqual(nomatch, binary:match(Msg, <<"successfully registered">>)),
  256:     {ok, _} = rpc(mim(), mongoose_account_api, unregister_user, [Username, Server]).
  257: 
  258: admin_remove_non_existing_user(Config) ->
  259:     Resp = execute_auth(remove_user_body(?NOT_EXISTING_JID), Config),
  260:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp), <<"not exist">>)).
  261: 
  262: admin_remove_existing_user(Config) ->
  263:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  264:         Path = [data, account, removeUser, message],
  265:         BinJID = escalus_client:full_jid(Alice),
  266:         Resp4 = execute_auth(remove_user_body(BinJID), Config),
  267:         ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Resp4),
  268:                                               <<"successfully unregister">>))
  269:     end).
  270: 
  271: admin_ban_user(Config) ->
  272:     Path = [data, account, banUser, message],
  273:     Reason = <<"annoying">>,
  274:     % Ban not existing user
  275:     Resp1 = execute_auth(ban_user_body(?NOT_EXISTING_JID, Reason), Config),
  276:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp1), <<"not allowed">>)),
  277:     % Ban an existing user
  278:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  279:         BinJID = escalus_client:full_jid(Alice),
  280:         Resp2 = execute_auth(ban_user_body(BinJID, Reason), Config),
  281:         ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Resp2), <<"successfully banned">>))
  282:     end).
  283: 
  284: admin_change_user_password(Config) ->
  285:     Path = [data, account, changeUserPassword, message],
  286:     NewPassword = <<"new password">>,
  287:     % Change password of not existing user
  288:     Resp1 = execute_auth(change_user_password_body(?NOT_EXISTING_JID, NewPassword), Config),
  289:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp1), <<"not allowed">>)),
  290:     % Set an empty password
  291:     Resp2 = execute_auth(change_user_password_body(?NOT_EXISTING_JID, <<>>), Config),
  292:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp2), <<"Empty password">>)),
  293:     % Change password of an existing user
  294:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  295:         BinJID = escalus_client:full_jid(Alice),
  296:         Resp3 = execute_auth(change_user_password_body(BinJID, NewPassword), Config),
  297:         ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Resp3), <<"Password changed">>))
  298:     end).
  299: 
  300: admin_remove_old_users_domain(Config) ->
  301:     [AliceName, Domain, _] = escalus_users:get_usp(Config, alice),
  302:     [BobName, Domain, _] = escalus_users:get_usp(Config, bob),
  303:     [AliceBisName, BisDomain, _] = escalus_users:get_usp(Config, alice_bis),
  304: 
  305:     Now = erlang:system_time(seconds),
  306:     set_last(AliceName, Domain, Now),
  307:     set_last(BobName, Domain, Now - 86400 * 30),
  308: 
  309:     Path = [data, account, removeOldUsers, users],
  310:     Resp = execute_auth(remove_old_users_body(Domain, 10), Config),
  311:     ?assertEqual(1, length(get_ok_value(Path, Resp))),
  312:     ?assertMatch({user_does_not_exist, _}, check_account(BobName, Domain)),
  313:     ?assertMatch({ok, _}, check_account(AliceName, Domain)),
  314:     ?assertMatch({ok, _}, check_account(AliceBisName, BisDomain)).
  315: 
  316: admin_remove_old_users_all(Config) ->
  317:     [AliceName, Domain, _] = escalus_users:get_usp(Config, alice),
  318:     [BobName, Domain, _] = escalus_users:get_usp(Config, bob),
  319:     [AliceBisName, BisDomain, _] = escalus_users:get_usp(Config, alice_bis),
  320: 
  321:     Now = erlang:system_time(seconds),
  322:     OldTime = Now - 86400 * 30,
  323:     set_last(AliceName, Domain, Now),
  324:     set_last(BobName, Domain, OldTime),
  325:     set_last(AliceBisName, BisDomain, OldTime),
  326: 
  327:     Path = [data, account, removeOldUsers, users],
  328:     Resp = execute_auth(remove_old_users_body(null, 10), Config),
  329:     ?assertEqual(2, length(get_ok_value(Path, Resp))),
  330:     ?assertMatch({user_does_not_exist, _}, check_account(BobName, Domain)),
  331:     ?assertMatch({ok, _}, check_account(AliceName, Domain)),
  332:     ?assertMatch({user_does_not_exist, _}, check_account(AliceBisName, BisDomain)).
  333: 
  334: admin_list_old_users_domain(Config) ->
  335:     [AliceName, Domain, _] = escalus_users:get_usp(Config, alice),
  336:     [BobName, Domain, _] = escalus_users:get_usp(Config, bob),
  337: 
  338:     Now = erlang:system_time(seconds),
  339:     set_last(AliceName, Domain, Now),
  340:     set_last(BobName, Domain, Now - 86400 * 30),
  341: 
  342:     LBob = escalus_utils:jid_to_lower(escalus_users:get_jid(Config, bob)),
  343: 
  344:     Path = [data, account, listOldUsers],
  345:     Resp = execute_auth(list_old_users_body(Domain, 10), Config),
  346:     Users = get_ok_value(Path, Resp),
  347:     ?assertEqual(1, length(Users)),
  348:     ?assert(lists:member(LBob, Users)).
  349: 
  350: admin_list_old_users_all(Config) ->
  351:     [AliceName, Domain, _] = escalus_users:get_usp(Config, alice),
  352:     [BobName, Domain, _] = escalus_users:get_usp(Config, bob),
  353:     [AliceBisName, BisDomain, _] = escalus_users:get_usp(Config, alice_bis),
  354: 
  355:     Now = erlang:system_time(seconds),
  356:     OldTime = Now - 86400 * 30,
  357:     set_last(AliceName, Domain, Now),
  358:     set_last(BobName, Domain, OldTime),
  359:     set_last(AliceBisName, BisDomain, OldTime),
  360: 
  361:     LBob = escalus_utils:jid_to_lower(escalus_users:get_jid(Config, bob)),
  362:     LAliceBis = escalus_utils:jid_to_lower(escalus_users:get_jid(Config, alice_bis)),
  363: 
  364:     Path = [data, account, listOldUsers],
  365:     Resp = execute_auth(list_old_users_body(null, 10), Config),
  366:     Users = get_ok_value(Path, Resp),
  367:     ?assertEqual(2, length(Users)),
  368:     ?assert(lists:member(LBob, Users)),
  369:     ?assert(lists:member(LAliceBis, Users)).
  370: 
  371: %% Helpers
  372: 
  373: get_md5(AccountPass) ->
  374:     lists:flatten([io_lib:format("~.16B", [X])
  375:                    || X <- binary_to_list(crypto:hash(md5, AccountPass))]).
  376: 
  377: set_last(User, Domain, TStamp) ->
  378:     rpc(mim(), mod_last, store_last_info,
  379:         [domain_helper:host_type(), escalus_utils:jid_to_lower(User), Domain, TStamp, <<>>]).
  380: 
  381: check_account(Username, Domain) ->
  382:     rpc(mim(), mongoose_account_api, check_account, [Username, Domain]).
  383: 
  384: %% Request bodies
  385: 
  386: list_users_body(Domain) ->
  387:     Query = <<"query Q1($domain: String!) { account { listUsers(domain: $domain) } }">>,
  388:     OpName = <<"Q1">>,
  389:     Vars = #{<<"domain">> => Domain},
  390:     #{query => Query, operationName => OpName, variables => Vars}.
  391: 
  392: count_users_body(Domain) ->
  393:     Query = <<"query Q1($domain: String!) { account { countUsers(domain: $domain) } }">>,
  394:     OpName = <<"Q1">>,
  395:     Vars = #{<<"domain">> => Domain},
  396:     #{query => Query, operationName => OpName, variables => Vars}.
  397: 
  398: get_active_users_number_body(Domain, Days) ->
  399:     Query = <<"query Q1($domain: String!, $days: Int!)
  400:               { account { countActiveUsers(domain: $domain, days: $days) } }">>,
  401:     OpName = <<"Q1">>,
  402:     Vars = #{<<"domain">> => Domain, <<"days">> => Days},
  403:     #{query => Query, operationName => OpName, variables => Vars}.
  404: 
  405: check_password_body(User, Password) ->
  406:     Query = <<"query Q1($user: JID!, $password: String!) 
  407:               { account { checkPassword(user: $user, password: $password) {correct message} } }">>,
  408:     OpName = <<"Q1">>,
  409:     Vars = #{<<"user">> => User, <<"password">> => Password},
  410:     #{query => Query, operationName => OpName, variables => Vars}.
  411: 
  412: check_password_hash_body(User, PasswordHash, HashMethod) ->
  413:     Query = <<"query Q1($user: JID!, $hash: String!, $method: String!)
  414:               { account { checkPasswordHash(user: $user, passwordHash: $hash, hashMethod: $method)
  415:               {correct message} } }">>,
  416:     OpName = <<"Q1">>,
  417:     Vars = #{<<"user">> => User, <<"hash">> => PasswordHash, <<"method">> => HashMethod},
  418:     #{query => Query, operationName => OpName, variables => Vars}.
  419: 
  420: check_user_body(User) ->
  421:     Query = <<"query Q1($user: JID!) 
  422:               { account { checkUser(user: $user) {exist message} } }">>,
  423:     OpName = <<"Q1">>,
  424:     Vars = #{<<"user">> => User},
  425:     #{query => Query, operationName => OpName, variables => Vars}.
  426: 
  427: register_user_body(Domain, Username, Password) ->
  428:     Query = <<"mutation M1($domain: String!, $username: String, $password: String!)
  429:               { account { registerUser(domain: $domain, username: $username, password: $password) 
  430:               { jid message } } }">>,
  431:     OpName = <<"M1">>,
  432:     Vars = #{<<"domain">> => Domain, <<"username">> => Username, <<"password">> => Password},
  433:     #{query => Query, operationName => OpName, variables => Vars}.
  434: 
  435: remove_user_body(User) ->
  436:     Query = <<"mutation M1($user: JID!) 
  437:               { account { removeUser(user: $user) { jid message } } }">>,
  438:     OpName = <<"M1">>,
  439:     Vars = #{<<"user">> => User},
  440:     #{query => Query, operationName => OpName, variables => Vars}.
  441: 
  442: remove_old_users_body(Domain, Days) ->
  443:     Query = <<"mutation M1($domain: String, $days: Int!) 
  444:               { account { removeOldUsers(domain: $domain, days: $days) { message users } } }">>,
  445:     OpName = <<"M1">>,
  446:     Vars = #{<<"domain">> => Domain, <<"days">> => Days},
  447:     #{query => Query, operationName => OpName, variables => Vars}.
  448: 
  449: list_old_users_body(Domain, Days) ->
  450:     Query = <<"query Q1($domain: String, $days: Int!)
  451:               { account { listOldUsers(domain: $domain, days: $days) } }">>,
  452:     OpName = <<"Q1">>,
  453:     Vars = #{<<"domain">> => Domain, <<"days">> => Days},
  454:     #{query => Query, operationName => OpName, variables => Vars}.
  455: 
  456: ban_user_body(JID, Reason) ->
  457:     Query = <<"mutation M1($user: JID!, $reason: String!) 
  458:               { account { banUser(user: $user, reason: $reason) { jid message } } }">>,
  459:     OpName = <<"M1">>,
  460:     Vars = #{<<"user">> => JID, <<"reason">> => Reason},
  461:     #{query => Query, operationName => OpName, variables => Vars}.
  462: 
  463: change_user_password_body(JID, NewPassword) ->
  464:     Query = <<"mutation M1($user: JID!, $newPassword: String!) 
  465:               { account { changeUserPassword(user: $user, newPassword: $newPassword) { jid message } } }">>,
  466:     OpName = <<"M1">>,
  467:     Vars = #{<<"user">> => JID, <<"newPassword">> => NewPassword},
  468:     #{query => Query, operationName => OpName, variables => Vars}.
  469: 
  470: user_change_password_body(NewPassword) ->
  471:     Query = <<"mutation M1($newPassword: String!)
  472:               { account { changePassword(newPassword: $newPassword) } }">>,
  473:     OpName = <<"M1">>,
  474:     Vars = #{<<"newPassword">> => NewPassword},
  475:     #{query => Query, operationName => OpName, variables => Vars}.