mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
feat: add support for blocking disposable email addresses
- Introduced a new exception for disposable email addresses. - Updated user account creation and email handling to check against a list of disposable email domains. - Added a new API endpoint to enable or disable disposable email checks for projects. - Updated project model to include disposable email settings. - Configured loading of disposable email domains from a separate configuration file.
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'0-mail.com' => true,
|
||||
'027168.com' => true,
|
||||
'062e.com' => true,
|
||||
'0815.ru' => true,
|
||||
'0815.su' => true,
|
||||
'0845.ru' => true,
|
||||
'0box.eu' => true,
|
||||
'0cd.cn' => true,
|
||||
'10minutemail.com' => true,
|
||||
'tempmail.org' => true,
|
||||
'guerrillamail.com' => true,
|
||||
'mailinator.com' => true,
|
||||
'temp-mail.org' => true,
|
||||
'throwaway.email' => true,
|
||||
'yopmail.com' => true,
|
||||
'maildrop.cc' => true,
|
||||
'getnada.com' => true,
|
||||
'tempail.com' => true,
|
||||
];
|
||||
@@ -221,6 +221,11 @@ return [
|
||||
'description' => 'A user with the same email already exists in the current project.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::USER_EMAIL_DISPOSABLE => [
|
||||
'name' => Exception::USER_EMAIL_DISPOSABLE,
|
||||
'description' => 'Disposable email addresses are not allowed. Please use a permanent email address.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::USER_PASSWORD_MISMATCH => [
|
||||
'name' => Exception::USER_PASSWORD_MISMATCH,
|
||||
'description' => 'Passwords do not match. Please check the password and confirm password.',
|
||||
|
||||
@@ -369,6 +369,14 @@ App::post('/v1/account')
|
||||
}
|
||||
}
|
||||
|
||||
if ($project->getAttribute('auths', [])['disposableEmails'] ?? false) {
|
||||
$disposableEmails = Config::getParam('disposableEmails', []);
|
||||
$emailDomain = substr(strrchr($email, "@"), 1);
|
||||
if (isset($disposableEmails[$emailDomain])) {
|
||||
throw new Exception(Exception::USER_EMAIL_DISPOSABLE);
|
||||
}
|
||||
}
|
||||
|
||||
$hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, true]);
|
||||
|
||||
$passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
|
||||
@@ -1967,6 +1975,14 @@ App::post('/v1/account/tokens/magic-url')
|
||||
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
if ($project->getAttribute('auths', [])['disposableEmails'] ?? false) {
|
||||
$disposableEmails = Config::getParam('disposableEmails', []);
|
||||
$emailDomain = substr(strrchr($email, "@"), 1);
|
||||
if (isset($disposableEmails[$emailDomain])) {
|
||||
throw new Exception(Exception::USER_EMAIL_DISPOSABLE);
|
||||
}
|
||||
}
|
||||
|
||||
$userId = $userId === 'unique()' ? ID::unique() : $userId;
|
||||
|
||||
$user->setAttributes([
|
||||
@@ -2217,6 +2233,14 @@ App::post('/v1/account/tokens/email')
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */
|
||||
}
|
||||
|
||||
if ($project->getAttribute('auths', [])['disposableEmails'] ?? false) {
|
||||
$disposableEmails = Config::getParam('disposableEmails', []);
|
||||
$emailDomain = substr(strrchr($email, "@"), 1);
|
||||
if (isset($disposableEmails[$emailDomain])) {
|
||||
throw new Exception(Exception::USER_EMAIL_DISPOSABLE);
|
||||
}
|
||||
}
|
||||
|
||||
$userId = $userId === 'unique()' ? ID::unique() : $userId;
|
||||
|
||||
$user->setAttributes([
|
||||
@@ -3050,6 +3074,14 @@ App::patch('/v1/account/email')
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */
|
||||
}
|
||||
|
||||
if ($project->getAttribute('auths', [])['disposableEmails'] ?? false) {
|
||||
$disposableEmails = Config::getParam('disposableEmails', []);
|
||||
$emailDomain = substr(strrchr($email, "@"), 1);
|
||||
if (isset($disposableEmails[$emailDomain])) {
|
||||
throw new Exception(Exception::USER_EMAIL_DISPOSABLE);
|
||||
}
|
||||
}
|
||||
|
||||
$user
|
||||
->setAttribute('email', $email)
|
||||
->setAttribute('emailVerification', false) // After this user needs to confirm mail again
|
||||
|
||||
@@ -120,6 +120,7 @@ App::post('/v1/projects')
|
||||
'passwordDictionary' => false,
|
||||
'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG,
|
||||
'personalDataCheck' => false,
|
||||
'disposableEmails' => false,
|
||||
'mockNumbers' => [],
|
||||
'sessionAlerts' => false,
|
||||
'membershipsUserName' => false,
|
||||
@@ -1021,6 +1022,44 @@ App::patch('/v1/projects/:projectId/auth/personal-data')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/auth/disposable-emails')
|
||||
->desc('Update disposable emails check')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'auth',
|
||||
name: 'updateDisposableEmails',
|
||||
description: '/docs/references/projects/update-disposable-emails.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PROJECT,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('enabled', false, new Boolean(false), 'Set whether or not to block disposable email addresses. Default is false.')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, bool $enabled, Response $response, Database $dbForPlatform) {
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
$auths['disposableEmails'] = $enabled;
|
||||
|
||||
$dbForPlatform->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 user sessions limit')
|
||||
->groups(['api', 'projects'])
|
||||
|
||||
@@ -97,6 +97,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$password = (!empty($password)) ? ($hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) : null;
|
||||
$user = new Document([
|
||||
'$id' => $userId,
|
||||
@@ -1324,6 +1325,7 @@ App::patch('/v1/users/:userId/password')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (\strlen($password) === 0) {
|
||||
$user
|
||||
->setAttribute('password', '')
|
||||
|
||||
@@ -40,3 +40,4 @@ Config::load('storage-outputs', __DIR__ . '/../config/storage/outputs.php');
|
||||
Config::load('specifications', __DIR__ . '/../config/specifications.php');
|
||||
Config::load('templates-function', __DIR__ . '/../config/templates/function.php');
|
||||
Config::load('templates-site', __DIR__ . '/../config/templates/site.php');
|
||||
Config::load('disposableEmails', __DIR__ . '/../config/domains/disposable-emails.php'); // (eldad) following cloud camelCase format. Wish we could be consistent with the rest of the codebase.
|
||||
@@ -81,6 +81,7 @@ class Exception extends \Exception
|
||||
public const string USER_PASSWORD_RECENTLY_USED = 'password_recently_used';
|
||||
public const string USER_PASSWORD_PERSONAL_DATA = 'password_personal_data';
|
||||
public const string USER_EMAIL_ALREADY_EXISTS = 'user_email_already_exists';
|
||||
public const string USER_EMAIL_DISPOSABLE = 'user_email_disposable';
|
||||
public const string USER_PASSWORD_MISMATCH = 'user_password_mismatch';
|
||||
public const string USER_SESSION_NOT_FOUND = 'user_session_not_found';
|
||||
public const string USER_IDENTITY_NOT_FOUND = 'user_identity_not_found';
|
||||
|
||||
@@ -377,6 +377,7 @@ class Project extends Model
|
||||
$document->setAttribute('authPasswordHistory', $authValues['passwordHistory'] ?? 0);
|
||||
$document->setAttribute('authPasswordDictionary', $authValues['passwordDictionary'] ?? false);
|
||||
$document->setAttribute('authPersonalDataCheck', $authValues['personalDataCheck'] ?? false);
|
||||
$document->setAttribute('authDisposableEmails', $authValues['disposableEmails'] ?? false);
|
||||
$document->setAttribute('authMockNumbers', $authValues['mockNumbers'] ?? []);
|
||||
$document->setAttribute('authSessionAlerts', $authValues['sessionAlerts'] ?? false);
|
||||
$document->setAttribute('authMembershipsUserName', $authValues['membershipsUserName'] ?? true);
|
||||
|
||||
Reference in New Issue
Block a user