Finish hashing controllers
This commit is contained in:
parent
7c45ca4d0f
commit
814bb5c6b6
|
@ -991,7 +991,7 @@ $collections = [
|
|||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['encrypt'],
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'hash', // Hashing algorithm used to hash the password
|
||||
|
|
|
@ -26,6 +26,7 @@ use Utopia\Validator\Assoc;
|
|||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\JSON;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
$oauthDefaultSuccess = '/v1/auth/oauth2/success';
|
||||
|
@ -50,7 +51,7 @@ App::post('/v1/account')
|
|||
->param('password', '', new Password(), 'User password. Must be at least 8 chars.')
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->param('hash', Auth::DEFAULT_ALGO, new WhiteList(\array_keys(Auth::SUPPORTED_ALGOS)), 'Hashing algorithm for password. Allowed values are: \'' . \implode('\', \'', \array_keys(Auth::SUPPORTED_ALGOS)) . '\'. The default value is \'' . Auth::DEFAULT_ALGO . '\'.', true)
|
||||
->param('hashOptions', Auth::DEFAULT_ALGO_OPTIONS, new Assoc(), 'Configuration of hashing algorithm. If left empty, default configuration is used.', true)
|
||||
->param('hashOptions', Auth::DEFAULT_ALGO_OPTIONS, new JSON(), 'Configuration of hashing algorithm. If left empty, default configuration is used.', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
|
@ -65,6 +66,8 @@ App::post('/v1/account')
|
|||
/** @var Appwrite\Event\Event $audits */
|
||||
/** @var Appwrite\Stats\Stats $usage */
|
||||
|
||||
$hashOptions = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array
|
||||
|
||||
$email = \strtolower($email);
|
||||
if ('console' === $project->getId()) {
|
||||
$whitelistEmails = $project->getAttribute('authWhitelistEmails');
|
||||
|
@ -100,7 +103,7 @@ App::post('/v1/account')
|
|||
'email' => $email,
|
||||
'emailVerification' => false,
|
||||
'status' => true,
|
||||
'password' => Auth::passwordHash($password, $hash, \json_decode($hashOptions, true)),
|
||||
'password' => Auth::passwordHash($password, $hash, $hashOptions, true),
|
||||
'hash' => $hash,
|
||||
'hashOptions' => $hashOptions,
|
||||
'passwordUpdate' => \time(),
|
||||
|
@ -173,7 +176,7 @@ App::post('/v1/account/sessions')
|
|||
|
||||
$profile = $dbForProject->findOne('users', [new Query('deleted', Query::TYPE_EQUAL, [false]), new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address
|
||||
|
||||
if (!$profile || !Auth::passwordVerify($password, $profile->getAttribute('password'), $profile->getAttribute('hash'), \json_decode($profile->getAttribute('hashOptions'), true))) {
|
||||
if (!$profile || !Auth::passwordVerify($password, $profile->getAttribute('password'), $profile->getAttribute('hash'), $profile->getAttribute('hashOptions'))) {
|
||||
$audits
|
||||
//->setParam('userId', $profile->getId())
|
||||
->setParam('event', 'account.sessions.failed')
|
||||
|
@ -187,6 +190,15 @@ App::post('/v1/account/sessions')
|
|||
throw new Exception('Invalid credentials. User is blocked', 401, Exception::USER_BLOCKED); // User is in status blocked
|
||||
}
|
||||
|
||||
// Re-hash if not using recommended algo
|
||||
if($profile->getAttribute('hash') !== Auth::DEFAULT_ALGO) {
|
||||
$profile
|
||||
->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS))
|
||||
->setAttribute('hash', Auth::DEFAULT_ALGO)
|
||||
->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS);
|
||||
$dbForProject->updateDocument('users', $profile->getId(), $profile);
|
||||
}
|
||||
|
||||
$detector = new Detector($request->getUserAgent('UNKNOWN'));
|
||||
$record = $geodb->get($request->getIP());
|
||||
$expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
|
@ -503,7 +515,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
|||
'email' => $email,
|
||||
'emailVerification' => true,
|
||||
'status' => true, // Email should already be authenticated by OAuth2 provider
|
||||
'password' => Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO),
|
||||
'password' => Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS),
|
||||
'hash' => Auth::DEFAULT_ALGO,
|
||||
'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS,
|
||||
'passwordUpdate' => 0,
|
||||
|
@ -1373,26 +1385,31 @@ App::patch('/v1/account/password')
|
|||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->param('password', '', new Password(), 'New user password. Must be at least 8 chars.')
|
||||
->param('oldPassword', '', new Password(), 'Current user password. Must be at least 8 chars.', true)
|
||||
->param('hash', 'bcrypt', new WhiteList(['bcrypt', 'scrypt']), 'Hashing algorithm for password. The default value is bcrypt.', true) // We don't allow md5 here on purpose.
|
||||
->param('hash', Auth::DEFAULT_ALGO, new WhiteList(\array_keys(Auth::SUPPORTED_ALGOS)), 'Hashing algorithm for password. Allowed values are: \'' . \implode('\', \'', \array_keys(Auth::SUPPORTED_ALGOS)) . '\'. The default value is \'' . Auth::DEFAULT_ALGO . '\'.', true)
|
||||
->param('hashOptions', Auth::DEFAULT_ALGO_OPTIONS, new JSON(), 'Configuration of hashing algorithm. If left empty, default configuration is used.', true)
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('audits')
|
||||
->inject('usage')
|
||||
->action(function ($password, $oldPassword, $hash, $response, $user, $dbForProject, $audits, $usage) {
|
||||
->action(function ($password, $oldPassword, $hash, $hashOptions, $response, $user, $dbForProject, $audits, $usage) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Document $user */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var Appwrite\Event\Event $audits */
|
||||
/** @var Appwrite\Stats\Stats $usage */
|
||||
|
||||
$hashOptions = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array
|
||||
|
||||
// Check old password only if its an existing user.
|
||||
if ($user->getAttribute('passwordUpdate') !== 0 && !Auth::passwordVerify($oldPassword, $user->getAttribute('password'), $user->getAttribute('hash'))) { // Double check user password
|
||||
if ($user->getAttribute('passwordUpdate') !== 0 && !Auth::passwordVerify($oldPassword, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions'))) { // Double check user password
|
||||
throw new Exception('Invalid credentials', 401, Exception::USER_INVALID_CREDENTIALS);
|
||||
}
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user
|
||||
->setAttribute('password', Auth::passwordHash($password, $hash))
|
||||
->setAttribute('password', Auth::passwordHash($password, $hash, $hashOptions))
|
||||
->setAttribute('hash', $hash)
|
||||
->setAttribute('hashOptions', $hashOptions)
|
||||
->setAttribute('passwordUpdate', \time())
|
||||
);
|
||||
|
||||
|
@ -1422,24 +1439,27 @@ App::patch('/v1/account/email')
|
|||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('password', '', new Password(), 'User password. Must be at least 8 chars.')
|
||||
->param('hash', 'bcrypt', new WhiteList(['bcrypt', 'scrypt']), 'Hashing algorithm for password. The default value is bcrypt.', true) // We don't allow md5 here on purpose.
|
||||
->param('hash', Auth::DEFAULT_ALGO, new WhiteList(\array_keys(Auth::SUPPORTED_ALGOS)), 'Hashing algorithm for password. Allowed values are: \'' . \implode('\', \'', \array_keys(Auth::SUPPORTED_ALGOS)) . '\'. The default value is \'' . Auth::DEFAULT_ALGO . '\'.', true)
|
||||
->param('hashOptions', Auth::DEFAULT_ALGO_OPTIONS, new JSON(), 'Configuration of hashing algorithm. If left empty, default configuration is used.', true)
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('audits')
|
||||
->inject('usage')
|
||||
->action(function ($email, $password, $hash, $response, $user, $dbForProject, $audits, $usage) {
|
||||
->action(function ($email, $password, $hash, $hashOptions, $response, $user, $dbForProject, $audits, $usage) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Document $user */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var Appwrite\Event\Event $audits */
|
||||
/** @var Appwrite\Stats\Stats $usage */
|
||||
|
||||
$hashOptions = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array
|
||||
|
||||
$isAnonymousUser = is_null($user->getAttribute('email')) && is_null($user->getAttribute('password')); // Check if request is from an anonymous account for converting
|
||||
|
||||
if (
|
||||
!$isAnonymousUser &&
|
||||
!Auth::passwordVerify($password, $user->getAttribute('password'), $user->getAttribute('hash'))
|
||||
!Auth::passwordVerify($password, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions'))
|
||||
) { // Double check user password
|
||||
throw new Exception('Invalid credentials', 401, Exception::USER_INVALID_CREDENTIALS);
|
||||
}
|
||||
|
@ -1453,8 +1473,9 @@ App::patch('/v1/account/email')
|
|||
|
||||
try {
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user
|
||||
->setAttribute('password', $isAnonymousUser ? Auth::passwordHash($password, $hash) : $user->getAttribute('password', ''))
|
||||
->setAttribute('password', $isAnonymousUser ? Auth::passwordHash($password, $hash, $hashOptions) : $user->getAttribute('password', ''))
|
||||
->setAttribute('hash', $isAnonymousUser ? $hash : $user->getAttribute('hash', ''))
|
||||
->setAttribute('hashOptions', $isAnonymousUser ? $hashOptions : $user->getAttribute('hashOptions', ''))
|
||||
->setAttribute('email', $email)
|
||||
->setAttribute('emailVerification', false) // After this user needs to confirm mail again
|
||||
->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name'), $user->getAttribute('email')]))
|
||||
|
@ -1981,17 +2002,20 @@ App::put('/v1/account/recovery')
|
|||
->param('secret', '', new Text(256), 'Valid reset token.')
|
||||
->param('password', '', new Password(), 'New user password. Must be at least 8 chars.')
|
||||
->param('passwordAgain', '', new Password(), 'Repeat new user password. Must be at least 8 chars.')
|
||||
->param('hash', 'bcrypt', new WhiteList(['bcrypt', 'scrypt']), 'Hashing algorithm for password. The default value is bcrypt.', true) // We don't allow md5 here on purpose.
|
||||
->param('hash', Auth::DEFAULT_ALGO, new WhiteList(\array_keys(Auth::SUPPORTED_ALGOS)), 'Hashing algorithm for password. Allowed values are: \'' . \implode('\', \'', \array_keys(Auth::SUPPORTED_ALGOS)) . '\'. The default value is \'' . Auth::DEFAULT_ALGO . '\'.', true)
|
||||
->param('hashOptions', Auth::DEFAULT_ALGO_OPTIONS, new JSON(), 'Configuration of hashing algorithm. If left empty, default configuration is used.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('audits')
|
||||
->inject('usage')
|
||||
->action(function ($userId, $secret, $password, $passwordAgain, $hash, $response, $dbForProject, $audits, $usage) {
|
||||
->action(function ($userId, $secret, $password, $passwordAgain, $hash, $hashOptions, $response, $dbForProject, $audits, $usage) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var Appwrite\Event\Event $audits */
|
||||
/** @var Appwrite\Stats\Stats $usage */
|
||||
|
||||
$hashOptions = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array
|
||||
|
||||
if ($password !== $passwordAgain) {
|
||||
throw new Exception('Passwords must match', 400, Exception::USER_PASSWORD_MISMATCH);
|
||||
}
|
||||
|
@ -2012,7 +2036,9 @@ App::put('/v1/account/recovery')
|
|||
Authorization::setRole('user:' . $profile->getId());
|
||||
|
||||
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile
|
||||
->setAttribute('password', Auth::passwordHash($password, $hash))
|
||||
->setAttribute('password', Auth::passwordHash($password, $hash, $hashOptions))
|
||||
->setAttribute('hash', $hash)
|
||||
->setAttribute('hashOptions', $hashOptions)
|
||||
->setAttribute('passwordUpdate', \time())
|
||||
->setAttribute('emailVerification', true)
|
||||
);
|
||||
|
|
|
@ -547,7 +547,7 @@ App::delete('/v1/projects/:projectId')
|
|||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
/** @var Appwrite\Event\Event $deletes */
|
||||
|
||||
if (!Auth::passwordVerify($password, $user->getAttribute('password'), $user->getAttribute('hash'))) { // Double check user password
|
||||
if (!Auth::passwordVerify($password, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions'))) { // Double check user password
|
||||
throw new Exception('Invalid credentials', 401, Exception::USER_INVALID_CREDENTIALS);
|
||||
}
|
||||
|
||||
|
|
|
@ -327,8 +327,9 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
'email' => $email,
|
||||
'emailVerification' => false,
|
||||
'status' => true,
|
||||
'password' => Auth::passwordHash(Auth::passwordGenerator(), 'bcrypt'),
|
||||
'hash' => 'bcrypt',
|
||||
'password' => Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS),
|
||||
'hash' => Auth::DEFAULT_ALGO,
|
||||
'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS,
|
||||
/**
|
||||
* Set the password update time to 0 for users created using
|
||||
* team invite and OAuth to allow password updates without an
|
||||
|
|
|
@ -21,6 +21,7 @@ use Utopia\Validator\WhiteList;
|
|||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\JSON;
|
||||
|
||||
App::post('/v1/users')
|
||||
->desc('Create User')
|
||||
|
@ -38,19 +39,17 @@ App::post('/v1/users')
|
|||
->param('email', '', new Email(), 'User email.')
|
||||
->param('password', '', new Password(), 'User password. Must be at least 8 chars.')
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->param('hash', 'bcrypt', new WhiteList(['bcrypt', 'scrypt', 'md5']), 'Hashing algorithm for password. The default value is bcrypt.', true)
|
||||
->param('import', false, new Boolean(), 'Are you importing hashed password?', true)
|
||||
->param('hash', Auth::DEFAULT_ALGO, new WhiteList(\array_keys(Auth::SUPPORTED_ALGOS)), 'Hashing algorithm for password. Allowed values are: \'' . \implode('\', \'', \array_keys(Auth::SUPPORTED_ALGOS)) . '\'. The default value is \'' . Auth::DEFAULT_ALGO . '\'.', true)
|
||||
->param('hashOptions', Auth::DEFAULT_ALGO_OPTIONS, new JSON(), 'Configuration of hashing algorithm. If left empty, default configuration is used.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('usage')
|
||||
->action(function ($userId, $email, $password, $name, $hash, $import, $response, $dbForProject, $usage) {
|
||||
->action(function ($userId, $email, $password, $name, $hash, $hashOptions, $response, $dbForProject, $usage) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var Appwrite\Stats\Stats $usage */
|
||||
|
||||
if(!$import && $hash === 'md5') {
|
||||
throw new Exception('For security reasons, MD5 hashing is only allowed for importing accounts. Please use bcrypt instead.');
|
||||
}
|
||||
$hashOptions = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array
|
||||
|
||||
$email = \strtolower($email);
|
||||
|
||||
|
@ -63,8 +62,9 @@ App::post('/v1/users')
|
|||
'email' => $email,
|
||||
'emailVerification' => false,
|
||||
'status' => true,
|
||||
'password' => $import ? $password : Auth::passwordHash($password, $hash),
|
||||
'password' => Auth::passwordHash($password, $hash, $hashOptions),
|
||||
'hash' => $hash,
|
||||
'hashOptions' => $hashOptions,
|
||||
'passwordUpdate' => \time(),
|
||||
'registration' => \time(),
|
||||
'reset' => false,
|
||||
|
@ -485,15 +485,18 @@ App::patch('/v1/users/:userId/password')
|
|||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('password', '', new Password(), 'New user password. Must be at least 8 chars.')
|
||||
->param('hash', 'bcrypt', new WhiteList(['bcrypt', 'scrypt']), 'Hashing algorithm for password. The default value is bcrypt.', true) // We don't allow md5 here on purpose.
|
||||
->param('hash', Auth::DEFAULT_ALGO, new WhiteList(\array_keys(Auth::SUPPORTED_ALGOS)), 'Hashing algorithm for password. Allowed values are: \'' . \implode('\', \'', \array_keys(Auth::SUPPORTED_ALGOS)) . '\'. The default value is \'' . Auth::DEFAULT_ALGO . '\'.', true)
|
||||
->param('hashOptions', Auth::DEFAULT_ALGO_OPTIONS, new JSON(), 'Configuration of hashing algorithm. If left empty, default configuration is used.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('audits')
|
||||
->action(function ($userId, $password, $hash, $response, $dbForProject, $audits) {
|
||||
->action(function ($userId, $password, $hash, $hashOptions, $response, $dbForProject, $audits) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var Appwrite\Event\Event $audits */
|
||||
|
||||
$hashOptions = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty() || $user->getAttribute('deleted')) {
|
||||
|
@ -501,7 +504,9 @@ App::patch('/v1/users/:userId/password')
|
|||
}
|
||||
|
||||
$user
|
||||
->setAttribute('password', Auth::passwordHash($password, $hash))
|
||||
->setAttribute('password', Auth::passwordHash($password, $hash, $hashOptions))
|
||||
->setAttribute('hash', $hash)
|
||||
->setAttribute('hashOptions', $hashOptions)
|
||||
->setAttribute('passwordUpdate', \time());
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
|
|
|
@ -25,7 +25,7 @@ class Auth
|
|||
];
|
||||
|
||||
const DEFAULT_ALGO = 'argon2';
|
||||
const DEFAULT_ALGO_OPTIONS = (object) array('memory_cost' => 2048, 'time_cost' => 4, 'threads' => 3);
|
||||
const DEFAULT_ALGO_OPTIONS = ['memory_cost' => 2048, 'time_cost' => 4, 'threads' => 3];
|
||||
|
||||
/**
|
||||
* User Roles.
|
||||
|
|
Loading…
Reference in a new issue