1
0
Fork 0
mirror of synced 2024-06-13 16:24:47 +12:00

Merge branch 'cl-1.4.x' of https://github.com/appwrite/appwrite into feat-implement-migrations

This commit is contained in:
Christy Jacob 2023-08-09 13:59:44 +00:00
commit 7d60e4272d
35 changed files with 640 additions and 116 deletions

3
.env
View file

@ -81,4 +81,5 @@ _APP_DOCKER_HUB_PASSWORD=
_APP_CONSOLE_GITHUB_SECRET=
_APP_CONSOLE_GITHUB_APP_ID=
_APP_MIGRATIONS_FIREBASE_CLIENT_ID=
_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET=
_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET=
OPENAI_API_KEY=YOUR_OPENAI_API_KEY

2
.gitmodules vendored
View file

@ -1,4 +1,4 @@
[submodule "app/console"]
path = app/console
url = https://github.com/appwrite/console
branch = 2.3.4
branch = disallow-personal-data

View file

@ -4,6 +4,7 @@
- Add error attribute to indexes and attributes [#4575](https://github.com/appwrite/appwrite/pull/4575)
- Add new index validation rules [#5710](https://github.com/appwrite/appwrite/pull/5710)
- Added support for disallowing passwords that contain personal data [#5371](https://github.com/appwrite/appwrite/pull/5371)
## Fixes

View file

@ -175,6 +175,16 @@ return [
'description' => 'Passwords do not match. Please check the password and confirm password.',
'code' => 400,
],
Exception::USER_PASSWORD_RECENTLY_USED => [
'name' => Exception::USER_PASSWORD_RECENTLY_USED,
'description' => 'The password you are trying to use is similar to your previous password. Please choose a stronger password.',
'code' => 400,
],
Exception::USER_PASSWORD_PERSONAL_DATA => [
'name' => Exception::USER_PASSWORD_PERSONAL_DATA,
'description' => 'The password you are trying to use contains references to your name, email, phone or userID. Please choose a different password and try again.',
'code' => 400,
],
Exception::USER_SESSION_NOT_FOUND => [
'name' => Exception::USER_SESSION_NOT_FOUND,
'description' => 'The current user session could not be found.',

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -31,6 +31,8 @@ return [
'audio/ogg', // Ogg Vorbis RFC 5334
'audio/vorbis', // Vorbis RFC 5215
'audio/vnd.wav', // wav RFC 2361
'audio/aac', //AAC audio
'audio/x-hx-aac-adts', // AAC audio
// Microsoft Word
'application/msword',

View file

@ -43,6 +43,7 @@ use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Appwrite\Auth\Validator\PasswordHistory;
use Appwrite\Auth\Validator\PasswordDictionary;
use Appwrite\Auth\Validator\PersonalData;
$oauthDefaultSuccess = '/auth/oauth2/success';
$oauthDefaultFailure = '/auth/oauth2/failure';
@ -194,6 +195,13 @@ App::post('/v1/account')
}
}
if ($project->getAttribute('auths', [])['personalDataCheck'] ?? false) {
$personalDataValidator = new PersonalData($userId, $email, $name, null);
if (!$personalDataValidator->isValid($password)) {
throw new Exception(Exception::USER_PASSWORD_PERSONAL_DATA);
}
}
$passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
$password = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS);
try {
@ -838,7 +846,7 @@ App::post('/v1/account/sessions/magic-url')
}
}
$userId = $userId == 'unique()' ? ID::unique() : $userId;
$userId = $userId === 'unique()' ? ID::unique() : $userId;
$user->setAttributes([
'$id' => $userId,
@ -1091,7 +1099,7 @@ App::post('/v1/account/sessions/phone')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_TOKEN)
->label('abuse-limit', 10)
->label('abuse-key', 'url:{url},email:{param-email}')
->label('abuse-key', 'url:{url},email:{param-phone}')
->param('userId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.')
->inject('request')
@ -1756,16 +1764,22 @@ App::patch('/v1/account/password')
$newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS);
$historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
$history = [];
$history = $user->getAttribute('passwordHistory', []);
if ($historyLimit > 0) {
$history = $user->getAttribute('passwordHistory', []);
$validator = new PasswordHistory($history, $user->getAttribute('hash'), $user->getAttribute('hashOptions'));
if (!$validator->isValid($password)) {
throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED, 'The password was recently used', 409);
throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED);
}
$history[] = $newPassword;
array_slice($history, (count($history) - $historyLimit), $historyLimit);
$history = array_slice($history, (count($history) - $historyLimit), $historyLimit);
}
if ($project->getAttribute('auths', [])['personalDataCheck'] ?? false) {
$personalDataValidator = new PersonalData($user->getId(), $user->getAttribute('email'), $user->getAttribute('name'), $user->getAttribute('phone'));
if (!$personalDataValidator->isValid($password)) {
throw new Exception(Exception::USER_PASSWORD_PERSONAL_DATA);
}
}
$user
@ -2360,8 +2374,9 @@ App::put('/v1/account/recovery')
->inject('response')
->inject('user')
->inject('dbForProject')
->inject('project')
->inject('events')
->action(function (string $userId, string $secret, string $password, string $passwordAgain, Response $response, Document $user, Database $dbForProject, Event $events) {
->action(function (string $userId, string $secret, string $password, string $passwordAgain, Response $response, Document $user, Database $dbForProject, Document $project, Event $events) {
if ($password !== $passwordAgain) {
throw new Exception(Exception::USER_PASSWORD_MISMATCH);
}
@ -2381,8 +2396,23 @@ App::put('/v1/account/recovery')
Authorization::setRole(Role::user($profile->getId())->toString());
$newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS);
$historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
$history = $profile->getAttribute('passwordHistory', []);
if ($historyLimit > 0) {
$validator = new PasswordHistory($history, $profile->getAttribute('hash'), $profile->getAttribute('hashOptions'));
if (!$validator->isValid($password)) {
throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED);
}
$history[] = $newPassword;
$history = array_slice($history, (count($history) - $historyLimit), $historyLimit);
}
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile
->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS))
->setAttribute('password', $newPassword)
->setAttribute('passwordHistory', $history)
->setAttribute('passwordUpdate', DateTime::now())
->setAttribute('hash', Auth::DEFAULT_ALGO)
->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS)

View file

@ -4,6 +4,7 @@ use Appwrite\Extend\Exception;
use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\Database\Document;
use Utopia\Validator\Text;
App::init()
->groups(['console'])
@ -38,3 +39,58 @@ App::get('/v1/console/variables')
$response->dynamic($variables, Response::MODEL_CONSOLE_VARIABLES);
});
App::post('/v1/console/assistant')
->desc('Ask Query')
->groups(['api', 'assistant'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION])
->label('sdk.namespace', 'assistant')
->label('sdk.method', 'chat')
->label('sdk.description', '/docs/references/assistant/chat.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_TEXT)
->label('abuse-limit', 15)
->label('abuse-key', 'userId:{userId}')
->param('query', '', new Text(2000), 'Query')
->inject('response')
->action(function (string $query, Response $response) {
$ch = curl_init('http://appwrite-assistant:3003/');
$responseHeaders = [];
$query = json_encode(['prompt' => $query]);
$headers = ['accept: text/event-stream'];
$handleEvent = function ($ch, $data) use ($response) {
$response->chunk($data);
return \strlen($data);
};
curl_setopt($ch, CURLOPT_WRITEFUNCTION, $handleEvent);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0);
curl_setopt($ch, CURLOPT_TIMEOUT, 9000);
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) {
$len = strlen($header);
$header = explode(':', $header, 2);
if (count($header) < 2) { // ignore invalid headers
return $len;
}
$responseHeaders[strtolower(trim($header[0]))] = trim($header[1]);
return $len;
});
curl_setopt($ch, CURLOPT_POSTFIELDS, $query);
curl_exec($ch);
curl_close($ch);
$response->chunk('', true);
});

View file

@ -88,7 +88,7 @@ App::post('/v1/projects')
}
$auth = Config::getParam('auth', []);
$auths = ['limit' => 0, 'maxSessions' => APP_LIMIT_USER_SESSIONS_DEFAULT, 'passwordHistory' => 0, 'passwordDictionary' => false, 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG];
$auths = ['limit' => 0, 'maxSessions' => APP_LIMIT_USER_SESSIONS_DEFAULT, 'passwordHistory' => 0, 'passwordDictionary' => false, 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, 'personalDataCheck' => false];
foreach ($auth as $index => $method) {
$auths[$method['key'] ?? ''] = true;
}
@ -615,7 +615,7 @@ App::patch('/v1/projects/:projectId/auth/password-history')
});
App::patch('/v1/projects/:projectId/auth/password-dictionary')
->desc('Update authentication password disctionary status. Use this endpoint to enable or disable the dicitonary check for user password')
->desc('Update authentication password dictionary status. Use this endpoint to enable or disable the dicitonary check for user password')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
@ -645,6 +645,37 @@ App::patch('/v1/projects/:projectId/auth/password-dictionary')
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/auth/personal-data')
->desc('Enable or disable checking user passwords for similarity with their personal data.')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'updatePersonalDataCheck')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PROJECT)
->param('projectId', '', new UID(), 'Project unique ID.')
->param('enabled', false, new Boolean(false), 'Set whether or not to check a password for similarity with personal data. Default is false.')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, bool $enabled, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$auths = $project->getAttribute('auths', []);
$auths['personalDataCheck'] = $enabled;
$dbForConsole->updateDocument('projects', $project->getId(), $project
->setAttribute('auths', $auths));
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/auth/max-sessions')
->desc('Update Project user sessions limit')
->groups(['api', 'projects'])

View file

@ -37,6 +37,7 @@ use MaxMind\Db\Reader;
use Utopia\Validator\Integer;
use Appwrite\Auth\Validator\PasswordHistory;
use Appwrite\Auth\Validator\PasswordDictionary;
use Appwrite\Auth\Validator\PersonalData;
/** TODO: Remove function when we move to using utopia/platform */
function createUser(string $hash, mixed $hashOptions, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Document $project, Database $dbForProject, Event $events): Document
@ -53,6 +54,13 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
? ID::unique()
: ID::custom($userId);
if ($project->getAttribute('auths', [])['personalDataCheck'] ?? false) {
$personalDataValidator = new PersonalData($userId, $email, $name, $phone);
if (!$personalDataValidator->isValid($password)) {
throw new Exception(Exception::USER_PASSWORD_PERSONAL_DATA);
}
}
$password = (!empty($password)) ? ($hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) : null;
$user = $dbForProject->createDocument('users', new Document([
'$id' => $userId,
@ -825,19 +833,25 @@ App::patch('/v1/users/:userId/password')
throw new Exception(Exception::USER_NOT_FOUND);
}
if ($project->getAttribute('auths', [])['personalDataCheck'] ?? false) {
$personalDataValidator = new PersonalData($userId, $user->getAttribute('email'), $user->getAttribute('name'), $user->getAttribute('phone'));
if (!$personalDataValidator->isValid($password)) {
throw new Exception(Exception::USER_PASSWORD_PERSONAL_DATA);
}
}
$newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS);
$historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
$history = [];
$history = $user->getAttribute('passwordHistory', []);
if ($historyLimit > 0) {
$history = $user->getAttribute('passwordHistory', []);
$validator = new PasswordHistory($history, $user->getAttribute('hash'), $user->getAttribute('hashOptions'));
if (!$validator->isValid($password)) {
throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED, 'The password was recently used', 409);
throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED);
}
$history[] = $newPassword;
array_slice($history, (count($history) - $historyLimit), $historyLimit);
$history = array_slice($history, (count($history) - $historyLimit), $historyLimit);
}
$user

View file

@ -29,7 +29,7 @@ $parseLabel = function (string $label, array $responsePayload, array $requestPar
$parts = explode('.', $match);
if (count($parts) !== 2) {
throw new Exception('Too less or too many parts', 400, Exception::GENERAL_ARGUMENT_INVALID);
throw new Exception(Exception::GENERAL_SERVER_ERROR, "The server encountered an error while parsing the label: $label. Please create an issue on GitHub to allow us to investigate further https://github.com/appwrite/appwrite/issues/new/choose");
}
$namespace = $parts[0] ?? '';

View file

@ -36,7 +36,6 @@ use Utopia\App;
use Utopia\Logger\Logger;
use Utopia\Cache\Adapter\Redis as RedisCache;
use Utopia\Cache\Cache;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Database;
@ -817,7 +816,6 @@ foreach ($locales as $locale) {
if (!\file_exists($path)) {
$path = __DIR__ . '/config/locale/translations/' . \substr($code, 0, 2) . '.json'; // if `ar-ae` doesn't exist, look for `ar`
if (!\file_exists($path)) {
var_dump('Unable to find tralsnations for ' . $locale['code'] . ' so using en.json');
$path = __DIR__ . '/config/locale/translations/en.json'; // if none translation exists, use default from `en.json`
}
}

File diff suppressed because one or more lines are too long

10
composer.lock generated
View file

@ -2504,12 +2504,12 @@
"source": {
"type": "git",
"url": "https://github.com/utopia-php/transfer.git",
"reference": "034c2dcacd4aa2595bfff84f11776dce66cf9153"
"reference": "7fca998f19e2d12dcfa003d7534e4ae1c2f205a1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/transfer/zipball/034c2dcacd4aa2595bfff84f11776dce66cf9153",
"reference": "034c2dcacd4aa2595bfff84f11776dce66cf9153",
"url": "https://api.github.com/repos/utopia-php/transfer/zipball/7fca998f19e2d12dcfa003d7534e4ae1c2f205a1",
"reference": "7fca998f19e2d12dcfa003d7534e4ae1c2f205a1",
"shasum": ""
},
"require": {
@ -2566,7 +2566,7 @@
"source": "https://github.com/utopia-php/transfer/tree/feat-improve-features",
"issues": "https://github.com/utopia-php/transfer/issues"
},
"time": "2023-08-08T14:38:06+00:00"
"time": "2023-08-09T11:33:01+00:00"
},
{
"name": "utopia-php/websocket",
@ -5391,5 +5391,5 @@
"platform-overrides": {
"php": "8.0"
},
"plugin-api-version": "2.1.0"
"plugin-api-version": "2.3.0"
}

View file

@ -727,6 +727,14 @@ services:
- _APP_CONNECTIONS_QUEUE
- _APP_REGION
appwrite-assistant:
container_name: appwrite-assistant
image: appwrite/assistant:0.1.0
networks:
- appwrite
environment:
- OPENAI_API_KEY
openruntimes-executor:
container_name: openruntimes-executor
hostname: exc1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 458 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

View file

@ -0,0 +1,102 @@
<?php
namespace Appwrite\Auth\Validator;
/**
* Validates user password string against their personal data
*/
class PersonalData extends Password
{
public function __construct(
protected ?string $userId = null,
protected ?string $email = null,
protected ?string $name = null,
protected ?string $phone = null,
protected bool $strict = false
) {
}
/**
* Get Description.
*
* Returns validator description
*
* @return string
*/
public function getDescription(): string
{
return 'Password must not include any personal data like your name, email, phone number, etc.';
}
/**
* Is valid.
*
* @param mixed $value
*
* @return bool
*/
public function isValid($password): bool
{
if (!parent::isValid($password)) {
return false;
}
if (!$this->strict) {
$password = strtolower($password);
$this->userId = strtolower($this->userId);
$this->email = strtolower($this->email);
$this->name = strtolower($this->name);
$this->phone = strtolower($this->phone);
}
if ($this->userId && strpos($password, $this->userId) !== false) {
return false;
}
if ($this->email && strpos($password, $this->email) !== false) {
return false;
}
if ($this->email && strpos($password, explode('@', $this->email)[0] ?? '') !== false) {
return false;
}
if ($this->name && strpos($password, $this->name) !== false) {
return false;
}
if ($this->phone && strpos($password, str_replace('+', '', $this->phone)) !== false) {
return false;
}
if ($this->phone && strpos($password, $this->phone) !== false) {
return false;
}
return true;
}
/**
* Is array
*
* Function will return true if object is array.
*
* @return bool
*/
public function isArray(): bool
{
return false;
}
/**
* Get Type
*
* Returns validator type.
*
* @return string
*/
public function getType(): string
{
return self::TYPE_STRING;
}
}

View file

@ -70,6 +70,7 @@ class Exception extends \Exception
public const USER_SESSION_ALREADY_EXISTS = 'user_session_already_exists';
public const USER_NOT_FOUND = 'user_not_found';
public const USER_PASSWORD_RECENTLY_USED = 'password_recently_used';
public const USER_PASSWORD_PERSONAL_DATA = 'password_personal_data';
public const USER_EMAIL_ALREADY_EXISTS = 'user_email_already_exists';
public const USER_PASSWORD_MISMATCH = 'user_password_mismatch';
public const USER_SESSION_NOT_FOUND = 'user_session_not_found';

View file

@ -132,6 +132,12 @@ class Project extends Model
'default' => false,
'example' => true,
])
->addRule('authPersonalDataCheck', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Whether or not to check the user password for similarity with their personal data.',
'default' => false,
'example' => true,
])
->addRule('providers', [
'type' => Response::MODEL_PROVIDER,
'description' => 'List of Providers.',
@ -307,6 +313,7 @@ class Project extends Model
$document->setAttribute('authSessionsLimit', $authValues['maxSessions'] ?? APP_LIMIT_USER_SESSIONS_DEFAULT);
$document->setAttribute('authPasswordHistory', $authValues['passwordHistory'] ?? 0);
$document->setAttribute('authPasswordDictionary', $authValues['passwordDictionary'] ?? false);
$document->setAttribute('authPersonalDataCheck', $authValues['personalDataCheck'] ?? false);
foreach ($auth as $index => $method) {
$key = $method['key'];

View file

@ -1336,7 +1336,7 @@ class ProjectsConsoleClientTest extends Scope
'password' => $password,
]);
$this->assertEquals(409, $response['headers']['status-code']);
$this->assertEquals(400, $response['headers']['status-code']);
$headers = array_merge($this->getHeaders(), [
'x-appwrite-mode' => 'admin',
@ -1348,7 +1348,7 @@ class ProjectsConsoleClientTest extends Scope
'password' => $password,
]);
$this->assertEquals(409, $response['headers']['status-code']);
$this->assertEquals(400, $response['headers']['status-code']);
/**
@ -1497,6 +1497,126 @@ class ProjectsConsoleClientTest extends Scope
return $data;
}
/**
* @depends testCreateProject
*/
public function testUpdateDisallowPersonalData($data): void
{
$id = $data['projectId'] ?? '';
/**
* Enable Disallowing of Personal Data
*/
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/auth/personal-data', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'enabled' => true,
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(true, $response['body']['authPersonalDataCheck']);
/**
* Test for failure
*/
$email = uniqid() . 'user@localhost.test';
$password = 'password';
$name = 'username';
$userId = ID::unique();
$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $id,
]), [
'email' => $email,
'password' => $email,
'name' => $name,
'userId' => $userId
]);
$this->assertEquals(400, $response['headers']['status-code']);
$this->assertEquals(400, $response['body']['code']);
$this->assertEquals(Exception::USER_PASSWORD_PERSONAL_DATA, $response['body']['type']);
$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $id,
]), [
'email' => $email,
'password' => $name,
'name' => $name,
'userId' => $userId
]);
$phone = '+123456789';
$response = $this->client->call(Client::METHOD_POST, '/users', array_merge($this->getHeaders(), [
'content-type' => 'application/json',
'x-appwrite-project' => $id,
'x-appwrite-mode' => 'admin',
]), [
'email' => $email,
'password' => $phone,
'name' => $name,
'userId' => $userId,
'phone' => $phone
]);
$this->assertEquals(400, $response['headers']['status-code']);
$this->assertEquals(400, $response['body']['code']);
$this->assertEquals(Exception::USER_PASSWORD_PERSONAL_DATA, $response['body']['type']);
/** Test for success */
$email = uniqid() . 'user@localhost.test';
$password = 'password';
$name = 'username';
$userId = ID::unique();
$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $id,
]), [
'email' => $email,
'password' => $password,
'name' => $name,
'userId' => $userId
]);
$this->assertEquals(201, $response['headers']['status-code']);
$email = uniqid() . 'user@localhost.test';
$userId = ID::unique();
$response = $this->client->call(Client::METHOD_POST, '/users', array_merge($this->getHeaders(), [
'content-type' => 'application/json',
'x-appwrite-project' => $id,
'x-appwrite-mode' => 'admin',
]), [
'email' => $email,
'password' => $password,
'name' => $name,
'userId' => $userId,
'phone' => $phone
]);
$this->assertEquals(201, $response['headers']['status-code']);
/**
* Reset
*/
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/auth/personal-data', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'enabled' => false,
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(false, $response['body']['authPersonalDataCheck']);
}
public function testUpdateProjectServiceStatusAdmin(): array
{

View file

@ -0,0 +1,81 @@
<?php
namespace Tests\Unit\Auth\Validator;
use Appwrite\Auth\Validator\PersonalData;
use PHPUnit\Framework\TestCase;
class PersonalDataTest extends TestCase
{
protected ?PersonalData $object = null;
public function testStrict(): void
{
$this->object = new PersonalData('userId', 'email@example.com', 'name', '+129492323', true);
$this->assertEquals($this->object->isValid('userId'), false);
$this->assertEquals($this->object->isValid('something.userId'), false);
$this->assertEquals($this->object->isValid('userId.something'), false);
$this->assertEquals($this->object->isValid('something.userId.something'), false);
$this->assertEquals($this->object->isValid('email@example.com'), false);
$this->assertEquals($this->object->isValid('something.email@example.com'), false);
$this->assertEquals($this->object->isValid('email@example.com.something'), false);
$this->assertEquals($this->object->isValid('something.email@example.com.something'), false);
$this->assertEquals($this->object->isValid('name'), false);
$this->assertEquals($this->object->isValid('something.name'), false);
$this->assertEquals($this->object->isValid('name.something'), false);
$this->assertEquals($this->object->isValid('something.name.something'), false);
$this->assertEquals($this->object->isValid('+129492323'), false);
$this->assertEquals($this->object->isValid('something.+129492323'), false);
$this->assertEquals($this->object->isValid('+129492323.something'), false);
$this->assertEquals($this->object->isValid('something.+129492323.something'), false);
$this->assertEquals($this->object->isValid('129492323'), false);
$this->assertEquals($this->object->isValid('something.129492323'), false);
$this->assertEquals($this->object->isValid('129492323.something'), false);
$this->assertEquals($this->object->isValid('something.129492323.something'), false);
$this->assertEquals($this->object->isValid('email'), false);
$this->assertEquals($this->object->isValid('something.email'), false);
$this->assertEquals($this->object->isValid('email.something'), false);
$this->assertEquals($this->object->isValid('something.email.something'), false);
/** Test for success */
$this->assertEquals($this->object->isValid('893pu5egerfsv3rgersvd'), true);
}
public function testNotStrict(): void
{
$this->object = new PersonalData('userId', 'email@example.com', 'name', '+129492323', false);
$this->assertEquals($this->object->isValid('userId'), false);
$this->assertEquals($this->object->isValid('USERID'), false);
$this->assertEquals($this->object->isValid('something.USERID'), false);
$this->assertEquals($this->object->isValid('USERID.something'), false);
$this->assertEquals($this->object->isValid('something.USERID.something'), false);
$this->assertEquals($this->object->isValid('email@example.com'), false);
$this->assertEquals($this->object->isValid('EMAIL@EXAMPLE.COM'), false);
$this->assertEquals($this->object->isValid('something.EMAIL@EXAMPLE.COM'), false);
$this->assertEquals($this->object->isValid('EMAIL@EXAMPLE.COM.something'), false);
$this->assertEquals($this->object->isValid('something.EMAIL@EXAMPLE.COM.something'), false);
$this->assertEquals($this->object->isValid('name'), false);
$this->assertEquals($this->object->isValid('NAME'), false);
$this->assertEquals($this->object->isValid('something.NAME'), false);
$this->assertEquals($this->object->isValid('NAME.something'), false);
$this->assertEquals($this->object->isValid('something.NAME.something'), false);
$this->assertEquals($this->object->isValid('+129492323'), false);
$this->assertEquals($this->object->isValid('129492323'), false);
$this->assertEquals($this->object->isValid('email'), false);
$this->assertEquals($this->object->isValid('EMAIL'), false);
$this->assertEquals($this->object->isValid('something.EMAIL'), false);
$this->assertEquals($this->object->isValid('EMAIL.something'), false);
$this->assertEquals($this->object->isValid('something.EMAIL.something'), false);
}
}