use dictionary validator
This commit is contained in:
parent
5649bc8060
commit
0760ea90d6
3 changed files with 89 additions and 52 deletions
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
78
src/Appwrite/Auth/Validator/PasswordDictionary.php
Normal file
78
src/Appwrite/Auth/Validator/PasswordDictionary.php
Normal file
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Auth\Validator;
|
||||
|
||||
use Utopia\Database\Document;
|
||||
|
||||
/**
|
||||
* Password.
|
||||
*
|
||||
* Validates user password string
|
||||
*/
|
||||
class PasswordDictionary extends Password
|
||||
{
|
||||
protected array $dictionary;
|
||||
protected Document $project;
|
||||
|
||||
public function __construct(array $dictionary, Document $project)
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue