From 8e7b89ec03bbff4b45324a94e037bd3be53f7f5d Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Fri, 16 Dec 2022 10:22:39 +0000 Subject: [PATCH 01/66] feat: check password history --- app/config/collections.php | 11 +++++++++++ app/controllers/api/account.php | 7 +++++++ app/controllers/api/users.php | 7 +++++++ src/Appwrite/Extend/Exception.php | 1 + 4 files changed, 26 insertions(+) diff --git a/app/config/collections.php b/app/config/collections.php index bdc14d9105..16c0f32c4c 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1234,6 +1234,17 @@ $collections = [ 'array' => false, 'filters' => [], ], + [ + '$id' => ID::custom('passwordHistory'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => true, + 'filters' => ['json', 'encrypt'], + ], [ '$id' => ID::custom('password'), 'type' => Database::VAR_STRING, diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index af5c8e4f53..4afa5a2090 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1516,6 +1516,13 @@ App::patch('/v1/account/password') throw new Exception(Exception::USER_INVALID_CREDENTIALS); } + $history = $user->getAttribute('passwordHistory', []); + $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); + + if(in_array($newPassword, $history)) { + throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED, 'The password was recently used', 409); + } + $user = $dbForProject->updateDocument('users', $user->getId(), $user ->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)) ->setAttribute('hash', Auth::DEFAULT_ALGO) diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index dce493b024..bd3278a60c 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -791,6 +791,13 @@ App::patch('/v1/users/:userId/password') throw new Exception(Exception::USER_NOT_FOUND); } + $history = $user->getAttribute('passwordHistory', []); + $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); + + if(in_array($newPassword, $history)) { + throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED, 'The password was recently used', 409); + } + $user ->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)) ->setAttribute('hash', Auth::DEFAULT_ALGO) diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index d25cfb0d40..3940ad6da9 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -64,6 +64,7 @@ class Exception extends \Exception public const USER_ANONYMOUS_CONSOLE_PROHIBITED = 'user_anonymous_console_prohibited'; 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_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'; From 61e03eeedfc8e8426fd62ecfd0aa64253cafd19f Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Fri, 16 Dec 2022 10:29:20 +0000 Subject: [PATCH 02/66] add new password to the history --- app/controllers/api/account.php | 7 ++++++- app/controllers/api/teams.php | 1 + app/controllers/api/users.php | 9 +++++++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 4afa5a2090..0b6423a6b1 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -109,6 +109,7 @@ App::post('/v1/account') 'email' => $email, 'emailVerification' => false, 'status' => true, + 'passwordHistory' => [Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)], 'password' => Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS), 'hash' => Auth::DEFAULT_ALGO, 'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS, @@ -501,6 +502,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') 'email' => $email, 'emailVerification' => true, 'status' => true, // Email should already be authenticated by OAuth2 provider + 'passwordHistory' => [Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)], 'password' => Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS), 'hash' => Auth::DEFAULT_ALGO, 'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS, @@ -1523,8 +1525,11 @@ App::patch('/v1/account/password') throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED, 'The password was recently used', 409); } + $history[] = $newPassword; + $user = $dbForProject->updateDocument('users', $user->getId(), $user - ->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)) + ->setAttribute('passwordHistory', $history) + ->setAttribute('password', $newPassword) ->setAttribute('hash', Auth::DEFAULT_ALGO) ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS) ->setAttribute('passwordUpdate', DateTime::now())); diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 567560c6a8..763bc22375 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -358,6 +358,7 @@ App::post('/v1/teams/:teamId/memberships') 'email' => $email, 'emailVerification' => false, 'status' => true, + 'passwordHistory' => [Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)], 'password' => Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS), 'hash' => Auth::DEFAULT_ALGO, 'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS, diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index bd3278a60c..32c7735dea 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -49,6 +49,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e ? ID::unique() : ID::custom($userId); + $password = (!empty($password)) ? ($hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) : null; $user = $dbForProject->createDocument('users', new Document([ '$id' => $userId, '$permissions' => [ @@ -61,7 +62,8 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e 'phone' => $phone, 'phoneVerification' => false, 'status' => true, - 'password' => (!empty($password)) ? ($hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) : null, + 'passwordHistory' => is_null($password) ? [] : [$password], + 'password' => $password, 'hash' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO : $hash, 'hashOptions' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO_OPTIONS : $hashOptions, 'passwordUpdate' => (!empty($password)) ? DateTime::now() : null, @@ -798,8 +800,11 @@ App::patch('/v1/users/:userId/password') throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED, 'The password was recently used', 409); } + $history[] = $newPassword; + $user - ->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)) + ->setAttribute('passwordHistory', $history) + ->setAttribute('password', $newPassword) ->setAttribute('hash', Auth::DEFAULT_ALGO) ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS) ->setAttribute('passwordUpdate', DateTime::now()); From cfd5a91c43e9893ae1740e517b95470b13471eb5 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Fri, 16 Dec 2022 10:37:49 +0000 Subject: [PATCH 03/66] feat: project endpoint to set password history limit --- app/controllers/api/projects.php | 31 +++++++++++++++++++++++++++++++ app/init.php | 1 + 2 files changed, 32 insertions(+) diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 26def36fa5..e9425e8b6e 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -576,6 +576,37 @@ App::patch('/v1/projects/:projectId/auth/:method') $response->dynamic($project, Response::MODEL_PROJECT); }); +App::patch('/v1/projects/:projectId/auth/password-history') + ->desc('Update Project users limit') + ->groups(['api', 'projects']) + ->label('scope', 'projects.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) + ->label('sdk.namespace', 'projects') + ->label('sdk.method', 'updateAuthLimit') + ->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('limit', false, new Range(1, APP_LIMIT_USER_SESSIONS_MAX), 'Set the max number of password history to keep for users. Value allowed is between 0-' . APP_LIMIT_USER_PASSWORD_HISTORY . '. Default is 0') + ->inject('response') + ->inject('dbForConsole') + ->action(function (string $projectId, int $limit, Response $response, Database $dbForConsole) { + + $project = $dbForConsole->getDocument('projects', $projectId); + + if ($project->isEmpty()) { + throw new Exception(Exception::PROJECT_NOT_FOUND); + } + + $auths = $project->getAttribute('auths', []); + $auths['passwordHistory'] = $limit; + + $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 users limit') ->groups(['api', 'projects']) diff --git a/app/init.php b/app/init.php index 2ab86e9b11..40e569f369 100644 --- a/app/init.php +++ b/app/init.php @@ -84,6 +84,7 @@ const APP_MODE_ADMIN = 'admin'; const APP_PAGING_LIMIT = 12; const APP_LIMIT_COUNT = 5000; const APP_LIMIT_USERS = 10000; +const APP_LIMIT_USER_PASSWORD_HISTORY = 20; const APP_LIMIT_USER_SESSIONS_MAX = 100; const APP_LIMIT_USER_SESSIONS_DEFAULT = 10; const APP_LIMIT_ANTIVIRUS = 20000000; //20MB From 0b1b7b817025709a784ae106bfd1c269c488dc49 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Fri, 16 Dec 2022 10:47:08 +0000 Subject: [PATCH 04/66] feat: check history limit --- app/controllers/api/account.php | 23 ++++++++++++++++------- app/controllers/api/projects.php | 2 +- app/controllers/api/users.php | 23 ++++++++++++++++------- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 0b6423a6b1..50b8460d4b 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1509,24 +1509,33 @@ App::patch('/v1/account/password') ->param('oldPassword', '', new Password(), 'Current user password. Must be at least 8 chars.', true) ->inject('response') ->inject('user') + ->inject('project') ->inject('dbForProject') ->inject('events') - ->action(function (string $password, string $oldPassword, Response $response, Document $user, Database $dbForProject, Event $events) { + ->action(function (string $password, string $oldPassword, Response $response, Document $user, Document $project, Database $dbForProject, Event $events) { // Check old password only if its an existing user. if (!empty($user->getAttribute('passwordUpdate')) && !Auth::passwordVerify($oldPassword, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions'))) { // Double check user password throw new Exception(Exception::USER_INVALID_CREDENTIALS); } - $history = $user->getAttribute('passwordHistory', []); - $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); + $historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; - if(in_array($newPassword, $history)) { - throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED, 'The password was recently used', 409); + $history = []; + if($historyLimit > 0) { + $history = $user->getAttribute('passwordHistory', []); + $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); + + if(in_array($newPassword, $history)) { + throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED, 'The password was recently used', 409); + } + + $history[] = $newPassword; + while(count($history) > $historyLimit) { + array_pop($history); + } } - $history[] = $newPassword; - $user = $dbForProject->updateDocument('users', $user->getId(), $user ->setAttribute('passwordHistory', $history) ->setAttribute('password', $newPassword) diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index e9425e8b6e..ab741a797e 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -81,7 +81,7 @@ App::post('/v1/projects') } $auth = Config::getParam('auth', []); - $auths = ['limit' => 0, 'maxSessions' => APP_LIMIT_USER_SESSIONS_DEFAULT, 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG]; + $auths = ['limit' => 0, 'maxSessions' => APP_LIMIT_USER_SESSIONS_DEFAULT, 'passwordHistory' => 0, 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG]; foreach ($auth as $index => $method) { $auths[$method['key'] ?? ''] = true; } diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 32c7735dea..3974234d38 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -783,9 +783,10 @@ App::patch('/v1/users/:userId/password') ->param('userId', '', new UID(), 'User ID.') ->param('password', '', new Password(), 'New user password. Must be at least 8 chars.') ->inject('response') + ->inject('project') ->inject('dbForProject') ->inject('events') - ->action(function (string $userId, string $password, Response $response, Database $dbForProject, Event $events) { + ->action(function (string $userId, string $password, Response $response, Document $project, Database $dbForProject, Event $events) { $user = $dbForProject->getDocument('users', $userId); @@ -793,15 +794,23 @@ App::patch('/v1/users/:userId/password') throw new Exception(Exception::USER_NOT_FOUND); } - $history = $user->getAttribute('passwordHistory', []); - $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); + $historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; - if(in_array($newPassword, $history)) { - throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED, 'The password was recently used', 409); + $history = []; + if($historyLimit > 0) { + $history = $user->getAttribute('passwordHistory', []); + $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); + + if(in_array($newPassword, $history)) { + throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED, 'The password was recently used', 409); + } + + $history[] = $newPassword; + while(count($history) > $historyLimit) { + array_pop($history); + } } - $history[] = $newPassword; - $user ->setAttribute('passwordHistory', $history) ->setAttribute('password', $newPassword) From 51ad9c6479627a6c71d07d14aa25dc3aed5ceb7b Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 18 Dec 2022 04:38:27 +0000 Subject: [PATCH 05/66] fix: get history limit before adding password to history --- app/controllers/api/account.php | 8 ++++-- app/controllers/api/teams.php | 4 ++- app/controllers/api/users.php | 45 ++++++++++++++++++++------------- 3 files changed, 36 insertions(+), 21 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 50b8460d4b..235c2ffbfc 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -97,6 +97,8 @@ App::post('/v1/account') } } + $passwordHistory = $project->getAttribute('auths',[])['passwordHistory'] ?? 0; + try { $userId = $userId == 'unique()' ? ID::unique() : $userId; $user = Authorization::skip(fn() => $dbForProject->createDocument('users', new Document([ @@ -109,7 +111,7 @@ App::post('/v1/account') 'email' => $email, 'emailVerification' => false, 'status' => true, - 'passwordHistory' => [Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)], + 'passwordHistory' => $passwordHistory > 0 ? [Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)] : [], 'password' => Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS), 'hash' => Auth::DEFAULT_ALGO, 'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS, @@ -490,6 +492,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') } } + $passwordHistory = $project->getAttribute('auths',[])['passwordHistory'] ?? 0; + try { $userId = ID::unique(); $user = Authorization::skip(fn() => $dbForProject->createDocument('users', new Document([ @@ -502,7 +506,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') 'email' => $email, 'emailVerification' => true, 'status' => true, // Email should already be authenticated by OAuth2 provider - 'passwordHistory' => [Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)], + 'passwordHistory' => $passwordHistory > 0 ? [Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)] : null, 'password' => Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS), 'hash' => Auth::DEFAULT_ALGO, 'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS, diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 763bc22375..5934ec9c4c 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -345,6 +345,8 @@ App::post('/v1/teams/:teamId/memberships') } } + $passwordHistory = $project->getAttribute('auths',[])['passwordHistory'] ?? 0; + try { $userId = ID::unique(); $invitee = Authorization::skip(fn() => $dbForProject->createDocument('users', new Document([ @@ -358,7 +360,7 @@ App::post('/v1/teams/:teamId/memberships') 'email' => $email, 'emailVerification' => false, 'status' => true, - 'passwordHistory' => [Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)], + 'passwordHistory' => $passwordHistory > 0 ? [Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)] : [], 'password' => Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS), 'hash' => Auth::DEFAULT_ALGO, 'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS, diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 3974234d38..33336c1add 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -36,9 +36,10 @@ use MaxMind\Db\Reader; use Utopia\Validator\Integer; /** 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, Database $dbForProject, Event $events): Document +function createUser(string $hash, mixed $hashOptions, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Document $project, Database $dbForProject, Event $events): Document { $hashOptionsObject = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array + $passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; if (!empty($email)) { $email = \strtolower($email); @@ -62,7 +63,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e 'phone' => $phone, 'phoneVerification' => false, 'status' => true, - 'passwordHistory' => is_null($password) ? [] : [$password], + 'passwordHistory' => is_null($password) && $passwordHistory === 0 ? [] : [$password], 'password' => $password, 'hash' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO : $hash, 'hashOptions' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO_OPTIONS : $hashOptions, @@ -106,10 +107,11 @@ App::post('/v1/users') ->param('password', null, new Password(), 'Plain text user password. Must be at least 8 chars.', true) ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true) ->inject('response') + ->inject('project') ->inject('dbForProject') ->inject('events') - ->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Database $dbForProject, Event $events) { - $user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $dbForProject, $events); + ->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $events) { + $user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $project, $dbForProject, $events); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -136,10 +138,11 @@ App::post('/v1/users/bcrypt') ->param('password', '', new Password(), 'User password hashed using Bcrypt.') ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true) ->inject('response') + ->inject('project') ->inject('dbForProject') ->inject('events') - ->action(function (string $userId, string $email, string $password, string $name, Response $response, Database $dbForProject, Event $events) { - $user = createUser('bcrypt', '{}', $userId, $email, $password, null, $name, $dbForProject, $events); + ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $events) { + $user = createUser('bcrypt', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $events); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -166,10 +169,11 @@ App::post('/v1/users/md5') ->param('password', '', new Password(), 'User password hashed using MD5.') ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true) ->inject('response') + ->inject('project') ->inject('dbForProject') ->inject('events') - ->action(function (string $userId, string $email, string $password, string $name, Response $response, Database $dbForProject, Event $events) { - $user = createUser('md5', '{}', $userId, $email, $password, null, $name, $dbForProject, $events); + ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $events) { + $user = createUser('md5', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $events); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -196,10 +200,11 @@ App::post('/v1/users/argon2') ->param('password', '', new Password(), 'User password hashed using Argon2.') ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true) ->inject('response') + ->inject('project') ->inject('dbForProject') ->inject('events') - ->action(function (string $userId, string $email, string $password, string $name, Response $response, Database $dbForProject, Event $events) { - $user = createUser('argon2', '{}', $userId, $email, $password, null, $name, $dbForProject, $events); + ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $events) { + $user = createUser('argon2', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $events); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -227,16 +232,17 @@ App::post('/v1/users/sha') ->param('passwordVersion', '', new WhiteList(['sha1', 'sha224', 'sha256', 'sha384', 'sha512/224', 'sha512/256', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512']), "Optional SHA version used to hash password. Allowed values are: 'sha1', 'sha224', 'sha256', 'sha384', 'sha512/224', 'sha512/256', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512'", true) ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true) ->inject('response') + ->inject('project') ->inject('dbForProject') ->inject('events') - ->action(function (string $userId, string $email, string $password, string $passwordVersion, string $name, Response $response, Database $dbForProject, Event $events) { + ->action(function (string $userId, string $email, string $password, string $passwordVersion, string $name, Response $response, Document $project, Database $dbForProject, Event $events) { $options = '{}'; if (!empty($passwordVersion)) { $options = '{"version":"' . $passwordVersion . '"}'; } - $user = createUser('sha', $options, $userId, $email, $password, null, $name, $dbForProject, $events); + $user = createUser('sha', $options, $userId, $email, $password, null, $name, $project, $dbForProject, $events); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -263,10 +269,11 @@ App::post('/v1/users/phpass') ->param('password', '', new Password(), 'User password hashed using PHPass.') ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true) ->inject('response') + ->inject('project') ->inject('dbForProject') ->inject('events') - ->action(function (string $userId, string $email, string $password, string $name, Response $response, Database $dbForProject, Event $events) { - $user = createUser('phpass', '{}', $userId, $email, $password, null, $name, $dbForProject, $events); + ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $events) { + $user = createUser('phpass', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $events); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -298,9 +305,10 @@ App::post('/v1/users/scrypt') ->param('passwordLength', 64, new Integer(), 'Optional hash length used to hash password.') ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true) ->inject('response') + ->inject('project') ->inject('dbForProject') ->inject('events') - ->action(function (string $userId, string $email, string $password, string $passwordSalt, int $passwordCpu, int $passwordMemory, int $passwordParallel, int $passwordLength, string $name, Response $response, Database $dbForProject, Event $events) { + ->action(function (string $userId, string $email, string $password, string $passwordSalt, int $passwordCpu, int $passwordMemory, int $passwordParallel, int $passwordLength, string $name, Response $response, Document $project, Database $dbForProject, Event $events) { $options = [ 'salt' => $passwordSalt, 'costCpu' => $passwordCpu, @@ -309,7 +317,7 @@ App::post('/v1/users/scrypt') 'length' => $passwordLength ]; - $user = createUser('scrypt', \json_encode($options), $userId, $email, $password, null, $name, $dbForProject, $events); + $user = createUser('scrypt', \json_encode($options), $userId, $email, $password, null, $name, $project, $dbForProject, $events); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -339,10 +347,11 @@ App::post('/v1/users/scrypt-modified') ->param('passwordSignerKey', '', new Text(128), 'Signer key used to hash password.') ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true) ->inject('response') + ->inject('project') ->inject('dbForProject') ->inject('events') - ->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Database $dbForProject, Event $events) { - $user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $dbForProject, $events); + ->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Document $project, Database $dbForProject, Event $events) { + $user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $project, $dbForProject, $events); $response ->setStatusCode(Response::STATUS_CODE_CREATED) From 0eae73e7b8c67095844c2abf419dacdd19454595 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 18 Dec 2022 06:27:41 +0000 Subject: [PATCH 06/66] fix: password comparison --- app/config/collections.php | 2 +- app/controllers/api/account.php | 20 +++-- app/controllers/api/projects.php | 2 +- app/controllers/api/users.php | 4 +- .../Utopia/Response/Model/Project.php | 7 ++ .../Projects/ProjectsConsoleClientTest.php | 85 +++++++++++++++++++ tests/e2e/Services/Users/UsersBase.php | 74 ++++++++++++++-- 7 files changed, 175 insertions(+), 19 deletions(-) diff --git a/app/config/collections.php b/app/config/collections.php index 16c0f32c4c..cb4d6adf36 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1243,7 +1243,7 @@ $collections = [ 'required' => false, 'default' => null, 'array' => true, - 'filters' => ['json', 'encrypt'], + 'filters' => [], ], [ '$id' => ID::custom('password'), diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 235c2ffbfc..078d3ce0ce 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -99,6 +99,7 @@ App::post('/v1/account') $passwordHistory = $project->getAttribute('auths',[])['passwordHistory'] ?? 0; + $password = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); try { $userId = $userId == 'unique()' ? ID::unique() : $userId; $user = Authorization::skip(fn() => $dbForProject->createDocument('users', new Document([ @@ -111,8 +112,8 @@ App::post('/v1/account') 'email' => $email, 'emailVerification' => false, 'status' => true, - 'passwordHistory' => $passwordHistory > 0 ? [Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)] : [], - 'password' => Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS), + 'passwordHistory' => $passwordHistory > 0 ? [$password] : [], + 'password' => $password, 'hash' => Auth::DEFAULT_ALGO, 'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS, 'passwordUpdate' => DateTime::now(), @@ -1523,17 +1524,20 @@ App::patch('/v1/account/password') throw new Exception(Exception::USER_INVALID_CREDENTIALS); } - $historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; + $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); + $historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; $history = []; if($historyLimit > 0) { $history = $user->getAttribute('passwordHistory', []); - $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); - - if(in_array($newPassword, $history)) { - throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED, 'The password was recently used', 409); + + foreach($history as $hash) { + if(Auth::passwordVerify($password, $hash, $user->getAttribute('hash'), $user->getAttribute('hashOptions'))) + { + throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED, 'The password was recently used', 409); + } } - + $history[] = $newPassword; while(count($history) > $historyLimit) { array_pop($history); diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index ab741a797e..f863741763 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -587,7 +587,7 @@ App::patch('/v1/projects/:projectId/auth/password-history') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_PROJECT) ->param('projectId', '', new UID(), 'Project unique ID.') - ->param('limit', false, new Range(1, APP_LIMIT_USER_SESSIONS_MAX), 'Set the max number of password history to keep for users. Value allowed is between 0-' . APP_LIMIT_USER_PASSWORD_HISTORY . '. Default is 0') + ->param('limit', false, new Range(0, APP_LIMIT_USER_SESSIONS_MAX), 'Set the max number of password history to keep for users. Value allowed is between 0-' . APP_LIMIT_USER_PASSWORD_HISTORY . '. Default is 0') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, int $limit, Response $response, Database $dbForConsole) { diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 33336c1add..72e4b594f8 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -803,12 +803,12 @@ App::patch('/v1/users/:userId/password') throw new Exception(Exception::USER_NOT_FOUND); } + $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); + $historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; - $history = []; if($historyLimit > 0) { $history = $user->getAttribute('passwordHistory', []); - $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); if(in_array($newPassword, $history)) { throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED, 'The password was recently used', 409); diff --git a/src/Appwrite/Utopia/Response/Model/Project.php b/src/Appwrite/Utopia/Response/Model/Project.php index 28a9c86ff0..a9e809c0a8 100644 --- a/src/Appwrite/Utopia/Response/Model/Project.php +++ b/src/Appwrite/Utopia/Response/Model/Project.php @@ -120,6 +120,12 @@ class Project extends Model 'default' => 10, 'example' => 10, ]) + ->addRule('authPasswordHistory', [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Max password history to save per user. 20 maximum. 0 for ignoring the password history.', + 'default' => 0, + 'example' => 5, + ]) ->addRule('providers', [ 'type' => Response::MODEL_PROVIDER, 'description' => 'List of Providers.', @@ -240,6 +246,7 @@ class Project extends Model $document->setAttribute('authLimit', $authValues['limit'] ?? 0); $document->setAttribute('authDuration', $authValues['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG); $document->setAttribute('authSessionLimit', $authValues['maxSessions'] ?? APP_LIMIT_USER_SESSIONS_DEFAULT); + $document->setAttribute('authPasswordHistory', $authValues['passwordHistory'] ?? 0); foreach ($auth as $index => $method) { $key = $method['key']; diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index 889b73d612..504643f61f 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -992,6 +992,91 @@ class ProjectsConsoleClientTest extends Scope return $data; } + + /** + * @depends testUpdateProjectAuthLimit + */ + public function testUpdateProjectAuthPasswordHistory($data): array + { + $id = $data['projectId'] ?? ''; + + /** + * Test for Success + */ + $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/auth/password-history', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'limit' => 1, + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(1, $response['body']['authPasswordHistory']); + + + $email = uniqid() . 'user@localhost.test'; + $password = 'password'; + $name = 'User Name'; + + /** + * Create new user + */ + $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $id, + ]), [ + 'userId' => ID::unique(), + 'email' => $email, + 'password' => $password, + 'name' => $name, + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // create session + $session = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $id, + ], [ + 'email' => $email, + 'password' => $password, + ]); + $this->assertEquals(201, $session['headers']['status-code']); + $session = $this->client->parseCookie((string)$session['headers']['set-cookie'])['a_session_' . $id]; + + $response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $id, + 'cookie' => 'a_session_' . $id . '=' . $session, + ]), [ + 'oldPassword' => $password, + 'password' => $password, + ]); + + var_dump($response['body']); + + $this->assertEquals(409, $response['headers']['status-code']); + + + /** + * Reset + */ + $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/auth/password-history', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'limit' => 0, + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(0, $response['body']['authPasswordHistory']); + return $data; + } + + public function testUpdateProjectServiceStatusAdmin(): array { $team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ diff --git a/tests/e2e/Services/Users/UsersBase.php b/tests/e2e/Services/Users/UsersBase.php index ac3b116b32..457b63a454 100644 --- a/tests/e2e/Services/Users/UsersBase.php +++ b/tests/e2e/Services/Users/UsersBase.php @@ -56,7 +56,7 @@ trait UsersBase $this->assertEquals(true, $res['body']['status']); $this->assertGreaterThan('2000-01-01 00:00:00', $res['body']['registration']); - /** + /** * Test Create with hashed passwords */ $res = $this->client->call(Client::METHOD_POST, '/users/md5', array_merge([ @@ -180,7 +180,7 @@ trait UsersBase */ public function testCreateUserSessionHashed(array $data): void { - $userIds = [ 'md5', 'bcrypt', 'argon2', 'sha512', 'scrypt', 'phpass', 'scrypt-modified' ]; + $userIds = ['md5', 'bcrypt', 'argon2', 'sha512', 'scrypt', 'phpass', 'scrypt-modified']; foreach ($userIds as $userId) { // Ensure sessions can be created with hashed passwords @@ -236,7 +236,7 @@ trait UsersBase { /** * Test for SUCCESS - */ + */ // Email + password $response = $this->client->call(Client::METHOD_POST, '/users', array_merge([ @@ -812,6 +812,66 @@ trait UsersBase return $data; } + /** + * @depends testUpdateUserPassword + */ + public function testUserPasswordHistory(array $data): array + { + $userId = $data['userId']; + $projectId = $this->getProject()['$id']; + /** + * Test for SUCCESS + */ + $user = $this->client->call(Client::METHOD_PATCH, '/users/' . $data['userId'] . '/password', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'password' => 'password2', + ]); + $this->assertEquals($user['headers']['status-code'], 200); + $this->assertNotEmpty($user['body']['$id']); + + $user = $this->client->call(Client::METHOD_PATCH, '/users/' . $data['userId'] . '/password', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'password' => 'password2', + ]); + $this->assertEquals($user['headers']['status-code'], 200); + $this->assertNotEmpty($user['body']['$id']); + + $project = $this->client->call(Client::METHOD_PATCH, '/projects/' . $projectId . '/auth/password-history', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => 'console', + 'cookie' => 'a_session_console=' . $this->getRoot()['session'], + 'x-appwrite-mode' => 'admin' + ]), [ + 'limit' => 2 + ]); + $this->assertEquals(200, $project['headers']['status-code']); + $this->assertEquals(2, $project['body']['passwordHistory']); + + $user = $this->client->call(Client::METHOD_PATCH, '/users/' . $data['userId'] . '/password', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'password' => 'password2', + ]); + $this->assertEquals($user['headers']['status-code'], 200); + $this->assertNotEmpty($user['body']['$id']); + + $user = $this->client->call(Client::METHOD_PATCH, '/users/' . $data['userId'] . '/password', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'password' => 'password2', + ]); + $this->assertEquals(409, $user['headers']['status-code']); + $this->assertNotEmpty($user['body']['$id']); + + return $data; + } + /** * @depends testGetUser */ @@ -953,7 +1013,7 @@ trait UsersBase $this->assertEquals($user['headers']['status-code'], 200); $this->assertEquals($user['body']['phone'], $updatedNumber); - /** + /** * Test for FAILURE */ @@ -1023,7 +1083,7 @@ trait UsersBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'limit(1)' ], + 'queries' => ['limit(1)'], ]); $this->assertEquals($logs['headers']['status-code'], 200); @@ -1035,7 +1095,7 @@ trait UsersBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'offset(1)' ], + 'queries' => ['offset(1)'], ]); $this->assertEquals($logs['headers']['status-code'], 200); @@ -1046,7 +1106,7 @@ trait UsersBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'limit(1)', 'offset(1)' ], + 'queries' => ['limit(1)', 'offset(1)'], ]); $this->assertEquals($logs['headers']['status-code'], 200); From 6612e6edf0de6c0d84160be0f67d2b2ecc9e9749 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 18 Dec 2022 06:28:19 +0000 Subject: [PATCH 07/66] fix: password verify --- app/controllers/api/users.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 72e4b594f8..65d31d8d3b 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -809,11 +809,14 @@ App::patch('/v1/users/:userId/password') $history = []; if($historyLimit > 0) { $history = $user->getAttribute('passwordHistory', []); - - if(in_array($newPassword, $history)) { - throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED, 'The password was recently used', 409); + + foreach($history as $hash) { + if(Auth::passwordVerify($password, $hash, $user->getAttribute('hash'), $user->getAttribute('hashOptions'))) + { + throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED, 'The password was recently used', 409); + } } - + $history[] = $newPassword; while(count($history) > $historyLimit) { array_pop($history); From 4abc30eed134c02fe0fafb6df52a0c1437e68eae Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 18 Dec 2022 06:31:14 +0000 Subject: [PATCH 08/66] refactor: fix formatting issues --- app/controllers/api/account.php | 15 +++++++-------- app/controllers/api/teams.php | 2 +- app/controllers/api/users.php | 13 ++++++------- .../Projects/ProjectsConsoleClientTest.php | 2 +- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 078d3ce0ce..02a05e7d92 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -97,7 +97,7 @@ App::post('/v1/account') } } - $passwordHistory = $project->getAttribute('auths',[])['passwordHistory'] ?? 0; + $passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; $password = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); try { @@ -493,7 +493,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') } } - $passwordHistory = $project->getAttribute('auths',[])['passwordHistory'] ?? 0; + $passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; try { $userId = ID::unique(); @@ -1528,18 +1528,17 @@ App::patch('/v1/account/password') $historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; $history = []; - if($historyLimit > 0) { + if ($historyLimit > 0) { $history = $user->getAttribute('passwordHistory', []); - - foreach($history as $hash) { - if(Auth::passwordVerify($password, $hash, $user->getAttribute('hash'), $user->getAttribute('hashOptions'))) - { + + foreach ($history as $hash) { + if (Auth::passwordVerify($password, $hash, $user->getAttribute('hash'), $user->getAttribute('hashOptions'))) { throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED, 'The password was recently used', 409); } } $history[] = $newPassword; - while(count($history) > $historyLimit) { + while (count($history) > $historyLimit) { array_pop($history); } } diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 5934ec9c4c..7775346a8a 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -345,7 +345,7 @@ App::post('/v1/teams/:teamId/memberships') } } - $passwordHistory = $project->getAttribute('auths',[])['passwordHistory'] ?? 0; + $passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; try { $userId = ID::unique(); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 65d31d8d3b..bcfe5575c8 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -804,21 +804,20 @@ App::patch('/v1/users/:userId/password') } $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); - + $historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; $history = []; - if($historyLimit > 0) { + if ($historyLimit > 0) { $history = $user->getAttribute('passwordHistory', []); - - foreach($history as $hash) { - if(Auth::passwordVerify($password, $hash, $user->getAttribute('hash'), $user->getAttribute('hashOptions'))) - { + + foreach ($history as $hash) { + if (Auth::passwordVerify($password, $hash, $user->getAttribute('hash'), $user->getAttribute('hashOptions'))) { throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED, 'The password was recently used', 409); } } $history[] = $newPassword; - while(count($history) > $historyLimit) { + while (count($history) > $historyLimit) { array_pop($history); } } diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index 504643f61f..ff49739976 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -1060,7 +1060,7 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(409, $response['headers']['status-code']); - + /** * Reset */ From ba77b35f05b0fe41f4bf52f5d78be5526adefc54 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 18 Dec 2022 14:05:55 +0545 Subject: [PATCH 09/66] Update app/controllers/api/projects.php Co-authored-by: Eldad A. Fux --- app/controllers/api/projects.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index f863741763..8db664df57 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -587,7 +587,7 @@ App::patch('/v1/projects/:projectId/auth/password-history') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_PROJECT) ->param('projectId', '', new UID(), 'Project unique ID.') - ->param('limit', false, new Range(0, APP_LIMIT_USER_SESSIONS_MAX), 'Set the max number of password history to keep for users. Value allowed is between 0-' . APP_LIMIT_USER_PASSWORD_HISTORY . '. Default is 0') + ->param('limit', false, new Range(0, APP_LIMIT_USER_SESSIONS_MAX), 'Set the max number of password to store in user history. User can't choose a new password that is already stored in the password history list. Max number of password allowed in history is' . APP_LIMIT_USER_PASSWORD_HISTORY . '. Default value is 0') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, int $limit, Response $response, Database $dbForConsole) { From 9114a61fb2cde2ad2b7543e314a86b6dadb2bce3 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 18 Dec 2022 14:08:45 +0545 Subject: [PATCH 10/66] Update src/Appwrite/Utopia/Response/Model/Project.php Co-authored-by: Eldad A. Fux --- src/Appwrite/Utopia/Response/Model/Project.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Utopia/Response/Model/Project.php b/src/Appwrite/Utopia/Response/Model/Project.php index a9e809c0a8..2dfd96c9eb 100644 --- a/src/Appwrite/Utopia/Response/Model/Project.php +++ b/src/Appwrite/Utopia/Response/Model/Project.php @@ -122,7 +122,7 @@ class Project extends Model ]) ->addRule('authPasswordHistory', [ 'type' => self::TYPE_INTEGER, - 'description' => 'Max password history to save per user. 20 maximum. 0 for ignoring the password history.', + 'description' => 'Max password history to save per user. Max passwords limit allowed in history is 20. Use 0 for disabling password history.', 'default' => 0, 'example' => 5, ]) From 15f52b933b060a6f78de55a7038c61ec121fb385 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 18 Dec 2022 08:34:57 +0000 Subject: [PATCH 11/66] fix excaping --- app/controllers/api/projects.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 8db664df57..327ced582b 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -587,7 +587,7 @@ App::patch('/v1/projects/:projectId/auth/password-history') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_PROJECT) ->param('projectId', '', new UID(), 'Project unique ID.') - ->param('limit', false, new Range(0, APP_LIMIT_USER_SESSIONS_MAX), 'Set the max number of password to store in user history. User can't choose a new password that is already stored in the password history list. Max number of password allowed in history is' . APP_LIMIT_USER_PASSWORD_HISTORY . '. Default value is 0') + ->param('limit', false, new Range(0, APP_LIMIT_USER_SESSIONS_MAX), 'Set the max number of password to store in user history. User can\'t choose a new password that is already stored in the password history list. Max number of password allowed in history is' . APP_LIMIT_USER_PASSWORD_HISTORY . '. Default value is 0') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, int $limit, Response $response, Database $dbForConsole) { From 3f26e66d267fd9cd9b82a5c0e39900a5ec7431d9 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 18 Dec 2022 08:35:12 +0000 Subject: [PATCH 12/66] not add random password to history --- app/controllers/api/teams.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 7775346a8a..567560c6a8 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -345,8 +345,6 @@ App::post('/v1/teams/:teamId/memberships') } } - $passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; - try { $userId = ID::unique(); $invitee = Authorization::skip(fn() => $dbForProject->createDocument('users', new Document([ @@ -360,7 +358,6 @@ App::post('/v1/teams/:teamId/memberships') 'email' => $email, 'emailVerification' => false, 'status' => true, - 'passwordHistory' => $passwordHistory > 0 ? [Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)] : [], 'password' => Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS), 'hash' => Auth::DEFAULT_ALGO, 'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS, From d098c8c6989695139a2395af46d7946d9931e256 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 18 Dec 2022 08:47:02 +0000 Subject: [PATCH 13/66] use array slice --- app/controllers/api/account.php | 4 ++-- app/controllers/api/users.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 02a05e7d92..5fb3eb7402 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1538,8 +1538,8 @@ App::patch('/v1/account/password') } $history[] = $newPassword; - while (count($history) > $historyLimit) { - array_pop($history); + if(count($history) > $historyLimit) { + array_slice($history, (count($history) - $historyLimit), $historyLimit); } } diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index bcfe5575c8..a9e40288ed 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -817,8 +817,8 @@ App::patch('/v1/users/:userId/password') } $history[] = $newPassword; - while (count($history) > $historyLimit) { - array_pop($history); + if(count($history) > $historyLimit) { + array_slice($history, (count($history) - $historyLimit), $historyLimit); } } From 7a9b9f271081422c7de7abc87c5458bf9b8eca4b Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 18 Dec 2022 08:53:01 +0000 Subject: [PATCH 14/66] remove test --- tests/e2e/Services/Users/UsersBase.php | 60 -------------------------- 1 file changed, 60 deletions(-) diff --git a/tests/e2e/Services/Users/UsersBase.php b/tests/e2e/Services/Users/UsersBase.php index 457b63a454..dfbcbaca33 100644 --- a/tests/e2e/Services/Users/UsersBase.php +++ b/tests/e2e/Services/Users/UsersBase.php @@ -812,66 +812,6 @@ trait UsersBase return $data; } - /** - * @depends testUpdateUserPassword - */ - public function testUserPasswordHistory(array $data): array - { - $userId = $data['userId']; - $projectId = $this->getProject()['$id']; - /** - * Test for SUCCESS - */ - $user = $this->client->call(Client::METHOD_PATCH, '/users/' . $data['userId'] . '/password', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'password' => 'password2', - ]); - $this->assertEquals($user['headers']['status-code'], 200); - $this->assertNotEmpty($user['body']['$id']); - - $user = $this->client->call(Client::METHOD_PATCH, '/users/' . $data['userId'] . '/password', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'password' => 'password2', - ]); - $this->assertEquals($user['headers']['status-code'], 200); - $this->assertNotEmpty($user['body']['$id']); - - $project = $this->client->call(Client::METHOD_PATCH, '/projects/' . $projectId . '/auth/password-history', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => 'console', - 'cookie' => 'a_session_console=' . $this->getRoot()['session'], - 'x-appwrite-mode' => 'admin' - ]), [ - 'limit' => 2 - ]); - $this->assertEquals(200, $project['headers']['status-code']); - $this->assertEquals(2, $project['body']['passwordHistory']); - - $user = $this->client->call(Client::METHOD_PATCH, '/users/' . $data['userId'] . '/password', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'password' => 'password2', - ]); - $this->assertEquals($user['headers']['status-code'], 200); - $this->assertNotEmpty($user['body']['$id']); - - $user = $this->client->call(Client::METHOD_PATCH, '/users/' . $data['userId'] . '/password', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'password' => 'password2', - ]); - $this->assertEquals(409, $user['headers']['status-code']); - $this->assertNotEmpty($user['body']['$id']); - - return $data; - } - /** * @depends testGetUser */ From f7dd37a9d16f9eabcf91ac55f635b1b0738544ce Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 18 Dec 2022 08:54:53 +0000 Subject: [PATCH 15/66] format fix --- app/controllers/api/account.php | 2 +- app/controllers/api/users.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 5fb3eb7402..8f2105152b 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1538,7 +1538,7 @@ App::patch('/v1/account/password') } $history[] = $newPassword; - if(count($history) > $historyLimit) { + if (count($history) > $historyLimit) { array_slice($history, (count($history) - $historyLimit), $historyLimit); } } diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index a9e40288ed..21a3430e4d 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -817,7 +817,7 @@ App::patch('/v1/users/:userId/password') } $history[] = $newPassword; - if(count($history) > $historyLimit) { + if (count($history) > $historyLimit) { array_slice($history, (count($history) - $historyLimit), $historyLimit); } } From 30ece2d36a4f252c9f69d2fe618782053756638a Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 18 Dec 2022 09:08:51 +0000 Subject: [PATCH 16/66] validator for password history --- app/controllers/api/account.php | 11 ++- app/controllers/api/users.php | 9 +-- .../Auth/Validator/PasswordHistory.php | 73 +++++++++++++++++++ 3 files changed, 82 insertions(+), 11 deletions(-) create mode 100644 src/Appwrite/Auth/Validator/PasswordHistory.php diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 8f2105152b..9762b6cf16 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -40,6 +40,7 @@ use Utopia\Validator\ArrayList; use Utopia\Validator\Assoc; use Utopia\Validator\Text; use Utopia\Validator\WhiteList; +use Appwrite\Auth\Validator\PasswordHistory; $oauthDefaultSuccess = '/auth/oauth2/success'; $oauthDefaultFailure = '/auth/oauth2/failure'; @@ -1530,11 +1531,9 @@ App::patch('/v1/account/password') $history = []; if ($historyLimit > 0) { $history = $user->getAttribute('passwordHistory', []); - - foreach ($history as $hash) { - if (Auth::passwordVerify($password, $hash, $user->getAttribute('hash'), $user->getAttribute('hashOptions'))) { - throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED, 'The password was recently used', 409); - } + $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); } $history[] = $newPassword; @@ -1544,7 +1543,7 @@ App::patch('/v1/account/password') } $user = $dbForProject->updateDocument('users', $user->getId(), $user - ->setAttribute('passwordHistory', $history) + ->setAttribute('a', $history) ->setAttribute('password', $newPassword) ->setAttribute('hash', Auth::DEFAULT_ALGO) ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS) diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 21a3430e4d..0e35c05746 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -34,6 +34,7 @@ use Utopia\Validator\Text; use Utopia\Validator\Boolean; use MaxMind\Db\Reader; use Utopia\Validator\Integer; +use Appwrite\Auth\Validator\PasswordHistory; /** 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 @@ -809,11 +810,9 @@ App::patch('/v1/users/:userId/password') $history = []; if ($historyLimit > 0) { $history = $user->getAttribute('passwordHistory', []); - - foreach ($history as $hash) { - if (Auth::passwordVerify($password, $hash, $user->getAttribute('hash'), $user->getAttribute('hashOptions'))) { - throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED, 'The password was recently used', 409); - } + $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); } $history[] = $newPassword; diff --git a/src/Appwrite/Auth/Validator/PasswordHistory.php b/src/Appwrite/Auth/Validator/PasswordHistory.php new file mode 100644 index 0000000000..e262e2394b --- /dev/null +++ b/src/Appwrite/Auth/Validator/PasswordHistory.php @@ -0,0 +1,73 @@ +history = $history; + } + + /** + * Get Description. + * + * Returns validator description + * + * @return string + */ + public function getDescription(): string + { + return 'Password shouldn\'t be in the history.'; + } + + /** + * Is valid. + * + * @param mixed $value + * + * @return bool + */ + public function isValid($value): bool + { + foreach ($this->history as $hash) { + if (Auth::passwordVerify($value, $hash, $this->algo, $this->algoOptions)) { + 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; + } +} From 28ac6c93555fbf53ceb48523e53a431d4a4913d5 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 18 Dec 2022 09:37:13 +0000 Subject: [PATCH 17/66] fix validator --- src/Appwrite/Auth/Validator/PasswordHistory.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Appwrite/Auth/Validator/PasswordHistory.php b/src/Appwrite/Auth/Validator/PasswordHistory.php index e262e2394b..9848f2c946 100644 --- a/src/Appwrite/Auth/Validator/PasswordHistory.php +++ b/src/Appwrite/Auth/Validator/PasswordHistory.php @@ -13,9 +13,11 @@ class PasswordHistory extends Password { protected array $history; - public function __construct(array $history) + public function __construct(array $history, string $algo, array $algoOptions = []) { $this->history = $history; + $this->algo = $algo; + $this->algoOptions = $algoOptions; } /** From 95791c85ffea4c3c00273e515be16c01f8d5d7e2 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 19 Dec 2022 10:49:18 +0545 Subject: [PATCH 18/66] Update app/controllers/api/projects.php Co-authored-by: Eldad A. Fux --- app/controllers/api/projects.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 327ced582b..8c69644511 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -587,7 +587,7 @@ App::patch('/v1/projects/:projectId/auth/password-history') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_PROJECT) ->param('projectId', '', new UID(), 'Project unique ID.') - ->param('limit', false, new Range(0, APP_LIMIT_USER_SESSIONS_MAX), 'Set the max number of password to store in user history. User can\'t choose a new password that is already stored in the password history list. Max number of password allowed in history is' . APP_LIMIT_USER_PASSWORD_HISTORY . '. Default value is 0') + ->param('limit', 0, new Range(0, APP_LIMIT_USER_SESSIONS_MAX), 'Set the max number of passwords to store in user history. User can\'t choose a new password that is already stored in the password history list. Max number of passwords allowed in history is' . APP_LIMIT_USER_PASSWORD_HISTORY . '. Default value is 0') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, int $limit, Response $response, Database $dbForConsole) { From 5b1c54c440e4efcb11449a59a4535da16cc3ccb0 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 19 Dec 2022 05:07:41 +0000 Subject: [PATCH 19/66] fix suggestions --- app/controllers/api/account.php | 6 ++---- app/controllers/api/projects.php | 2 +- app/controllers/api/users.php | 4 +--- .../Services/Projects/ProjectsConsoleClientTest.php | 11 ++++++++++- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 9762b6cf16..cdb4024e7a 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1537,13 +1537,11 @@ App::patch('/v1/account/password') } $history[] = $newPassword; - if (count($history) > $historyLimit) { - array_slice($history, (count($history) - $historyLimit), $historyLimit); - } + array_slice($history, (count($history) - $historyLimit), $historyLimit); } $user = $dbForProject->updateDocument('users', $user->getId(), $user - ->setAttribute('a', $history) + ->setAttribute('passwordHistory', $history) ->setAttribute('password', $newPassword) ->setAttribute('hash', Auth::DEFAULT_ALGO) ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS) diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 8c69644511..25045fc64d 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -587,7 +587,7 @@ App::patch('/v1/projects/:projectId/auth/password-history') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_PROJECT) ->param('projectId', '', new UID(), 'Project unique ID.') - ->param('limit', 0, new Range(0, APP_LIMIT_USER_SESSIONS_MAX), 'Set the max number of passwords to store in user history. User can\'t choose a new password that is already stored in the password history list. Max number of passwords allowed in history is' . APP_LIMIT_USER_PASSWORD_HISTORY . '. Default value is 0') + ->param('limit', 0, new Range(0, APP_LIMIT_USER_PASSWORD_HISTORY), 'Set the max number of passwords to store in user history. User can\'t choose a new password that is already stored in the password history list. Max number of passwords allowed in history is' . APP_LIMIT_USER_PASSWORD_HISTORY . '. Default value is 0') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, int $limit, Response $response, Database $dbForConsole) { diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 0e35c05746..54e132f085 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -816,9 +816,7 @@ App::patch('/v1/users/:userId/password') } $history[] = $newPassword; - if (count($history) > $historyLimit) { - array_slice($history, (count($history) - $historyLimit), $historyLimit); - } + array_slice($history, (count($history) - $historyLimit), $historyLimit); } $user diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index ff49739976..f354625efd 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -1034,6 +1034,8 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(201, $response['headers']['status-code']); + $userId = $response['body']['$id']; + // create session $session = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ 'origin' => 'http://localhost', @@ -1056,7 +1058,14 @@ class ProjectsConsoleClientTest extends Scope 'password' => $password, ]); - var_dump($response['body']); + $this->assertEquals(409, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_PATCH, '/users/' . $userId . '/password', array_merge($this->getHeaders(), [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $id, + ]), [ + 'password' => $password, + ]); $this->assertEquals(409, $response['headers']['status-code']); From f72d075c40ae3f80c68fd7e2ead9b0245c7992e3 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 19 Dec 2022 10:54:56 +0545 Subject: [PATCH 20/66] Update src/Appwrite/Utopia/Response/Model/Project.php Co-authored-by: Eldad A. Fux --- src/Appwrite/Utopia/Response/Model/Project.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Utopia/Response/Model/Project.php b/src/Appwrite/Utopia/Response/Model/Project.php index 2dfd96c9eb..59bd8ea78c 100644 --- a/src/Appwrite/Utopia/Response/Model/Project.php +++ b/src/Appwrite/Utopia/Response/Model/Project.php @@ -122,7 +122,7 @@ class Project extends Model ]) ->addRule('authPasswordHistory', [ 'type' => self::TYPE_INTEGER, - 'description' => 'Max password history to save per user. Max passwords limit allowed in history is 20. Use 0 for disabling password history.', + 'description' => 'Max allowed passwords in the history list per user. Max passwords limit allowed in history is 20. Use 0 for disabling password history.', 'default' => 0, 'example' => 5, ]) From b35e5a04beda53d122b6edc618a4982cdb45a4b8 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 19 Dec 2022 08:28:02 +0000 Subject: [PATCH 21/66] fix users endpoint test --- tests/e2e/Services/Projects/ProjectsConsoleClientTest.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index f354625efd..030600d0a2 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -1060,10 +1060,13 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(409, $response['headers']['status-code']); - $response = $this->client->call(Client::METHOD_PATCH, '/users/' . $userId . '/password', array_merge($this->getHeaders(), [ + $headers = array_merge($this->getHeaders(), [ + 'x-appwrite-mode' => 'admin', 'content-type' => 'application/json', 'x-appwrite-project' => $id, - ]), [ + ]); + + $response = $this->client->call(Client::METHOD_PATCH, '/users/' . $userId . '/password', $headers, [ 'password' => $password, ]); From 679fd665070ce3bc5beae17b01fe0cd169dbc8bb Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 19 Dec 2022 08:35:02 +0000 Subject: [PATCH 22/66] add to changelog --- CHANGES.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 0ba56af243..c45f7cbabd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,10 @@ +## Version 1.2.0 + +## Features + +- Password history setting allows to save user's last used password so that it may not be used again. Maximum number of history saved is 20, which can be configured [#4866](https://github.com/appwrite/appwrite/pull/4866) + +## Bugs - Fix invited account verified status [#4776](https://github.com/appwrite/appwrite/pull/4776) - Get default region from environment on project create [#4780](https://github.com/appwrite/appwrite/pull/4780) From 82d20233e2807617b6d5727f2f66787f137e46fb Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 21 Dec 2022 05:41:16 +0000 Subject: [PATCH 23/66] feat: add migration for password history --- src/Appwrite/Migration/Version/V17.php | 96 ++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 src/Appwrite/Migration/Version/V17.php diff --git a/src/Appwrite/Migration/Version/V17.php b/src/Appwrite/Migration/Version/V17.php new file mode 100644 index 0000000000..b6af07bb03 --- /dev/null +++ b/src/Appwrite/Migration/Version/V17.php @@ -0,0 +1,96 @@ + null, + fn () => [] + ); + } + + Console::log('Migrating Project: ' . $this->project->getAttribute('name') . ' (' . $this->project->getId() . ')'); + + Console::info('Migrating Collections'); + $this->migrateCollections(); + + Console::info('Migrating Documents'); + $this->forEachDocument([$this, 'fixDocument']); + } + + /** + * Migrate all Collections. + * + * @return void + */ + protected function migrateCollections(): void + { + foreach ($this->collections as $collection) { + $id = $collection['$id']; + + Console::log("Migrating Collection \"{$id}\""); + + $this->projectDB->setNamespace("_{$this->project->getInternalId()}"); + + switch ($id) { + + case 'users': + try { + /** + * Create 'passwordHistory' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'passwordHistory'); + } catch (\Throwable $th) { + Console::warning("'region' from {$id}: {$th->getMessage()}"); + } + default: + break; + } + + usleep(50000); + } + } + + /** + * Fix run on each document + * + * @param \Utopia\Database\Document $document + * @return \Utopia\Database\Document + */ + protected function fixDocument(Document $document) + { + switch ($document->getCollection()) { + case 'users': + /** + * Bump version number. + */ + $document->setAttribute('passwordHistory', []); + + /** + * Set default passwordHistory + */ + $document->setAttribute('auths', array_merge($document->getAttribute('auths', []), [ + 'passwordHistory' => 0 + ])); + + break; + } + + return $document; + } +} From ba5e0b4ae496e6dae80509dfc891f8239eb0255a Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 21 Dec 2022 05:43:10 +0000 Subject: [PATCH 24/66] bump version --- src/Appwrite/Migration/Version/V17.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Appwrite/Migration/Version/V17.php b/src/Appwrite/Migration/Version/V17.php index b6af07bb03..9e56b886d1 100644 --- a/src/Appwrite/Migration/Version/V17.php +++ b/src/Appwrite/Migration/Version/V17.php @@ -75,6 +75,12 @@ class V17 extends Migration protected function fixDocument(Document $document) { switch ($document->getCollection()) { + case 'projects': + /** + * Bump version number. + */ + $document->setAttribute('version', '1.2.0'); + break; case 'users': /** * Bump version number. From 1388f58b5922dd48e32f3469d35ba5281a470ccf Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 21 Dec 2022 06:16:36 +0000 Subject: [PATCH 25/66] fix: formatting --- src/Appwrite/Migration/Version/V17.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Appwrite/Migration/Version/V17.php b/src/Appwrite/Migration/Version/V17.php index 9e56b886d1..597d26bb77 100644 --- a/src/Appwrite/Migration/Version/V17.php +++ b/src/Appwrite/Migration/Version/V17.php @@ -48,7 +48,6 @@ class V17 extends Migration $this->projectDB->setNamespace("_{$this->project->getInternalId()}"); switch ($id) { - case 'users': try { /** From 772481451c49365399620c939f01e4a257e78989 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 21 Dec 2022 06:42:27 +0000 Subject: [PATCH 26/66] test to set password history limit above 20 --- .../Services/Projects/ProjectsConsoleClientTest.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index 030600d0a2..8dc886adf5 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -1000,6 +1000,19 @@ class ProjectsConsoleClientTest extends Scope { $id = $data['projectId'] ?? ''; + /** + * Test for Failure + */ + $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/auth/password-history', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'limit' => 25, + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + /** * Test for Success */ From eb9e7c737e37ec3a658677d19eb9cf2b3c682f12 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 22 Dec 2022 04:56:17 +0000 Subject: [PATCH 27/66] fixed forgotten break --- src/Appwrite/Migration/Version/V17.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Appwrite/Migration/Version/V17.php b/src/Appwrite/Migration/Version/V17.php index 597d26bb77..999bcc047b 100644 --- a/src/Appwrite/Migration/Version/V17.php +++ b/src/Appwrite/Migration/Version/V17.php @@ -57,6 +57,7 @@ class V17 extends Migration } catch (\Throwable $th) { Console::warning("'region' from {$id}: {$th->getMessage()}"); } + break; default: break; } From 5d61ff5fab78cddb67a24fb4934e839167fe0886 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Fri, 23 Dec 2022 07:19:11 +0000 Subject: [PATCH 28/66] password dictionary --- app/assets/security/10k-common-passwords | 10000 +++++++++++++++++++++ app/init.php | 8 + 2 files changed, 10008 insertions(+) create mode 100644 app/assets/security/10k-common-passwords diff --git a/app/assets/security/10k-common-passwords b/app/assets/security/10k-common-passwords new file mode 100644 index 0000000000..487a4faf54 --- /dev/null +++ b/app/assets/security/10k-common-passwords @@ -0,0 +1,10000 @@ +password +123456 +12345678 +1234 +qwerty +12345 +dragon +pussy +baseball +football +letmein +monkey +696969 +abc123 +mustang +michael +shadow +master +jennifer +111111 +2000 +jordan +superman +harley +1234567 +fuckme +hunter +fuckyou +trustno1 +ranger +buster +thomas +tigger +robert +soccer +fuck +batman +test +pass +killer +hockey +george +charlie +andrew +michelle +love +sunshine +jessica +asshole +6969 +pepper +daniel +access +123456789 +654321 +joshua +maggie +starwars +silver +william +dallas +yankees +123123 +ashley +666666 +hello +amanda +orange +biteme +freedom +computer +sexy +thunder +nicole +ginger +heather +hammer +summer +corvette +taylor +fucker +austin +1111 +merlin +matthew +121212 +golfer +cheese +princess +martin +chelsea +patrick +richard +diamond +yellow +bigdog +secret +asdfgh +sparky +cowboy +camaro +anthony +matrix +falcon +iloveyou +bailey +guitar +jackson +purple +scooter +phoenix +aaaaaa +morgan +tigers +porsche +mickey +maverick +cookie +nascar +peanut +justin +131313 +money +horny +samantha +panties +steelers +joseph +snoopy +boomer +whatever +iceman +smokey +gateway +dakota +cowboys +eagles +chicken +dick +black +zxcvbn +please +andrea +ferrari +knight +hardcore +melissa +compaq +coffee +booboo +bitch +johnny +bulldog +xxxxxx +welcome +james +player +ncc1701 +wizard +scooby +charles +junior +internet +bigdick +mike +brandy +tennis +blowjob +banana +monster +spider +lakers +miller +rabbit +enter +mercedes +brandon +steven +fender +john +yamaha +diablo +chris +boston +tiger +marine +chicago +rangers +gandalf +winter +bigtits +barney +edward +raiders +porn +badboy +blowme +spanky +bigdaddy +johnson +chester +london +midnight +blue +fishing +000000 +hannah +slayer +11111111 +rachel +sexsex +redsox +thx1138 +asdf +marlboro +panther +zxcvbnm +arsenal +oliver +qazwsx +mother +victoria +7777777 +jasper +angel +david +winner +crystal +golden +butthead +viking +jack +iwantu +shannon +murphy +angels +prince +cameron +girls +madison +wilson +carlos +hooters +willie +startrek +captain +maddog +jasmine +butter +booger +angela +golf +lauren +rocket +tiffany +theman +dennis +liverpoo +flower +forever +green +jackie +muffin +turtle +sophie +danielle +redskins +toyota +jason +sierra +winston +debbie +giants +packers +newyork +jeremy +casper +bubba +112233 +sandra +lovers +mountain +united +cooper +driver +tucker +helpme +fucking +pookie +lucky +maxwell +8675309 +bear +suckit +gators +5150 +222222 +shithead +fuckoff +jaguar +monica +fred +happy +hotdog +tits +gemini +lover +xxxxxxxx +777777 +canada +nathan +victor +florida +88888888 +nicholas +rosebud +metallic +doctor +trouble +success +stupid +tomcat +warrior +peaches +apples +fish +qwertyui +magic +buddy +dolphins +rainbow +gunner +987654 +freddy +alexis +braves +cock +2112 +1212 +cocacola +xavier +dolphin +testing +bond007 +member +calvin +voodoo +7777 +samson +alex +apollo +fire +tester +walter +beavis +voyager +peter +porno +bonnie +rush2112 +beer +apple +scorpio +jonathan +skippy +sydney +scott +red123 +power +gordon +travis +beaver +star +jackass +flyers +boobs +232323 +zzzzzz +steve +rebecca +scorpion +doggie +legend +ou812 +yankee +blazer +bill +runner +birdie +bitches +555555 +parker +topgun +asdfasdf +heaven +viper +animal +2222 +bigboy +4444 +arthur +baby +private +godzilla +donald +williams +lifehack +phantom +dave +rock +august +sammy +cool +brian +platinum +jake +bronco +paul +mark +frank +heka6w2 +copper +billy +cumshot +garfield +willow +cunt +little +carter +slut +albert +69696969 +kitten +super +jordan23 +eagle1 +shelby +america +11111 +jessie +house +free +123321 +chevy +bullshit +white +broncos +horney +surfer +nissan +999999 +saturn +airborne +elephant +marvin +shit +action +adidas +qwert +kevin +1313 +explorer +walker +police +christin +december +benjamin +wolf +sweet +therock +king +online +dickhead +brooklyn +teresa +cricket +sharon +dexter +racing +penis +gregory +0000 +teens +redwings +dreams +michigan +hentai +magnum +87654321 +nothing +donkey +trinity +digital +333333 +stella +cartman +guinness +123abc +speedy +buffalo +kitty +pimpin +eagle +einstein +kelly +nelson +nirvana +vampire +xxxx +playboy +louise +pumpkin +snowball +test123 +girl +sucker +mexico +beatles +fantasy +ford +gibson +celtic +marcus +cherry +cassie +888888 +natasha +sniper +chance +genesis +hotrod +reddog +alexande +college +jester +passw0rd +bigcock +smith +lasvegas +carmen +slipknot +3333 +death +kimberly +1q2w3e +eclipse +1q2w3e4r +stanley +samuel +drummer +homer +montana +music +aaaa +spencer +jimmy +carolina +colorado +creative +hello1 +rocky +goober +friday +bollocks +scotty +abcdef +bubbles +hawaii +fluffy +mine +stephen +horses +thumper +5555 +pussies +darkness +asdfghjk +pamela +boobies +buddha +vanessa +sandman +naughty +douglas +honda +matt +azerty +6666 +shorty +money1 +beach +loveme +4321 +simple +poohbear +444444 +badass +destiny +sarah +denise +vikings +lizard +melanie +assman +sabrina +nintendo +water +good +howard +time +123qwe +november +xxxxx +october +leather +bastard +young +101010 +extreme +hard +password1 +vincent +pussy1 +lacrosse +hotmail +spooky +amateur +alaska +badger +paradise +maryjane +poop +crazy +mozart +video +russell +vagina +spitfire +anderson +norman +eric +cherokee +cougar +barbara +long +420420 +family +horse +enigma +allison +raider +brazil +blonde +jones +55555 +dude +drowssap +jeff +school +marshall +lovely +1qaz2wsx +jeffrey +caroline +franklin +booty +molly +snickers +leslie +nipples +courtney +diesel +rocks +eminem +westside +suzuki +daddy +passion +hummer +ladies +zachary +frankie +elvis +reggie +alpha +suckme +simpson +patricia +147147 +pirate +tommy +semperfi +jupiter +redrum +freeuser +wanker +stinky +ducati +paris +natalie +babygirl +bishop +windows +spirit +pantera +monday +patches +brutus +houston +smooth +penguin +marley +forest +cream +212121 +flash +maximus +nipple +bobby +bradley +vision +pokemon +champion +fireman +indian +softball +picard +system +clinton +cobra +enjoy +lucky1 +claire +claudia +boogie +timothy +marines +security +dirty +admin +wildcats +pimp +dancer +hardon +veronica +fucked +abcd1234 +abcdefg +ironman +wolverin +remember +great +freepass +bigred +squirt +justice +francis +hobbes +kermit +pearljam +mercury +domino +9999 +denver +brooke +rascal +hitman +mistress +simon +tony +bbbbbb +friend +peekaboo +naked +budlight +electric +sluts +stargate +saints +bondage +brittany +bigman +zombie +swimming +duke +qwerty1 +babes +scotland +disney +rooster +brenda +mookie +swordfis +candy +duncan +olivia +hunting +blink182 +alicia +8888 +samsung +bubba1 +whore +virginia +general +passport +aaaaaaaa +erotic +liberty +arizona +jesus +abcd +newport +skipper +rolltide +balls +happy1 +galore +christ +weasel +242424 +wombat +digger +classic +bulldogs +poopoo +accord +popcorn +turkey +jenny +amber +bunny +mouse +007007 +titanic +liverpool +dreamer +everton +friends +chevelle +carrie +gabriel +psycho +nemesis +burton +pontiac +connor +eatme +lickme +roland +cumming +mitchell +ireland +lincoln +arnold +spiderma +patriots +goblue +devils +eugene +empire +asdfg +cardinal +brown +shaggy +froggy +qwer +kawasaki +kodiak +people +phpbb +light +54321 +kramer +chopper +hooker +honey +whynot +lesbian +lisa +baxter +adam +snake +teen +ncc1701d +qqqqqq +airplane +britney +avalon +sandy +sugar +sublime +stewart +wildcat +raven +scarface +elizabet +123654 +trucks +wolfpack +pervert +lawrence +raymond +redhead +american +alyssa +bambam +movie +woody +shaved +snowman +tiger1 +chicks +raptor +1969 +stingray +shooter +france +stars +madmax +kristen +sports +jerry +789456 +garcia +simpsons +lights +ryan +looking +chronic +alison +hahaha +packard +hendrix +perfect +service +spring +srinivas +spike +katie +252525 +oscar +brother +bigmac +suck +single +cannon +georgia +popeye +tattoo +texas +party +bullet +taurus +sailor +wolves +panthers +japan +strike +flowers +pussycat +chris1 +loverboy +berlin +sticky +marina +tarheels +fisher +russia +connie +wolfgang +testtest +mature +bass +catch22 +juice +michael1 +nigger +159753 +women +alpha1 +trooper +hawkeye +head +freaky +dodgers +pakistan +machine +pyramid +vegeta +katana +moose +tinker +coyote +infinity +inside +pepsi +letmein1 +bang +control +hercules +morris +james1 +tickle +outlaw +browns +billybob +pickle +test1 +michele +antonio +sucks +pavilion +changeme +caesar +prelude +tanner +adrian +darkside +bowling +wutang +sunset +robbie +alabama +danger +zeppelin +juan +rusty +pppppp +nick +2001 +ping +darkstar +madonna +qwe123 +bigone +casino +cheryl +charlie1 +mmmmmm +integra +wrangler +apache +tweety +qwerty12 +bobafett +simone +none +business +sterling +trevor +transam +dustin +harvey +england +2323 +seattle +ssssss +rose +harry +openup +pandora +pussys +trucker +wallace +indigo +storm +malibu +weed +review +babydoll +doggy +dilbert +pegasus +joker +catfish +flipper +valerie +herman +fuckit +detroit +kenneth +cheyenne +bruins +stacey +smoke +joey +seven +marino +fetish +xfiles +wonder +stinger +pizza +babe +pretty +stealth +manutd +gracie +gundam +cessna +longhorn +presario +mnbvcxz +wicked +mustang1 +victory +21122112 +shelly +awesome +athena +q1w2e3r4 +help +holiday +knicks +street +redneck +12341234 +casey +gizmo +scully +dragon1 +devildog +triumph +eddie +bluebird +shotgun +peewee +ronnie +angel1 +daisy +special +metallica +madman +country +impala +lennon +roscoe +omega +access14 +enterpri +miranda +search +smitty +blizzard +unicorn +tight +rick +ronald +asdf1234 +harrison +trigger +truck +danny +home +winnie +beauty +thailand +1234567890 +cadillac +castle +tyler +bobcat +buddy1 +sunny +stones +asian +freddie +chuck +butt +loveyou +norton +hellfire +hotsex +indiana +short +panzer +lonewolf +trumpet +colors +blaster +12121212 +fireball +logan +precious +aaron +elaine +jungle +atlanta +gold +corona +curtis +nikki +polaris +timber +theone +baller +chipper +orlando +island +skyline +dragons +dogs +benson +licker +goldie +engineer +kong +pencil +basketba +open +hornet +world +linda +barbie +chan +farmer +valentin +wetpussy +indians +larry +redman +foobar +travel +morpheus +bernie +target +141414 +hotstuff +photos +laura +savage +holly +rocky1 +fuck_inside +dollar +turbo +design +newton +hottie +moon +202020 +blondes +4128 +lestat +avatar +future +goforit +random +abgrtyu +jjjjjj +cancer +q1w2e3 +smiley +goldberg +express +virgin +zipper +wrinkle1 +stone +andy +babylon +dong +powers +consumer +dudley +monkey1 +serenity +samurai +99999999 +bigboobs +skeeter +lindsay +joejoe +master1 +aaaaa +chocolat +christia +birthday +stephani +tang +1234qwer +alfred +ball +98765432 +maria +sexual +maxima +77777777 +sampson +buckeye +highland +kristin +seminole +reaper +bassman +nugget +lucifer +airforce +nasty +watson +warlock +2121 +philip +always +dodge +chrissy +burger +bird +snatch +missy +pink +gang +maddie +holmes +huskers +piglet +photo +joanne +hamilton +dodger +paladin +christy +chubby +buckeyes +hamlet +abcdefgh +bigfoot +sunday +manson +goldfish +garden +deftones +icecream +blondie +spartan +julie +harold +charger +brandi +stormy +sherry +pleasure +juventus +rodney +galaxy +holland +escort +zxcvb +planet +jerome +wesley +blues +song +peace +david1 +ncc1701e +1966 +51505150 +cavalier +gambit +karen +sidney +ripper +oicu812 +jamie +sister +marie +martha +nylons +aardvark +nadine +minnie +whiskey +bing +plastic +anal +babylon5 +chang +savannah +loser +racecar +insane +yankees1 +mememe +hansolo +chiefs +fredfred +freak +frog +salmon +concrete +yvonne +zxcv +shamrock +atlantis +warren +wordpass +julian +mariah +rommel +1010 +harris +predator +sylvia +massive +cats +sammy1 +mister +stud +marathon +rubber +ding +trunks +desire +montreal +justme +faster +kathleen +irish +1999 +bertha +jessica1 +alpine +sammie +diamonds +tristan +00000 +swinger +shan +stallion +pitbull +letmein2 +roberto +ready +april +palmer +ming +shadow1 +audrey +chong +clitoris +wang +shirley +fuckers +jackoff +bluesky +sundance +renegade +hollywoo +151515 +bernard +wolfman +soldier +picture +pierre +ling +goddess +manager +nikita +sweety +titans +hang +fang +ficken +niners +bottom +bubble +hello123 +ibanez +webster +sweetpea +stocking +323232 +tornado +lindsey +content +bruce +buck +aragorn +griffin +chen +campbell +trojan +christop +newman +wayne +tina +rockstar +father +geronimo +pascal +crimson +brooks +hector +penny +anna +google +camera +chandler +fatcat +lovelove +cody +cunts +waters +stimpy +finger +cindy +wheels +viper1 +latin +robin +greenday +987654321 +creampie +brendan +hiphop +willy +snapper +funtime +duck +trombone +adult +cotton +cookies +kaiser +mulder +westham +latino +jeep +ravens +aurora +drizzt +madness +energy +kinky +314159 +sophia +stefan +slick +rocker +55555555 +freeman +french +mongoose +speed +dddddd +hong +henry +hungry +yang +catdog +cheng +ghost +gogogo +randy +tottenha +curious +butterfl +mission +january +singer +sherman +shark +techno +lancer +lalala +autumn +chichi +orion +trixie +clifford +delta +bobbob +bomber +holden +kang +kiss +1968 +spunky +liquid +mary +beagle +granny +network +bond +kkkkkk +millie +1973 +biggie +beetle +teacher +susan +toronto +anakin +genius +dream +cocks +dang +bush +karate +snakes +bangkok +callie +fuckyou2 +pacific +daytona +kelsey +infantry +skywalke +foster +felix +sailing +raistlin +vanhalen +huang +herbert +jacob +blackie +tarzan +strider +sherlock +lang +gong +sang +dietcoke +ultimate +tree +shai +sprite +ting +artist +chai +chao +devil +python +ninja +misty +ytrewq +sweetie +superfly +456789 +tian +jing +jesus1 +freedom1 +dian +drpepper +potter +chou +darren +hobbit +violet +yong +shen +phillip +maurice +gloria +nolimit +mylove +biscuit +yahoo +shasta +sex4me +smoker +smile +pebbles +pics +philly +tong +tintin +lesbians +marlin +cactus +frank1 +tttttt +chun +danni +emerald +showme +pirates +lian +dogg +colleen +xiao +xian +tazman +tanker +patton +toshiba +richie +alberto +gotcha +graham +dillon +rang +emily +keng +jazz +bigguy +yuan +woman +tomtom +marion +greg +chaos +fossil +flight +racerx +tuan +creamy +boss +bobo +musicman +warcraft +window +blade +shuang +sheila +shun +lick +jian +microsoft +rong +allen +feng +getsome +sally +quality +kennedy +morrison +1977 +beng +wwwwww +yoyoyo +zhang +seng +teddy +joanna +andreas +harder +luke +qazxsw +qian +cong +chuan +deng +nang +boeing +keeper +western +isabelle +1963 +subaru +sheng +thuglife +teng +jiong +miao +martina +mang +maniac +pussie +tracey +a1b2c3 +clayton +zhou +zhuang +xing +stonecol +snow +spyder +liang +jiang +memphis +regina +ceng +magic1 +logitech +chuang +dark +million +blow +sesame +shao +poison +titty +terry +kuan +kuai +kyle +mian +guan +hamster +guai +ferret +florence +geng +duan +pang +maiden +quan +velvet +nong +neng +nookie +buttons +bian +bingo +biao +zhong +zeng +xiong +zhun +ying +zong +xuan +zang +0.0.000 +suan +shei +shui +sharks +shang +shua +small +peng +pian +piao +liao +meng +miami +reng +guang +cang +change +ruan +diao +luan +lucas +qing +chui +chuo +cuan +nuan +ning +heng +huan +kansas +muscle +monroe +weng +whitney +1passwor +bluemoon +zhui +zhua +xiang +zheng +zhen +zhei +zhao +zhan +yomama +zhai +zhuo +zuan +tarheel +shou +shuo +tiao +lady +leonard +leng +kuang +jiao +13579 +basket +qiao +qiong +qiang +chuai +nian +niao +niang +huai +22222222 +bianca +zhuan +zhuai +shuan +shuai +stardust +jumper +margaret +archie +66666666 +charlott +forget +qwertz +bones +history +milton +waterloo +2002 +stuff +11223344 +office +oldman +preston +trains +murray +vertigo +246810 +black1 +swallow +smiles +standard +alexandr +parrot +luther +user +nicolas +1976 +surfing +pioneer +pete +masters +apple1 +asdasd +auburn +hannibal +frontier +panama +lucy +buffy +brianna +welcome1 +vette +blue22 +shemale +111222 +baggins +groovy +global +turner +181818 +1979 +blades +spanking +life +byteme +lobster +collins +dawg +hilton +japanese +1970 +1964 +2424 +polo +markus +coco +deedee +mikey +1972 +171717 +1701 +strip +jersey +green1 +capital +sasha +sadie +putter +vader +seven7 +lester +marcel +banshee +grendel +gilbert +dicks +dead +hidden +iloveu +1980 +sound +ledzep +michel +147258 +female +bugger +buffett +bryan +hell +kristina +molson +2020 +wookie +sprint +thanks +jericho +102030 +grace +fuckin +mandy +ranger1 +trebor +deepthroat +bonehead +molly1 +mirage +models +1984 +2468 +stuart +showtime +squirrel +pentium +mario +anime +gator +powder +twister +connect +neptune +bruno +butts +engine +eatshit +mustangs +woody1 +shogun +septembe +pooh +jimbo +roger +annie +bacon +center +russian +sabine +damien +mollie +voyeur +2525 +363636 +leonardo +camel +chair +germany +giant +qqqq +nudist +bone +sleepy +tequila +megan +fighter +garrett +dominic +obiwan +makaveli +vacation +walnut +1974 +ladybug +cantona +ccbill +satan +rusty1 +passwor1 +columbia +napoleon +dusty +kissme +motorola +william1 +1967 +zzzz +skater +smut +play +matthew1 +robinson +valley +coolio +dagger +boner +bull +horndog +jason1 +blake +penguins +rescue +griffey +8j4ye3uz +californ +champs +qwertyuiop +portland +queen +colt45 +boat +xxxxxxx +xanadu +tacoma +mason +carpet +gggggg +safety +palace +italia +stevie +picturs +picasso +thongs +tempest +ricardo +roberts +asd123 +hairy +foxtrot +gary +nimrod +hotboy +343434 +1111111 +asdfghjkl +goose +overlord +blood +wood +stranger +454545 +shaolin +sooners +socrates +spiderman +peanuts +maxine +rogers +13131313 +andrew1 +filthy +donnie +ohyeah +africa +national +kenny +keith +monique +intrepid +jasmin +pickles +assass +fright +potato +darwin +hhhhhh +kingdom +weezer +424242 +pepsi1 +throat +romeo +gerard +looker +puppy +butch +monika +suzanne +sweets +temple +laurie +josh +megadeth +analsex +nymets +ddddddd +bigballs +support +stick +today +down +oakland +oooooo +qweasd +chucky +bridge +carrot +chargers +discover +dookie +condor +night +butler +hoover +horny1 +isabella +sunrise +sinner +jojo +megapass +martini +assfuck +grateful +ffffff +abigail +esther +mushroom +janice +jamaica +wright +sims +space +there +timmy +7654321 +77777 +cccccc +gizmodo +roxanne +ralph +tractor +cristina +dance +mypass +hongkong +helena +1975 +blue123 +pissing +thomas1 +redred +rich +basketball +attack +cash +satan666 +drunk +dixie +dublin +bollox +kingkong +katrina +miles +1971 +22222 +272727 +sexx +penelope +thompson +anything +bbbb +battle +grizzly +passat +porter +tracy +defiant +bowler +knickers +monitor +wisdom +wild +slappy +thor +letsgo +robert1 +feet +rush +brownie +hudson +098765 +playing +playtime +lightnin +melvin +atomic +bart +hawk +goku +glory +llllll +qwaszx +cosmos +bosco +knights +bentley +beast +slapshot +lewis +assword +frosty +gillian +sara +dumbass +mallard +dddd +deanna +elwood +wally +159357 +titleist +angelo +aussie +guest +golfing +doobie +loveit +chloe +elliott +werewolf +vipers +janine +1965 +blabla +surf +sucking +tardis +serena +shelley +thegame +legion +rebels +fernando +fast +gerald +sarah1 +double +onelove +loulou +toto +crash +blackcat +0007 +tacobell +soccer1 +jedi +manuel +method +river +chase +ludwig +poopie +derrick +boob +breast +kittycat +isabel +belly +pikachu +thunder1 +thankyou +jose +celeste +celtics +frances +frogger +scoobydo +sabbath +coltrane +budman +willis +jackal +bigger +zzzzz +silvia +sooner +licking +gopher +geheim +lonestar +primus +pooper +newpass +brasil +heather1 +husker +element +moomoo +beefcake +zzzzzzzz +tammy +shitty +smokin +personal +jjjj +anthony1 +anubis +backup +gorilla +fuckface +painter +lowrider +punkrock +traffic +claude +daniela +dale +delta1 +nancy +boys +easy +kissing +kelley +wendy +theresa +amazon +alan +fatass +dodgeram +dingdong +malcolm +qqqqqqqq +breasts +boots +honda1 +spidey +poker +temp +johnjohn +miguel +147852 +archer +asshole1 +dogdog +tricky +crusader +weather +syracuse +spankme +speaker +meridian +amadeus +back +harley1 +falcons +dorothy +turkey50 +kenwood +keyboard +ilovesex +1978 +blackman +shazam +shalom +lickit +jimbob +richmond +roller +carson +check +fatman +funny +garbage +sandiego +loving +magnus +cooldude +clover +mobile +bell +payton +plumber +texas1 +tool +topper +jenna +mariners +rebel +harmony +caliente +celica +fletcher +german +diana +oxford +osiris +orgasm +punkin +porsche9 +tuesday +close +breeze +bossman +kangaroo +billie +latinas +judith +astros +scruffy +donna +qwertyu +davis +hearts +kathy +jammer +java +springer +rhonda +ricky +1122 +goodtime +chelsea1 +freckles +flyboy +doodle +city +nebraska +bootie +kicker +webmaster +vulcan +iverson +191919 +blueeyes +stoner +321321 +farside +rugby +director +pussy69 +power1 +bobbie +hershey +hermes +monopoly +west +birdman +blessed +blackjac +southern +peterpan +thumbs +lawyer +melinda +fingers +fuckyou1 +rrrrrr +a1b2c3d4 +coke +nicola +bohica +heart +elvis1 +kids +blacky +stories +sentinel +snake1 +phoebe +jesse +richard1 +1234abcd +guardian +candyman +fisting +scarlet +dildo +pancho +mandingo +lucky7 +condom +munchkin +billyboy +summer1 +student +sword +skiing +sergio +site +sony +thong +rootbeer +assassin +cassidy +frederic +fffff +fitness +giovanni +scarlett +durango +postal +achilles +dawn +dylan +kisses +warriors +imagine +plymouth +topdog +asterix +hallo +cameltoe +fuckfuck +bridget +eeeeee +mouth +weird +will +sithlord +sommer +toby +theking +juliet +avenger +backdoor +goodbye +chevrole +faith +lorraine +trance +cosworth +brad +houses +homers +eternity +kingpin +verbatim +incubus +1961 +blond +zaphod +shiloh +spurs +station +jennie +maynard +mighty +aliens +hank +charly +running +dogman +omega1 +printer +aggies +chocolate +deadhead +hope +javier +bitch1 +stone55 +pineappl +thekid +lizzie +rockets +ashton +camels +formula +forrest +rosemary +oracle +rain +pussey +porkchop +abcde +clancy +nellie +mystic +inferno +blackdog +steve1 +pauline +alexander +alice +alfa +grumpy +flames +scream +lonely +puffy +proxy +valhalla +unreal +cynthia +herbie +engage +yyyyyy +010101 +solomon +pistol +melody +celeb +flying +gggg +santiago +scottie +oakley +portugal +a12345 +newbie +mmmm +venus +1qazxsw2 +beverly +zorro +work +writer +stripper +sebastia +spread +phil +tobias +links +members +metal +1221 +andre +565656 +funfun +trojans +again +cyber +hurrican +moneys +1x2zkg8w +zeus +thing +tomato +lion +atlantic +celine +usa123 +trans +account +aaaaaaa +homerun +hyperion +kevin1 +blacks +44444444 +skittles +sean +hastings +fart +gangbang +fubar +sailboat +older +oilers +craig +conrad +church +damian +dean +broken +buster1 +hithere +immortal +sticks +pilot +peters +lexmark +jerkoff +maryland +anders +cheers +possum +columbus +cutter +muppet +beautiful +stolen +swordfish +sport +sonic +peter1 +jethro +rockon +asdfghj +pass123 +paper +pornos +ncc1701a +bootys +buttman +bonjour +escape +1960 +becky +bears +362436 +spartans +tinman +threesom +lemons +maxmax +1414 +bbbbb +camelot +chad +chewie +gogo +fusion +saint +dilligaf +nopass +myself +hustler +hunter1 +whitey +beast1 +yesyes +spank +smudge +pinkfloy +patriot +lespaul +annette +hammers +catalina +finish +formula1 +sausage +scooter1 +orioles +oscar1 +over +colombia +cramps +natural +eating +exotic +iguana +bella +suckers +strong +sheena +start +slave +pearl +topcat +lancelot +angelica +magelan +racer +ramona +crunch +british +button +eileen +steph +456123 +skinny +seeking +rockhard +chief +filter +first +freaks +sakura +pacman +poontang +dalton +newlife +homer1 +klingon +watcher +walleye +tasha +tasty +sinatra +starship +steel +starbuck +poncho +amber1 +gonzo +grover +catherin +carol +candle +firefly +goblin +scotch +diver +usmc +huskies +eleven +kentucky +kitkat +israel +beckham +bicycle +yourmom +studio +tara +33333333 +shane +splash +jimmy1 +reality +12344321 +caitlin +focus +sapphire +mailman +raiders1 +clark +ddddd +hopper +excalibu +more +wilbur +illini +imperial +phillips +lansing +maxx +gothic +golfball +carlton +camille +facial +front242 +macdaddy +qwer1234 +vectra +cowboys1 +crazy1 +dannyboy +jane +betty +benny +bennett +leader +martinez +aquarius +barkley +hayden +caught +franky +ffff +floyd +sassy +pppp +pppppppp +prodigy +clarence +noodle +eatpussy +vortex +wanking +beatrice +billy1 +siemens +pedro +phillies +research +groups +carolyn +chevy1 +cccc +fritz +gggggggg +doughboy +dracula +nurses +loco +madrid +lollipop +trout +utopia +chrono +cooler +conner +nevada +wibble +werner +summit +marco +marilyn +1225 +babies +capone +fugazi +panda +mama +qazwsxed +puppies +triton +9876 +command +nnnnnn +ernest +momoney +iforgot +wolfie +studly +shawn +renee +alien +hamburg +81fukkc +741852 +catman +china +forgot +gagging +scott1 +drew +oregon +qweqwe +train +crazybab +daniel1 +cutlass +brothers +holes +heidi +mothers +music1 +what +walrus +1957 +bigtime +bike +xtreme +simba +ssss +rookie +angie +bathing +fresh +sanchez +rotten +maestro +luis +look +turbo1 +99999 +butthole +hhhh +elijah +monty +bender +yoda +shania +shock +phish +thecat +rightnow +reagan +baddog +asia +greatone +gateway1 +randall +abstr +napster +brian1 +bogart +high +hitler +emma +kill +weaver +wildfire +jackson1 +isaiah +1981 +belinda +beaner +yoyo +0.0.0.000 +super1 +select +snuggles +slutty +some +phoenix1 +technics +toon +raven1 +rayray +123789 +1066 +albion +greens +fashion +gesperrt +santana +paint +powell +credit +darling +mystery +bowser +bottle +brucelee +hehehe +kelly1 +mojo +1998 +bikini +woofwoof +yyyy +strap +sites +spears +theodore +julius +richards +amelia +central +f**k +nyjets +punisher +username +vanilla +twisted +bryant +brent +bunghole +here +elizabeth +erica +kimber +viagra +veritas +pony +pool +titts +labtec +lifetime +jenny1 +masterbate +mayhem +redbull +govols +gremlin +505050 +gmoney +rupert +rovers +diamond1 +lorenzo +trident +abnormal +davidson +deskjet +cuddles +nice +bristol +karina +milano +vh5150 +jarhead +1982 +bigbird +bizkit +sixers +slider +star69 +starfish +penetration +tommy1 +john316 +meghan +michaela +market +grant +caligula +carl +flicks +films +madden +railroad +cosmo +cthulhu +bradford +br0d3r +military +bearbear +swedish +spawn +patrick1 +polly +these +todd +reds +anarchy +groove +franco +fuckher +oooo +tyrone +vegas +airbus +cobra1 +christine +clips +delete +duster +kitty1 +mouse1 +monkeys +jazzman +1919 +262626 +swinging +stroke +stocks +sting +pippen +labrador +jordan1 +justdoit +meatball +females +saturday +park +vector +cooter +defender +desert +demon +nike +bubbas +bonkers +english +kahuna +wildman +4121 +sirius +static +piercing +terror +teenage +leelee +marissa +microsof +mechanic +robotech +rated +hailey +chaser +sanders +salsero +nuts +macross +quantum +rachael +tsunami +universe +daddy1 +cruise +nguyen +newpass6 +nudes +hellyeah +vernon +1959 +zaq12wsx +striker +sixty +steele +spice +spectrum +smegma +thumb +jjjjjjjj +mellow +astrid +cancun +cartoon +sabres +samiam +pants +oranges +oklahoma +lust +coleman +denali +nude +noodles +buzz +brest +hooter +mmmmmmmm +warthog +bloody +blueblue +zappa +wolverine +sniffing +lance +jean +jjjjj +harper +calico +freee +rover +door +pooter +closeup +bonsai +evelyn +emily1 +kathryn +keystone +iiii +1955 +yzerman +theboss +tolkien +jill +megaman +rasta +bbbbbbbb +bean +handsome +hal9000 +goofy +gringo +gofish +gizmo1 +samsam +scuba +onlyme +tttttttt +corrado +clown +clapton +deborah +boris +bulls +vivian +jayhawk +bethany +wwww +sharky +seeker +ssssssss +somethin +pillow +thesims +lighter +lkjhgf +melissa1 +marcius2 +barry +guiness +gymnast +casey1 +goalie +godsmack +doug +lolo +rangers1 +poppy +abby +clemson +clipper +deeznuts +nobody +holly1 +elliot +eeee +kingston +miriam +belle +yosemite +sucked +sex123 +sexy69 +pic\'s +tommyboy +lamont +meat +masterbating +marianne +marc +gretzky +happyday +frisco +scratch +orchid +orange1 +manchest +quincy +unbelievable +aberdeen +dawson +nathalie +ne1469 +boxing +hill +korn +intercourse +161616 +1985 +ziggy +supersta +stoney +senior +amature +barber +babyboy +bcfields +goliath +hack +hardrock +children +frodo +scout +scrappy +rosie +qazqaz +tracker +active +craving +commando +cohiba +deep +cyclone +dana +bubba69 +katie1 +mpegs +vsegda +jade +irish1 +better +sexy1 +sinclair +smelly +squerting +lions +jokers +jeanette +julia +jojojo +meathead +ashley1 +groucho +cheetah +champ +firefox +gandalf1 +packer +magnolia +love69 +tyler1 +typhoon +tundra +bobby1 +kenworth +village +volley +beth +wolf359 +0420 +000007 +swimmer +skydive +smokes +patty +peugeot +pompey +legolas +kristy +redhot +rodman +redalert +having +grapes +4runner +carrera +floppy +dollars +ou8122 +quattro +adams +cloud9 +davids +nofear +busty +homemade +mmmmm +whisper +vermont +webmaste +wives +insertion +jayjay +philips +phone +topher +tongue +temptress +midget +ripken +havefun +gretchen +canon +celebrity +five +getting +ghetto +direct +otto +ragnarok +trinidad +usnavy +conover +cruiser +dalshe +nicole1 +buzzard +hottest +kingfish +misfit +moore +milfnew +warlord +wassup +bigsexy +blackhaw +zippy +shearer +tights +thursday +kungfu +labia +journey +meatloaf +marlene +rider +area51 +batman1 +bananas +636363 +cancel +ggggg +paradox +mack +lynn +queens +adults +aikido +cigars +nova +hoosier +eeyore +moose1 +warez +interacial +streaming +313131 +pertinant +pool6123 +mayday +rivers +revenge +animated +banker +baddest +gordon24 +ccccc +fortune +fantasies +touching +aisan +deadman +homepage +ejaculation +whocares +iscool +jamesbon +1956 +1pussy +womam +sweden +skidoo +spock +sssss +petra +pepper1 +pinhead +micron +allsop +amsterda +army +aside +gunnar +666999 +chip +foot +fowler +february +face +fletch +george1 +sapper +science +sasha1 +luckydog +lover1 +magick +popopo +public +ultima +derek +cypress +booker +businessbabe +brandon1 +edwards +experience +vulva +vvvv +jabroni +bigbear +yummy +010203 +searay +secret1 +showing +sinbad +sexxxx +soleil +software +piccolo +thirteen +leopard +legacy +jensen +justine +memorex +marisa +mathew +redwing +rasputin +134679 +anfield +greenbay +gore +catcat +feather +scanner +pa55word +contortionist +danzig +daisy1 +hores +erik +exodus +vinnie +iiiiii +zero +1001 +subway +tank +second +snapple +sneakers +sonyfuck +picks +poodle +test1234 +their +llll +junebug +june +marker +mellon +ronaldo +roadkill +amanda1 +asdfjkl +beaches +greene +great1 +cheerleaers +force +doitnow +ozzy +madeline +radio +tyson +christian +daphne +boxster +brighton +housewifes +emmanuel +emerson +kkkk +mnbvcx +moocow +vides +wagner +janet +1717 +bigmoney +blonds +1000 +storys +stereo +4545 +420247 +seductive +sexygirl +lesbean +live +justin1 +124578 +animals +balance +hansen +cabbage +canadian +gangbanged +dodge1 +dimas +lori +loud +malaka +puss +probes +adriana +coolman +crawford +dante +nacked +hotpussy +erotica +kool +mirror +wearing +implants +intruder +bigass +zenith +woohoo +womans +tanya +tango +stacy +pisces +laguna +krystal +maxell +andyod22 +barcelon +chainsaw +chickens +flash1 +downtown +orgasms +magicman +profit +pusyy +pothead +coconut +chuckie +contact +clevelan +designer +builder +budweise +hotshot +horizon +hole +experienced +mondeo +wifes +1962 +strange +stumpy +smiths +sparks +slacker +piper +pitchers +passwords +laptop +jeremiah +allmine +alliance +bbbbbbb +asscock +halflife +grandma +hayley +88888 +cecilia +chacha +saratoga +sandy1 +santos +doogie +number +positive +qwert40 +transexual +crow +close-up +darrell +bonita +ib6ub9 +volvo +jacob1 +iiiii +beastie +sunnyday +stoned +sonics +starfire +snapon +pictuers +pepe +testing1 +tiberius +lisalisa +lesbain +litle +retard +ripple +austin1 +badgirl +golfgolf +flounder +garage +royals +dragoon +dickie +passwor +ocean +majestic +poppop +trailers +dammit +nokia +bobobo +br549 +emmitt +knock +minime +mikemike +whitesox +1954 +3232 +353535 +seamus +solo +sparkle +sluttey +pictere +titten +lback +1024 +angelina +goodluck +charlton +fingerig +gallaries +goat +ruby +passme +oasis +lockerroom +logan1 +rainman +twins +treasure +absolutely +club +custom +cyclops +nipper +bucket +homepage- +hhhhh +momsuck +indain +2345 +beerbeer +bimmer +susanne +stunner +stevens +456456 +shell +sheba +tootsie +tiny +testerer +reefer +really +1012 +harcore +gollum +545454 +chico +caveman +carole +fordf150 +fishes +gaymen +saleen +doodoo +pa55w0rd +looney +presto +qqqqq +cigar +bogey +brewer +helloo +dutch +kamikaze +monte +wasser +vietnam +visa +japanees +0123 +swords +slapper +peach +jump +marvel +masterbaiting +march +redwood +rolling +1005 +ametuer +chiks +cathy +callaway +fucing +sadie1 +panasoni +mamas +race +rambo +unknown +absolut +deacon +dallas1 +housewife +kristi +keywest +kirsten +kipper +morning +wings +idiot +18436572 +1515 +beating +zxczxc +sullivan +303030 +shaman +sparrow +terrapin +jeffery +masturbation +mick +redfish +1492 +angus +barrett +goirish +hardcock +felicia +forfun +galary +freeporn +duchess +olivier +lotus +pornographic +ramses +purdue +traveler +crave +brando +enter1 +killme +moneyman +welder +windsor +wifey +indon +yyyyy +stretch +taylor1 +4417 +shopping +picher +pickup +thumbnils +johnboy +jets +jess +maureen +anne +ameteur +amateurs +apollo13 +hambone +goldwing +5050 +charley +sally1 +doghouse +padres +pounding +quest +truelove +underdog +trader +crack +climber +bolitas +bravo +hohoho +model +italian +beanie +beretta +wrestlin +stroker +tabitha +sherwood +sexyman +jewels +johannes +mets +marcos +rhino +bdsm +balloons +goodman +grils +happy123 +flamingo +games +route66 +devo +dino +outkast +paintbal +magpie +llllllll +twilight +critter +christie +cupcake +nickel +bullseye +krista +knickerless +mimi +murder +videoes +binladen +xerxes +slim +slinky +pinky +peterson +thanatos +meister +menace +ripley +retired +albatros +balloon +bank +goten +5551212 +getsdown +donuts +divorce +nwo4life +lord +lost +underwear +tttt +comet +deer +damnit +dddddddd +deeznutz +nasty1 +nonono +nina +enterprise +eeeee +misfit99 +milkman +vvvvvv +isaac +1818 +blueboy +beans +bigbutt +wyatt +tech +solution +poetry +toolman +laurel +juggalo +jetski +meredith +barefoot +50spanks +gobears +scandinavian +original +truman +cubbies +nitram +briana +ebony +kings +warner +bilbo +yumyum +zzzzzzz +stylus +321654 +shannon1 +server +secure +silly +squash +starman +steeler +staples +phrases +techniques +laser +135790 +allan +barker +athens +cbr600 +chemical +fester +gangsta +fucku2 +freeze +game +salvador +droopy +objects +passwd +lllll +loaded +louis +manchester +losers +vedder +clit +chunky +darkman +damage +buckshot +buddah +boobed +henti +hillary +webber +winter1 +ingrid +bigmike +beta +zidane +talon +slave1 +pissoff +person +thegreat +living +lexus +matador +readers +riley +roberta +armani +ashlee +goldstar +5656 +cards +fmale +ferris +fuking +gaston +fucku +ggggggg +sauron +diggler +pacers +looser +pounded +premier +pulled +town +trisha +triangle +cornell +collin +cosmic +deeper +depeche +norway +bright +helmet +kristine +kendall +mustard +misty1 +watch +jagger +bertie +berger +word +3x7pxr +silver1 +smoking +snowboar +sonny +paula +penetrating +photoes +lesbens +lambert +lindros +lillian +roadking +rockford +1357 +143143 +asasas +goodboy +898989 +chicago1 +card +ferrari1 +galeries +godfathe +gawker +gargoyle +gangster +rubble +rrrr +onetime +pussyman +pooppoop +trapper +twenty +abraham +cinder +company +newcastl +boricua +bunny1 +boxer +hotred +hockey1 +hooper +edward1 +evan +kris +misery +moscow +milk +mortgage +bigtit +show +snoopdog +three +lionel +leanne +joshua1 +july +1230 +assholes +cedric +fallen +farley +gene +frisky +sanity +script +divine +dharma +lucky13 +property +tricia +akira +desiree +broadway +butterfly +hunt +hotbox +hootie +heat +howdy +earthlink +karma +kiteboy +motley +westwood +1988 +bert +blackbir +biggles +wrench +working +wrestle +slippery +pheonix +penny1 +pianoman +tomorrow +thedude +jenn +jonjon +jones1 +mattie +memory +micheal +roadrunn +arrow +attitude +azzer +seahawks +diehard +dotcom +lola +tunafish +chivas +cinnamon +clouds +deluxe +northern +nuclear +north +boom +boobie +hurley +krishna +momomo +modles +volume +23232323 +bluedog +wwwwwww +zerocool +yousuck +pluto +limewire +link +joung +marcia +awnyce +gonavy +haha +films+pic+galeries +fabian +francois +girsl +fuckthis +girfriend +rufus +drive +uncencored +a123456 +airport +clay +chrisbln +combat +cygnus +cupoi +never +netscape +brett +hhhhhhhh +eagles1 +elite +knockers +kendra +mommy +1958 +tazmania +shonuf +piano +pharmacy +thedog +lips +jillian +jenkins +midway +arsenal1 +anaconda +australi +gromit +gotohell +787878 +66666 +carmex2 +camber +gator1 +ginger1 +fuzzy +seadoo +dorian +lovesex +rancid +uuuuuu +911911 +nature +bulldog1 +helen +health +heater +higgins +kirk +monalisa +mmmmmmm +whiteout +virtual +ventura +jamie1 +japanes +james007 +2727 +2469 +blam +bitchass +believe +zephyr +stiffy +sweet1 +silent +southpar +spectre +tigger1 +tekken +lenny +lakota +lionking +jjjjjjj +medical +megatron +1369 +hawaiian +gymnastic +golfer1 +gunners +7779311 +515151 +famous +glass +screen +rudy +royal +sanfran +drake +optimus +panther1 +love1 +mail +maggie1 +pudding +venice +aaron1 +delphi +niceass +bounce +busted +house1 +killer1 +miracle +momo +musashi +jammin +2003 +234567 +wp2003wp +submit +silence +sssssss +state +spikes +sleeper +passwort +toledo +kume +media +meme +medusa +mantis +remote +reading +reebok +1017 +artemis +hampton +harry1 +cafc91 +fettish +friendly +oceans +oooooooo +mango +ppppp +trainer +troy +uuuu +909090 +cross +death1 +news +bullfrog +hokies +holyshit +eeeeeee +mitch +jasmine1 +& +& +sergeant +spinner +leon +jockey +records +right +babyblue +hans +gooner +474747 +cheeks +cars +candice +fight +glow +pass1234 +parola +okokok +pablo +magical +major +ramsey +poseidon +989898 +confused +circle +crusher +cubswin +nnnn +hollywood +erin +kotaku +milo +mittens +whatsup +vvvvv +iomega +insertions +bengals +bermuda +biit +yellow1 +012345 +spike1 +south +sowhat +pitures +peacock +pecker +theend +juliette +jimmie +romance +augusta +hayabusa +hawkeyes +castro +florian +geoffrey +dolly +lulu +qaz123 +usarmy +twinkle +cloud +chuckles +cold +hounddog +hover +hothot +europa +ernie +kenshin +kojak +mikey1 +water1 +196969 +because +wraith +zebra +wwwww +33333 +simon1 +spider1 +snuffy +philippe +thunderb +teddy1 +lesley +marino13 +maria1 +redline +renault +aloha +antoine +handyman +cerberus +gamecock +gobucks +freesex +duffman +ooooo +papa +nuggets +magician +longbow +preacher +porno1 +county +chrysler +contains +dalejr +darius +darlene +dell +navy +buffy1 +hedgehog +hoosiers +honey1 +hott +heyhey +europe +dutchess +everest +wareagle +ihateyou +sunflowe +3434 +senators +shag +spoon +sonoma +stalker +poochie +terminal +terefon +laurence +maradona +maryann +marty +roman +1007 +142536 +alibaba +america1 +bartman +astro +goth +century +chicken1 +cheater +four +ghost1 +passpass +oral +r2d2c3po +civic +cicero +myxworld +kkkkk +missouri +wishbone +infiniti +jameson +1a2b3c +1qwerty +wonderboy +skip +shojou +stanford +sparky1 +smeghead +poiuy +titanium +torres +lantern +jelly +jeanne +meier +1213 +bayern +basset +gsxr750 +cattle +charlene +fishing1 +fullmoon +gilles +dima +obelix +popo +prissy +ramrod +unique +absolute +bummer +hotone +dynasty +entry +konyor +missy1 +moses +282828 +yeah +xyz123 +stop +426hemi +404040 +seinfeld +simmons +pingpong +lazarus +matthews +marine1 +manning +recovery +12345a +beamer +babyface +greece +gustav +7007 +charity +camilla +ccccccc +faggot +foxy +frozen +gladiato +duckie +dogfood +paranoid +packers1 +longjohn +radical +tuna +clarinet +claudio +circus +danny1 +novell +nights +bonbon +kashmir +kiki +mortimer +modelsne +moondog +monaco +vladimir +insert +1953 +zxc123 +supreme +3131 +sexxx +selena +softail +poipoi +pong +together +mars +martin1 +rogue +alone +avalanch +audia4 +55bgates +cccccccc +chick +came11 +figaro +geneva +dogboy +dnsadm +dipshit +paradigm +othello +operator +officer +malone +post +rafael +valencia +tripod +choice +chopin +coucou +coach +cocksuck +common +creature +borussia +book +browning +heritage +hiziad +homerj +eight +earth +millions +mullet +whisky +jacques +store +4242 +speedo +starcraf +skylar +spaceman +piggy +pierce +tiger2 +legos +lala +jezebel +judy +joker1 +mazda +barton +baker +727272 +chester1 +fishman +food +rrrrrrrr +sandwich +dundee +lumber +magazine +radar +ppppppp +tranny +aaliyah +admiral +comics +cleo +delight +buttfuck +homeboy +eternal +kilroy +kellie +khan +violin +wingman +walmart +bigblue +blaze +beemer +beowulf +bigfish +yyyyyyy +woodie +yeahbaby +0123456 +tbone +style +syzygy +starter +lemon +linda1 +merlot +mexican +11235813 +anita +banner +bangbang +badman +barfly +grease +carla +charles1 +ffffffff +screw +doberman +diane +dogshit +overkill +counter +coolguy +claymore +demons +demo +nomore +normal +brewster +hhhhhhh +hondas +iamgod +enterme +everett +electron +eastside +kayla +minimoni +mybaby +wildbill +wildcard +ipswich +200000 +bearcat +zigzag +yyyyyyyy +xander +sweetnes +369369 +skyler +skywalker +pigeon +peyton +tipper +lilly +asdf123 +alphabet +asdzxc +babybaby +banane +barnes +guyver +graphics +grand +chinook +florida1 +flexible +fuckinside +otis +ursitesux +tototo +trust +tower +adam12 +christma +corey +chrome +buddie +bombers +bunker +hippie +keegan +misfits +vickie +292929 +woofer +wwwwwwww +stubby +sheep +secrets +sparta +stang +spud +sporty +pinball +jorge +just4fun +johanna +maxxxx +rebecca1 +gunther +fatima +fffffff +freeway +garion +score +rrrrr +sancho +outback +maggot +puddin +trial +adrienne +987456 +colton +clyde +brain +brains +hoops +eleanor +dwayne +kirby +mydick +villa +19691969 +bigcat +becker +shiner +silverad +spanish +templar +lamer +juicy +marsha +mike1 +maximum +rhiannon +real +1223 +10101010 +arrows +andres +alucard +baldwin +baron +avenue +ashleigh +haggis +channel +cheech +safari +ross +dog123 +orion1 +paloma +qwerasdf +presiden +vegitto +trees +969696 +adonis +colonel +cookie1 +newyork1 +brigitte +buddyboy +hellos +heineken +dwight +eraser +kerstin +motion +moritz +millwall +visual +jaybird +1983 +beautifu +bitter +yvette +zodiac +steven1 +sinister +slammer +smashing +slick1 +sponge +teddybea +theater +this +ticklish +lipstick +jonny +massage +mann +reynolds +ring +1211 +amazing +aptiva +applepie +bailey1 +guitar1 +chanel +canyon +gagged +fuckme1 +rough +digital1 +dinosaur +punk +98765 +90210 +clowns +cubs +daniels +deejay +nigga +naruto +boxcar +icehouse +hotties +electra +kent +widget +india +insanity +1986 +2004 +best +bluefish +bingo1 +***** +stratus +strength +sultan +storm1 +44444 +4200 +sentnece +season +sexyboy +sigma +smokie +spam +point +pippo +ticket +temppass +joel +manman +medicine +1022 +anton +almond +bacchus +aztnm +axio +awful +bamboo +hakr +gregor +hahahaha +5678 +casanova +caprice +camero1 +fellow +fountain +dupont +dolphin1 +dianne +paddle +magnet +qwert1 +pyon +porsche1 +tripper +vampires +coming +noway +burrito +bozo +highheel +hughes +hookem +eddie1 +ellie +entropy +kkkkkkkk +kkkkkkk +illinois +jacobs +1945 +1951 +24680 +21212121 +100000 +stonecold +taco +subzero +sharp +sexxxy +skolko +shanna +skyhawk +spurs1 +sputnik +piazza +testpass +letter +lane +kurt +jiggaman +matilda +1224 +harvard +hannah1 +525252 +4ever +carbon +chef +federico +ghosts +gina +scorpio1 +rt6ytere +madison1 +loki +raquel +promise +coolness +christina +coldbeer +citadel +brittney +highway +evil +monarch +morgan1 +washingt +1997 +bella1 +berry +yaya +yolanda +superb +taxman +studman +stephanie +3636 +sherri +sheriff +shepherd +poland +pizzas +tiffany1 +toilet +latina +lassie +larry1 +joseph1 +mephisto +meagan +marian +reptile +rico +razor +1013 +barron +hammer1 +gypsy +grande +carroll +camper +chippy +cat123 +call +chimera +fiesta +glock +glenn +domain +dieter +dragonba +onetwo +nygiants +odessa +password2 +louie +quartz +prowler +prophet +towers +ultra +cocker +corleone +dakota1 +cumm +nnnnnnn +natalia +boxers +hugo +heynow +hollow +iceberg +elvira +kittykat +kate +kitchen +wasabi +vikings1 +impact +beerman +string +sleep +splinter +snoopy1 +pipeline +pocket +legs +maple +mickey1 +manuela +mermaid +micro +meowmeow +redbird +alisha +baura +battery +grass +chevys +chestnut +caravan +carina +charmed +fraser +frogman +diving +dogger +draven +drifter +oatmeal +paris1 +longdong +quant4307s +rachel1 +vegitta +cole +cobras +corsair +dadada +noelle +mylife +nine +bowwow +body +hotrats +eastwood +moonligh +modena +wave +illusion +iiiiiii +jayhawks +birgit +zone +sutton +susana +swingers +shocker +shrimp +sexgod +squall +stefanie +squeeze +soul +patrice +poiu +players +tigers1 +toejam +tickler +line +julie1 +jimbo1 +jefferso +juanita +michael2 +rodeo +robot +1023 +annie1 +bball +guess +happy2 +charter +farm +flasher +falcon1 +fiction +fastball +gadget +scrabble +diaper +dirtbike +dinner +oliver1 +partner +paco +lucille +macman +poopy +popper +postman +ttttttt +ursula +acura +cowboy1 +conan +daewoo +cyrus +customer +nation +nemrac58 +nnnnn +nextel +bolton +bobdylan +hopeless +eureka +extra +kimmie +kcj9wx5n +killbill +musica +volkswag +wage +windmill +wert +vintage +iloveyou1 +itsme +bessie +zippo +311311 +starligh +smokey1 +spot +snappy +soulmate +plasma +thelma +tonight +krusty +just4me +mcdonald +marius +rochelle +rebel1 +1123 +alfredo +aubrey +audi +chantal +fick +goaway +roses +sales +rusty2 +dirt +dogbone +doofus +ooooooo +oblivion +mankind +luck +mahler +lllllll +pumper +puck +pulsar +valkyrie +tupac +compass +concorde +costello +cougars +delaware +niceguy +nocturne +bob123 +boating +bronze +hopkins +herewego +hewlett +houhou +hubert +earnhard +eeeeeeee +keller +mingus +mobydick +venture +verizon +imation +1950 +1948 +1949 +223344 +bigbig +blossom +zack +wowwow +sissy +skinner +spiker +square +snooker +sluggo +player1 +junk +jeannie +jsbach +jumbo +jewel +medic +robins +reddevil +reckless +123456a +1125 +1031 +beacon +astra +gumby +hammond +hassan +757575 +585858 +chillin +fuck1 +sander +lowell +radiohea +upyours +trek +courage +coolcool +classics +choochoo +darryl +nikki1 +nitro +bugs +boytoy +ellen +excite +kirsty +kane +wingnut +wireless +icu812 +1master +beatle +bigblock +blanca +wolfen +summer99 +sugar1 +tartar +sexysexy +senna +sexman +sick +someone +soprano +pippin +platypus +pixies +telephon +land +laura1 +laurent +rimmer +road +report +1020 +12qwaszx +arturo +around +hamish +halifax +fishhead +forum +dododo +doit +outside +paramedi +lonesome +mandy1 +twist +uuuuu +uranus +ttttt +butcher +bruce1 +helper +hopeful +eduard +dusty1 +kathy1 +katherin +moonbeam +muscles +monster1 +monkeybo +morton +windsurf +vvvvvvv +vivid +install +1947 +187187 +1941 +1952 +tatiana +susan1 +31415926 +sinned +sexxy +senator +sebastian +shadows +smoothie +snowflak +playstat +playa +playboy1 +toaster +jerry1 +marie1 +mason1 +merlin1 +roger1 +roadster +112358 +1121 +andrea1 +bacardi +auto +hardware +hardy +789789 +5555555 +captain1 +flores +fergus +sascha +rrrrrrr +dome +onion +nutter +lololo +qqqqqqq +quick +undertak +uuuuuuuu +uuuuuuu +criminal +cobain +cindy1 +coors +dani +descent +nimbus +nomad +nanook +norwich +bomb +bombay +broker +hookup +kiwi +winners +jackpot +1a2b3c4d +1776 +beardog +bighead +blast +bird33 +0987 +stress +shot +spooge +pelican +peepee +perry +pointer +titan +thedoors +jeremy1 +annabell +altima +baba +hallie +hate +hardone +5454 +candace +catwoman +flip +faithful +finance +farmboy +farscape +genesis1 +salomon +destroy +papers +option +page +loser1 +lopez +r2d2 +pumpkins +training +chriss +cumcum +ninjas +ninja1 +hung +erika +eduardo +killers +miller1 +islander +jamesbond +intel +jarvis +19841984 +2626 +bizzare +blue12 +biker +yoyoma +sushi +styles +shitface +series +shanti +spanker +steffi +smart +sphinx +please1 +paulie +pistons +tiburon +limited +maxwell1 +mdogg +rockies +armstron +alexia +arlene +alejandr +arctic +banger +audio +asimov +augustus +grandpa +753951 +4you +chilly +care1839 +chapman +flyfish +fantasia +freefall +santa +sandrine +oreo +ohshit +macbeth +madcat +loveya +mallory +rage +quentin +qwerqwer +project +ramirez +colnago +citizen +chocha +cobalt +crystal1 +dabears +nevets +nineinch +broncos1 +helene +huge +edgar +epsilon +easter +kestrel +moron +virgil +winston1 +warrior1 +iiiiiiii +iloveyou2 +1616 +beat +bettina +woowoo +zander +straight +shower +sloppy +specialk +tinkerbe +jellybea +reader +romero +redsox1 +ride +1215 +1112 +annika +arcadia +answer +baggio +base +guido +555666 +carmel +cayman +cbr900rr +chips +gabriell +gertrude +glennwei +roxy +sausages +disco +pass1 +luna +lovebug +macmac +queenie +puffin +vanguard +trip +trinitro +airwolf +abbott +aaa111 +cocaine +cisco +cottage +dayton +deadly +datsun +bricks +bumper +eldorado +kidrock +wizard1 +whiskers +wind +wildwood +istheman +interest +italy +25802580 +benoit +bigones +woodland +wolfpac +strawber +suicide +3030 +sheba1 +sixpack +peace1 +physics +pearson +tigger2 +toad +megan1 +meow +ringo +roll +amsterdam +717171 +686868 +5424 +catherine +canuck +football1 +footjob +fulham +seagull +orgy +lobo +mancity +truth +trace +vancouve +vauxhall +acidburn +derf +myspace1 +boozer +buttercu +howell +hola +easton +minemine +munch +jared +1dragon +biology +bestbuy +bigpoppa +blackout +blowfish +bmw325 +bigbob +stream +talisman +tazz +sundevil +3333333 +skate +shutup +shanghai +shop +spencer1 +slowhand +polish +pinky1 +tootie +thecrow +leroy +jonathon +jubilee +jingle +martine +matrix1 +manowar +michaels +messiah +mclaren +resident +reilly +redbaron +rollins +romans +return +rivera +andromed +athlon +beach1 +badgers +guitars +harald +harddick +gotribe +6996 +7grout +5wr2i7h8 +635241 +chase1 +carver +charlotte +fallout +fiddle +fredrick +fenris +francesc +fortuna +ferguson +fairlane +felipe +felix1 +forward +gasman +frost +fucks +sahara +sassy1 +dogpound +dogbert +divx1 +manila +loretta +priest +pornporn +quasar +venom +987987 +access1 +clippers +daylight +decker +daman +data +dentist +crusty +nathan1 +nnnnnnnn +bruno1 +bucks +brodie +budapest +kittens +kerouac +mother1 +waldo1 +wedding +whistler +whatwhat +wanderer +idontkno +1942 +1946 +bigdawg +bigpimp +zaqwsx +414141 +3000gt +434343 +shoes +serpent +starr +smurf +pasword +tommie +thisisit +lake +john1 +robotics +redeye +rebelz +1011 +alatam +asses +asians +bama +banzai +harvest +gonzalez +hair +hanson +575757 +5329 +cascade +chinese +fatty +fender1 +flower2 +funky +sambo +drummer1 +dogcat +dottie +oedipus +osama +macleod +prozac +private1 +rampage +punch +presley +concord +cook +cinema +cornwall +cleaner +christopher +ciccio +corinne +clutch +corvet07 +daemon +bruiser +boiler +hjkl +eyes +egghead +expert +ethan +kasper +mordor +wasted +jamess +iverson3 +bluesman +zouzou +090909 +1002 +switch +stone1 +4040 +sisters +sexo +shawna +smith1 +sperma +sneaky +polska +thewho +terminat +krypton +lawson +library +lekker +jules +johnson1 +johann +justus +rockie +romano +aspire +bastards +goodie +cheese1 +fenway +fishon +fishin +fuckoff1 +girls1 +sawyer +dolores +desmond +duane +doomsday +pornking +ramones +rabbits +transit +aaaaa1 +clock +delilah +noel +boyz +bookworm +bongo +bunnies +brady +buceta +highbury +henry1 +heels +eastern +krissy +mischief +mopar +ministry +vienna +weston +wildone +vodka +jayson +bigbooty +beavis1 +betsy +xxxxxx1 +yogibear +000001 +0815 +zulu +420000 +september +sigmar +sprout +stalin +peggy +patch +lkjhgfds +lagnaf +rolex +redfox +referee +123123123 +1231 +angus1 +ariana +ballin +attila +hall +greedy +grunt +747474 +carpedie +cecile +caramel +foxylady +field +gatorade +gidget +futbol +frosch +saiyan +schmidt +drums +donner +doggy1 +drum +doudou +pack +pain +nutmeg +quebec +valdepen +trash +triple +tosser +tuscl +track +comfort +choke +comein +cola +deputy +deadpool +bremen +borders +bronson +break +hotass +hotmail1 +eskimo +eggman +koko +kieran +katrin +kordell1 +komodo +mone +munich +vvvvvvvv +winger +jaeger +ivan +jackson5 +2222222 +bergkamp +bennie +bigben +zanzibar +worm +xxx123 +sunny1 +373737 +services +sheridan +slater +slayer1 +snoop +stacie +peachy +thecure +times +little1 +jennaj +marquis +middle +rasta69 +1114 +aries +havana +gratis +calgary +checkers +flanker +salope +dirty1 +draco +dogface +luv2epus +rainbow6 +qwerty123 +umpire +turnip +vbnm +tucson +troll +aileen +codered +commande +damon +nana +neon +nico +nightwin +neil +boomer1 +bushido +hotmail0 +horace +enternow +kaitlyn +keepout +karen1 +mindy +mnbv +viewsoni +volcom +wizards +wine +1995 +berkeley +bite +zach +woodstoc +tarpon +shinobi +starstar +phat +patience +patrol +toolbox +julien +johnny1 +joebob +marble +riders +reflex +120676 +1235 +angelus +anthrax +atlas +hawks +grandam +harlem +hawaii50 +gorgeous +655321 +cabron +challeng +callisto +firewall +firefire +fischer +flyer +flower1 +factory +federal +gambler +frodo1 +funk +sand +sam123 +scania +dingo +papito +passmast +olive +palermo +ou8123 +lock +ranch +pride +randy1 +twiggy +travis1 +transfer +treetop +addict +admin1 +963852 +aceace +clarissa +cliff +cirrus +clifton +colin +bobdole +bonner +bogus +bonjovi +bootsy +boater +elway7 +edison +kelvin +kenny1 +moonshin +montag +moreno +wayne1 +white1 +jazzy +jakejake +1994 +1991 +2828 +blunt +bluejays +beau +belmont +worthy +systems +sensei +southpark +stan +peeper +pharao +pigpen +tomahawk +teensex +leedsutd +larkin +jermaine +jeepster +jimjim +josephin +melons +marlon +matthias +marriage +robocop +1003 +1027 +antelope +azsxdc +gordo +hazard +granada +8989 +7894 +ceasar +cabernet +cheshire +california +chelle +candy1 +fergie +fanny +fidelio +giorgio +fuckhead +ruth +sanford +diego +dominion +devon +panic +longer +mackie +qawsed +trucking +twelve +chloe1 +coral +daddyo +nostromo +boyboy +booster +bucky +honolulu +esquire +dynamite +motor +mollydog +wilder +windows1 +waffle +wallet +warning +virus +washburn +wealth +vincent1 +jabber +jaguars +javelin +irishman +idefix +bigdog1 +blue42 +blanked +blue32 +biteme1 +bearcats +blaine +yessir +sylveste +team +stephan +sunfire +tbird +stryker +3ip76k2 +sevens +sheldon +pilgrim +tenchi +titman +leeds +lithium +lander +linkin +landon +marijuan +mariner +markie +midnite +reddwarf +1129 +123asd +12312312 +allstar +albany +asdf12 +antonia +aspen +hardball +goldfing +7734 +49ers +carlo +chambers +cable +carnage +callum +carlos1 +fitter +fandango +festival +flame +gofast +gamma +fucmy69 +scrapper +dogwood +django +magneto +loose +premium +addison +9999999 +abc1234 +cromwell +newyear +nichole +bookie +burns +bounty +brown1 +bologna +earl +entrance +elway +killjoy +kerry +keenan +kick +klondike +mini +mouser +mohammed +wayer +impreza +irene +insomnia +24682468 +2580 +24242424 +billbill +bellaco +blessing +blues1 +bedford +blanco +blunts +stinks +teaser +streets +sf49ers +shovel +solitude +spikey +sonia +pimpdadd +timeout +toffee +lefty +johndoe +johndeer +mega +manolo +mentor +margie +ratman +ridge +record +rhodes +robin1 +1124 +1210 +1028 +1226 +another +babylove +barbados +harbor +gramma +646464 +carpente +chaos1 +fishbone +fireblad +glasgow +frogs +scissors +screamer +salem +scuba1 +ducks +driven +doggies +dicky +donovan +obsidian +rams +progress +tottenham +aikman +comanche +corolla +clarke +conway +cumslut +cyborg +dancing +boston1 +bong +houdini +helmut +elvisp +edge +keksa12 +misha +monty1 +monsters +wetter +watford +wiseguy +veronika +visitor +janelle +1989 +1987 +20202020 +biatch +beezer +bigguns +blueball +bitchy +wyoming +yankees2 +wrestler +stupid1 +sealteam +sidekick +simple1 +smackdow +sporting +spiral +smeller +sperm +plato +tophat +test2 +theatre +thick +toomuch +leigh +jello +jewish +junkie +maxim +maxime +meadow +remingto +roofer +124038 +1018 +1269 +1227 +123457 +arkansas +alberta +aramis +andersen +beaker +barcelona +baltimor +googoo +goochi +852456 +4711 +catcher +carman +champ1 +chess +fortress +fishfish +firefigh +geezer +rsalinas +samuel1 +saigon +scooby1 +doors +dick1 +devin +doom +dirk +doris +dontknow +load +magpies +manfred +raleigh +vader1 +universa +tulips +defense +mygirl +burn +bowtie +bowman +holycow +heinrich +honeys +enforcer +katherine +minerva +wheeler +witch +waterboy +jaime +irving +1992 +23skidoo +bimbo +blue11 +birddog +woodman +womble +zildjian +030303 +stinker +stoppedby +sexybabe +speakers +slugger +spotty +smoke1 +polopolo +perfect1 +things +torpedo +tender +thrasher +lakeside +lilith +jimmys +jerk +junior1 +marsh +masamune +rice +root +1214 +april1 +allgood +bambi +grinch +767676 +5252 +cherries +chipmunk +cezer121 +carnival +capecod +finder +flint +fearless +goats +funstuff +gideon +savior +seabee +sandro +schalke +salasana +disney1 +duckman +options +pancake +pantera1 +malice +lookin +love123 +lloyd +qwert123 +puppet +prayers +union +tracer +crap +creation +cwoui +nascar24 +hookers +hollie +hewitt +estrella +erection +ernesto +ericsson +edthom +kaylee +kokoko +kokomo +kimball +morales +mooses +monk +walton +weekend +inter +internal +1michael +1993 +19781978 +25252525 +worker +summers +surgery +shibby +shamus +skibum +sheepdog +sex69 +spliff +slipper +spoons +spanner +snowbird +slow +toriamos +temp123 +tennesse +lakers1 +jomama +julio +mazdarx7 +rosario +recon +riddle +room +revolver +1025 +1101 +barney1 +babycake +baylor +gotham +gravity +hallowee +hancock +616161 +515000 +caca +cannabis +castor +chilli +fdsa +getout +fuck69 +gators1 +sail +sable +rumble +dolemite +dork +dickens +duffer +dodgers1 +painting +onions +logger +lorena +lookout +magic32 +port +poon +prime +twat +coventry +citroen +christmas +civicsi +cocksucker +coochie +compaq1 +nancy1 +buzzer +boulder +butkus +bungle +hogtied +honor +hero +hotgirls +hilary +heidi1 +eggplant +mustang6 +mortal +monkey12 +wapapapa +wendy1 +volleyba +vibrate +vicky +bledsoe +blink +birthday4 +woof +xxxxx1 +talk +stephen1 +suburban +stock +tabatha +sheeba +start1 +soccer10 +something +starcraft +soccer12 +peanut1 +plastics +penthous +peterbil +tools +tetsuo +torino +tennis1 +termite +ladder +last +lemmein +lakewood +jughead +melrose +megane +reginald +redone +request +angela1 +alive +alissa +goodgirl +gonzo1 +golden1 +gotyoass +656565 +626262 +capricor +chains +calvin1 +foolish +fallon +getmoney +godfather +gabber +gilligan +runaway +salami +dummy +dungeon +dudedude +dumb +dope +opus +paragon +oxygen +panhead +pasadena +opendoor +odyssey +magellan +lottie +printing +pressure +prince1 +trustme +christa +court +davies +neville +nono +bread +buffet +hound +kajak +killkill +mona +moto +mildred +winner1 +vixen +whiteboy +versace +winona +voyager1 +instant +indy +jackjack +bigal +beech +biggun +blake1 +blue99 +big1 +woods +synergy +success1 +336699 +sixty9 +shark1 +skin +simba1 +sharpe +sebring +spongebo +spunk +springs +sliver +phialpha +password9 +pizza1 +plane +perkins +pookey +tickling +lexingky +lawman +joe123 +jolly +mike123 +romeo1 +redheads +reserve +apple123 +alanis +ariane +antony +backbone +aviation +band +hand +green123 +haley +carlitos +byebye +cartman1 +camden +chewy +camaross +favorite6 +forumwp +franks +ginscoot +fruity +sabrina1 +devil666 +doughnut +pantie +oldone +paintball +lumina +rainbow1 +prosper +total +true +umbrella +ajax +951753 +achtung +abc12345 +compact +color +corn +complete +christi +closer +corndog +deerhunt +darklord +dank +nimitz +brandy1 +bowl +breanna +holidays +hetfield +holein1 +hillbill +hugetits +east +evolutio +kenobi +whiplash +waldo +wg8e3wjf +wing +istanbul +invis +1996 +benton +bigjohn +bluebell +beef +beater +benji +bluejay +xyzzy +wrestling +storage +superior +suckdick +taichi +stellar +stephane +shaker +skirt +seymour +semper +splurge +squeak +pearls +playball +pitch +phyllis +pooky +piss +tomas +titfuck +joemama +johnny5 +marcello +marjorie +married +maxi +rhubarb +rockwell +ratboy +reload +rooney +redd +1029 +1030 +1220 +anchor +bbking +baritone +gryphon +gone +57chevy +494949 +celeron +fishy +gladiator +fucker1 +roswell +dougie +downer +dicker +diva +domingo +donjuan +nympho +omar +praise +racers +trick +trauma +truck1 +trample +acer +corwin +cricket1 +clemente +climax +denmark +cuervo +notnow +nittany +neutron +native +bosco1 +buffa +breaker +hello2 +hydro +estelle +exchange +explore +kisskiss +kittys +kristian +montecar +modem +mississi +mooney +weiner +washington +20012001 +bigdick1 +bibi +benfica +yahoo1 +striper +tabasco +supra +383838 +456654 +seneca +serious +shuttle +socks +stanton +penguin1 +pathfind +testibil +thethe +listen +lightning +lighting +jeter2 +marma +mark1 +metoo +republic +rollin +redleg +redbone +redskin +rocco +1245 +armand +anthony7 +altoids +andrews +barley +away +asswipe +bauhaus +bbbbbb1 +gohome +harrier +golfpro +goldeney +818181 +6666666 +5000 +5rxypn +cameron1 +calling +checker +calibra +fields +freefree +faith1 +fist +fdm7ed +finally +giraffe +glasses +giggles +fringe +gate +georgie +scamper +rrpass1 +screwyou +duffy +deville +dimples +pacino +ontario +passthie +oberon +quest1 +postov1000 +puppydog +puffer +raining +protect +qwerty7 +trey +tribe +ulysses +tribal +adam25 +a1234567 +compton +collie +cleopatr +contract +davide +norris +namaste +myrtle +buffalo1 +bonovox +buckley +bukkake +burning +burner +bordeaux +burly +hun999 +emilie +elmo +enters +enrique +keisha +mohawk +willard +vgirl +whale +vince +jayden +jarrett +1812 +1943 +222333 +bigjim +bigd +zoom +wordup +ziggy1 +yahooo +workout +young1 +written +xmas +zzzzzz1 +surfer1 +strife +sunlight +tasha1 +skunk +shauna +seth +soft +sprinter +peaches1 +planes +pinetree +plum +pimping +theforce +thedon +toocool +leeann +laddie +list +lkjh +lara +joke +jupiter1 +mckenzie +matty +rene +redrose +1200 +102938 +annmarie +alexa +antares +austin31 +ground +goose1 +737373 +78945612 +789987 +6464 +calimero +caster +casper1 +cement +chevrolet +chessie +caddy +chill +child +canucks +feeling +favorite +fellatio +f00tball +francine +gateway2 +gigi +gamecube +giovanna +rugby1 +scheisse +dshade +dudes +dixie1 +owen +offshore +olympia +lucas1 +macaroni +manga +pringles +puff +tribble +trouble1 +ussy +core +clint +coolhand +colonial +colt +debra +darthvad +dealer +cygnusx1 +natalie1 +newark +husband +hiking +errors +eighteen +elcamino +emmett +emilia +koolaid +knight1 +murphy1 +volcano +idunno +2005 +2233 +block +benito +blueberr +biguns +yamahar1 +zapper +zorro1 +0911 +3006 +sixsix +shopper +siobhan +sextoy +stafford +snowboard +speedway +sounds +pokey +peabody +playboy2 +titi +think +toast +toonarmy +lister +lambda +joecool +jonas +joyce +juniper +mercer +max123 +manny +massimo +mariposa +met2002 +reggae +ricky1 +1236 +1228 +1016 +all4one +arianna +baberuth +asgard +gonzales +484848 +5683 +6669 +catnip +chiquita +charisma +capslock +cashmone +chat +figure +galant +frenchy +gizmodo1 +girlies +gabby +garner +screwy +doubled +divers +dte4uw +done +dragonfl +maker +locks +rachelle +treble +twinkie +trailer +tropical +acid +crescent +cooking +cococo +cory +dabomb +daffy +dandfa +cyrano +nathanie +briggs +boners +helium +horton +hoffman +hellas +espresso +emperor +killa +kikimora +wanda +w4g8at +verona +ilikeit +iforget +1944 +20002000 +birthday1 +beatles1 +blue1 +bigdicks +beethove +blacklab +blazers +benny1 +woodwork +0069 +0101 +taffy +susie +survivor +swim +stokes +4567 +shodan +spoiled +steffen +pissed +pavlov +pinnacle +place +petunia +terrell +thirty +toni +tito +teenie +lemonade +lily +lillie +lalakers +lebowski +lalalala +ladyboy +jeeper +joyjoy +mercury1 +mantle +mannn +rocknrol +riversid +reeves +123aaa +11112222 +121314 +1021 +1004 +1120 +allen1 +ambers +amstel +ambrose +alice1 +alleycat +allegro +ambrosia +alley +australia +hatred +gspot +graves +goodsex +hattrick +harpoon +878787 +8inches +4wwvte +cassandr +charlie123 +case +chavez +fighting +gabriela +gatsby +fudge +gerry +generic +gareth +fuckme2 +samm +sage +seadog +satchmo +scxakv +santafe +dipper +dingle +dizzy +outoutout +madmad +london1 +qbg26i +pussy123 +randolph +vaughn +tzpvaw +vamp +comedy +comp +cowgirl +coldplay +dawgs +delaney +nt5d27 +novifarm +needles +notredam +newness +mykids +bryan1 +bouncer +hihihi +honeybee +iceman1 +herring +horn +hook +hotlips +dynamo +klaus +kittie +kappa +kahlua +muffy +mizzou +mohamed +musical +wannabe +wednesda +whatup +weller +waterfal +willy1 +invest +blanche +bear1 +billabon +youknow +zelda +yyyyyy1 +zachary1 +01234567 +070462 +zurich +superstar +storms +tail +stiletto +strat +427900 +sigmachi +shelter +shells +sexy123 +smile1 +sophie1 +stefano +stayout +somerset +smithers +playmate +pinkfloyd +phish1 +payday +thebear +telefon +laetitia +kswbdu +larson +jetta +jerky +melina +metro +revoluti +retire +respect +1216 +1201 +1204 +1222 +1115 +archange +barry1 +handball +676767 +chandra +chewbacc +flesh +furball +gocubs +fruit +fullback +gman +gentle +dunbar +dewalt +dominiqu +diver1 +dhip6a +olemiss +ollie +mandrake +mangos +pretzel +pusssy +tripleh +valdez +vagabond +clean +comment +crew +clovis +deaths +dandan +csfbr5yy +deadspin +darrel +ninguna +noah +ncc74656 +bootsie +bp2002 +bourbon +brennan +bumble +books +hose +heyyou +houston1 +hemlock +hippo +hornets +hurricane +horseman +hogan +excess +extensa +muffin1 +virginie +werdna +idontknow +info +iron +jack1 +1bitch +151nxjmt +bendover +bmwbmw +bills +zaq123 +wxcvbn +surprise +supernov +tahoe +talbot +simona +shakur +sexyone +seviyi +sonja +smart1 +speed1 +pepito +phantom1 +playoffs +terry1 +terrier +laser1 +lite +lancia +johngalt +jenjen +jolene +midori +message +maserati +matteo +mental +miami1 +riffraff +ronald1 +reason +rhythm +1218 +1026 +123987 +1015 +1103 +armada +architec +austria +gotmilk +hawkins +gray +camila +camp +cambridg +charge +camero +flex +foreplay +getoff +glacier +glotest +froggie +gerbil +rugger +sanity72 +salesman +donna1 +dreaming +deutsch +orchard +oyster +palmtree +ophelia +pajero +m5wkqf +magenta +luckyone +treefrog +vantage +usmarine +tyvugq +uptown +abacab +aaaaaa1 +advance +chuck1 +delmar +darkange +cyclones +nate +navajo +nope +border +bubba123 +building +iawgk2 +hrfzlz +dylan1 +enrico +encore +emilio +eclipse1 +killian +kayleigh +mutant +mizuno +mustang2 +video1 +viewer +weed420 +whales +jaguar1 +insight +1990 +159159 +1love +bliss +bears1 +bigtruck +binder +bigboss +blitz +xqgann +yeahyeah +zeke +zardoz +stickman +table +3825 +signal +sentra +side +shiva +skipper1 +singapor +southpaw +sonora +squid +slamdunk +slimjim +placid +photon +placebo +pearl1 +test12 +therock1 +tiger123 +leinad +legman +jeepers +joeblow +mccarthy +mike23 +redcar +rhinos +rjw7x4 +1102 +13576479 +112211 +alcohol +gwju3g +greywolf +7bgiqk +7878 +535353 +4snz9g +candyass +cccccc1 +carola +catfight +cali +fister +fosters +finland +frankie1 +gizzmo +fuller +royalty +rugrat +sandie +rudolf +dooley +dive +doreen +dodo +drop +oemdlg +out3xf +paddy +opennow +puppy1 +qazwsxedc +pregnant +quinn +ramjet +under +uncle +abraxas +corner +creed +cocoa +crown +cows +cn42qj +dancer1 +death666 +damned +nudity +negative +nimda2k +buick +bobb +braves1 +brook +henrik +higher +hooligan +dust +everlast +karachi +mortis +mulligan +monies +motocros +wally1 +weapon +waterman +view +willie1 +vicki +inspiron +1test +2929 +bigblack +xytfu7 +yackwin +zaq1xsw2 +yy5rbfsc +100100 +0660 +tahiti +takehana +talks +332211 +3535 +sedona +seawolf +skydiver +shine +spleen +slash +spjfet +special1 +spooner +slimshad +sopranos +spock1 +penis1 +patches1 +terri +thierry +thething +toohot +large +limpone +johnnie +mash4077 +matchbox +masterp +maxdog +ribbit +reed +rita +rockin +redhat +rising +1113 +14789632 +1331 +allday +aladin +andrey +amethyst +ariel +anytime +baseball1 +athome +basil +goofy1 +greenman +gustavo +goofball +ha8fyp +goodday +778899 +charon +chappy +castillo +caracas +cardiff +capitals +canada1 +cajun +catter +freddy1 +favorite2 +frazier +forme +follow +forsaken +feelgood +gavin +gfxqx686 +garlic +sarge +saskia +sanjose +russ +salsa +dilbert1 +dukeduke +downhill +longhair +loop +locutus +lockdown +malachi +mamacita +lolipop +rainyday +pumpkin1 +punker +prospect +rambo1 +rainbows +quake +twin +trinity1 +trooper1 +aimee +citation +coolcat +crappy +default +dental +deniro +d9ungl +daddys +napoli +nautica +nermal +bukowski +brick +bubbles1 +bogota +board +branch +breath +buds +hulk +humphrey +hitachi +evans +ender +export +kikiki +kcchiefs +kram +morticia +montrose +mongo +waqw3p +wizzard +visited +whdbtp +whkzyc +image +154ugeiu +1fuck +binky +blind +bigred1 +blubber +benz +becky1 +year2005 +wonderfu +wooden +xrated +0001 +tampabay +survey +tammy1 +stuffer +3mpz4r +3000 +3some +selina +sierra1 +shampoo +silk +shyshy +slapnuts +standby +spartan1 +sprocket +sometime +stanley1 +poker1 +plus +thought +theshit +torture +thinking +lavalamp +light1 +laserjet +jediknig +jjjjj1 +jocelyn +mazda626 +menthol +maximo +margaux +medic1 +release +richter +rhino1 +roach +renate +repair +reveal +1209 +1234321 +amigos +apricot +alexandra +asdfgh1 +hairball +hatter +graduate +grimace +7xm5rq +6789 +cartoons +capcom +cheesy +cashflow +carrots +camping +fanatic +fool +format +fleming +girlie +glover +gilmore +gardner +safeway +ruthie +dogfart +dondon +diapers +outsider +odin +opiate +lollol +love12 +loomis +mallrats +prague +primetime21 +pugsley +program +r29hqq +touch +valleywa +airman +abcdefg1 +darkone +cummer +dempsey +damn +nadia +natedogg +nineball +ndeyl5 +natchez +newone +normandy +nicetits +buddy123 +buddys +homely +husky +iceland +hr3ytm +highlife +holla +earthlin +exeter +eatmenow +kimkim +karine +k2trix +kernel +kirkland +money123 +moonman +miles1 +mufasa +mousey +wilma +wilhelm +whites +warhamme +instinct +jackass1 +2277 +20spanks +blobby +blair +blinky +bikers +blackjack +becca +blue23 +xman +wyvern +085tzzqi +zxzxzx +zsmj2v +suede +t26gn4 +sugars +sylvie +tantra +swoosh +swiss +4226 +4271 +321123 +383pdjvl +shoe +shane1 +shelby1 +spades +spain +smother +soup +sparhawk +pisser +photo1 +pebble +phones +peavey +picnic +pavement +terra +thistle +tokyo +therapy +lives +linden +kronos +lilbit +linux +johnston +material +melanie1 +marbles +redlight +reno +recall +1208 +1138 +1008 +alchemy +aolsucks +alexalex +atticus +auditt +ballet +b929ezzh +goodyear +hanna +griffith +gubber +863abgsg +7474 +797979 +464646 +543210 +4zqauf +4949 +ch5nmk +carlito +chewey +carebear +caleb +checkmat +cheddar +chachi +fever +forgetit +fine +forlife +giants1 +gates +getit +gamble +gerhard +galileo +g3ujwg +ganja +rufus1 +rushmore +scouts +discus +dudeman +olympus +oscars +osprey +madcow +locust +loyola +mammoth +proton +rabbit1 +question +ptfe3xxp +pwxd5x +purple1 +punkass +prophecy +uyxnyd +tyson1 +aircraft +access99 +abcabc +cocktail +colts +civilwar +cleveland +claudia1 +contour +clement +dddddd1 +cypher +denied +dapzu455 +dagmar +daisydog +name +noles +butters +buford +hoochie +hotel +hoser +eddy +ellis +eldiablo +kingrich +mudvayne +motown +mp8o6d +wife +vipergts +italiano +innocent +2055 +2211 +beavers +bloke +blade1 +yamato +zooropa +yqlgr667 +050505 +zxcvbnm1 +zw6syj +suckcock +tango1 +swing +stern +stephens +swampy +susanna +tammie +445566 +333666 +380zliki +sexpot +sexylady +sixtynin +sickboy +spiffy +sleeping +skylark +sparkles +slam +pintail +phreak +places +teller +timtim +tires +thighs +left +latex +llamas +letsdoit +lkjhg +landmark +letters +lizzard +marlins +marauder +metal1 +manu +register +righton +1127 +alain +alcat +amigo +basebal1 +azertyui +attract +azrael +hamper +gotenks +golfgti +gutter +hawkwind +h2slca +harman +grace1 +6chid8 +789654 +canine +casio +cazzo +chamber +cbr900 +cabrio +calypso +capetown +feline +flathead +fisherma +flipmode +fungus +goal +g9zns4 +full +giggle +gabriel1 +fuck123 +saffron +dogmeat +dreamcas +dirtydog +dunlop +douche +dresden +dickdick +destiny1 +pappy +oaktree +lydia +luft4 +puta +prayer +ramada +trumpet1 +vcradq +tulip +tracy71 +tycoon +aaaaaaa1 +conquest +click +chitown +corps +creepers +constant +couples +code +cornhole +danman +dada +density +d9ebk7 +cummins +darth +cute +nash +nirvana1 +nixon +norbert +nestle +brenda1 +bonanza +bundy +buddies +hotspur +heavy +horror +hufmqw +electro +erasure +enough +elisabet +etvww4 +ewyuza +eric1 +kinder +kenken +kismet +klaatu +musician +milamber +willi +waiting +isacs155 +igor +1million +1letmein +x35v8l +yogi +ywvxpz +xngwoj +zippy1 +020202 +**** +stonewal +sweeney +story +sentry +sexsexsex +spence +sonysony +smirnoff +star12 +solace +sledge +states +snyder +star1 +paxton +pentagon +pkxe62 +pilot1 +pommes +paulpaul +plants +tical +tictac +toes +lighthou +lemans +kubrick +letmein22 +letmesee +jys6wz +jonesy +jjjjjj1 +jigga +joelle +mate +merchant +redstorm +riley1 +rosa +relief +14141414 +1126 +allison1 +badboy1 +asthma +auggie +basement +hartley +hartford +hardwood +gumbo +616913 +57np39 +56qhxs +4mnveh +cake +forbes +fatluvr69 +fqkw5m +fidelity +feathers +fresno +godiva +gecko +gladys +gibson1 +gogators +fridge +general1 +saxman +rowing +sammys +scotts +scout1 +sasasa +samoht +dragon69 +ducky +dragonball +driller +p3wqaw +nurse +papillon +oneone +openit +optimist +longshot +portia +rapier +pussy2 +ralphie +tuxedo +ulrike +undertow +trenton +copenhag +come +delldell +culinary +deltas +mytime +nicky +nickie +noname +noles1 +bucker +bopper +bullock +burnout +bryce +hedges +ibilltes +hihje863 +hitter +ekim +espana +eatme69 +elpaso +envelope +express1 +eeeeee1 +eatme1 +karaoke +kara +mustang5 +misses +wellingt +willem +waterski +webcam +jasons +infinite +iloveyou! +jakarta +belair +bigdad +beerme +yoshi +yinyang +zimmer +x24ik3 +063dyjuy +0000007 +ztmfcq +stopit +stooges +survival +stockton +symow8 +strato +2hot4u +ship +simons +skins +shakes +sex1 +shield +snacks +softtail +slimed123 +pizzaman +pipe +pitt +pathetic +pinto +tigercat +tonton +lager +lizzy +juju +john123 +jennings +josiah +jesse1 +jordon +jingles +martian +mario1 +rootedit +rochard +redwine +requiem +riverrat +rats +1117 +1014 +1205 +althea +allie +amor +amiga +alpina +alert +atreides +banana1 +bahamut +hart +golfman +happines +7uftyx +5432 +5353 +5151 +4747 +byron +chatham +chadwick +cherie +foxfire +ffvdj474 +freaked +foreskin +gayboy +gggggg1 +glenda +gameover +glitter +funny1 +scoobydoo +scroll +rudolph +saddle +saxophon +dingbat +digimon +omicron +parsons +ohio +panda1 +loloxx +macintos +lululu +lollypop +racer1 +queen1 +qwertzui +prick +upnfmc +tyrant +trout1 +9skw5g +aceman +adelaide +acls2h +aaabbb +acapulco +aggie +comcast +craft +crissy +cloudy +cq2kph +custer +d6o8pm +cybersex +davecole +darian +crumbs +daisey +davedave +dasani +needle +mzepab +myporn +narnia +nineteen +booger1 +bravo1 +budgie +btnjey +highlander +hotel6 +humbug +edwin +ewtosi +kristin1 +kobe +knuckles +keith1 +katarina +muff +muschi +montana1 +wingchun +wiggle +whatthe +walking +watching +vette1 +vols +virago +intj3a +ishmael +intern +jachin +illmatic +199999 +2010 +beck +blender +bigpenis +bengal +blue1234 +your +zaqxsw +xray +xxxxxxx1 +zebras +yanks +worlds +tadpole +stripes +svetlana +3737 +4343 +3728 +4444444 +368ejhih +solar +sonne +smalls +sniffer +sonata +squirts +pitcher +playstation +pktmxr +pescator +points +texaco +lesbos +lilian +l8v53x +jo9k2jw2 +jimbeam +josie +jimi +jupiter2 +jurassic +marines1 +maya +rocket1 +ringer +14725836 +12345679 +1219 +123098 +1233 +alessand +althor +angelika +arch +armando +alpha123 +basher +barefeet +balboa +bbbbb1 +banks +badabing +harriet +gopack +golfnut +gsxr1000 +gregory1 +766rglqy +8520 +753159 +8dihc6 +69camaro +666777 +cheeba +chino +calendar +cheeky +camel1 +fishcake +falling +flubber +giuseppe +gianni +gloves +gnasher23 +frisbee +fuzzy1 +fuzzball +sauce +save13tx +schatz +russell1 +sandra1 +scrotum +scumbag +sabre +samdog +dripping +dragon12 +dragster +paige +orwell +mainland +lunatic +lonnie +lotion +maine +maddux +qn632o +poophead +rapper +porn4life +producer +rapunzel +tracks +velocity +vanessa1 +ulrich +trueblue +vampire1 +abacus +902100 +crispy +corky +crane +chooch +d6wnro +cutie +deal +dabulls +dehpye +navyseal +njqcw4 +nownow +nigger1 +nightowl +nonenone +nightmar +bustle +buddy2 +boingo +bugman +bulletin +bosshog +bowie +hybrid +hillside +hilltop +hotlegs +honesty +hzze929b +hhhhh1 +hellohel +eloise +evilone +edgewise +e5pftu +eded +embalmer +excalibur +elefant +kenzie +karl +karin +killah +kleenex +mouses +mounta1n +motors +mutley +muffdive +vivitron +winfield +wednesday +w00t88 +iloveit +jarjar +incest +indycar +17171717 +1664 +17011701 +222777 +2663 +beelch +benben +yitbos +yyyyy1 +yasmin +zapata +zzzzz1 +stooge +tangerin +taztaz +stewart1 +summer69 +sweetness +system1 +surveyor +stirling +3qvqod +3way +456321 +sizzle +simhrq +shrink +shawnee +someday +sparty +ssptx452 +sphere +spark +slammed +sober +persian +peppers +ploppy +pn5jvw +poobear +pianos +plaster +testme +tiff +thriller +larissa +lennox +jewell +master12 +messier +rockey +1229 +1217 +1478 +1009 +anastasi +almighty +amonra +aragon +argentin +albino +azazel +grinder +6uldv8 +83y6pv +8888888 +4tlved +515051 +carsten +changes +flanders +flyers88 +ffffff1 +firehawk +foreman +firedog +flashman +ggggg1 +gerber +godspeed +galway +giveitup +funtimes +gohan +giveme +geryfe +frenchie +sayang +rudeboy +savanna +sandals +devine +dougal +drag0n +dga9la +disaster +desktop +only +onlyone +otter +pandas +mafia +lombard +luckys +lovejoy +lovelife +manders +product +qqh92r +qcmfd454 +pork +radar1 +punani +ptbdhw +turtles +undertaker +trs8f7 +tramp +ugejvp +abba +911turbo +acdc +abcd123 +clever +corina +cristian +create +crash1 +colony +crosby +delboy +daniele +davinci +daughter +notebook +niki +nitrox +borabora +bonzai +budd +brisbane +hotter +heeled +heroes +hooyah +hotgirl +i62gbq +horse1 +hills +hpk2qc +epvjb6 +echo +korean +kristie +mnbvc +mohammad +mind +mommy1 +munster +wade +wiccan +wanted +jacket +2369 +bettyboo +blondy +bismark +beanbag +bjhgfi +blackice +yvtte545 +ynot +yess +zlzfrh +wolvie +007bond +****** +tailgate +tanya1 +sxhq65 +stinky1 +3234412 +3ki42x +seville +shimmer +sheryl +sienna +shitshit +skillet +seaman +sooners1 +solaris +smartass +pastor +pasta +pedros +pennywis +pfloyd +tobydog +thetruth +lethal +letme1n +leland +jenifer +mario66 +micky +rocky2 +rewq +ripped +reindeer +1128 +1207 +1104 +1432 +aprilia +allstate +alyson +bagels +basic +baggies +barb +barrage +greatest +gomez +guru +guard +72d5tn +606060 +4wcqjn +caldwell +chance1 +catalog +faust +film +flange +fran +fartman +geil +gbhcf2 +fussball +glen +fuaqz4 +gameboy +garnet +geneviev +rotary +seahawk +russel +saab +seal +samadams +devlt4 +ditto +drevil +drinker +deuce +dipstick +donut +octopus +ottawa +losangel +loverman +porky +q9umoz +rapture +pump +pussy4me +university +triplex +ue8fpw +trent +trophy +turbos +troubles +agent +aaa340 +churchil +crazyman +consult +creepy +craven +class +cutiepie +ddddd1 +dejavu +cuxldv +nettie +nbvibt +nikon +niko +norwood +nascar1 +nolan +bubba2 +boobear +boogers +buff +bullwink +bully +bulldawg +horsemen +escalade +editor +eagle2 +dynamic +ella +efyreg +edition +kidney +minnesot +mogwai +morrow +msnxbi +moonlight +mwq6qlzo +wars +werder +verygood +voodoo1 +wheel +iiiiii1 +159951 +1624 +1911a1 +2244 +bellagio +bedlam +belkin +bill1 +woodrow +xirt2k +worship +?????? +tanaka +swift +susieq +sundown +sukebe +tales +swifty +2fast4u +senate +sexe +sickness +shroom +shaun +seaweed +skeeter1 +status +snicker +sorrow +spanky1 +spook +patti +phaedrus +pilots +pinch +peddler +theo +thumper1 +tessie +tiger7 +tmjxn151 +thematri +l2g7k3 +letmeinn +lazy +jeffjeff +joan +johnmish +mantra +mariana +mike69 +marshal +mart +mazda6 +riptide +robots +rental +1107 +1130 +142857 +11001001 +1134 +armored +alvin +alec +allnight +alright +amatuers +bartok +attorney +astral +baboon +bahamas +balls1 +bassoon +hcleeb +happyman +granite +graywolf +golf1 +gomets +8vjzus +7890 +789123 +8uiazp +5757 +474jdvff +551scasi +50cent +camaro1 +cherry1 +chemist +final +firenze +fishtank +farrell +freewill +glendale +frogfrog +gerhardt +ganesh +same +scirocco +devilman +doodles +dinger +okinawa +olympic +nursing +orpheus +ohmygod +paisley +pallmall +null +lounge +lunchbox +manhatta +mahalo +mandarin +qwqwqw +qguvyt +pxx3eftp +president +rambler +puzzle +poppy1 +turk182 +trotter +vdlxuc +trish +tugboat +valiant +tracie +uwrl7c +chris123 +coaster +cmfnpu +decimal +debbie1 +dandy +daedalus +dede +natasha1 +nissan1 +nancy123 +nevermin +napalm +newcastle +boats +branden +britt +bonghit +hester +ibxnsm +hhhhhh1 +holger +durham +edmonton +erwin +equinox +dvader +kimmy +knulla +mustafa +monsoon +mistral +morgana +monica1 +mojave +month +monterey +mrbill +vkaxcs +victor1 +wacker +wendell +violator +vfdhif +wilson1 +wavpzt +verena +wildstar +winter99 +iqzzt580 +jarrod +imback +1914 +19741974 +1monkey +1q2w3e4r5t +2500 +2255 +blank +bigshow +bigbucks +blackcoc +zoomer +wtcacq +wobble +xmen +xjznq5 +yesterda +yhwnqc +zzzxxx +streak +393939 +2fchbg +skinhead +skilled +shakira +shaft +shadow12 +seaside +sigrid +sinful +silicon +smk7366 +snapshot +sniper1 +soccer11 +staff +slap +smutty +peepers +pleasant +plokij +pdiddy +pimpdaddy +thrust +terran +topaz +today1 +lionhear +littlema +lauren1 +lincoln1 +lgnu9d +laughing +juneau +methos +medina +merlyn +rogue1 +romulus +redshift +1202 +1469 +12locked +arizona1 +alfarome +al9agd +aol123 +altec +apollo1 +arse +baker1 +bbb747 +bach +axeman +astro1 +hawthorn +goodfell +hawks1 +gstring +hannes +8543852 +868686 +4ng62t +554uzpad +5401 +567890 +5232 +catfood +frame +flow +fire1 +flipflop +fffff1 +fozzie +fluff +garrison +fzappa +furious +round +rustydog +sandberg +scarab +satin +ruger +samsung1 +destin +diablo2 +dreamer1 +detectiv +dominick +doqvq3 +drywall +paladin1 +papabear +offroad +panasonic +nyyankee +luetdi +qcfmtz +pyf8ah +puddles +privacy +rainer +pussyeat +ralph1 +princeto +trivia +trewq +tri5a3 +advent +9898 +agyvorc +clarkie +coach1 +courier +contest +christo +corinna +chowder +concept +climbing +cyzkhw +davidb +dad2ownu +days +daredevi +de7mdf +nose +necklace +nazgul +booboo1 +broad +bonzo +brenna +boot +butch1 +huskers1 +hgfdsa +hornyman +elmer +elektra +england1 +elodie +kermit1 +knife +kaboom +minute +modern +motherfucker +morten +mocha +monday1 +morgoth +ward +weewee +weenie +walters +vorlon +website +wahoo +ilovegod +insider +jayman +1911 +1dallas +1900 +1ranger +201jedlz +2501 +1qaz +bertram +bignuts +bigbad +beebee +billows +belize +bebe +wvj5np +wu4etd +yamaha1 +wrinkle5 +zebra1 +yankee1 +zoomzoom +09876543 +0311 +????? +stjabn +tainted +3tmnej +shoot +skooter +skelter +sixteen +starlite +smack +spice1 +stacey1 +smithy +perrin +pollux +peternorth +pixie +paulina +piston +pick +poets +pine +toons +tooth +topspin +kugm7b +legends +jeepjeep +juliana +joystick +junkmail +jojojojo +jonboy +judge +midland +meteor +mccabe +matter +mayfair +meeting +merrill +raul +riches +reznor +rockrock +reboot +reject +robyn +renee1 +roadway +rasta220 +1411 +1478963 +1019 +archery +allman +andyandy +barks +bagpuss +auckland +gooseman +hazmat +gucci +guns +grammy +happydog +greek +7kbe9d +7676 +6bjvpe +5lyedn +5858 +5291 +charlie2 +chas +c7lrwu +candys +chateau +ccccc1 +cardinals +fear +fihdfv +fortune12 +gocats +gaelic +fwsadn +godboy +gldmeo +fx3tuo +fubar1 +garland +generals +gforce +rxmtkp +rulz +sairam +dunhill +division +dogggg +detect +details +doll +drinks +ozlq6qwm +ov3ajy +lockout +makayla +macgyver +mallorca +loves +prima +pvjegu +qhxbij +raphael +prelude1 +totoro +tusymo +trousers +tunnel +valeria +tulane +turtle1 +tracy1 +aerosmit +abbey1 +address +clticic +clueless +cooper1 +comets +collect +corbin +delpiero +derick +cyprus +dante1 +dave1 +nounours +neal +nexus6 +nero +nogard +norfolk +brent1 +booyah +bootleg +buckaroo +bulls23 +bulls1 +booper +heretic +icecube +hellno +hounds +honeydew +hooters1 +hoes +howie +hevnm4 +hugohugo +eighty +epson +evangeli +eeeee1 +eyphed \ No newline at end of file diff --git a/app/init.php b/app/init.php index 40e569f369..c02dffff95 100644 --- a/app/init.php +++ b/app/init.php @@ -605,6 +605,9 @@ $register->set('smtp', function () { $register->set('geodb', function () { return new Reader(__DIR__ . '/assets/dbip/dbip-country-lite-2022-06.mmdb'); }); +$register->set('passwordsdb', function () { + return \file_get_contents(__DIR__ . '/assets/security/10k-common-passwords'); +}); $register->set('db', function () { // This is usually for our workers or CLI commands scope $dbHost = App::getEnv('_APP_DB_HOST', ''); @@ -1022,6 +1025,11 @@ App::setResource('geodb', function ($register) { return $register->get('geodb'); }, ['register']); +App::setResource('passwordsdb', function ($register) { + /** @var Utopia\Registry\Registry $register */ + return $register->get('passwordsdb'); +}, ['register']); + App::setResource('sms', function () { $dsn = new DSN(App::getEnv('_APP_SMS_PROVIDER')); $user = $dsn->getUser(); From 2603f91a6a524b9444927cb83edce138b05741e4 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Fri, 23 Dec 2022 07:19:35 +0000 Subject: [PATCH 29/66] use password history on account create and update password --- app/controllers/api/account.php | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index cdb4024e7a..43c8fc9cb4 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -67,12 +67,13 @@ App::post('/v1/account') ->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) + ->inject('passwordsdb') ->inject('request') ->inject('response') ->inject('project') ->inject('dbForProject') ->inject('events') - ->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $project, Database $dbForProject, Event $events) { + ->action(function (string $userId, string $email, string $password, string $name, string $passwordsdb, Request $request, Response $response, Document $project, Database $dbForProject, Event $events) { $email = \strtolower($email); if ('console' === $project->getId()) { @@ -98,6 +99,12 @@ App::post('/v1/account') } } + if(str_contains($passwordsdb, $password)) { + throw new Exception(Exception::USER_PASSWORD_IN_DICTIONARY, + 'The password is among the common passwords in dictionary.', + 403); + } + $passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; $password = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); @@ -1513,18 +1520,25 @@ App::patch('/v1/account/password') ->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) + ->inject('passwordsdb') ->inject('response') ->inject('user') ->inject('project') ->inject('dbForProject') ->inject('events') - ->action(function (string $password, string $oldPassword, Response $response, Document $user, Document $project, Database $dbForProject, Event $events) { + ->action(function (string $password, string $oldPassword, string $passwordsdb, Response $response, Document $user, Document $project, Database $dbForProject, Event $events) { // Check old password only if its an existing user. if (!empty($user->getAttribute('passwordUpdate')) && !Auth::passwordVerify($oldPassword, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions'))) { // Double check user password throw new Exception(Exception::USER_INVALID_CREDENTIALS); } + if(str_contains($passwordsdb, $password)) { + throw new Exception(Exception::USER_PASSWORD_IN_DICTIONARY, + 'The password is among the common passwords in dictionary.', + 403); + } + $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); $historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; From 1ff8ce2382b2dbe60ade9ef8e99769cfb89ef5c0 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 26 Dec 2022 05:24:26 +0000 Subject: [PATCH 30/66] check against passwords dictionary --- app/controllers/api/account.php | 12 ++++++------ app/controllers/api/users.php | 19 +++++++++++++++++-- app/init.php | 6 +++--- src/Appwrite/Extend/Exception.php | 1 + 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 43c8fc9cb4..2b7877b640 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -67,13 +67,13 @@ App::post('/v1/account') ->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) - ->inject('passwordsdb') + ->inject('passwordsDB') ->inject('request') ->inject('response') ->inject('project') ->inject('dbForProject') ->inject('events') - ->action(function (string $userId, string $email, string $password, string $name, string $passwordsdb, Request $request, Response $response, Document $project, Database $dbForProject, Event $events) { + ->action(function (string $userId, string $email, string $password, string $name, string $passwordsDB, Request $request, Response $response, Document $project, Database $dbForProject, Event $events) { $email = \strtolower($email); if ('console' === $project->getId()) { @@ -99,7 +99,7 @@ App::post('/v1/account') } } - if(str_contains($passwordsdb, $password)) { + if(str_contains($passwordsDB, $password)) { throw new Exception(Exception::USER_PASSWORD_IN_DICTIONARY, 'The password is among the common passwords in dictionary.', 403); @@ -1520,20 +1520,20 @@ App::patch('/v1/account/password') ->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) - ->inject('passwordsdb') + ->inject('passwordsDB') ->inject('response') ->inject('user') ->inject('project') ->inject('dbForProject') ->inject('events') - ->action(function (string $password, string $oldPassword, string $passwordsdb, Response $response, Document $user, Document $project, Database $dbForProject, Event $events) { + ->action(function (string $password, string $oldPassword, string $passwordsDB, Response $response, Document $user, Document $project, Database $dbForProject, Event $events) { // Check old password only if its an existing user. if (!empty($user->getAttribute('passwordUpdate')) && !Auth::passwordVerify($oldPassword, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions'))) { // Double check user password throw new Exception(Exception::USER_INVALID_CREDENTIALS); } - if(str_contains($passwordsdb, $password)) { + if(str_contains($passwordsDB, $password)) { throw new Exception(Exception::USER_PASSWORD_IN_DICTIONARY, 'The password is among the common passwords in dictionary.', 403); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 54e132f085..2b8ed631b2 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -107,11 +107,19 @@ App::post('/v1/users') ->param('phone', null, new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true) ->param('password', null, new Password(), 'Plain text user password. Must be at least 8 chars.', true) ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true) + ->inject('passwordsDB') ->inject('response') ->inject('project') ->inject('dbForProject') ->inject('events') - ->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $events) { + ->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, string $passwordsDB, Response $response, Document $project, Database $dbForProject, Event $events) { + + if(str_contains($passwordsDB, $password)) { + throw new Exception(Exception::USER_PASSWORD_IN_DICTIONARY, + 'The password is among the common passwords in dictionary.', + 403); + } + $user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $project, $dbForProject, $events); $response @@ -792,11 +800,12 @@ 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.') + ->inject('passwordsDB') ->inject('response') ->inject('project') ->inject('dbForProject') ->inject('events') - ->action(function (string $userId, string $password, Response $response, Document $project, Database $dbForProject, Event $events) { + ->action(function (string $userId, string $password, string $passwordsDB, Response $response, Document $project, Database $dbForProject, Event $events) { $user = $dbForProject->getDocument('users', $userId); @@ -804,6 +813,12 @@ App::patch('/v1/users/:userId/password') throw new Exception(Exception::USER_NOT_FOUND); } + if(str_contains($passwordsDB, $password)) { + throw new Exception(Exception::USER_PASSWORD_IN_DICTIONARY, + 'The password is among the common passwords in dictionary.', + 403); + } + $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); $historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; diff --git a/app/init.php b/app/init.php index c02dffff95..5762085eae 100644 --- a/app/init.php +++ b/app/init.php @@ -605,7 +605,7 @@ $register->set('smtp', function () { $register->set('geodb', function () { return new Reader(__DIR__ . '/assets/dbip/dbip-country-lite-2022-06.mmdb'); }); -$register->set('passwordsdb', function () { +$register->set('passwordsDB', function () { return \file_get_contents(__DIR__ . '/assets/security/10k-common-passwords'); }); $register->set('db', function () { @@ -1025,9 +1025,9 @@ App::setResource('geodb', function ($register) { return $register->get('geodb'); }, ['register']); -App::setResource('passwordsdb', function ($register) { +App::setResource('passwordsDB', function ($register) { /** @var Utopia\Registry\Registry $register */ - return $register->get('passwordsdb'); + return $register->get('passwordsDB'); }, ['register']); App::setResource('sms', function () { diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index 3940ad6da9..2af560c18f 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -65,6 +65,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_IN_DICTIONARY = 'password_in_dictionary'; 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'; From 3bcb1f846ed26b1d50b6b6d4f768eb200d02b2e4 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 26 Dec 2022 05:31:49 +0000 Subject: [PATCH 31/66] password disctionary endpont and project model --- app/controllers/api/projects.php | 31 +++++++++++++++++++ .../Utopia/Response/Model/Project.php | 1 + 2 files changed, 32 insertions(+) diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 25045fc64d..cfe4a66acc 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -607,6 +607,37 @@ App::patch('/v1/projects/:projectId/auth/password-history') $response->dynamic($project, Response::MODEL_PROJECT); }); +App::patch('/v1/projects/:projectId/auth/password-dictionary') + ->desc('Update Project users limit') + ->groups(['api', 'projects']) + ->label('scope', 'projects.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) + ->label('sdk.namespace', 'projects') + ->label('sdk.method', 'updateAuthLimit') + ->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(true), 'Set whether or not to enable checking user\'s password against most commonly used passwords. 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['passwordDisctionary'] = (bool) filter_var($enabled, FILTER_VALIDATE_BOOLEAN); + + $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 users limit') ->groups(['api', 'projects']) diff --git a/src/Appwrite/Utopia/Response/Model/Project.php b/src/Appwrite/Utopia/Response/Model/Project.php index 59bd8ea78c..599bb041cd 100644 --- a/src/Appwrite/Utopia/Response/Model/Project.php +++ b/src/Appwrite/Utopia/Response/Model/Project.php @@ -247,6 +247,7 @@ class Project extends Model $document->setAttribute('authDuration', $authValues['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG); $document->setAttribute('authSessionLimit', $authValues['maxSessions'] ?? APP_LIMIT_USER_SESSIONS_DEFAULT); $document->setAttribute('authPasswordHistory', $authValues['passwordHistory'] ?? 0); + $document->setAttribute('authPasswordDictionary', $authValues['passwordDictionary'] ?? false); foreach ($auth as $index => $method) { $key = $method['key']; From 124403b8044a139dbfbc12b95613090c469d1379 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 26 Dec 2022 05:46:35 +0000 Subject: [PATCH 32/66] test for password discionary --- .../Projects/ProjectsConsoleClientTest.php | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index 8dc886adf5..573b7b99f8 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -1101,6 +1101,137 @@ class ProjectsConsoleClientTest extends Scope return $data; } + /** + * @depends testUpdateProjectAuthLimit + */ + public function testUpdateProjectAuthPasswordDictionary($data): array + { + $id = $data['projectId'] ?? ''; + + $password = 'password'; + $name = 'User Name'; + + /** + * Test for Success + */ + + /** + * create account + */ + $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $id, + ]), [ + 'userId' => ID::unique(), + 'email' => uniqid() . 'user@localhost.test', + 'password' => $password, + 'name' => $name, + ]); + $this->assertEquals(201, $response['headers']['status-code']); + $userId = $response['body']['$id']; + + /** + * Create user + */ + $user = $this->client->call(Client::METHOD_POST, '/users', array_merge($this->getHeaders(), [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $id, + 'x-appwrite-mode' => 'admin', + ]), [ + 'userId' => ID::unique(), + 'email' => uniqid() . 'user@localhost.test', + 'password' => 'password', + 'name' => 'Cristiano Ronaldo', + ]); + $this->assertEquals(201, $response['headers']['status-code']); + + /** + * Enable Disctionary + */ + $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/auth/password-dictionary', 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']['authPasswordDictionary']); + + /** + * Test for failure + */ + $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $id, + ]), [ + 'userId' => ID::unique(), + 'email' => uniqid() . 'user@localhost.test', + 'password' => $password, + 'name' => $name, + ]); + + $this->assertEquals(403, $response['headers']['status-code']); + + /** + * Create user + */ + $user = $this->client->call(Client::METHOD_POST, '/users', array_merge($this->getHeaders(), [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $id, + 'x-appwrite-mode' => 'admin', + ]), [ + 'userId' => ID::unique(), + 'email' => uniqid() . 'user@localhost.test', + 'password' => 'password', + 'name' => 'Cristiano Ronaldo', + ]); + $this->assertEquals(403, $response['headers']['status-code']); + + $headers = array_merge($this->getHeaders(), [ + 'x-appwrite-mode' => 'admin', + 'content-type' => 'application/json', + 'x-appwrite-project' => $id, + ]); + + $response = $this->client->call(Client::METHOD_PATCH, '/users/' . $userId . '/password', $headers, [ + 'password' => $password, + ]); + + $this->assertEquals(403, $response['headers']['status-code']); + + + /** + * Reset + */ + $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/auth/password-history', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'limit' => 0, + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(0, $response['body']['authPasswordHistory']); + + /** + * Reset + */ + $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/auth/password-dictionary', 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']['authPasswordDictionary']); + + return $data; + } + public function testUpdateProjectServiceStatusAdmin(): array { From e9710bdb76787ef32675740fbc162e28e5ea6abe Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 26 Dec 2022 05:47:20 +0000 Subject: [PATCH 33/66] formatting fix --- app/controllers/api/account.php | 16 ++++++++++------ app/controllers/api/users.php | 16 ++++++++++------ .../Projects/ProjectsConsoleClientTest.php | 2 +- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 2b7877b640..7a2d268d78 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -99,10 +99,12 @@ App::post('/v1/account') } } - if(str_contains($passwordsDB, $password)) { - throw new Exception(Exception::USER_PASSWORD_IN_DICTIONARY, + if (str_contains($passwordsDB, $password)) { + throw new Exception( + Exception::USER_PASSWORD_IN_DICTIONARY, 'The password is among the common passwords in dictionary.', - 403); + 403 + ); } $passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; @@ -1533,10 +1535,12 @@ App::patch('/v1/account/password') throw new Exception(Exception::USER_INVALID_CREDENTIALS); } - if(str_contains($passwordsDB, $password)) { - throw new Exception(Exception::USER_PASSWORD_IN_DICTIONARY, + if (str_contains($passwordsDB, $password)) { + throw new Exception( + Exception::USER_PASSWORD_IN_DICTIONARY, 'The password is among the common passwords in dictionary.', - 403); + 403 + ); } $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 2b8ed631b2..b087237422 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -114,10 +114,12 @@ App::post('/v1/users') ->inject('events') ->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, string $passwordsDB, Response $response, Document $project, Database $dbForProject, Event $events) { - if(str_contains($passwordsDB, $password)) { - throw new Exception(Exception::USER_PASSWORD_IN_DICTIONARY, + if (str_contains($passwordsDB, $password)) { + throw new Exception( + Exception::USER_PASSWORD_IN_DICTIONARY, 'The password is among the common passwords in dictionary.', - 403); + 403 + ); } $user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $project, $dbForProject, $events); @@ -813,10 +815,12 @@ App::patch('/v1/users/:userId/password') throw new Exception(Exception::USER_NOT_FOUND); } - if(str_contains($passwordsDB, $password)) { - throw new Exception(Exception::USER_PASSWORD_IN_DICTIONARY, + if (str_contains($passwordsDB, $password)) { + throw new Exception( + Exception::USER_PASSWORD_IN_DICTIONARY, 'The password is among the common passwords in dictionary.', - 403); + 403 + ); } $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index 573b7b99f8..a7ca0cc86e 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -1174,7 +1174,7 @@ class ProjectsConsoleClientTest extends Scope ]); $this->assertEquals(403, $response['headers']['status-code']); - + /** * Create user */ From 574ffa4d4b860ba8e2c4e565d511e8486ca6dbf6 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 26 Dec 2022 05:52:49 +0000 Subject: [PATCH 34/66] check dictionary is enabled before checking password --- app/controllers/api/account.php | 12 +++++++----- app/controllers/api/users.php | 10 ++++++---- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 7a2d268d78..c55940591d 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -99,7 +99,8 @@ App::post('/v1/account') } } - if (str_contains($passwordsDB, $password)) { + $passwordDictionary = $project->getAttribute('auths', []['passwordDictionary']) ?? false; + if ($passwordDictionary && str_contains($passwordsDB, $password)) { throw new Exception( Exception::USER_PASSWORD_IN_DICTIONARY, 'The password is among the common passwords in dictionary.', @@ -108,7 +109,6 @@ App::post('/v1/account') } $passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; - $password = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); try { $userId = $userId == 'unique()' ? ID::unique() : $userId; @@ -1535,7 +1535,11 @@ App::patch('/v1/account/password') throw new Exception(Exception::USER_INVALID_CREDENTIALS); } - if (str_contains($passwordsDB, $password)) { + + $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); + + $passwordDictionary = $project->getAttribute('auths', []['passwordDictionary']) ?? false; + if ($passwordDictionary && str_contains($passwordsDB, $password)) { throw new Exception( Exception::USER_PASSWORD_IN_DICTIONARY, 'The password is among the common passwords in dictionary.', @@ -1543,8 +1547,6 @@ App::patch('/v1/account/password') ); } - $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); - $historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; $history = []; if ($historyLimit > 0) { diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index b087237422..de6b192296 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -114,7 +114,8 @@ App::post('/v1/users') ->inject('events') ->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, string $passwordsDB, Response $response, Document $project, Database $dbForProject, Event $events) { - if (str_contains($passwordsDB, $password)) { + $passwordDictionary = $project->getAttribute('auths', []['passwordDictionary']) ?? false; + if ($passwordDictionary && str_contains($passwordsDB, $password)) { throw new Exception( Exception::USER_PASSWORD_IN_DICTIONARY, 'The password is among the common passwords in dictionary.', @@ -815,7 +816,10 @@ App::patch('/v1/users/:userId/password') throw new Exception(Exception::USER_NOT_FOUND); } - if (str_contains($passwordsDB, $password)) { + $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); + + $passwordDictionary = $project->getAttribute('auths', []['passwordDictionary']) ?? false; + if ($passwordDictionary && str_contains($passwordsDB, $password)) { throw new Exception( Exception::USER_PASSWORD_IN_DICTIONARY, 'The password is among the common passwords in dictionary.', @@ -823,8 +827,6 @@ App::patch('/v1/users/:userId/password') ); } - $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); - $historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; $history = []; if ($historyLimit > 0) { From 276032b3f75fcb7f5e0bc2a8986b8b1ecaa5f0f8 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 26 Dec 2022 05:55:02 +0000 Subject: [PATCH 35/66] error fix --- app/controllers/api/account.php | 4 ++-- app/controllers/api/users.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index c55940591d..1acad8fed5 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -99,7 +99,7 @@ App::post('/v1/account') } } - $passwordDictionary = $project->getAttribute('auths', []['passwordDictionary']) ?? false; + $passwordDictionary = $project->getAttribute('auths', [])['passwordDictionary'] ?? false; if ($passwordDictionary && str_contains($passwordsDB, $password)) { throw new Exception( Exception::USER_PASSWORD_IN_DICTIONARY, @@ -1538,7 +1538,7 @@ App::patch('/v1/account/password') $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); - $passwordDictionary = $project->getAttribute('auths', []['passwordDictionary']) ?? false; + $passwordDictionary = $project->getAttribute('auths', [])['passwordDictionary'] ?? false; if ($passwordDictionary && str_contains($passwordsDB, $password)) { throw new Exception( Exception::USER_PASSWORD_IN_DICTIONARY, diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index de6b192296..15192c7e07 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -114,7 +114,7 @@ App::post('/v1/users') ->inject('events') ->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, string $passwordsDB, Response $response, Document $project, Database $dbForProject, Event $events) { - $passwordDictionary = $project->getAttribute('auths', []['passwordDictionary']) ?? false; + $passwordDictionary = $project->getAttribute('auths', [])['passwordDictionary'] ?? false; if ($passwordDictionary && str_contains($passwordsDB, $password)) { throw new Exception( Exception::USER_PASSWORD_IN_DICTIONARY, @@ -818,7 +818,7 @@ App::patch('/v1/users/:userId/password') $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); - $passwordDictionary = $project->getAttribute('auths', []['passwordDictionary']) ?? false; + $passwordDictionary = $project->getAttribute('auths', [])['passwordDictionary'] ?? false; if ($passwordDictionary && str_contains($passwordsDB, $password)) { throw new Exception( Exception::USER_PASSWORD_IN_DICTIONARY, From 44082a92f77b7a97bcaf79c202babc6eda85f10d Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 26 Dec 2022 05:58:16 +0000 Subject: [PATCH 36/66] fix resonse model --- src/Appwrite/Utopia/Response/Model/Project.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Appwrite/Utopia/Response/Model/Project.php b/src/Appwrite/Utopia/Response/Model/Project.php index 599bb041cd..2c9cb720e1 100644 --- a/src/Appwrite/Utopia/Response/Model/Project.php +++ b/src/Appwrite/Utopia/Response/Model/Project.php @@ -126,6 +126,12 @@ class Project extends Model 'default' => 0, 'example' => 5, ]) + ->addRule('authPasswordDictionary', [ + 'type' => self::TYPE_BOOLEAN, + 'description' => 'Whether or not to check user\'s password against most commonly used passwords.', + 'default' => false, + 'example' => true, + ]) ->addRule('providers', [ 'type' => Response::MODEL_PROVIDER, 'description' => 'List of Providers.', From abde948b43d8145762e77ecdcfc8a7a8331aa757 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 26 Dec 2022 06:03:43 +0000 Subject: [PATCH 37/66] fix typo --- app/controllers/api/projects.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index cfe4a66acc..f23518d739 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -630,7 +630,7 @@ App::patch('/v1/projects/:projectId/auth/password-dictionary') } $auths = $project->getAttribute('auths', []); - $auths['passwordDisctionary'] = (bool) filter_var($enabled, FILTER_VALIDATE_BOOLEAN); + $auths['passwordDictionary'] = (bool) filter_var($enabled, FILTER_VALIDATE_BOOLEAN); $dbForConsole->updateDocument('projects', $project->getId(), $project ->setAttribute('auths', $auths)); From 87b3df919f0f3538d806847f30f8790e91affc63 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 26 Dec 2022 08:20:16 +0000 Subject: [PATCH 38/66] clear cache after migration --- src/Appwrite/Migration/Version/V17.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Appwrite/Migration/Version/V17.php b/src/Appwrite/Migration/Version/V17.php index 999bcc047b..6fddab5b3f 100644 --- a/src/Appwrite/Migration/Version/V17.php +++ b/src/Appwrite/Migration/Version/V17.php @@ -54,6 +54,7 @@ class V17 extends Migration * Create 'passwordHistory' attribute */ $this->createAttributeFromCollection($this->projectDB, $id, 'passwordHistory'); + $this->projectDB->deleteCachedCollection($id); } catch (\Throwable $th) { Console::warning("'region' from {$id}: {$th->getMessage()}"); } From 50a5329b30ed88b472958bb5498c3749a60d33e2 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 26 Dec 2022 15:04:06 +0545 Subject: [PATCH 39/66] Update app/init.php Co-authored-by: Eldad A. Fux --- app/init.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/init.php b/app/init.php index 5762085eae..2d182c4820 100644 --- a/app/init.php +++ b/app/init.php @@ -1025,7 +1025,7 @@ App::setResource('geodb', function ($register) { return $register->get('geodb'); }, ['register']); -App::setResource('passwordsDB', function ($register) { +App::setResource('passwordsDictionary', function ($register) { /** @var Utopia\Registry\Registry $register */ return $register->get('passwordsDB'); }, ['register']); From a9078cded5e684e8c27c692da0be74f7fbc3fb79 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 26 Dec 2022 09:24:51 +0000 Subject: [PATCH 40/66] rename resource --- app/controllers/api/account.php | 12 ++++++------ app/controllers/api/users.php | 12 ++++++------ app/init.php | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 1acad8fed5..9f3b82d0f3 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -67,13 +67,13 @@ App::post('/v1/account') ->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) - ->inject('passwordsDB') + ->inject('passwordsDictionary') ->inject('request') ->inject('response') ->inject('project') ->inject('dbForProject') ->inject('events') - ->action(function (string $userId, string $email, string $password, string $name, string $passwordsDB, Request $request, Response $response, Document $project, Database $dbForProject, Event $events) { + ->action(function (string $userId, string $email, string $password, string $name, string $passwordsDictionary, Request $request, Response $response, Document $project, Database $dbForProject, Event $events) { $email = \strtolower($email); if ('console' === $project->getId()) { @@ -100,7 +100,7 @@ App::post('/v1/account') } $passwordDictionary = $project->getAttribute('auths', [])['passwordDictionary'] ?? false; - if ($passwordDictionary && str_contains($passwordsDB, $password)) { + if ($passwordDictionary && str_contains($passwordsDictionary, $password)) { throw new Exception( Exception::USER_PASSWORD_IN_DICTIONARY, 'The password is among the common passwords in dictionary.', @@ -1522,13 +1522,13 @@ App::patch('/v1/account/password') ->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) - ->inject('passwordsDB') + ->inject('passwordsDictionary') ->inject('response') ->inject('user') ->inject('project') ->inject('dbForProject') ->inject('events') - ->action(function (string $password, string $oldPassword, string $passwordsDB, Response $response, Document $user, Document $project, Database $dbForProject, Event $events) { + ->action(function (string $password, string $oldPassword, string $passwordsDictionary, Response $response, Document $user, Document $project, Database $dbForProject, Event $events) { // Check old password only if its an existing user. if (!empty($user->getAttribute('passwordUpdate')) && !Auth::passwordVerify($oldPassword, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions'))) { // Double check user password @@ -1539,7 +1539,7 @@ App::patch('/v1/account/password') $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); $passwordDictionary = $project->getAttribute('auths', [])['passwordDictionary'] ?? false; - if ($passwordDictionary && str_contains($passwordsDB, $password)) { + if ($passwordDictionary && str_contains($passwordsDictionary, $password)) { throw new Exception( Exception::USER_PASSWORD_IN_DICTIONARY, 'The password is among the common passwords in dictionary.', diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 15192c7e07..8b04809a96 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -107,15 +107,15 @@ App::post('/v1/users') ->param('phone', null, new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true) ->param('password', null, new Password(), 'Plain text user password. Must be at least 8 chars.', true) ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true) - ->inject('passwordsDB') + ->inject('passwordsDictionary') ->inject('response') ->inject('project') ->inject('dbForProject') ->inject('events') - ->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, string $passwordsDB, Response $response, Document $project, Database $dbForProject, Event $events) { + ->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, string $passwordsDictionary, Response $response, Document $project, Database $dbForProject, Event $events) { $passwordDictionary = $project->getAttribute('auths', [])['passwordDictionary'] ?? false; - if ($passwordDictionary && str_contains($passwordsDB, $password)) { + if ($passwordDictionary && str_contains($passwordsDictionary, $password)) { throw new Exception( Exception::USER_PASSWORD_IN_DICTIONARY, 'The password is among the common passwords in dictionary.', @@ -803,12 +803,12 @@ 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.') - ->inject('passwordsDB') + ->inject('passwordsDictionary') ->inject('response') ->inject('project') ->inject('dbForProject') ->inject('events') - ->action(function (string $userId, string $password, string $passwordsDB, Response $response, Document $project, Database $dbForProject, Event $events) { + ->action(function (string $userId, string $password, string $passwordsDictionary, Response $response, Document $project, Database $dbForProject, Event $events) { $user = $dbForProject->getDocument('users', $userId); @@ -819,7 +819,7 @@ App::patch('/v1/users/:userId/password') $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); $passwordDictionary = $project->getAttribute('auths', [])['passwordDictionary'] ?? false; - if ($passwordDictionary && str_contains($passwordsDB, $password)) { + if ($passwordDictionary && str_contains($passwordsDictionary, $password)) { throw new Exception( Exception::USER_PASSWORD_IN_DICTIONARY, 'The password is among the common passwords in dictionary.', diff --git a/app/init.php b/app/init.php index 2d182c4820..3dac3fd772 100644 --- a/app/init.php +++ b/app/init.php @@ -605,7 +605,7 @@ $register->set('smtp', function () { $register->set('geodb', function () { return new Reader(__DIR__ . '/assets/dbip/dbip-country-lite-2022-06.mmdb'); }); -$register->set('passwordsDB', function () { +$register->set('passwordsDictionary', function () { return \file_get_contents(__DIR__ . '/assets/security/10k-common-passwords'); }); $register->set('db', function () { @@ -1027,7 +1027,7 @@ App::setResource('geodb', function ($register) { App::setResource('passwordsDictionary', function ($register) { /** @var Utopia\Registry\Registry $register */ - return $register->get('passwordsDB'); + return $register->get('passwordsDictionary'); }, ['register']); App::setResource('sms', function () { From f03035451fc7abefe53dbe8684de4318ce083f6f Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 26 Dec 2022 10:22:25 +0000 Subject: [PATCH 41/66] fix test --- .../Services/Projects/ProjectsConsoleClientTest.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index a7ca0cc86e..53eb8ba2c8 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -1173,7 +1173,7 @@ class ProjectsConsoleClientTest extends Scope 'name' => $name, ]); - $this->assertEquals(403, $response['headers']['status-code']); + $this->assertEquals(400, $response['headers']['status-code']); /** * Create user @@ -1188,7 +1188,7 @@ class ProjectsConsoleClientTest extends Scope 'password' => 'password', 'name' => 'Cristiano Ronaldo', ]); - $this->assertEquals(403, $response['headers']['status-code']); + $this->assertEquals(400, $response['headers']['status-code']); $headers = array_merge($this->getHeaders(), [ 'x-appwrite-mode' => 'admin', @@ -1200,7 +1200,7 @@ class ProjectsConsoleClientTest extends Scope 'password' => $password, ]); - $this->assertEquals(403, $response['headers']['status-code']); + $this->assertEquals(400, $response['headers']['status-code']); /** @@ -1223,11 +1223,11 @@ class ProjectsConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'enabled' => true, + 'enabled' => false, ]); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(true, $response['body']['authPasswordDictionary']); + $this->assertEquals(false, $response['body']['authPasswordDictionary']); return $data; } From 5649bc8060aff64a859efaeaf776592a5830adba Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 26 Dec 2022 10:22:38 +0000 Subject: [PATCH 42/66] load password dictionary as array --- app/init.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/init.php b/app/init.php index 3dac3fd772..f0d5a075ae 100644 --- a/app/init.php +++ b/app/init.php @@ -606,7 +606,10 @@ $register->set('geodb', function () { return new Reader(__DIR__ . '/assets/dbip/dbip-country-lite-2022-06.mmdb'); }); $register->set('passwordsDictionary', function () { - return \file_get_contents(__DIR__ . '/assets/security/10k-common-passwords'); + $content = \file_get_contents(__DIR__ . '/assets/security/10k-common-passwords'); + $content = explode('\n', $content); + $content = array_flip($content); + return $content; }); $register->set('db', function () { // This is usually for our workers or CLI commands scope From 0760ea90d60dd4ef0892cc645ec6a6882836b1bd Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 26 Dec 2022 10:22:49 +0000 Subject: [PATCH 43/66] use dictionary validator --- app/controllers/api/account.php | 34 ++------ app/controllers/api/users.php | 29 ++----- .../Auth/Validator/PasswordDictionary.php | 78 +++++++++++++++++++ 3 files changed, 89 insertions(+), 52 deletions(-) create mode 100644 src/Appwrite/Auth/Validator/PasswordDictionary.php diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 9f3b82d0f3..dea2ee0ee7 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -41,6 +41,7 @@ use Utopia\Validator\Assoc; use Utopia\Validator\Text; use Utopia\Validator\WhiteList; use Appwrite\Auth\Validator\PasswordHistory; +use Appwrite\Auth\Validator\PasswordDictionary; $oauthDefaultSuccess = '/auth/oauth2/success'; $oauthDefaultFailure = '/auth/oauth2/failure'; @@ -65,16 +66,14 @@ App::post('/v1/account') ->label('abuse-limit', 10) ->param('userId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string `ID.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('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary']) ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true) - ->inject('passwordsDictionary') ->inject('request') ->inject('response') ->inject('project') ->inject('dbForProject') ->inject('events') - ->action(function (string $userId, string $email, string $password, string $name, string $passwordsDictionary, Request $request, Response $response, Document $project, Database $dbForProject, Event $events) { - + ->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $project, Database $dbForProject, Event $events) { $email = \strtolower($email); if ('console' === $project->getId()) { $whitelistEmails = $project->getAttribute('authWhitelistEmails'); @@ -99,15 +98,6 @@ App::post('/v1/account') } } - $passwordDictionary = $project->getAttribute('auths', [])['passwordDictionary'] ?? false; - if ($passwordDictionary && str_contains($passwordsDictionary, $password)) { - throw new Exception( - Exception::USER_PASSWORD_IN_DICTIONARY, - 'The password is among the common passwords in dictionary.', - 403 - ); - } - $passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; $password = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); try { @@ -1520,33 +1510,21 @@ App::patch('/v1/account/password') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_ACCOUNT) - ->param('password', '', new Password(), 'New user password. Must be at least 8 chars.') + ->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary']) ->param('oldPassword', '', new Password(), 'Current user password. Must be at least 8 chars.', true) - ->inject('passwordsDictionary') ->inject('response') ->inject('user') ->inject('project') ->inject('dbForProject') ->inject('events') - ->action(function (string $password, string $oldPassword, string $passwordsDictionary, Response $response, Document $user, Document $project, Database $dbForProject, Event $events) { + ->action(function (string $password, string $oldPassword, Response $response, Document $user, Document $project, Database $dbForProject, Event $events) { // Check old password only if its an existing user. if (!empty($user->getAttribute('passwordUpdate')) && !Auth::passwordVerify($oldPassword, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions'))) { // Double check user password throw new Exception(Exception::USER_INVALID_CREDENTIALS); } - - + $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); - - $passwordDictionary = $project->getAttribute('auths', [])['passwordDictionary'] ?? false; - if ($passwordDictionary && str_contains($passwordsDictionary, $password)) { - throw new Exception( - Exception::USER_PASSWORD_IN_DICTIONARY, - 'The password is among the common passwords in dictionary.', - 403 - ); - } - $historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; $history = []; if ($historyLimit > 0) { diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 8b04809a96..907e72a6ed 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -35,6 +35,7 @@ use Utopia\Validator\Boolean; use MaxMind\Db\Reader; use Utopia\Validator\Integer; use Appwrite\Auth\Validator\PasswordHistory; +use Appwrite\Auth\Validator\PasswordDictionary; /** 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 @@ -105,23 +106,13 @@ App::post('/v1/users') ->param('userId', '', new CustomId(), 'User ID. Choose your own unique ID or pass the string `ID.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', null, new Email(), 'User email.', true) ->param('phone', null, new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true) - ->param('password', null, new Password(), 'Plain text user password. Must be at least 8 chars.', true) + ->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project), 'Plain text user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary']) ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true) - ->inject('passwordsDictionary') ->inject('response') ->inject('project') ->inject('dbForProject') ->inject('events') - ->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, string $passwordsDictionary, Response $response, Document $project, Database $dbForProject, Event $events) { - - $passwordDictionary = $project->getAttribute('auths', [])['passwordDictionary'] ?? false; - if ($passwordDictionary && str_contains($passwordsDictionary, $password)) { - throw new Exception( - Exception::USER_PASSWORD_IN_DICTIONARY, - 'The password is among the common passwords in dictionary.', - 403 - ); - } + ->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $events) { $user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $project, $dbForProject, $events); @@ -802,13 +793,12 @@ App::patch('/v1/users/:userId/password') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->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.') - ->inject('passwordsDictionary') + ->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary']) ->inject('response') ->inject('project') ->inject('dbForProject') ->inject('events') - ->action(function (string $userId, string $password, string $passwordsDictionary, Response $response, Document $project, Database $dbForProject, Event $events) { + ->action(function (string $userId, string $password, Response $response, Document $project, Database $dbForProject, Event $events) { $user = $dbForProject->getDocument('users', $userId); @@ -818,15 +808,6 @@ App::patch('/v1/users/:userId/password') $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); - $passwordDictionary = $project->getAttribute('auths', [])['passwordDictionary'] ?? false; - if ($passwordDictionary && str_contains($passwordsDictionary, $password)) { - throw new Exception( - Exception::USER_PASSWORD_IN_DICTIONARY, - 'The password is among the common passwords in dictionary.', - 403 - ); - } - $historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; $history = []; if ($historyLimit > 0) { diff --git a/src/Appwrite/Auth/Validator/PasswordDictionary.php b/src/Appwrite/Auth/Validator/PasswordDictionary.php new file mode 100644 index 0000000000..b2adf78141 --- /dev/null +++ b/src/Appwrite/Auth/Validator/PasswordDictionary.php @@ -0,0 +1,78 @@ +dictionary = $dictionary; + $this->project = $project; + } + + /** + * Get Description. + * + * Returns validator description + * + * @return string + */ + public function getDescription(): string + { + return 'Password must be at least 8 characters and should not be one of the commonly used password.'; + } + + /** + * Is valid. + * + * @param mixed $value + * + * @return bool + */ + public function isValid($value): bool + { + if(!parent::isValid($value)) { + return false; + } + + $dictionaryEnabled = $this->project->getAttribute('auths', [])['passwordDictionary'] ?? false; + if ($dictionaryEnabled && array_key_exists($value, $this->dictionary)) { + 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; + } +} From 96cf2e6330250c50ceabbac1f4da9e5e33c32a9b Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 26 Dec 2022 10:27:04 +0000 Subject: [PATCH 44/66] password dictionary test --- .../Auth/Validator/PasswordDictionaryTest.php | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 tests/unit/Auth/Validator/PasswordDictionaryTest.php diff --git a/tests/unit/Auth/Validator/PasswordDictionaryTest.php b/tests/unit/Auth/Validator/PasswordDictionaryTest.php new file mode 100644 index 0000000000..ab6cd884a3 --- /dev/null +++ b/tests/unit/Auth/Validator/PasswordDictionaryTest.php @@ -0,0 +1,32 @@ +object = new PasswordDictionary( + ['password' => true, '123456' => true], + new Document([ + 'auths' => [ + 'passwordDictionary' => true + ] + ]) + ); + } + + public function testValues(): void + { + $this->assertEquals($this->object->isValid('1'), false); // to check parent is being called + $this->assertEquals($this->object->isValid('123456'), false); + $this->assertEquals($this->object->isValid('password'), false); + $this->assertEquals($this->object->isValid('myPasswordIsRight'), true); + } +} From 8927c71b69100fc7045dd4fb55509c306c60fe7c Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 26 Dec 2022 10:34:41 +0000 Subject: [PATCH 45/66] fix formatting --- app/controllers/api/account.php | 2 +- src/Appwrite/Auth/Validator/PasswordDictionary.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index dea2ee0ee7..c3c3818e45 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1523,7 +1523,7 @@ App::patch('/v1/account/password') if (!empty($user->getAttribute('passwordUpdate')) && !Auth::passwordVerify($oldPassword, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions'))) { // Double check user password throw new Exception(Exception::USER_INVALID_CREDENTIALS); } - + $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); $historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; $history = []; diff --git a/src/Appwrite/Auth/Validator/PasswordDictionary.php b/src/Appwrite/Auth/Validator/PasswordDictionary.php index b2adf78141..5c6a2cba93 100644 --- a/src/Appwrite/Auth/Validator/PasswordDictionary.php +++ b/src/Appwrite/Auth/Validator/PasswordDictionary.php @@ -41,7 +41,7 @@ class PasswordDictionary extends Password */ public function isValid($value): bool { - if(!parent::isValid($value)) { + if (!parent::isValid($value)) { return false; } From 62dd5c275da1ad6722d7d94dc1b4bddb1158a787 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 27 Dec 2022 06:16:19 +0000 Subject: [PATCH 46/66] fix explode --- app/init.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/init.php b/app/init.php index f0d5a075ae..4901e350d0 100644 --- a/app/init.php +++ b/app/init.php @@ -607,7 +607,7 @@ $register->set('geodb', function () { }); $register->set('passwordsDictionary', function () { $content = \file_get_contents(__DIR__ . '/assets/security/10k-common-passwords'); - $content = explode('\n', $content); + $content = explode("\n", $content); $content = array_flip($content); return $content; }); From a0a08b35888e8ff95c4c085d9ca47831ab41c573 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 27 Dec 2022 07:13:39 +0000 Subject: [PATCH 47/66] fix make password optional --- app/controllers/api/users.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 907e72a6ed..edea4a2e2e 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -106,7 +106,7 @@ App::post('/v1/users') ->param('userId', '', new CustomId(), 'User ID. Choose your own unique ID or pass the string `ID.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', null, new Email(), 'User email.', true) ->param('phone', null, new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true) - ->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project), 'Plain text user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary']) + ->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project), 'Plain text user password. Must be at least 8 chars.', true, ['project', 'passwordsDictionary']) ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true) ->inject('response') ->inject('project') From 16b80e1ef8e434d73c5b0295fe2d1d3518f13ec7 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 16 Jan 2023 05:04:35 +0000 Subject: [PATCH 48/66] new migration --- src/Appwrite/Migration/Version/V18.php | 103 +++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 src/Appwrite/Migration/Version/V18.php diff --git a/src/Appwrite/Migration/Version/V18.php b/src/Appwrite/Migration/Version/V18.php new file mode 100644 index 0000000000..6fddab5b3f --- /dev/null +++ b/src/Appwrite/Migration/Version/V18.php @@ -0,0 +1,103 @@ + null, + fn () => [] + ); + } + + Console::log('Migrating Project: ' . $this->project->getAttribute('name') . ' (' . $this->project->getId() . ')'); + + Console::info('Migrating Collections'); + $this->migrateCollections(); + + Console::info('Migrating Documents'); + $this->forEachDocument([$this, 'fixDocument']); + } + + /** + * Migrate all Collections. + * + * @return void + */ + protected function migrateCollections(): void + { + foreach ($this->collections as $collection) { + $id = $collection['$id']; + + Console::log("Migrating Collection \"{$id}\""); + + $this->projectDB->setNamespace("_{$this->project->getInternalId()}"); + + switch ($id) { + case 'users': + try { + /** + * Create 'passwordHistory' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'passwordHistory'); + $this->projectDB->deleteCachedCollection($id); + } catch (\Throwable $th) { + Console::warning("'region' from {$id}: {$th->getMessage()}"); + } + break; + default: + break; + } + + usleep(50000); + } + } + + /** + * Fix run on each document + * + * @param \Utopia\Database\Document $document + * @return \Utopia\Database\Document + */ + protected function fixDocument(Document $document) + { + switch ($document->getCollection()) { + case 'projects': + /** + * Bump version number. + */ + $document->setAttribute('version', '1.2.0'); + break; + case 'users': + /** + * Bump version number. + */ + $document->setAttribute('passwordHistory', []); + + /** + * Set default passwordHistory + */ + $document->setAttribute('auths', array_merge($document->getAttribute('auths', []), [ + 'passwordHistory' => 0 + ])); + + break; + } + + return $document; + } +} From b7683364dd0bfd6200044d04d9ed6745aceabcc1 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 16 Jan 2023 05:12:26 +0000 Subject: [PATCH 49/66] update changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index b56318450e..52de122483 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,7 @@ ## Features - Password history setting allows to save user's last used password so that it may not be used again. Maximum number of history saved is 20, which can be configured [#4866](https://github.com/appwrite/appwrite/pull/4866) +- Password dictionary setting allows to compare user's password against command password database [4906](https://github.com/appwrite/appwrite/pull/4906) # Version 1.2.0 ## Features From 92e020b20934f6631cbacf9a23465832d6f78578 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 16 Jan 2023 06:41:44 +0000 Subject: [PATCH 50/66] fix migration name --- src/Appwrite/Migration/Version/V18.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Migration/Version/V18.php b/src/Appwrite/Migration/Version/V18.php index 6fddab5b3f..668544391d 100644 --- a/src/Appwrite/Migration/Version/V18.php +++ b/src/Appwrite/Migration/Version/V18.php @@ -9,7 +9,7 @@ use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\Document; -class V17 extends Migration +class V18 extends Migration { public function execute(): void { From 7f3a2dcce72df7bd76cb9c4c03e18fd71c74ac8a Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 16 Jan 2023 09:46:53 +0000 Subject: [PATCH 51/66] fix model --- src/Appwrite/Utopia/Response/Model/Project.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Utopia/Response/Model/Project.php b/src/Appwrite/Utopia/Response/Model/Project.php index 59bd8ea78c..e0db63e3e7 100644 --- a/src/Appwrite/Utopia/Response/Model/Project.php +++ b/src/Appwrite/Utopia/Response/Model/Project.php @@ -245,7 +245,7 @@ class Project extends Model $document->setAttribute('authLimit', $authValues['limit'] ?? 0); $document->setAttribute('authDuration', $authValues['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG); - $document->setAttribute('authSessionLimit', $authValues['maxSessions'] ?? APP_LIMIT_USER_SESSIONS_DEFAULT); + $document->setAttribute('authSessionsLimit', $authValues['maxSessions'] ?? APP_LIMIT_USER_SESSIONS_DEFAULT); $document->setAttribute('authPasswordHistory', $authValues['passwordHistory'] ?? 0); foreach ($auth as $index => $method) { From f15dd063fdd230ecd2f9367e3925ac64e9b09853 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 29 Jan 2023 15:11:42 +0545 Subject: [PATCH 52/66] Update src/Appwrite/Migration/Version/V18.php MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matej Bačo --- src/Appwrite/Migration/Version/V18.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Migration/Version/V18.php b/src/Appwrite/Migration/Version/V18.php index 668544391d..b05d0bc7bb 100644 --- a/src/Appwrite/Migration/Version/V18.php +++ b/src/Appwrite/Migration/Version/V18.php @@ -56,7 +56,7 @@ class V18 extends Migration $this->createAttributeFromCollection($this->projectDB, $id, 'passwordHistory'); $this->projectDB->deleteCachedCollection($id); } catch (\Throwable $th) { - Console::warning("'region' from {$id}: {$th->getMessage()}"); + Console::warning("'passwordHistory' from {$id}: {$th->getMessage()}"); } break; default: From 31b4c73b16f0353006a7cb19a566a216b970320e Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 29 Jan 2023 15:30:39 +0545 Subject: [PATCH 53/66] use variable --- app/controllers/api/account.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 66fde19360..e3b4f5d84b 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -498,6 +498,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') try { $userId = ID::unique(); + $password = Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); $user = Authorization::skip(fn() => $dbForProject->createDocument('users', new Document([ '$id' => $userId, '$permissions' => [ @@ -508,8 +509,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') 'email' => $email, 'emailVerification' => true, 'status' => true, // Email should already be authenticated by OAuth2 provider - 'passwordHistory' => $passwordHistory > 0 ? [Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)] : null, - 'password' => Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS), + 'passwordHistory' => $passwordHistory > 0 ? [$password] : null, + 'password' => $password, 'hash' => Auth::DEFAULT_ALGO, 'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS, 'passwordUpdate' => null, From 2070cccd54c7831d9386dfa1cc23856b0a6f06cc Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 30 Jan 2023 06:05:32 +0000 Subject: [PATCH 54/66] update description --- app/controllers/api/projects.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 445f46f4fc..e8c4763072 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -577,7 +577,7 @@ App::patch('/v1/projects/:projectId/auth/:method') }); App::patch('/v1/projects/:projectId/auth/password-history') - ->desc('Update Project users limit') + ->desc('Update authentication password history. Use this endpoint to set the number of password history to save and 0 to disable password history.') ->groups(['api', 'projects']) ->label('scope', 'projects.write') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) @@ -608,7 +608,7 @@ App::patch('/v1/projects/:projectId/auth/password-history') }); App::patch('/v1/projects/:projectId/auth/password-dictionary') - ->desc('Update Project users limit') + ->desc('Update authentication password disctionary 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]) From 91abc2b0792eed3d681dfd800c8ad1096dc6f616 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 30 Jan 2023 06:06:05 +0000 Subject: [PATCH 55/66] fix SDK method --- app/controllers/api/projects.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index e8c4763072..d7369c3938 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -582,7 +582,7 @@ App::patch('/v1/projects/:projectId/auth/password-history') ->label('scope', 'projects.write') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'projects') - ->label('sdk.method', 'updateAuthLimit') + ->label('sdk.method', 'updateAuthPasswordHistory') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_PROJECT) @@ -613,7 +613,7 @@ App::patch('/v1/projects/:projectId/auth/password-dictionary') ->label('scope', 'projects.write') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'projects') - ->label('sdk.method', 'updateAuthLimit') + ->label('sdk.method', 'updateAuthPasswordDictionary') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_PROJECT) From daa121262b0a67d9f87be3472c8c46e3ba108aa5 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 30 Jan 2023 06:06:52 +0000 Subject: [PATCH 56/66] fix boolean param --- app/controllers/api/projects.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index d7369c3938..21df413de5 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -618,7 +618,7 @@ App::patch('/v1/projects/:projectId/auth/password-dictionary') ->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(true), 'Set whether or not to enable checking user\'s password against most commonly used passwords. Default is false.') + ->param('enabled', false, new Boolean(false), 'Set whether or not to enable checking user\'s password against most commonly used passwords. Default is false.') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, bool $enabled, Response $response, Database $dbForConsole) { @@ -630,7 +630,7 @@ App::patch('/v1/projects/:projectId/auth/password-dictionary') } $auths = $project->getAttribute('auths', []); - $auths['passwordDictionary'] = (bool) filter_var($enabled, FILTER_VALIDATE_BOOLEAN); + $auths['passwordDictionary'] = $enabled; $dbForConsole->updateDocument('projects', $project->getId(), $project ->setAttribute('auths', $auths)); From 81494052074a68ddd693643bee09b2857bafbbe6 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 30 Jan 2023 06:08:56 +0000 Subject: [PATCH 57/66] exception constant not needed --- src/Appwrite/Extend/Exception.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index 9e46744f18..d0fb86db5f 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -66,7 +66,6 @@ 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_IN_DICTIONARY = 'password_in_dictionary'; 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'; From 45bc0f4066a3f8e5e2c6d9baa8bb7aae0d258aab Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 31 Jan 2023 06:51:43 +0000 Subject: [PATCH 58/66] fix migration --- src/Appwrite/Migration/Version/V18.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Migration/Version/V18.php b/src/Appwrite/Migration/Version/V18.php index 668544391d..bf4abd8aa0 100644 --- a/src/Appwrite/Migration/Version/V18.php +++ b/src/Appwrite/Migration/Version/V18.php @@ -82,7 +82,7 @@ class V18 extends Migration */ $document->setAttribute('version', '1.2.0'); break; - case 'users': + case 'projects': /** * Bump version number. */ From 9e4f66cac4410c1b9261cf14b8b93c6320917224 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 31 Jan 2023 06:52:40 +0000 Subject: [PATCH 59/66] update migration --- src/Appwrite/Migration/Version/V18.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Appwrite/Migration/Version/V18.php b/src/Appwrite/Migration/Version/V18.php index bf4abd8aa0..2b670b6fe1 100644 --- a/src/Appwrite/Migration/Version/V18.php +++ b/src/Appwrite/Migration/Version/V18.php @@ -92,7 +92,8 @@ class V18 extends Migration * Set default passwordHistory */ $document->setAttribute('auths', array_merge($document->getAttribute('auths', []), [ - 'passwordHistory' => 0 + 'passwordHistory' => 0, + 'passwordDictionary' => false, ])); break; From 452ee5c0862c182baa880d4577b8e53f546cdc76 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 20 Feb 2023 01:34:05 +0000 Subject: [PATCH 60/66] update dictonary validator --- app/controllers/api/account.php | 4 ++-- app/controllers/api/users.php | 4 ++-- src/Appwrite/Auth/Validator/PasswordDictionary.php | 9 ++++----- tests/unit/Auth/Validator/PasswordDictionaryTest.php | 8 +------- 4 files changed, 9 insertions(+), 16 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 41db80ecd9..55b77e549b 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -66,7 +66,7 @@ App::post('/v1/account') ->label('abuse-limit', 10) ->param('userId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string `ID.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', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary']) + ->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths',[])['passwordDictionary']), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary']) ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true) ->inject('request') ->inject('response') @@ -1532,7 +1532,7 @@ App::patch('/v1/account/password') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_ACCOUNT) - ->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary']) + ->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths',[])['passwordDictionary']), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary']) ->param('oldPassword', '', new Password(), 'Current user password. Must be at least 8 chars.', true) ->inject('response') ->inject('user') diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 817d9b92b3..5d92595767 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -106,7 +106,7 @@ App::post('/v1/users') ->param('userId', '', new CustomId(), 'User ID. Choose your own unique ID or pass the string `ID.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', null, new Email(), 'User email.', true) ->param('phone', null, new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true) - ->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project), 'Plain text user password. Must be at least 8 chars.', true, ['project', 'passwordsDictionary']) + ->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths',[])['passwordDictionary']), 'Plain text user password. Must be at least 8 chars.', true, ['project', 'passwordsDictionary']) ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true) ->inject('response') ->inject('project') @@ -793,7 +793,7 @@ App::patch('/v1/users/:userId/password') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_USER) ->param('userId', '', new UID(), 'User ID.') - ->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary']) + ->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths',[])['passwordDictionary']), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary']) ->inject('response') ->inject('project') ->inject('dbForProject') diff --git a/src/Appwrite/Auth/Validator/PasswordDictionary.php b/src/Appwrite/Auth/Validator/PasswordDictionary.php index 5c6a2cba93..269babef37 100644 --- a/src/Appwrite/Auth/Validator/PasswordDictionary.php +++ b/src/Appwrite/Auth/Validator/PasswordDictionary.php @@ -12,12 +12,12 @@ use Utopia\Database\Document; class PasswordDictionary extends Password { protected array $dictionary; - protected Document $project; + protected bool $enabled; - public function __construct(array $dictionary, Document $project) + public function __construct(array $dictionary, bool $enabled = false) { $this->dictionary = $dictionary; - $this->project = $project; + $this->enabled = $enabled; } /** @@ -45,8 +45,7 @@ class PasswordDictionary extends Password return false; } - $dictionaryEnabled = $this->project->getAttribute('auths', [])['passwordDictionary'] ?? false; - if ($dictionaryEnabled && array_key_exists($value, $this->dictionary)) { + if ($this->enabled && array_key_exists($value, $this->dictionary)) { return false; } return true; diff --git a/tests/unit/Auth/Validator/PasswordDictionaryTest.php b/tests/unit/Auth/Validator/PasswordDictionaryTest.php index ab6cd884a3..2ed5ee9636 100644 --- a/tests/unit/Auth/Validator/PasswordDictionaryTest.php +++ b/tests/unit/Auth/Validator/PasswordDictionaryTest.php @@ -13,13 +13,7 @@ class PasswordDictionaryTest extends TestCase public function setUp(): void { $this->object = new PasswordDictionary( - ['password' => true, '123456' => true], - new Document([ - 'auths' => [ - 'passwordDictionary' => true - ] - ]) - ); + ['password' => true, '123456' => true], true); } public function testValues(): void From 7e055c585dc761255e5da42684973b054209350f Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 20 Feb 2023 01:37:33 +0000 Subject: [PATCH 61/66] formatting fix --- app/controllers/api/account.php | 4 ++-- app/controllers/api/users.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 55b77e549b..845a3a84ca 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -66,7 +66,7 @@ App::post('/v1/account') ->label('abuse-limit', 10) ->param('userId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string `ID.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', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths',[])['passwordDictionary']), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary']) + ->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary']), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary']) ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true) ->inject('request') ->inject('response') @@ -1532,7 +1532,7 @@ App::patch('/v1/account/password') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_ACCOUNT) - ->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths',[])['passwordDictionary']), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary']) + ->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary']), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary']) ->param('oldPassword', '', new Password(), 'Current user password. Must be at least 8 chars.', true) ->inject('response') ->inject('user') diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 5d92595767..25c37fe467 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -106,7 +106,7 @@ App::post('/v1/users') ->param('userId', '', new CustomId(), 'User ID. Choose your own unique ID or pass the string `ID.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', null, new Email(), 'User email.', true) ->param('phone', null, new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true) - ->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths',[])['passwordDictionary']), 'Plain text user password. Must be at least 8 chars.', true, ['project', 'passwordsDictionary']) + ->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary']), 'Plain text user password. Must be at least 8 chars.', true, ['project', 'passwordsDictionary']) ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true) ->inject('response') ->inject('project') @@ -793,7 +793,7 @@ App::patch('/v1/users/:userId/password') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_USER) ->param('userId', '', new UID(), 'User ID.') - ->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths',[])['passwordDictionary']), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary']) + ->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary']), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary']) ->inject('response') ->inject('project') ->inject('dbForProject') From 1904e3df81af6256c32f8093b73b10bf8423d674 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 20 Feb 2023 01:38:57 +0000 Subject: [PATCH 62/66] fix formatting --- tests/unit/Auth/Validator/PasswordDictionaryTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/unit/Auth/Validator/PasswordDictionaryTest.php b/tests/unit/Auth/Validator/PasswordDictionaryTest.php index 2ed5ee9636..fd7f51ff16 100644 --- a/tests/unit/Auth/Validator/PasswordDictionaryTest.php +++ b/tests/unit/Auth/Validator/PasswordDictionaryTest.php @@ -13,7 +13,9 @@ class PasswordDictionaryTest extends TestCase public function setUp(): void { $this->object = new PasswordDictionary( - ['password' => true, '123456' => true], true); + ['password' => true, '123456' => true], + true + ); } public function testValues(): void From 4e1a4b961c15dd648371c07ecfd52bb7eb18375f Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 20 Feb 2023 01:51:56 +0000 Subject: [PATCH 63/66] update attribute errors --- app/controllers/api/account.php | 10 +++++----- app/controllers/api/users.php | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 845a3a84ca..f08543aba6 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -112,11 +112,11 @@ App::post('/v1/account') 'email' => $email, 'emailVerification' => false, 'status' => true, - 'passwordHistory' => $passwordHistory > 0 ? [$password] : [], 'password' => $password, + 'passwordHistory' => $passwordHistory > 0 ? [$password] : [], + 'passwordUpdate' => DateTime::now(), 'hash' => Auth::DEFAULT_ALGO, 'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS, - 'passwordUpdate' => DateTime::now(), 'registration' => DateTime::now(), 'reset' => false, 'name' => $name, @@ -1561,11 +1561,11 @@ App::patch('/v1/account/password') } $user = $dbForProject->updateDocument('users', $user->getId(), $user - ->setAttribute('passwordHistory', $history) ->setAttribute('password', $newPassword) + ->setAttribute('passwordHistory', $history) + ->setAttribute('passwordUpdate', DateTime::now())); ->setAttribute('hash', Auth::DEFAULT_ALGO) ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS) - ->setAttribute('passwordUpdate', DateTime::now())); $events->setParam('userId', $user->getId()); @@ -2133,9 +2133,9 @@ App::put('/v1/account/recovery') $profile = $dbForProject->updateDocument('users', $profile->getId(), $profile ->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)) + ->setAttribute('passwordUpdate', DateTime::now()) ->setAttribute('hash', Auth::DEFAULT_ALGO) ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS) - ->setAttribute('passwordUpdate', DateTime::now()) ->setAttribute('emailVerification', true)); $recoveryDocument = $dbForProject->getDocument('tokens', $recovery); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 25c37fe467..2768b28412 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -65,11 +65,11 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e 'phone' => $phone, 'phoneVerification' => false, 'status' => true, - 'passwordHistory' => is_null($password) && $passwordHistory === 0 ? [] : [$password], 'password' => $password, + 'passwordHistory' => is_null($password) && $passwordHistory === 0 ? [] : [$password], + 'passwordUpdate' => (!empty($password)) ? DateTime::now() : null, 'hash' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO : $hash, 'hashOptions' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO_OPTIONS : $hashOptionsObject + ['type' => $hash], - 'passwordUpdate' => (!empty($password)) ? DateTime::now() : null, 'registration' => DateTime::now(), 'reset' => false, 'name' => $name, @@ -822,11 +822,11 @@ App::patch('/v1/users/:userId/password') } $user - ->setAttribute('passwordHistory', $history) ->setAttribute('password', $newPassword) + ->setAttribute('passwordHistory', $history) + ->setAttribute('passwordUpdate', DateTime::now()) ->setAttribute('hash', Auth::DEFAULT_ALGO) - ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS) - ->setAttribute('passwordUpdate', DateTime::now()); + ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS); $user = $dbForProject->updateDocument('users', $user->getId(), $user); From 5863ba6285efafc16a20ed6b34c151216ff52ba6 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 20 Feb 2023 02:08:28 +0000 Subject: [PATCH 64/66] update changes --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index b56318450e..205697ac1d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,7 @@ ## Version 1.3.0 ## Features -- Password history setting allows to save user's last used password so that it may not be used again. Maximum number of history saved is 20, which can be configured [#4866](https://github.com/appwrite/appwrite/pull/4866) +- Password history setting allows to save user's last used password so that it may not be used again. Maximum number of history saved is 20, which can be configured. Minimum is 0 which means disabled. [#4866](https://github.com/appwrite/appwrite/pull/4866) # Version 1.2.0 ## Features From 81add700c6189ee7e1f6c8f7cc3ff36df79fff26 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 20 Feb 2023 04:01:08 +0000 Subject: [PATCH 65/66] fix error --- app/controllers/api/account.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 350a8062f2..d3deb02819 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1564,9 +1564,9 @@ App::patch('/v1/account/password') $user = $dbForProject->updateDocument('users', $user->getId(), $user ->setAttribute('password', $newPassword) ->setAttribute('passwordHistory', $history) - ->setAttribute('passwordUpdate', DateTime::now())); + ->setAttribute('passwordUpdate', DateTime::now())) ->setAttribute('hash', Auth::DEFAULT_ALGO) - ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS) + ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS); $events->setParam('userId', $user->getId()); From 104d99e636b064ecff554926c74d8a4fb96b7d00 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 20 Feb 2023 07:08:27 +0000 Subject: [PATCH 66/66] fix default value error --- app/controllers/api/account.php | 4 ++-- app/controllers/api/projects.php | 2 +- app/controllers/api/users.php | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index d3deb02819..d488e2c515 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -66,7 +66,7 @@ App::post('/v1/account') ->label('abuse-limit', 10) ->param('userId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string `ID.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', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary']), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary']) + ->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary']) ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true) ->inject('request') ->inject('response') @@ -1533,7 +1533,7 @@ App::patch('/v1/account/password') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_ACCOUNT) - ->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary']), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary']) + ->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary']) ->param('oldPassword', '', new Password(), 'Current user password. Must be at least 8 chars.', true) ->inject('response') ->inject('user') diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 21df413de5..2d29feb0ec 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -81,7 +81,7 @@ App::post('/v1/projects') } $auth = Config::getParam('auth', []); - $auths = ['limit' => 0, 'maxSessions' => APP_LIMIT_USER_SESSIONS_DEFAULT, 'passwordHistory' => 0, '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]; foreach ($auth as $index => $method) { $auths[$method['key'] ?? ''] = true; } diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 2768b28412..6fdb210319 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -106,7 +106,7 @@ App::post('/v1/users') ->param('userId', '', new CustomId(), 'User ID. Choose your own unique ID or pass the string `ID.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', null, new Email(), 'User email.', true) ->param('phone', null, new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true) - ->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary']), 'Plain text user password. Must be at least 8 chars.', true, ['project', 'passwordsDictionary']) + ->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'Plain text user password. Must be at least 8 chars.', true, ['project', 'passwordsDictionary']) ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true) ->inject('response') ->inject('project') @@ -793,7 +793,7 @@ App::patch('/v1/users/:userId/password') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_USER) ->param('userId', '', new UID(), 'User ID.') - ->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary']), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary']) + ->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary']) ->inject('response') ->inject('project') ->inject('dbForProject')