1
0
Fork 0
mirror of synced 2024-06-03 03:14:50 +12:00

Algo2 hash, adding pr review changes

This commit is contained in:
Matej Bačo 2022-05-04 14:37:37 +00:00
parent 71155a0bf3
commit 55c8caf0cc
7 changed files with 146 additions and 13 deletions

View file

@ -1003,6 +1003,17 @@ $collections = [
'array' => false,
'filters' => [],
],
[
'$id' => 'hashOptions', // Configuration of hashing algorithm
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => '{}',
'array' => false,
'filters' => ['json'],
],
[
'$id' => 'passwordUpdate',
'type' => Database::VAR_INTEGER,

View file

@ -49,15 +49,15 @@ 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)
->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 Text(16384), 'Configuration of hashing algorithm. If left empty, default configuration is used.', true)
->inject('request')
->inject('response')
->inject('project')
->inject('dbForProject')
->inject('audits')
->inject('usage')
->action(function ($userId, $email, $password, $name, $hash, $import, $request, $response, $project, $dbForProject, $audits, $usage) {
->action(function ($userId, $email, $password, $name, $hash, $hashOptions, $request, $response, $project, $dbForProject, $audits, $usage) {
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
@ -65,10 +65,6 @@ App::post('/v1/account')
/** @var Appwrite\Event\Event $audits */
/** @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.');
}
$email = \strtolower($email);
if ('console' === $project->getId()) {
$whitelistEmails = $project->getAttribute('authWhitelistEmails');
@ -104,8 +100,9 @@ App::post('/v1/account')
'email' => $email,
'emailVerification' => false,
'status' => true,
'password' => $import ? $password : Auth::passwordHash($password, $hash),
'password' => Auth::passwordHash($password, $hash, \json_decode($hashOptions, true)),
'hash' => $hash,
'hashOptions' => $hashOptions,
'passwordUpdate' => \time(),
'registration' => \time(),
'reset' => false,
@ -176,7 +173,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'))) {
if (!$profile || !Auth::passwordVerify($password, $profile->getAttribute('password'), $profile->getAttribute('hash'), \json_decode($profile->getAttribute('hashOptions'), true))) {
$audits
//->setParam('userId', $profile->getId())
->setParam('event', 'account.sessions.failed')
@ -506,8 +503,9 @@ 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(), 'bcrypt'),
'hash' => 'bcrypt',
'password' => Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO),
'hash' => Auth::DEFAULT_ALGO,
'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS,
'passwordUpdate' => 0,
'registration' => \time(),
'reset' => false,
@ -683,7 +681,8 @@ App::post('/v1/account/sessions/magic-url')
'emailVerification' => false,
'status' => true,
'password' => null,
'hash' => 'bcrypt',
'hash' => Auth::DEFAULT_ALGO,
'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS,
'passwordUpdate' => 0,
'registration' => \time(),
'reset' => false,
@ -955,7 +954,8 @@ App::post('/v1/account/sessions/anonymous')
'emailVerification' => false,
'status' => true,
'password' => null,
'hash' => 'bcrypt',
'hash' => Auth::DEFAULT_ALGO,
'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS,
'passwordUpdate' => 0,
'registration' => \time(),
'reset' => false,

View file

@ -5,6 +5,7 @@ namespace Appwrite\Auth;
use Appwrite\Auth\Hash\BCrypt;
use Appwrite\Auth\Hash\MD5;
use Appwrite\Auth\Hash\PHPass;
use Appwrite\Auth\Hash\Argon2;
use Appwrite\Auth\Hash\SCrypt;
use Appwrite\Auth\Hash\SCryptModified;
use Utopia\Database\Document;
@ -12,6 +13,20 @@ use Utopia\Database\Validator\Authorization;
class Auth
{
const SUPPORTED_ALGOS = [
'argon2' => 'Argon2',
'bcrypt' => 'BCrypt',
'md5' => 'MD5',
'phpass' => 'PHPass',
'scrypt' => 'SCrypt',
'scrypt_modified' => 'SCryptModified',
'plaintext' => 'PlainText'
];
const DEFAULT_ALGO = 'argon2';
const DEFAULT_ALGO_OPTIONS = ['memory_cost' => 2048, 'time_cost' => 4, 'threads' => 3];
/**
* User Roles.
*/
@ -161,6 +176,10 @@ class Auth
$hasher = new MD5($options);
$hash = $hasher->hash($string);
return $hash;
case 'argon2':
$hasher = new Argon2($options);
$hash = $hasher->hash($string);
return $hash;
case 'phpass':
// TODO: Rework to use abstract class
$hahser = new PHPass(8, FALSE);
@ -201,6 +220,10 @@ class Auth
$hasher = new MD5($options ?? []);
$verify = $hasher->verify($plain, $hash);
return $verify;
case 'argon2':
$hasher = new Argon2($options ?? []);
$verify = $hasher->verify($plain, $hash);
return $verify;
case 'phpass':
// TODO: Rework to use abstract class
$hahser = new PHPass(8, FALSE);

View file

@ -0,0 +1,44 @@
<?php
namespace Appwrite\Auth\Hash;
use Appwrite\Auth\Hash;
/*
* Argon2 accepted options:
* int threads
* int time_cost
* int memory_cost
*
* Refference: https://www.php.net/manual/en/function.password-hash.php#example-983
*/
class Argon2 extends Hash
{
/**
* @param string $password Input password to hash
*
* @return string hash
*/
public function hash(string $password): string {
return \password_hash($password, PASSWORD_ARGON2ID, $this->getOptions());
}
/**
* @param string $password Input password to validate
* @param string $hash Hash to verify password against
*
* @return boolean true if password matches hash
*/
public function verify(string $password, string $hash): bool {
return \password_verify($password, $hash);
}
/**
* Get default options for specific hashing algo
*
* @return mixed options named array
*/
public function getDefaultOptions(): mixed {
return ['memory_cost' => 2048, 'time_cost' => 4, 'threads' => 3];
}
}

View file

@ -0,0 +1,42 @@
<?php
namespace Appwrite\Auth\Hash;
use Appwrite\Auth\Hash;
/*
* Argon2 accepted options:
* none
*
* Refference: None. Simple plain text stored.
*/
class Argon2 extends Hash
{
/**
* @param string $password Input password to hash
*
* @return string hash
*/
public function hash(string $password): string {
return $password;
}
/**
* @param string $password Input password to validate
* @param string $hash Hash to verify password against
*
* @return boolean true if password matches hash
*/
public function verify(string $password, string $hash): bool {
return $password === $hash;
}
/**
* Get default options for specific hashing algo
*
* @return mixed options named array
*/
public function getDefaultOptions(): mixed {
return [];
}
}

View file

@ -29,6 +29,12 @@ class User extends Model
'default' => '',
'example' => 'bcrypt',
])
->addRule('hashOptions', [
'type' => self::TYPE_STRING,
'description' => 'Password hashing algorithm configuration.',
'default' => '',
'example' => '{}',
])
->addRule('registration', [
'type' => self::TYPE_INTEGER,
'description' => 'User registration date in Unix timestamp.',

View file

@ -101,6 +101,13 @@ class AuthTest extends TestCase
$this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'phpass'));
$this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'phpass'));
// Argon2
$plain = 'safe-argon-password';
$hash = '$argon2id$v=19$m=2048,t=3,p=4$MWc5NWRmc2QxZzU2$41mp7rSgBZ49YxLbbxIac7aRaxfp5/e1G45ckwnK0g8';
$generatedHash = Auth::passwordHash($plain, 'argon2');
$this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'argon2'));
$this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'argon2'));
// SCryptModified
// TODO: Add tests