2020-01-11 13:58:02 +00:00
< ? php
2020-01-12 06:35:37 +00:00
namespace Tests\E2E\Services\Account ;
2020-01-11 13:58:02 +00:00
use Tests\E2E\Client ;
2023-02-05 20:07:46 +00:00
use Utopia\Database\Helpers\ID ;
2023-03-01 12:00:36 +00:00
use Utopia\Database\Validator\Datetime as DatetimeValidator ;
2020-01-11 13:58:02 +00:00
trait AccountBase
{
2022-06-02 12:47:07 +00:00
public function testCreateAccount () : array
2020-01-11 13:58:02 +00:00
{
2022-06-02 12:47:07 +00:00
$email = uniqid () . 'user@localhost.test' ;
2020-01-11 21:53:57 +00:00
$password = 'password' ;
2020-01-11 13:58:02 +00:00
$name = 'User Name' ;
/**
* Test for SUCCESS
*/
2020-01-12 21:28:26 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account' , array_merge ([
2020-01-11 13:58:02 +00:00
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
2020-02-17 07:16:11 +00:00
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2020-01-12 21:28:26 +00:00
]), [
2022-08-14 10:33:36 +00:00
'userId' => ID :: unique (),
2020-01-11 13:58:02 +00:00
'email' => $email ,
'password' => $password ,
'name' => $name ,
]);
2020-02-17 07:16:11 +00:00
$id = $response [ 'body' ][ '$id' ];
2025-11-10 13:00:18 +00:00
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2020-01-11 13:58:02 +00:00
$this -> assertNotEmpty ( $response [ 'body' ]);
2020-02-17 07:16:11 +00:00
$this -> assertNotEmpty ( $response [ 'body' ][ '$id' ]);
2023-02-05 20:39:41 +00:00
$this -> assertEquals ( true , ( new DatetimeValidator ()) -> isValid ( $response [ 'body' ][ 'registration' ]));
2020-01-11 13:58:02 +00:00
$this -> assertEquals ( $response [ 'body' ][ 'email' ], $email );
$this -> assertEquals ( $response [ 'body' ][ 'name' ], $name );
2023-05-27 00:15:38 +00:00
$this -> assertEquals ( $response [ 'body' ][ 'labels' ], []);
2023-07-07 00:12:39 +00:00
$this -> assertArrayHasKey ( 'accessedAt' , $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ 'accessedAt' ]);
2025-07-29 06:27:57 +00:00
$this -> assertArrayHasKey ( 'targets' , $response [ 'body' ]);
$this -> assertEquals ( $email , $response [ 'body' ][ 'targets' ][ 0 ][ 'identifier' ]);
2025-11-10 13:00:18 +00:00
$this -> assertArrayNotHasKey ( 'emailCanonical' , $response [ 'body' ]);
$this -> assertArrayNotHasKey ( 'emailIsFree' , $response [ 'body' ]);
$this -> assertArrayNotHasKey ( 'emailIsDisposable' , $response [ 'body' ]);
$this -> assertArrayNotHasKey ( 'emailIsCorporate' , $response [ 'body' ]);
$this -> assertArrayNotHasKey ( 'emailIsCanonical' , $response [ 'body' ]);
2020-01-11 13:58:02 +00:00
/**
* Test for FAILURE
*/
2024-01-24 12:54:48 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
2020-02-17 07:16:11 +00:00
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2020-01-12 21:28:26 +00:00
]), [
2022-08-14 10:33:36 +00:00
'userId' => ID :: unique (),
2020-01-11 13:58:02 +00:00
'email' => $email ,
'password' => $password ,
'name' => $name ,
]);
$this -> assertEquals ( $response [ 'headers' ][ 'status-code' ], 409 );
2021-02-16 13:49:21 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
2022-08-14 10:33:36 +00:00
'userId' => ID :: unique (),
2021-02-16 13:49:21 +00:00
'email' => '' ,
'password' => '' ,
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2021-02-16 13:49:21 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
2022-08-14 10:33:36 +00:00
'userId' => ID :: unique (),
2021-02-16 13:49:21 +00:00
'email' => $email ,
'password' => '' ,
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2021-02-16 13:49:21 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-01-29 04:25:52 +00:00
'x-appwrite-dev-key' => $this -> getProject ()[ 'devKey' ] ? ? '' ,
2021-02-16 13:49:21 +00:00
]), [
2022-08-14 10:33:36 +00:00
'userId' => ID :: unique (),
2021-02-16 13:49:21 +00:00
'email' => '' ,
'password' => $password ,
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2021-02-16 13:49:21 +00:00
2024-01-02 10:59:35 +00:00
$shortPassword = 'short' ;
$response = $this -> client -> call ( Client :: METHOD_POST , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2024-12-15 10:49:35 +00:00
'x-appwrite-dev-key' => $this -> getProject ()[ 'devKey' ] ? ? ''
2024-01-02 10:59:35 +00:00
]), [
'userId' => ID :: unique (),
'email' => 'shortpass@appwrite.io' ,
'password' => $shortPassword
]);
2024-12-15 10:41:01 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2024-01-02 10:59:35 +00:00
$longPassword = '' ;
for ( $i = 0 ; $i < 257 ; $i ++ ) { // 256 is the limit
$longPassword .= 'p' ;
}
$response = $this -> client -> call ( Client :: METHOD_POST , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2024-12-15 10:49:35 +00:00
'x-appwrite-dev-key' => $this -> getProject ()[ 'devKey' ] ? ? ''
2024-01-02 10:59:35 +00:00
]), [
'userId' => ID :: unique (),
'email' => 'longpass@appwrite.io' ,
'password' => $longPassword ,
]);
2024-12-15 10:41:01 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2024-01-02 10:59:35 +00:00
2020-01-11 13:58:02 +00:00
return [
2020-04-22 07:03:34 +00:00
'id' => $id ,
2020-01-11 13:58:02 +00:00
'email' => $email ,
'password' => $password ,
'name' => $name ,
];
}
2024-01-19 13:42:26 +00:00
public function testEmailOTPSession () : void
{
2025-09-18 08:39:42 +00:00
$isConsoleProject = $this -> getProject ()[ '$id' ] === 'console' ;
2024-01-19 13:42:26 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/tokens/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'userId' => ID :: unique (),
'email' => 'otpuser@appwrite.io'
]);
2025-07-29 06:27:57 +00:00
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2024-01-19 13:42:26 +00:00
$this -> assertNotEmpty ( $response [ 'body' ][ '$id' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ '$createdAt' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ 'userId' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ 'expire' ]);
$this -> assertEmpty ( $response [ 'body' ][ 'secret' ]);
2024-02-01 10:41:01 +00:00
$this -> assertEmpty ( $response [ 'body' ][ 'phrase' ]);
2024-01-19 13:42:26 +00:00
$userId = $response [ 'body' ][ 'userId' ];
$lastEmail = $this -> getLastEmail ();
2025-07-24 14:09:23 +00:00
2024-01-19 13:42:26 +00:00
$this -> assertEquals ( 'otpuser@appwrite.io' , $lastEmail [ 'to' ][ 0 ][ 'address' ]);
2024-01-22 14:41:23 +00:00
$this -> assertEquals ( 'OTP for ' . $this -> getProject ()[ 'name' ] . ' Login' , $lastEmail [ 'subject' ]);
2024-01-19 13:42:26 +00:00
// FInd 6 concurrent digits in email text - OTP
preg_match_all ( " / \ b \ d { 6} \ b/ " , $lastEmail [ 'text' ], $matches );
$code = ( $matches [ 0 ] ? ? [])[ 0 ] ? ? '' ;
$this -> assertNotEmpty ( $code );
2025-07-24 14:09:23 +00:00
$this -> assertStringContainsStringIgnoringCase ( 'Use OTP ' . $code . ' to sign in to ' . $this -> getProject ()[ 'name' ] . '. Expires in 15 minutes.' , $lastEmail [ 'text' ]);
2024-01-19 13:42:26 +00:00
2025-09-18 08:39:42 +00:00
// Only Console project has branded logo in email.
if ( $isConsoleProject ) {
$this -> assertStringContainsStringIgnoringCase ( 'Appwrite logo' , $lastEmail [ 'html' ]);
2025-09-24 07:36:20 +00:00
} else {
$this -> assertStringNotContainsStringIgnoringCase ( 'Appwrite logo' , $lastEmail [ 'html' ]);
2025-09-18 08:39:42 +00:00
}
2024-01-19 13:42:26 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/token' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'userId' => $userId ,
'secret' => $code
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( $userId , $response [ 'body' ][ 'userId' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ '$id' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ 'expire' ]);
$this -> assertEmpty ( $response [ 'body' ][ 'secret' ]);
$session = $response [ 'cookies' ][ 'a_session_' . $this -> getProject ()[ '$id' ]];
$response = $this -> client -> call ( Client :: METHOD_GET , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]));
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( $userId , $response [ 'body' ][ '$id' ]);
2024-05-07 09:05:54 +00:00
$this -> assertEquals ( $userId , $response [ 'body' ][ '$id' ]);
$this -> assertTrue ( $response [ 'body' ][ 'emailVerification' ]);
2025-07-29 06:27:57 +00:00
$this -> assertArrayHasKey ( 'targets' , $response [ 'body' ]);
$this -> assertEquals ( 'otpuser@appwrite.io' , $response [ 'body' ][ 'targets' ][ 0 ][ 'identifier' ]);
2024-01-19 13:42:26 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/token' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'userId' => $userId ,
'secret' => $code
]);
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'user_invalid_token' , $response [ 'body' ][ 'type' ]);
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/tokens/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'userId' => ID :: unique (),
'email' => 'otpuser@appwrite.io' ,
2024-02-01 10:41:01 +00:00
'phrase' => true
2024-01-19 13:42:26 +00:00
]);
$this -> assertEquals ( $response [ 'headers' ][ 'status-code' ], 201 );
2024-02-01 10:41:01 +00:00
$this -> assertNotEmpty ( $response [ 'body' ][ 'phrase' ]);
2024-01-19 13:42:26 +00:00
$this -> assertEmpty ( $response [ 'body' ][ 'secret' ]);
$this -> assertEquals ( $userId , $response [ 'body' ][ 'userId' ]);
2024-02-01 14:13:30 +00:00
$phrase = $response [ 'body' ][ 'phrase' ];
2024-01-19 13:42:26 +00:00
$lastEmail = $this -> getLastEmail ();
$this -> assertEquals ( 'otpuser@appwrite.io' , $lastEmail [ 'to' ][ 0 ][ 'address' ]);
2024-01-22 14:46:53 +00:00
$this -> assertEquals ( 'OTP for ' . $this -> getProject ()[ 'name' ] . ' Login' , $lastEmail [ 'subject' ]);
2024-01-19 13:42:26 +00:00
$this -> assertStringContainsStringIgnoringCase ( 'security phrase' , $lastEmail [ 'text' ]);
2024-02-01 14:13:30 +00:00
$this -> assertStringContainsStringIgnoringCase ( $phrase , $lastEmail [ 'text' ]);
2024-01-19 13:42:26 +00:00
2024-01-19 14:42:06 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/tokens/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'userId' => ID :: unique (),
'email' => 'wrongemail'
]);
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'general_argument_invalid' , $response [ 'body' ][ 'type' ]);
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/tokens/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'userId' => 'wrongId$' ,
'email' => 'email@appwrite.io'
]);
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'general_argument_invalid' , $response [ 'body' ][ 'type' ]);
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/tokens/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'userId' => ID :: unique (),
]);
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'general_argument_invalid' , $response [ 'body' ][ 'type' ]);
2024-01-19 13:42:26 +00:00
}
2024-01-09 11:58:36 +00:00
public function testDeleteAccount () : void
{
2024-03-06 17:34:21 +00:00
$email = uniqid () . 'user@localhost.test' ;
$password = 'password' ;
$name = 'User Name' ;
$response = $this -> client -> call ( Client :: METHOD_POST , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2024-12-15 10:49:35 +00:00
'x-appwrite-dev-key' => $this -> getProject ()[ 'devKey' ] ? ? ''
2024-03-06 17:34:21 +00:00
]), [
'userId' => ID :: unique (),
'email' => $email ,
'password' => $password ,
'name' => $name ,
]);
$this -> assertEquals ( $response [ 'headers' ][ 'status-code' ], 201 );
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'email' => $email ,
'password' => $password ,
]);
$this -> assertEquals ( $response [ 'headers' ][ 'status-code' ], 201 );
$session = $response [ 'cookies' ][ 'a_session_' . $this -> getProject ()[ '$id' ]];
$response = $this -> client -> call ( Client :: METHOD_DELETE , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]));
$this -> assertEquals ( $response [ 'headers' ][ 'status-code' ], 204 );
2024-01-09 11:58:36 +00:00
}
2025-12-12 07:42:49 +00:00
2025-12-15 12:22:29 +00:00
public function testFallbackForTrustedIp () : void
2025-12-12 07:42:49 +00:00
{
$email = uniqid () . 'user@localhost.test' ;
$password = 'password' ;
$name = 'User Name' ;
2025-12-15 12:22:29 +00:00
// call appwrite directly to avoid proxy stripping the headers
$this -> client -> setEndpoint ( 'http://localhost/v1' );
2025-12-12 07:42:49 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-12-15 12:22:29 +00:00
'x-forwarded-for' => '191.0.113.195' ,
2025-12-12 07:42:49 +00:00
]), [
'userId' => ID :: unique (),
'email' => $email ,
'password' => $password ,
'name' => $name ,
]);
$this -> assertEquals ( $response [ 'headers' ][ 'status-code' ], 201 );
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-12-15 12:22:29 +00:00
'x-forwarded-for' => '191.0.113.195' ,
2025-12-12 07:42:49 +00:00
]), [
'email' => $email ,
'password' => $password ,
]);
$this -> assertEquals ( $response [ 'headers' ][ 'status-code' ], 201 );
2025-12-15 12:22:29 +00:00
$this -> assertEquals ( '191.0.113.195' , $response [ 'body' ][ 'clientIp' ] ? ? $response [ 'body' ][ 'ip' ] ? ? '' );
2025-12-12 07:42:49 +00:00
}
2026-01-05 21:51:03 +00:00
/**
* @ group abuseEnabled
*/
public function testAccountAbuseReset () : void
{
$email = \uniqid () . '.abuse.reset.test@example.com' ;
$password = 'password' ;
$account = $this -> client -> call ( Client :: METHOD_POST , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'userId' => ID :: unique (),
'email' => $email ,
'password' => $password ,
'name' => 'Abuse Reset Test' ,
]);
$this -> assertEquals ( $account [ 'headers' ][ 'status-code' ], 201 );
2026-01-06 14:18:22 +00:00
// 20 successful requests won't get blocked
2026-01-05 21:51:03 +00:00
for ( $i = 0 ; $i < 20 ; $i ++ ) {
$session = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'email' => $email ,
'password' => $password ,
]);
$this -> assertEquals ( $session [ 'headers' ][ 'status-code' ], 201 );
}
// 10 failures are OK
for ( $i = 0 ; $i < 10 ; $i ++ ) {
$session = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'email' => $email ,
'password' => 'wrongPassword' ,
]);
$this -> assertEquals ( $session [ 'headers' ][ 'status-code' ], 401 );
}
2026-01-06 14:18:22 +00:00
// 11th request gets limited
2026-01-05 21:51:03 +00:00
$session = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'email' => $email ,
'password' => 'wrongPassword' ,
]);
$this -> assertEquals ( $session [ 'headers' ][ 'status-code' ], 429 );
2026-01-06 14:18:44 +00:00
// Even correct password is now blocked, correctness doesn't matter
2026-01-05 21:51:03 +00:00
$session = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'email' => $email ,
'password' => $password ,
]);
$this -> assertEquals ( $session [ 'headers' ][ 'status-code' ], 429 );
}
2022-06-02 12:47:07 +00:00
}