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}.