diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 9dbfa7387b..383e659c87 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -44,14 +44,14 @@ App::post('/v1/account') ->label('sdk.description', '/docs/references/account/create.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_USER) + ->label('sdk.response.model', Response::MODEL_ACCOUNT) ->label('abuse-limit', 10) ->param('userId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string "unique()" to auto generate it. 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('email', '', new Email(), 'User email.') ->param('password', '', new Password(), 'User password. Must be at least 8 chars.') ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true) - ->param('hash', Auth::DEFAULT_ALGO, new WhiteList(\array_keys(Auth::SUPPORTED_ALGOS)), 'Hashing algorithm for password. Allowed values are: \'' . \implode('\', \'', \array_keys(Auth::SUPPORTED_ALGOS)) . '\'. The default value is \'' . Auth::DEFAULT_ALGO . '\'.', true) - ->param('hashOptions', Auth::DEFAULT_ALGO_OPTIONS, new JSON(), 'Configuration of hashing algorithm. If left empty, default configuration is used.', true) + ->param('hash', 'plaintext', new WhiteList(\array_keys(Auth::SUPPORTED_ALGOS)), 'Hashing algorithm for password. Allowed values are: \'' . \implode('\', \'', \array_keys(Auth::SUPPORTED_ALGOS)) . '\'. The default algorithm is \'' . Auth::DEFAULT_ALGO . '\'.', true) + ->param('hashOptions', '{}', new JSON(), 'Configuration of hashing algorithm. If left empty, default configuration is used.', true) ->inject('request') ->inject('response') ->inject('project') @@ -66,8 +66,7 @@ App::post('/v1/account') /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Stats\Stats $usage */ - // TODO: Here and everywhere else.. If you dont provide options, it should not be default algo options.. it should be empty. Only default options if we also default algo - $hashOptions = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array + $hashOptionsObject = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array $email = \strtolower($email); if ('console' === $project->getId()) { @@ -104,9 +103,9 @@ App::post('/v1/account') 'email' => $email, 'emailVerification' => false, 'status' => true, - 'password' => Auth::passwordHash($password, $hash, $hashOptions, true), - 'hash' => $hash, - 'hashOptions' => $hashOptions, + 'password' => $hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password, + 'hash' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO : $hash, + 'hashOptions' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO_OPTIONS : $hashOptions, 'passwordUpdate' => \time(), 'registration' => \time(), 'reset' => false, @@ -136,7 +135,7 @@ App::post('/v1/account') ->setParam('users.create', 1) ; $response->setStatusCode(Response::STATUS_CODE_CREATED); - $response->dynamic($user, Response::MODEL_USER); + $response->dynamic($user, Response::MODEL_ACCOUNT); }); App::post('/v1/account/sessions') @@ -191,15 +190,6 @@ App::post('/v1/account/sessions') throw new Exception('Invalid credentials. User is blocked', 401, Exception::USER_BLOCKED); // User is in status blocked } - // Re-hash if not using recommended algo - if($profile->getAttribute('hash') !== Auth::DEFAULT_ALGO) { - $profile - ->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)) - ->setAttribute('hash', Auth::DEFAULT_ALGO) - ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS); - $dbForProject->updateDocument('users', $profile->getId(), $profile); - } - $detector = new Detector($request->getUserAgent('UNKNOWN')); $record = $geodb->get($request->getIP()); $expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG; @@ -220,6 +210,15 @@ App::post('/v1/account/sessions') Authorization::setRole('user:' . $profile->getId()); + // Re-hash if not using recommended algo + if($profile->getAttribute('hash') !== Auth::DEFAULT_ALGO) { + $profile + ->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)) + ->setAttribute('hash', Auth::DEFAULT_ALGO) + ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS); + $dbForProject->updateDocument('users', $profile->getId(), $profile); + } + $session = $dbForProject->createDocument('sessions', $session ->setAttribute('$read', ['user:' . $profile->getId()]) ->setAttribute('$write', ['user:' . $profile->getId()]) @@ -1106,7 +1105,7 @@ App::get('/v1/account') ->label('sdk.description', '/docs/references/account/get.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_USER) + ->label('sdk.response.model', Response::MODEL_ACCOUNT) ->inject('response') ->inject('user') ->inject('usage') @@ -1118,7 +1117,7 @@ App::get('/v1/account') $usage ->setParam('users.read', 1) ; - $response->dynamic($user, Response::MODEL_USER); + $response->dynamic($user, Response::MODEL_ACCOUNT); }); App::get('/v1/account/prefs') @@ -1340,7 +1339,7 @@ App::patch('/v1/account/name') ->label('sdk.description', '/docs/references/account/update-name.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_USER) + ->label('sdk.response.model', Response::MODEL_ACCOUNT) ->param('name', '', new Text(128), 'User name. Max length: 128 chars.') ->inject('response') ->inject('user') @@ -1369,7 +1368,7 @@ App::patch('/v1/account/name') ->setParam('users.update', 1) ; - $response->dynamic($user, Response::MODEL_USER); + $response->dynamic($user, Response::MODEL_ACCOUNT); }); App::patch('/v1/account/password') @@ -1383,11 +1382,11 @@ App::patch('/v1/account/password') ->label('sdk.description', '/docs/references/account/update-password.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_USER) + ->label('sdk.response.model', Response::MODEL_ACCOUNT) ->param('password', '', new Password(), 'New user password. Must be at least 8 chars.') ->param('oldPassword', '', new Password(), 'Current user password. Must be at least 8 chars.', true) - ->param('hash', Auth::DEFAULT_ALGO, new WhiteList(\array_keys(Auth::SUPPORTED_ALGOS)), 'Hashing algorithm for password. Allowed values are: \'' . \implode('\', \'', \array_keys(Auth::SUPPORTED_ALGOS)) . '\'. The default value is \'' . Auth::DEFAULT_ALGO . '\'.', true) - ->param('hashOptions', Auth::DEFAULT_ALGO_OPTIONS, new JSON(), 'Configuration of hashing algorithm. If left empty, default configuration is used.', true) + ->param('hash', 'plaintext', new WhiteList(\array_keys(Auth::SUPPORTED_ALGOS)), 'Hashing algorithm for password. Allowed values are: \'' . \implode('\', \'', \array_keys(Auth::SUPPORTED_ALGOS)) . '\'. The default algorithm is \'' . Auth::DEFAULT_ALGO . '\'.', true) + ->param('hashOptions', '{}', new JSON(), 'Configuration of hashing algorithm. If left empty, default configuration is used.', true) ->inject('response') ->inject('user') ->inject('dbForProject') @@ -1400,7 +1399,7 @@ App::patch('/v1/account/password') /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Stats\Stats $usage */ - $hashOptions = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array + $hashOptionsObject = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array // Check old password only if its an existing user. if ($user->getAttribute('passwordUpdate') !== 0 && !Auth::passwordVerify($oldPassword, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions'))) { // Double check user password @@ -1408,9 +1407,9 @@ App::patch('/v1/account/password') } $user = $dbForProject->updateDocument('users', $user->getId(), $user - ->setAttribute('password', Auth::passwordHash($password, $hash, $hashOptions)) - ->setAttribute('hash', $hash) - ->setAttribute('hashOptions', $hashOptions) + ->setAttribute('password', $hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) + ->setAttribute('hash', $hash === 'plaintext' ? Auth::DEFAULT_ALGO : $hash) + ->setAttribute('hashOptions', $hash === 'plaintext' ? Auth::DEFAULT_ALGO_OPTIONS : $hashOptions) ->setAttribute('passwordUpdate', \time()) ); @@ -1423,7 +1422,7 @@ App::patch('/v1/account/password') $usage ->setParam('users.update', 1) ; - $response->dynamic($user, Response::MODEL_USER); + $response->dynamic($user, Response::MODEL_ACCOUNT); }); App::patch('/v1/account/email') @@ -1437,11 +1436,11 @@ App::patch('/v1/account/email') ->label('sdk.description', '/docs/references/account/update-email.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_USER) + ->label('sdk.response.model', Response::MODEL_ACCOUNT) ->param('email', '', new Email(), 'User email.') ->param('password', '', new Password(), 'User password. Must be at least 8 chars.') - ->param('hash', Auth::DEFAULT_ALGO, new WhiteList(\array_keys(Auth::SUPPORTED_ALGOS)), 'Hashing algorithm for password. Allowed values are: \'' . \implode('\', \'', \array_keys(Auth::SUPPORTED_ALGOS)) . '\'. The default value is \'' . Auth::DEFAULT_ALGO . '\'.', true) - ->param('hashOptions', Auth::DEFAULT_ALGO_OPTIONS, new JSON(), 'Configuration of hashing algorithm. If left empty, default configuration is used.', true) + ->param('hash', 'plaintext', new WhiteList(\array_keys(Auth::SUPPORTED_ALGOS)), 'Hashing algorithm for password. Allowed values are: \'' . \implode('\', \'', \array_keys(Auth::SUPPORTED_ALGOS)) . '\'. The default algorithm is \'' . Auth::DEFAULT_ALGO . '\'.', true) + ->param('hashOptions', '{}', new JSON(), 'Configuration of hashing algorithm. If left empty, default configuration is used.', true) ->inject('response') ->inject('user') ->inject('dbForProject') @@ -1454,7 +1453,7 @@ App::patch('/v1/account/email') /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Stats\Stats $usage */ - $hashOptions = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array + $hashOptionsObject = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array $isAnonymousUser = is_null($user->getAttribute('email')) && is_null($user->getAttribute('password')); // Check if request is from an anonymous account for converting @@ -1474,9 +1473,9 @@ App::patch('/v1/account/email') try { $user = $dbForProject->updateDocument('users', $user->getId(), $user - ->setAttribute('password', $isAnonymousUser ? Auth::passwordHash($password, $hash, $hashOptions) : $user->getAttribute('password', '')) - ->setAttribute('hash', $isAnonymousUser ? $hash : $user->getAttribute('hash', '')) - ->setAttribute('hashOptions', $isAnonymousUser ? $hashOptions : $user->getAttribute('hashOptions', '')) + ->setAttribute('password', $isAnonymousUser ? ($hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) : $user->getAttribute('password', '')) + ->setAttribute('hash', $isAnonymousUser ? ($hash === 'plaintext' ? Auth::DEFAULT_ALGO : $hash) : $user->getAttribute('hash', '')) + ->setAttribute('hashOptions', $isAnonymousUser ? ($hash === 'plaintext' ? Auth::DEFAULT_ALGO_OPTIONS : $hashOptions) : $user->getAttribute('hashOptions', '')) ->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')])) @@ -1494,7 +1493,7 @@ App::patch('/v1/account/email') $usage ->setParam('users.update', 1) ; - $response->dynamic($user, Response::MODEL_USER); + $response->dynamic($user, Response::MODEL_ACCOUNT); }); App::patch('/v1/account/prefs') @@ -1508,7 +1507,7 @@ App::patch('/v1/account/prefs') ->label('sdk.description', '/docs/references/account/update-prefs.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_USER) + ->label('sdk.response.model', Response::MODEL_ACCOUNT) ->param('prefs', [], new Assoc(), 'Prefs key-value JSON object.') ->inject('response') ->inject('user') @@ -1532,7 +1531,7 @@ App::patch('/v1/account/prefs') $usage ->setParam('users.update', 1) ; - $response->dynamic($user, Response::MODEL_USER); + $response->dynamic($user, Response::MODEL_ACCOUNT); }); App::delete('/v1/account') @@ -1583,7 +1582,7 @@ App::delete('/v1/account') ; $events - ->setParam('eventData', $response->output($user, Response::MODEL_USER)) + ->setParam('eventData', $response->output($user, Response::MODEL_ACCOUNT)) ; if (!Config::getParam('domainVerification')) { @@ -2003,8 +2002,8 @@ App::put('/v1/account/recovery') ->param('secret', '', new Text(256), 'Valid reset token.') ->param('password', '', new Password(), 'New user password. Must be at least 8 chars.') ->param('passwordAgain', '', new Password(), 'Repeat new user password. Must be at least 8 chars.') - ->param('hash', Auth::DEFAULT_ALGO, new WhiteList(\array_keys(Auth::SUPPORTED_ALGOS)), 'Hashing algorithm for password. Allowed values are: \'' . \implode('\', \'', \array_keys(Auth::SUPPORTED_ALGOS)) . '\'. The default value is \'' . Auth::DEFAULT_ALGO . '\'.', true) - ->param('hashOptions', Auth::DEFAULT_ALGO_OPTIONS, new JSON(), 'Configuration of hashing algorithm. If left empty, default configuration is used.', true) + ->param('hash', 'plaintext', new WhiteList(\array_keys(Auth::SUPPORTED_ALGOS)), 'Hashing algorithm for password. Allowed values are: \'' . \implode('\', \'', \array_keys(Auth::SUPPORTED_ALGOS)) . '\'. The default algorithm is \'' . Auth::DEFAULT_ALGO . '\'.', true) + ->param('hashOptions', '{}', new JSON(), 'Configuration of hashing algorithm. If left empty, default configuration is used.', true) ->inject('response') ->inject('dbForProject') ->inject('audits') @@ -2015,7 +2014,7 @@ App::put('/v1/account/recovery') /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Stats\Stats $usage */ - $hashOptions = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array + $hashOptionsObject = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array if ($password !== $passwordAgain) { throw new Exception('Passwords must match', 400, Exception::USER_PASSWORD_MISMATCH); @@ -2037,9 +2036,9 @@ App::put('/v1/account/recovery') Authorization::setRole('user:' . $profile->getId()); $profile = $dbForProject->updateDocument('users', $profile->getId(), $profile - ->setAttribute('password', Auth::passwordHash($password, $hash, $hashOptions)) - ->setAttribute('hash', $hash) - ->setAttribute('hashOptions', $hashOptions) + ->setAttribute('password', $hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) + ->setAttribute('hash', $hash === 'plaintext' ? Auth::DEFAULT_ALGO : $hash) + ->setAttribute('hashOptions', $hash === 'plaintext' ? Auth::DEFAULT_ALGO_OPTIONS : $hashOptions) ->setAttribute('passwordUpdate', \time()) ->setAttribute('emailVerification', true) ); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index f4bebf15c6..21bb40a2ce 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -39,8 +39,8 @@ App::post('/v1/users') ->param('email', '', new Email(), 'User email.') ->param('password', '', new Password(), 'User password. Must be at least 8 chars.') ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true) - ->param('hash', Auth::DEFAULT_ALGO, new WhiteList(\array_keys(Auth::SUPPORTED_ALGOS)), 'Hashing algorithm for password. Allowed values are: \'' . \implode('\', \'', \array_keys(Auth::SUPPORTED_ALGOS)) . '\'. The default value is \'' . Auth::DEFAULT_ALGO . '\'.', true) - ->param('hashOptions', Auth::DEFAULT_ALGO_OPTIONS, new JSON(), 'Configuration of hashing algorithm. If left empty, default configuration is used.', true) + ->param('hash', 'plaintext', new WhiteList(\array_keys(Auth::SUPPORTED_ALGOS)), 'Hashing algorithm for password. Allowed values are: \'' . \implode('\', \'', \array_keys(Auth::SUPPORTED_ALGOS)) . '\'. The default algorithm is \'' . Auth::DEFAULT_ALGO . '\'.', true) + ->param('hashOptions', '{}', new JSON(), 'Configuration of hashing algorithm. If left empty, default configuration is used.', true) ->inject('response') ->inject('dbForProject') ->inject('usage') @@ -49,7 +49,7 @@ App::post('/v1/users') /** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Stats\Stats $usage */ - $hashOptions = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array + $hashOptionsObject = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array $email = \strtolower($email); @@ -62,9 +62,9 @@ App::post('/v1/users') 'email' => $email, 'emailVerification' => false, 'status' => true, - 'password' => Auth::passwordHash($password, $hash, $hashOptions), - 'hash' => $hash, - 'hashOptions' => $hashOptions, + 'password' => $hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password, + 'hash' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO : $hash, + 'hashOptions' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO_OPTIONS : $hashOptions, 'passwordUpdate' => \time(), 'registration' => \time(), 'reset' => false, @@ -485,8 +485,8 @@ App::patch('/v1/users/:userId/password') ->label('sdk.response.model', Response::MODEL_USER) ->param('userId', '', new UID(), 'User ID.') ->param('password', '', new Password(), 'New user password. Must be at least 8 chars.') - ->param('hash', Auth::DEFAULT_ALGO, new WhiteList(\array_keys(Auth::SUPPORTED_ALGOS)), 'Hashing algorithm for password. Allowed values are: \'' . \implode('\', \'', \array_keys(Auth::SUPPORTED_ALGOS)) . '\'. The default value is \'' . Auth::DEFAULT_ALGO . '\'.', true) - ->param('hashOptions', Auth::DEFAULT_ALGO_OPTIONS, new JSON(), 'Configuration of hashing algorithm. If left empty, default configuration is used.', true) + ->param('hash', 'plaintext', new WhiteList(\array_keys(Auth::SUPPORTED_ALGOS)), 'Hashing algorithm for password. Allowed values are: \'' . \implode('\', \'', \array_keys(Auth::SUPPORTED_ALGOS)) . '\'. The default algorithm is \'' . Auth::DEFAULT_ALGO . '\'.', true) + ->param('hashOptions', '{}', new JSON(), 'Configuration of hashing algorithm. If left empty, default configuration is used.', true) ->inject('response') ->inject('dbForProject') ->inject('audits') @@ -495,7 +495,7 @@ App::patch('/v1/users/:userId/password') /** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Event\Event $audits */ - $hashOptions = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array + $hashOptionsObject = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array $user = $dbForProject->getDocument('users', $userId); @@ -504,9 +504,9 @@ App::patch('/v1/users/:userId/password') } $user - ->setAttribute('password', Auth::passwordHash($password, $hash, $hashOptions)) - ->setAttribute('hash', $hash) - ->setAttribute('hashOptions', $hashOptions) + ->setAttribute('password', $hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) + ->setAttribute('hash', $hash === 'plaintext' ? Auth::DEFAULT_ALGO : $hash) + ->setAttribute('hashOptions', $hash === 'plaintext' ? Auth::DEFAULT_ALGO_OPTIONS : $hashOptions) ->setAttribute('passwordUpdate', \time()); $user = $dbForProject->updateDocument('users', $user->getId(), $user); diff --git a/src/Appwrite/Utopia/Response.php b/src/Appwrite/Utopia/Response.php index 778724d36e..1ae2e21205 100644 --- a/src/Appwrite/Utopia/Response.php +++ b/src/Appwrite/Utopia/Response.php @@ -8,6 +8,7 @@ use Swoole\Http\Response as SwooleHTTPResponse; use Utopia\Database\Document; use Appwrite\Utopia\Response\Filter; use Appwrite\Utopia\Response\Model; +use Appwrite\Utopia\Response\Model\Account; use Appwrite\Utopia\Response\Model\None; use Appwrite\Utopia\Response\Model\Any; use Appwrite\Utopia\Response\Model\Attribute; @@ -114,6 +115,7 @@ class Response extends SwooleResponse const MODEL_ATTRIBUTE_URL= 'attributeUrl'; // Users + const MODEL_ACCOUNT = 'account'; const MODEL_USER = 'user'; const MODEL_USER_LIST = 'userList'; const MODEL_SESSION = 'session'; @@ -253,6 +255,7 @@ class Response extends SwooleResponse ->setModel(new ModelDocument()) ->setModel(new Log()) ->setModel(new User()) + ->setModel(new Account()) ->setModel(new Preferences()) ->setModel(new Session()) ->setModel(new Token()) diff --git a/src/Appwrite/Utopia/Response/Model.php b/src/Appwrite/Utopia/Response/Model.php index 9a98e55eae..dae0e92c7f 100644 --- a/src/Appwrite/Utopia/Response/Model.php +++ b/src/Appwrite/Utopia/Response/Model.php @@ -87,6 +87,22 @@ abstract class Model return $this; } + /** + * Delete an existing Rule + * If rule exists, it will be removed + * + * @param string $key + * @param array $options + */ + protected function removeRule(string $key): self + { + if(isset($this->rules[$key])) { + unset($this->rules[$key]); + } + + return $this; + } + public function getRequired() { $list = []; diff --git a/src/Appwrite/Utopia/Response/Model/Account.php b/src/Appwrite/Utopia/Response/Model/Account.php new file mode 100644 index 0000000000..52b57b7cac --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/Account.php @@ -0,0 +1,38 @@ +removeRule('password') + ->removeRule('hash') + ->removeRule('hashOptions'); + } + + /** + * Get Name + * + * @return string + */ + public function getName():string + { + return 'Account'; + } + + /** + * Get Type + * + * @return string + */ + public function getType():string + { + return Response::MODEL_ACCOUNT; + } +}