7, 'realm' => 0, 'enabled' => 'on', 'password_change' => ''], ]; $user = reset_consume_fetch_user($accounts, 7); expect($user)->not->toBeEmpty() ->and($user['id'])->toBe(7); }); test('reset link consumes for a self-service account with password_change on', function () { $accounts = [ ['id' => 8, 'realm' => 0, 'enabled' => 'on', 'password_change' => 'on'], ]; expect(reset_consume_fetch_user($accounts, 8))->not->toBeEmpty(); }); test('reset link is rejected for a disabled account', function () { $accounts = [ ['id' => 9, 'realm' => 0, 'enabled' => '', 'password_change' => 'on'], ]; expect(reset_consume_fetch_user($accounts, 9))->toBeEmpty(); }); test('reset link is rejected for a non-local realm account', function () { $accounts = [ ['id' => 10, 'realm' => 2, 'enabled' => 'on', 'password_change' => 'on'], ]; expect(reset_consume_fetch_user($accounts, 10))->toBeEmpty(); }); test('the consumption query in source does not gate on password_change', function () use ($root) { $reset = file_get_contents($root . '/auth_resetpassword.php'); $consume = substr($reset, strpos($reset, "case 'resetpassword'")); $consume = substr($consume, 0, strpos($consume, '// Get passwords entered for change')); expect($consume)->toContain('FROM user_auth') ->and($consume)->toContain('AND realm = 0') ->and($consume)->toContain('AND enabled = "on"') ->and($consume)->not->toContain('AND password_change = "on"'); }); test('issuing a reset link replaces all outstanding links under a user-row lock', function () use ($root) { $auth = file_get_contents($root . '/lib/auth.php'); expect($auth)->toContain('function auth_reset_token_replace(') ->and($auth)->toContain('FOR UPDATE') ->and($auth)->toContain('DELETE FROM user_auth_reset_hashes WHERE user_id = ?'); }); test('successful reset revalidates the token under lock and revokes sibling credentials', function () use ($root) { $reset = file_get_contents($root . '/auth_resetpassword.php'); $body = substr($reset, strpos($reset, "case 'resetpassword'")); expect($body)->toContain('AND hash = ?') ->and($body)->toContain('FOR UPDATE') ->and($body)->toContain("DELETE FROM user_auth_reset_hashes\n\t\t\t\tWHERE user_id = ?") ->and($body)->toContain('DELETE FROM user_auth_cache WHERE user_id = ?') ->and($body)->toContain('DELETE FROM sessions WHERE user_id = ?') ->and($body)->not->toContain("DELETE FROM user_auth_reset_hashes\n\t\t\t\tWHERE hash = ?"); });