getProject()['$id']; $apiKey = $this->getProject()['apiKey']; $serverHeaders = [ 'content-type' => 'application/json', 'x-appwrite-project' => $projectId, 'x-appwrite-key' => $apiKey, 'x-appwrite-response-format' => '1.9.4', ]; // Step 1: Enable password history policy with limit 3 $response = $this->client->call(Client::METHOD_PATCH, '/project/policies/password-history', $serverHeaders, [ 'total' => 3, ]); $this->assertSame(200, $response['headers']['status-code']); $this->assertSame(3, $response['body']['authPasswordHistory']); $firstPassword = 'firstpassword'; $secondPassword = 'secondpassword'; $thirdPassword = 'thirdpassword'; $fourthPassword = 'fourthpassword'; // Step 2: Sign up user with firstpassword (policy on, so signup populates history) $email = 'history_' . uniqid() . '@localhost.test'; $userId = ID::unique(); $account = $this->client->call(Client::METHOD_POST, '/account', [ 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $projectId, ], [ 'userId' => $userId, 'email' => $email, 'password' => $firstPassword, 'name' => 'History User', ]); $this->assertSame(201, $account['headers']['status-code']); $session = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $projectId, ], [ 'email' => $email, 'password' => $firstPassword, ]); $this->assertSame(201, $session['headers']['status-code']); $sessionCookie = $session['cookies']['a_session_' . $projectId]; $clientHeaders = [ 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $projectId, 'cookie' => 'a_session_' . $projectId . '=' . $sessionCookie, ]; // Change password: first -> second $response = $this->client->call(Client::METHOD_PATCH, '/account/password', $clientHeaders, [ 'password' => $secondPassword, 'oldPassword' => $firstPassword, ]); $this->assertSame(200, $response['headers']['status-code']); // Change password: second -> third $response = $this->client->call(Client::METHOD_PATCH, '/account/password', $clientHeaders, [ 'password' => $thirdPassword, 'oldPassword' => $secondPassword, ]); $this->assertSame(200, $response['headers']['status-code']); // Step 3: Attempt to reuse each of the 3 previous passwords - all should fail foreach ([$firstPassword, $secondPassword, $thirdPassword] as $reused) { $response = $this->client->call(Client::METHOD_PATCH, '/account/password', $clientHeaders, [ 'password' => $reused, 'oldPassword' => $thirdPassword, ]); $this->assertSame(400, $response['headers']['status-code'], 'Reusing password "' . $reused . '" should be blocked by history policy'); $this->assertSame('password_recently_used', $response['body']['type']); } // Step 4: Setting fourthpassword succeeds $response = $this->client->call(Client::METHOD_PATCH, '/account/password', $clientHeaders, [ 'password' => $fourthPassword, 'oldPassword' => $thirdPassword, ]); $this->assertSame(200, $response['headers']['status-code']); // Verify the new password works by signing in again $session = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $projectId, ], [ 'email' => $email, 'password' => $fourthPassword, ]); $this->assertSame(201, $session['headers']['status-code']); // Step 5: Disable password history policy $response = $this->client->call(Client::METHOD_PATCH, '/project/policies/password-history', $serverHeaders, [ 'total' => null, ]); $this->assertSame(200, $response['headers']['status-code']); $this->assertSame(0, $response['body']['authPasswordHistory']); // Step 6: With policy off, reusing any previous password should succeed, as should setting a brand new one. // oldPassword must match current password, so walk through each previous password sequentially. $fifthPassword = 'fifthpassword'; $chain = [ [$fourthPassword, $firstPassword], [$firstPassword, $secondPassword], [$secondPassword, $thirdPassword], [$thirdPassword, $fourthPassword], [$fourthPassword, $fifthPassword], ]; foreach ($chain as [$current, $next]) { $response = $this->client->call(Client::METHOD_PATCH, '/account/password', $clientHeaders, [ 'password' => $next, 'oldPassword' => $current, ]); $this->assertSame(200, $response['headers']['status-code'], 'Changing password from "' . $current . '" to "' . $next . '" should succeed with history policy disabled'); } // Verify the final password works by signing in $session = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $projectId, ], [ 'email' => $email, 'password' => $fifthPassword, ]); $this->assertSame(201, $session['headers']['status-code']); } }