From 1ce84f1650f1df409bad72d00ca7995c226cbcb6 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 17 Mar 2025 12:39:35 +0100 Subject: [PATCH] WIP --- app/controllers/api/account.php | 14 +++++++++----- app/controllers/api/teams.php | 11 +++++++---- app/init.php | 21 ++++++++++++++++++++- src/Appwrite/Auth/Auth.php | 14 -------------- src/Appwrite/Platform/Tasks/Install.php | 3 ++- tests/unit/Auth/AuthTest.php | 6 ------ 6 files changed, 38 insertions(+), 31 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 9445d937b7..3abedb1f3f 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -38,6 +38,7 @@ use MaxMind\Db\Reader; use Utopia\Abuse\Abuse; use Utopia\App; use Utopia\Audit\Audit as EventAudit; +use Utopia\Auth\Proofs\Password as ProofsPassword; use Utopia\Auth\Store; use Utopia\Config\Config; use Utopia\Database\Database; @@ -364,7 +365,9 @@ App::post('/v1/account') $hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, true]); $passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; - $password = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); + $proof = new ProofsPassword(); + $hash = $proof->hash($password); + try { $userId = $userId == 'unique()' ? ID::unique() : $userId; $user->setAttributes([ @@ -377,7 +380,7 @@ App::post('/v1/account') 'email' => $email, 'emailVerification' => false, 'status' => true, - 'password' => $password, + 'password' => $hash, 'passwordHistory' => $passwordHistory > 0 ? [$password] : [], 'passwordUpdate' => DateTime::now(), 'hash' => Auth::DEFAULT_ALGO, @@ -942,7 +945,7 @@ App::post('/v1/account/sessions/email') // Re-hash if not using recommended algo if ($user->getAttribute('hash') !== Auth::DEFAULT_ALGO) { $user - ->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)) + ->setAttribute('password', (new ProofsPassword())->hash($password)) ->setAttribute('hash', Auth::DEFAULT_ALGO) ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS); $dbForProject->updateDocument('users', $user->getId(), $user); @@ -2930,13 +2933,14 @@ App::patch('/v1/account/email') ->inject('queueForEvents') ->inject('project') ->inject('hooks') - ->action(function (string $email, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks) { + ->inject('proofForPassword') + ->action(function (string $email, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks, ProofsPassword $proofForPassword) { // passwordUpdate will be empty if the user has never set a password $passwordUpdate = $user->getAttribute('passwordUpdate'); if ( !empty($passwordUpdate) && - !Auth::passwordVerify($password, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions')) + !$proofForPassword->verify($password, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions')) ) { // Double check user password throw new Exception(Exception::USER_INVALID_CREDENTIALS); } diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 60d6960445..b1b4445de0 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -27,6 +27,7 @@ use MaxMind\Db\Reader; use Utopia\Abuse\Abuse; use Utopia\App; use Utopia\Audit\Audit; +use Utopia\Auth\Proofs\Password; use Utopia\Auth\Store; use Utopia\Config\Config; use Utopia\Database\Database; @@ -469,7 +470,8 @@ App::post('/v1/teams/:teamId/memberships') ->inject('timelimit') ->inject('queueForStatsUsage') ->inject('plan') - ->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan) { + ->inject('proofForPassword') + ->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Password $proofForPassword) { $isAppUser = Auth::isAppUser(Authorization::getRoles()); $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); @@ -542,6 +544,7 @@ App::post('/v1/teams/:teamId/memberships') try { $userId = ID::unique(); + $hash = $proofForPassword->hash($proofForPassword->generate()); $invitee = Authorization::skip(fn () => $dbForProject->createDocument('users', new Document([ '$id' => $userId, '$permissions' => [ @@ -555,9 +558,9 @@ App::post('/v1/teams/:teamId/memberships') 'emailVerification' => false, 'status' => true, // TODO: Set password empty? - 'password' => Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS), - 'hash' => Auth::DEFAULT_ALGO, - 'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS, + 'password' => $hash, + 'hash' => $proofForPassword->getHash()->getName(), + 'hashOptions' => $proofForPassword->getHash()->getOptions(), /** * Set the password update time to 0 for users created using * team invite and OAuth to allow password updates without an diff --git a/app/init.php b/app/init.php index e54b7b3cc8..ffab067206 100644 --- a/app/init.php +++ b/app/init.php @@ -51,6 +51,9 @@ use PHPMailer\PHPMailer\PHPMailer; use Swoole\Database\PDOProxy; use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis; use Utopia\App; +use Utopia\Auth\Proofs\Code; +use Utopia\Auth\Proofs\Password; +use Utopia\Auth\Proofs\Token; use Utopia\Auth\Store; use Utopia\Cache\Adapter\Redis as RedisCache; use Utopia\Cache\Adapter\Sharding; @@ -1976,6 +1979,22 @@ App::setResource('apiKey', function (Request $request, Document $project): ?Key return Key::decode($project, $key); }, ['request', 'project']); -App::setResource('store', function () { +App::setResource('store', function (): Store { return new Store(); }); + +App::setResource('proofForPassword', function (): Password { + return new Password(); +}); + +App::setResource('proofForToken', function (): Token { + return new Token(); +}); + +App::setResource('proofForCode', function (): Code { + return new Code(); +}); + + + + diff --git a/src/Appwrite/Auth/Auth.php b/src/Appwrite/Auth/Auth.php index b0cba3235e..d47d9ec4b5 100644 --- a/src/Appwrite/Auth/Auth.php +++ b/src/Appwrite/Auth/Auth.php @@ -237,20 +237,6 @@ class Auth } } - /** - * Password Generator. - * - * Generate random password string - * - * @param int $length - * - * @return string - */ - public static function passwordGenerator(int $length = 20): string - { - return \bin2hex(\random_bytes($length)); - } - /** * Token Generator. * diff --git a/src/Appwrite/Platform/Tasks/Install.php b/src/Appwrite/Platform/Tasks/Install.php index c7b1f72453..9b6ec64154 100644 --- a/src/Appwrite/Platform/Tasks/Install.php +++ b/src/Appwrite/Platform/Tasks/Install.php @@ -6,6 +6,7 @@ use Appwrite\Auth\Auth; use Appwrite\Docker\Compose; use Appwrite\Docker\Env; use Appwrite\Utopia\View; +use Utopia\Auth\Proofs\Password; use Utopia\CLI\Console; use Utopia\Config\Config; use Utopia\Platform\Action; @@ -162,7 +163,7 @@ class Install extends Action } if ($var['filter'] === 'password') { - $input[$var['name']] = Auth::passwordGenerator(); + $input[$var['name']] = (new Password())->generate(); continue; } } diff --git a/tests/unit/Auth/AuthTest.php b/tests/unit/Auth/AuthTest.php index 2b19439da6..c2057394d3 100644 --- a/tests/unit/Auth/AuthTest.php +++ b/tests/unit/Auth/AuthTest.php @@ -163,12 +163,6 @@ class AuthTest extends TestCase $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'md8')); } - public function testPasswordGenerator(): void - { - $this->assertEquals(\mb_strlen(Auth::passwordGenerator()), 40); - $this->assertEquals(\mb_strlen(Auth::passwordGenerator(5)), 10); - } - public function testTokenGenerator(): void { $this->assertEquals(\strlen(Auth::tokenGenerator()), 256);