feat: add update and verification method for account and users
This commit is contained in:
parent
8ce669da6f
commit
dc25883685
8 changed files with 607 additions and 148 deletions
|
@ -985,7 +985,7 @@ $collections = [
|
|||
'$id' => 'phone',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 320,
|
||||
'size' => 16, // leading '+' and 15 digitts maximum by E.164 format
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
|
|
|
@ -1519,18 +1519,15 @@ App::patch('/v1/account/email')
|
|||
}
|
||||
|
||||
$email = \strtolower($email);
|
||||
$profile = $dbForProject->findOne('users', [new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address
|
||||
|
||||
if ($profile) {
|
||||
throw new Exception('User already registered', 409, Exception::USER_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
try {
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user
|
||||
$user
|
||||
->setAttribute('password', $isAnonymousUser ? Auth::passwordHash($password) : $user->getAttribute('password', ''))
|
||||
->setAttribute('email', $email)
|
||||
->setAttribute('emailVerification', false) // After this user needs to confirm mail again
|
||||
->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name'), $user->getAttribute('email')])));
|
||||
->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name'), $user->getAttribute('email')]));
|
||||
|
||||
try {
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception('Email already exists', 409, Exception::USER_EMAIL_ALREADY_EXISTS);
|
||||
}
|
||||
|
@ -1546,6 +1543,59 @@ App::patch('/v1/account/email')
|
|||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::patch('/v1/account/phone')
|
||||
->desc('Update Account Phone')
|
||||
->groups(['api', 'account'])
|
||||
->label('event', 'users.[userId].update.phone')
|
||||
->label('scope', 'account')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'account')
|
||||
->label('sdk.method', 'updatePhone')
|
||||
->label('sdk.description', '/docs/references/account/update-phone.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->param('number', '', new ValidatorPhone(), 'Phone number.')
|
||||
->param('password', '', new Password(), 'User password. Must be at least 8 chars.')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('audits')
|
||||
->inject('usage')
|
||||
->inject('events')
|
||||
->action(function (string $phone, string $password, Response $response, Document $user, Database $dbForProject, Audit $audits, Stats $usage, Event $events) {
|
||||
|
||||
$isAnonymousUser = Auth::isAnonymousUser($user); // Check if request is from an anonymous account for converting
|
||||
|
||||
if (
|
||||
!$isAnonymousUser &&
|
||||
!Auth::passwordVerify($password, $user->getAttribute('password'))
|
||||
) { // Double check user password
|
||||
throw new Exception('Invalid credentials', 401, Exception::USER_INVALID_CREDENTIALS);
|
||||
}
|
||||
|
||||
$user
|
||||
->setAttribute('phone', $phone)
|
||||
->setAttribute('phoneVerification', false) // After this user needs to confirm phone number again
|
||||
->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name'), $user->getAttribute('email')]));
|
||||
|
||||
try {
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception('Phone number already exists', 409, Exception::USER_EMAIL_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$audits
|
||||
->setResource('user/' . $user->getId())
|
||||
->setUser($user)
|
||||
;
|
||||
|
||||
$usage->setParam('users.update', 1);
|
||||
$events->setParam('userId', $user->getId());
|
||||
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::patch('/v1/account/prefs')
|
||||
->desc('Update Account Preferences')
|
||||
->groups(['api', 'account'])
|
||||
|
@ -2172,3 +2222,143 @@ App::put('/v1/account/verification')
|
|||
|
||||
$response->dynamic($verificationDocument, Response::MODEL_TOKEN);
|
||||
});
|
||||
|
||||
App::post('/v1/account/verification/phone')
|
||||
->desc('Create Phone Verification')
|
||||
->groups(['api', 'account'])
|
||||
->label('scope', 'account')
|
||||
->label('event', 'users.[userId].verification.[tokenId].create')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'account')
|
||||
->label('sdk.method', 'createPhoneVerification')
|
||||
->label('sdk.description', '/docs/references/account/create-phone-verification.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TOKEN)
|
||||
->label('abuse-limit', 10)
|
||||
->label('abuse-key', 'userId:{userId}')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('phone')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('audits')
|
||||
->inject('events')
|
||||
->inject('usage')
|
||||
->action(function (Request $request, Response $response, Phone $phone, Document $user, Database $dbForProject, Audit $audits, Event $events, Stats $usage) {
|
||||
|
||||
if (empty(App::getEnv('_APP_SMTP_HOST'))) {
|
||||
throw new Exception('SMTP Disabled', 503, Exception::GENERAL_SMTP_DISABLED);
|
||||
}
|
||||
|
||||
if (empty($user->getAttribute('phone'))) {
|
||||
throw new Exception('User has no phone number.', 400);
|
||||
}
|
||||
|
||||
$roles = Authorization::getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
|
||||
$verificationSecret = Auth::tokenGenerator();
|
||||
|
||||
$secret = $phone->generateSecretDigits();
|
||||
$expire = \time() + Auth::TOKEN_EXPIRATION_CONFIRM;
|
||||
|
||||
$verification = new Document([
|
||||
'$id' => $dbForProject->getId(),
|
||||
'userId' => $user->getId(),
|
||||
'type' => Auth::TOKEN_TYPE_PHONE,
|
||||
'secret' => $secret,
|
||||
'expire' => $expire,
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'ip' => $request->getIP(),
|
||||
]);
|
||||
|
||||
Authorization::setRole('user:' . $user->getId());
|
||||
|
||||
$verification = $dbForProject->createDocument('tokens', $verification
|
||||
->setAttribute('$read', ['user:' . $user->getId()])
|
||||
->setAttribute('$write', ['user:' . $user->getId()]));
|
||||
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
$phone->send(APP::getEnv('_APP_PHONE_FROM'), $user->getAttribute('phone'), $secret);
|
||||
|
||||
$events
|
||||
->setParam('userId', $user->getId())
|
||||
->setParam('tokenId', $verification->getId())
|
||||
->setPayload($response->output(
|
||||
$verification->setAttribute('secret', $verificationSecret),
|
||||
Response::MODEL_TOKEN
|
||||
))
|
||||
;
|
||||
|
||||
// Hide secret for clients
|
||||
$verification->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $verificationSecret : '');
|
||||
|
||||
$audits->setResource('user/' . $user->getId());
|
||||
$usage->setParam('users.update', 1);
|
||||
|
||||
$response->setStatusCode(Response::STATUS_CODE_CREATED);
|
||||
$response->dynamic($verification, Response::MODEL_TOKEN);
|
||||
});
|
||||
|
||||
App::put('/v1/account/verification/phone')
|
||||
->desc('Create Phone Verification (confirmation)')
|
||||
->groups(['api', 'account'])
|
||||
->label('scope', 'public')
|
||||
->label('event', 'users.[userId].verification.[tokenId].update')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'account')
|
||||
->label('sdk.method', 'updatePhoneVerification')
|
||||
->label('sdk.description', '/docs/references/account/update-phone-verification.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TOKEN)
|
||||
->label('abuse-limit', 10)
|
||||
->label('abuse-key', 'userId:{param-userId}')
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('secret', '', new Text(256), 'Valid verification token.')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('audits')
|
||||
->inject('usage')
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $secret, Response $response, Document $user, Database $dbForProject, Audit $audits, Stats $usage, Event $events) {
|
||||
|
||||
$profile = Authorization::skip(fn() => $dbForProject->getDocument('users', $userId));
|
||||
|
||||
if ($profile->isEmpty()) {
|
||||
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$verification = Auth::phoneTokenVerify($user->getAttribute('tokens', []), $secret);
|
||||
|
||||
if (!$verification) {
|
||||
throw new Exception('Invalid verification token', 401, Exception::USER_INVALID_TOKEN);
|
||||
}
|
||||
|
||||
Authorization::setRole('user:' . $profile->getId());
|
||||
|
||||
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('phoneVerification', true));
|
||||
|
||||
$verificationDocument = $dbForProject->getDocument('tokens', $verification);
|
||||
|
||||
/**
|
||||
* We act like we're updating and validating the verification token but actually we don't need it anymore.
|
||||
*/
|
||||
$dbForProject->deleteDocument('tokens', $verification);
|
||||
$dbForProject->deleteCachedDocument('users', $profile->getId());
|
||||
|
||||
$audits->setResource('user/' . $user->getId());
|
||||
|
||||
$usage->setParam('users.update', 1);
|
||||
|
||||
$events
|
||||
->setParam('userId', $user->getId())
|
||||
->setParam('tokenId', $verificationDocument->getId())
|
||||
;
|
||||
|
||||
$response->dynamic($verificationDocument, Response::MODEL_TOKEN);
|
||||
});
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\Validator\Password;
|
||||
use Appwrite\Auth\Validator\Phone;
|
||||
use Appwrite\Detector\Detector;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
|
@ -438,6 +439,45 @@ App::patch('/v1/users/:userId/verification')
|
|||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::patch('/v1/users/:userId/verification')
|
||||
->desc('Update Phone Verification')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].update.verification')
|
||||
->label('scope', 'users.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'users')
|
||||
->label('sdk.method', 'updatePhoneVerification')
|
||||
->label('sdk.description', '/docs/references/users/update-user-phone-verification.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('phoneVerification', false, new Boolean(), 'User phone verification status.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('usage')
|
||||
->inject('events')
|
||||
->action(function (string $userId, bool $phoneVerification, Response $response, Database $dbForProject, Stats $usage, Event $events) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('phoneVerification', $phoneVerification));
|
||||
|
||||
$usage
|
||||
->setParam('users.update', 1)
|
||||
;
|
||||
|
||||
$events
|
||||
->setParam('userId', $user->getId())
|
||||
;
|
||||
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::patch('/v1/users/:userId/name')
|
||||
->desc('Update Name')
|
||||
->groups(['api', 'users'])
|
||||
|
@ -555,6 +595,7 @@ App::patch('/v1/users/:userId/email')
|
|||
|
||||
$user
|
||||
->setAttribute('email', $email)
|
||||
->setAttribute('emailVerification', false)
|
||||
->setAttribute('search', \implode(' ', [$user->getId(), $email, $user->getAttribute('name')]))
|
||||
;
|
||||
|
||||
|
@ -565,6 +606,55 @@ App::patch('/v1/users/:userId/email')
|
|||
}
|
||||
|
||||
|
||||
$audits
|
||||
->setResource('user/' . $user->getId())
|
||||
;
|
||||
|
||||
$events
|
||||
->setParam('userId', $user->getId())
|
||||
;
|
||||
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::patch('/v1/users/:userId/email')
|
||||
->desc('Update Phone')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].update.phone')
|
||||
->label('scope', 'users.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'users')
|
||||
->label('sdk.method', 'updatePhone')
|
||||
->label('sdk.description', '/docs/references/users/update-user-phone.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('number', '', new Phone(), 'User phone number.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('audits')
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $number, Response $response, Database $dbForProject, EventAudit $audits, Event $events) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$user
|
||||
->setAttribute('phone', $number)
|
||||
->setAttribute('phoneVerification', false)
|
||||
;
|
||||
|
||||
try {
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception('Email already exists', 409, Exception::USER_EMAIL_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
|
||||
$audits
|
||||
->setResource('user/' . $user->getId())
|
||||
;
|
||||
|
|
|
@ -298,11 +298,10 @@ App::init(function (App $utopia, Request $request, Response $response, Document
|
|||
|
||||
$service = $route->getLabel('sdk.namespace', '');
|
||||
if (!empty($service)) {
|
||||
$roles = Authorization::getRoles();
|
||||
if (
|
||||
array_key_exists($service, $project->getAttribute('services', []))
|
||||
&& !$project->getAttribute('services', [])[$service]
|
||||
&& !(Auth::isPrivilegedUser($roles) || Auth::isAppUser($roles))
|
||||
&& !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles()))
|
||||
) {
|
||||
throw new AppwriteException('Service is disabled', 503, AppwriteException::GENERAL_SERVICE_DISABLED);
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ class Phone extends Validator
|
|||
*/
|
||||
public function isValid($value): bool
|
||||
{
|
||||
return !!\preg_match('/^\+[1-9]\d{1,14}$/', $value);
|
||||
return is_string($value) && !!\preg_match('/^\+[1-9]\d{1,14}$/', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Tests\E2E\Services\Account;
|
||||
|
||||
use Appwrite\Auth\Phone\Mock;
|
||||
use Tests\E2E\Client;
|
||||
|
||||
trait AccountBase
|
||||
|
@ -484,8 +483,7 @@ trait AccountBase
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
||||
]), [
|
||||
]);
|
||||
]), []);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
|
@ -560,8 +558,7 @@ trait AccountBase
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
||||
]), [
|
||||
]);
|
||||
]), []);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
|
@ -641,8 +638,7 @@ trait AccountBase
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
||||
]), [
|
||||
]);
|
||||
]), []);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
|
@ -1330,132 +1326,6 @@ trait AccountBase
|
|||
return $data;
|
||||
}
|
||||
|
||||
public function testCreatePhone(): array
|
||||
{
|
||||
$number = '+1 234 56789';
|
||||
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/phone', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => 'unique()',
|
||||
'number' => $number,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEmpty($response['body']['secret']);
|
||||
$this->assertIsNumeric($response['body']['expire']);
|
||||
|
||||
$userId = $response['body']['userId'];
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/phone', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => 'unique()'
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
$data['token'] = Mock::$defaultDigits;
|
||||
$data['id'] = $userId;
|
||||
$data['number'] = $number;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreatePhone
|
||||
*/
|
||||
public function testCreateSessionWithPhone($data): void
|
||||
{
|
||||
$id = $data['id'] ?? '';
|
||||
$token = $data['token'] ?? '';
|
||||
$number = $data['number'] ?? '';
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/account/sessions/phone', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => 'ewewe',
|
||||
'secret' => $token,
|
||||
]);
|
||||
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/account/sessions/phone', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => $id,
|
||||
'secret' => 'sdasdasdasd',
|
||||
]);
|
||||
|
||||
$this->assertEquals(401, $response['headers']['status-code']);
|
||||
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/account/sessions/phone', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => $id,
|
||||
'secret' => $token,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertIsArray($response['body']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertNotEmpty($response['body']['userId']);
|
||||
|
||||
$session = $this->client->parseCookie((string)$response['headers']['set-cookie'])['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($response['headers']['status-code'], 200);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertIsNumeric($response['body']['registration']);
|
||||
$this->assertEquals($response['body']['phone'], $number);
|
||||
$this->assertTrue($response['body']['phoneVerification']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/account/sessions/phone', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => $id,
|
||||
'secret' => $token,
|
||||
]);
|
||||
|
||||
$this->assertEquals(401, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateMagicUrl
|
||||
*/
|
||||
|
@ -1586,8 +1456,7 @@ trait AccountBase
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
||||
]), [
|
||||
]);
|
||||
]), []);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Tests\E2E\Services\Account;
|
||||
|
||||
use Appwrite\Auth\Phone\Mock;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
|
@ -673,4 +674,312 @@ class AccountCustomClientTest extends Scope
|
|||
$this->assertCount(1, $response['body']['users']);
|
||||
$this->assertEquals($response['body']['users'][0]['email'], $email);
|
||||
}
|
||||
|
||||
|
||||
public function testCreatePhone(): array
|
||||
{
|
||||
$number = '+123456789';
|
||||
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/phone', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => 'unique()',
|
||||
'number' => $number,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEmpty($response['body']['secret']);
|
||||
$this->assertIsNumeric($response['body']['expire']);
|
||||
|
||||
$userId = $response['body']['userId'];
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/phone', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => 'unique()'
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
$data['token'] = Mock::$defaultDigits;
|
||||
$data['id'] = $userId;
|
||||
$data['number'] = $number;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreatePhone
|
||||
*/
|
||||
public function testCreateSessionWithPhone(array $data): array
|
||||
{
|
||||
$id = $data['id'] ?? '';
|
||||
$token = $data['token'] ?? '';
|
||||
$number = $data['number'] ?? '';
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/account/sessions/phone', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => 'ewewe',
|
||||
'secret' => $token,
|
||||
]);
|
||||
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/account/sessions/phone', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => $id,
|
||||
'secret' => 'sdasdasdasd',
|
||||
]);
|
||||
|
||||
$this->assertEquals(401, $response['headers']['status-code']);
|
||||
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/account/sessions/phone', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => $id,
|
||||
'secret' => $token,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertIsArray($response['body']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertNotEmpty($response['body']['userId']);
|
||||
|
||||
$session = $this->client->parseCookie((string)$response['headers']['set-cookie'])['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($response['headers']['status-code'], 200);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertIsNumeric($response['body']['registration']);
|
||||
$this->assertEquals($response['body']['phone'], $number);
|
||||
$this->assertTrue($response['body']['phoneVerification']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/account/sessions/phone', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => $id,
|
||||
'secret' => $token,
|
||||
]);
|
||||
|
||||
$this->assertEquals(401, $response['headers']['status-code']);
|
||||
|
||||
$data['session'] = $session;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateSessionWithPhone
|
||||
*/
|
||||
public function testConvertPhoneToPassword(array $data): array
|
||||
{
|
||||
$session = $data['session'];
|
||||
$email = uniqid() . 'new@localhost.test';
|
||||
$password = 'new-password';
|
||||
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$email = uniqid() . 'new@localhost.test';
|
||||
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/account/email', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
||||
]), [
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 200);
|
||||
$this->assertIsArray($response['body']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertIsNumeric($response['body']['registration']);
|
||||
$this->assertEquals($response['body']['email'], $email);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/sessions', 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);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testConvertPhoneToPassword
|
||||
*/
|
||||
public function testUpdatePhone(array $data): array
|
||||
{
|
||||
$newPhone = '+45632569856';
|
||||
$session = $data['session'] ?? '';
|
||||
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/account/phone', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
||||
]), [
|
||||
'number' => $newPhone,
|
||||
'password' => 'new-password'
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 200);
|
||||
$this->assertIsArray($response['body']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertIsNumeric($response['body']['registration']);
|
||||
$this->assertEquals($response['body']['phone'], $newPhone);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/account/phone', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]));
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 401);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/account/phone', 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'], 400);
|
||||
|
||||
$data['phone'] = $newPhone;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testUpdatePhone
|
||||
*/
|
||||
public function testPhoneVerification(array $data): array
|
||||
{
|
||||
$session = $data['session'] ?? '';
|
||||
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/verification/phone', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
||||
|
||||
]));
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEmpty($response['body']['secret']);
|
||||
$this->assertIsNumeric($response['body']['expire']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testPhoneVerification
|
||||
*/
|
||||
public function testUpdatePhoneVerification($data): array
|
||||
{
|
||||
$id = $data['id'] ?? '';
|
||||
$session = $data['session'] ?? '';
|
||||
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/account/verification/phone', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
||||
]), [
|
||||
'userId' => $id,
|
||||
'secret' => Mock::$defaultDigits,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/account/verification/phone', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
||||
]), [
|
||||
'userId' => 'ewewe',
|
||||
'secret' => Mock::$defaultDigits,
|
||||
]);
|
||||
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/account/verification/phone', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
||||
]), [
|
||||
'userId' => $id,
|
||||
'secret' => '999999',
|
||||
]);
|
||||
|
||||
$this->assertEquals(401, $response['headers']['status-code']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,8 @@ class PhoneTest extends TestCase
|
|||
$this->assertEquals($this->object->isValid('786-307-3615'), false);
|
||||
$this->assertEquals($this->object->isValid('+16308A520397'), false);
|
||||
$this->assertEquals($this->object->isValid('+0415553452342'), false);
|
||||
$this->assertEquals($this->object->isValid('+14 155 5524564'), false);
|
||||
$this->assertEquals($this->object->isValid(+14155552456), false);
|
||||
|
||||
$this->assertEquals($this->object->isValid('+14155552'), true);
|
||||
$this->assertEquals($this->object->isValid('+141555526'), true);
|
||||
|
|
Loading…
Reference in a new issue