Algo2 hash, adding pr review changes
This commit is contained in:
parent
71155a0bf3
commit
55c8caf0cc
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
44
src/Appwrite/Auth/Hash/Argon2.php
Normal file
44
src/Appwrite/Auth/Hash/Argon2.php
Normal 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];
|
||||
}
|
||||
}
|
42
src/Appwrite/Auth/Hash/PlainText.php
Normal file
42
src/Appwrite/Auth/Hash/PlainText.php
Normal 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 [];
|
||||
}
|
||||
}
|
|
@ -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.',
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in a new issue