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