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