diff --git a/frontend/src/components/user-settings/email-notice.js b/frontend/src/components/user-settings/email-notice.js
index d8ebf1b1b6..1d3d96a7bc 100644
--- a/frontend/src/components/user-settings/email-notice.js
+++ b/frontend/src/components/user-settings/email-notice.js
@@ -1,12 +1,15 @@
import React from 'react';
import { gettext } from '../../utils/constants';
-import { seafileAPI } from '../../utils/seafile-api';
+import { userAPI } from '../../utils/user-api';
import { Utils } from '../../utils/utils';
import toaster from '../toast';
const {
fileUpdatesEmailInterval,
- collaborateEmailInterval
+ collaborateEmailInterval,
+ enableLoginEmail,
+ enablePasswordUpdateEmail,
+
} = window.app.pageOptions;
class EmailNotice extends React.Component {
@@ -28,9 +31,21 @@ class EmailNotice extends React.Component {
{ interval: 3600, text: gettext('Per hour') + ' (' + gettext('If notifications have not been read within one hour, they will be sent to your mailbox.') + ')' }
];
+ this.passwordOption = [
+ { enabled: 0, text: gettext('Don\'t send emails') },
+ { enabled: 1, text: gettext('Send email after changing password') }
+ ];
+
+ this.loginOption = [
+ { enabled: 0, text: gettext('Don\'t send emails') },
+ { enabled: 1, text: gettext('Send an email when a new device or browser logs in for the first time') }
+ ];
+
this.state = {
fileUpdatesEmailInterval: fileUpdatesEmailInterval,
- collaborateEmailInterval: collaborateEmailInterval
+ collaborateEmailInterval: collaborateEmailInterval,
+ enableLoginEmail: enableLoginEmail,
+ enablePasswordUpdateEmail: enablePasswordUpdateEmail
};
}
@@ -50,11 +65,27 @@ class EmailNotice extends React.Component {
}
};
+ inputPasswordEmailEnabledChange = (e) => {
+ if (e.target.checked) {
+ this.setState({
+ enablePasswordUpdateEmail: parseInt(e.target.value)
+ });
+ }
+ };
+
+ inputLoginEmailEnabledChange = (e) => {
+ if (e.target.checked) {
+ this.setState({
+ enableLoginEmail: parseInt(e.target.value)
+ });
+ }
+ };
+
formSubmit = (e) => {
e.preventDefault();
- let { fileUpdatesEmailInterval, collaborateEmailInterval } = this.state;
- seafileAPI.updateEmailNotificationInterval(fileUpdatesEmailInterval, collaborateEmailInterval).then((res) => {
- toaster.success(gettext('Email notification updated'));
+ let { fileUpdatesEmailInterval, collaborateEmailInterval, enablePasswordUpdateEmail, enableLoginEmail } = this.state;
+ userAPI.updateEmailNotificationInterval(fileUpdatesEmailInterval, collaborateEmailInterval, enablePasswordUpdateEmail, enableLoginEmail).then((res) => {
+ toaster.success(gettext('Success'));
}).catch((error) => {
let errorMsg = Utils.getErrorMsg(error);
toaster.danger(errorMsg);
@@ -62,7 +93,7 @@ class EmailNotice extends React.Component {
};
render() {
- const { fileUpdatesEmailInterval, collaborateEmailInterval } = this.state;
+ const { fileUpdatesEmailInterval, collaborateEmailInterval, enableLoginEmail, enablePasswordUpdateEmail } = this.state;
return (
{gettext('Email Notification')}
@@ -88,6 +119,27 @@ class EmailNotice extends React.Component {
);
})}
+
+ {gettext('Notifications of change password')}
+ {this.passwordOption.map((item, index) => {
+ return (
+
+
+
+
+ );
+ })}
+
+ {gettext('Notifications of login')}
+ {gettext('Send a mail as soon as a new device or browser has signed into the account (like google and many other services do).')}
+ {this.loginOption.map((item, index) => {
+ return (
+
+
+
+
+ );
+ })}
diff --git a/frontend/src/utils/user-api.js b/frontend/src/utils/user-api.js
index 5296dd015c..b5e37d216c 100644
--- a/frontend/src/utils/user-api.js
+++ b/frontend/src/utils/user-api.js
@@ -81,6 +81,17 @@ class UserAPI {
return this.req.get(url);
}
+ updateEmailNotificationInterval(fileUpdatesEmailInterval, collaborateEmailInterval, enablePasswordUpdateEmail, enableLoginEmail) {
+ let url = this.server + '/api2/account/info/';
+ let data = {
+ 'file_updates_email_interval': fileUpdatesEmailInterval,
+ 'collaborate_email_interval': collaborateEmailInterval,
+ 'enable_password_update_email': enablePasswordUpdateEmail,
+ 'enable_login_email': enableLoginEmail
+ };
+ return this.req.put(url, data);
+ }
+
}
let userAPI = new UserAPI();
diff --git a/seahub/api2/endpoints/user.py b/seahub/api2/endpoints/user.py
index 7041aa3b2a..ada8cf6e71 100644
--- a/seahub/api2/endpoints/user.py
+++ b/seahub/api2/endpoints/user.py
@@ -26,6 +26,8 @@ from seahub.base.templatetags.seahub_tags import email2nickname, \
from seahub.profile.models import Profile, DetailedProfile
from seahub.settings import ENABLE_UPDATE_USER_INFO, ENABLE_USER_SET_CONTACT_EMAIL, ENABLE_CONVERT_TO_TEAM_ACCOUNT, \
ENABLE_USER_SET_NAME
+from seahub.options.models import UserOptions
+
import seaserv
from seaserv import ccnet_api, seafile_api
@@ -251,19 +253,21 @@ class ResetPasswordView(APIView):
user.set_password(new_password)
user.save()
- email_template_name = 'registration/password_change_email.html'
- send_to = email2contact_email(request.user.username)
- site_name = get_site_name()
- c = {
- 'email': send_to,
- 'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
- }
- try:
- send_html_email(_("Successfully Changed Password on %s") % site_name,
- email_template_name, c, None,
- [send_to])
- except Exception as e:
- logger.error('Failed to send notification to %s' % send_to)
+ enable_pwd_email = bool(UserOptions.objects.get_password_update_email_enable_status(user.username))
+ if enable_pwd_email:
+ email_template_name = 'registration/password_change_email.html'
+ send_to = email2contact_email(request.user.username)
+ site_name = get_site_name()
+ c = {
+ 'email': send_to,
+ 'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+ }
+ try:
+ send_html_email(_("Successfully Changed Password on %s") % site_name,
+ email_template_name, c, None,
+ [send_to])
+ except Exception as e:
+ logger.error('Failed to send notification to %s' % send_to)
if not request.session.is_empty():
# invalidate all active sessions after change password.
diff --git a/seahub/api2/utils.py b/seahub/api2/utils.py
index 04c18735b6..5dc5ff9683 100644
--- a/seahub/api2/utils.py
+++ b/seahub/api2/utils.py
@@ -34,6 +34,8 @@ from seahub.utils import get_user_repos, send_html_email, get_site_name
from seahub.utils.mail import send_html_email_with_dj_template
from django.utils.translation import gettext as _
import seahub.settings as settings
+from seahub.options.models import UserOptions
+
JWT_PRIVATE_KEY = getattr(settings, 'JWT_PRIVATE_KEY', '')
@@ -210,8 +212,8 @@ def get_token_v2(request, username, platform, device_id, device_name,
raise serializers.ValidationError('invalid device id')
else:
raise serializers.ValidationError('invalid platform')
-
- if not TokenV2.objects.filter(user=username, device_id=device_id).first():
+ enable_new_device_email = bool(UserOptions.objects.get_login_email_enable_status(username))
+ if not TokenV2.objects.filter(user=username, device_id=device_id).first() and enable_new_device_email:
email_template_name='registration/new_device_login_email.html'
send_to = email2contact_email(username)
site_name = get_site_name()
diff --git a/seahub/api2/views.py b/seahub/api2/views.py
index 3f08fb1b19..29c3cce452 100644
--- a/seahub/api2/views.py
+++ b/seahub/api2/views.py
@@ -383,6 +383,21 @@ class AccountInfo(APIView):
return api_error(status.HTTP_400_BAD_REQUEST,
'collaborate_email_interval invalid')
+ enable_login_email = request.data.get("enable_login_email", None)
+ if enable_login_email is not None:
+ try:
+ enable_login_email = int(enable_login_email)
+ except ValueError:
+ return api_error(status.HTTP_400_BAD_REQUEST,
+ 'enable_login_email invalid')
+
+ enable_password_update_email = request.data.get("enable_password_update_email", None)
+ if enable_password_update_email is not None:
+ try:
+ enable_password_update_email = int(enable_password_update_email)
+ except ValueError:
+ return api_error(status.HTTP_400_BAD_REQUEST,
+ 'enable_password_update_email invalid')
# update user info
if name is not None:
@@ -403,6 +418,14 @@ class AccountInfo(APIView):
UserOptions.objects.set_collaborate_email_interval(
username, collaborate_email_interval)
+ if enable_password_update_email is not None:
+ UserOptions.objects.set_password_update_email_enable_status(
+ username, enable_password_update_email)
+
+ if enable_login_email is not None:
+ UserOptions.objects.set_login_email_enable_status(
+ username, enable_login_email)
+
return Response(self._get_account_info(request))
diff --git a/seahub/auth/__init__.py b/seahub/auth/__init__.py
index da3fcc025d..682fbf5858 100644
--- a/seahub/auth/__init__.py
+++ b/seahub/auth/__init__.py
@@ -119,7 +119,11 @@ def logout(request):
session data.
Also remove all passwords used to decrypt repos.
"""
+ already_logged_list = request.session.get('_already_logged', [])
request.session.flush()
+ if request.user.username not in already_logged_list:
+ already_logged_list.append(request.user.username)
+ request.session['_already_logged'] = already_logged_list
if hasattr(request, 'user'):
from seahub.base.accounts import User
if isinstance(request.user, User):
diff --git a/seahub/auth/views.py b/seahub/auth/views.py
index 41f2ec16ba..21006296d5 100644
--- a/seahub/auth/views.py
+++ b/seahub/auth/views.py
@@ -43,7 +43,6 @@ from seahub.utils.two_factor_auth import two_factor_auth_enabled, handle_two_fac
from seahub.utils.user_permissions import get_user_role
from seahub.utils.auth import get_login_bg_image_path
from seahub.organizations.models import OrgSAMLConfig
-from seahub.sysadmin_extra.models import UserLoginLog
from constance import config
@@ -78,7 +77,9 @@ def log_user_in(request, user, redirect_to):
# Okay, security checks complete. Log the user in.
auth_login(request, user)
- if UserLoginLog.objects.filter(username=user.username, login_success=1).count() == 1:
+ enable_login_email = bool(UserOptions.objects.get_login_email_enable_status(user.username))
+ already_logs = request.session.get('_already_logged', [])
+ if user.username not in already_logs and enable_login_email:
email_template_name = 'registration/browse_login_email.html'
send_to = email2contact_email(request.user.username)
site_name = get_site_name()
diff --git a/seahub/options/models.py b/seahub/options/models.py
index 3166b55279..5a38c5f832 100644
--- a/seahub/options/models.py
+++ b/seahub/options/models.py
@@ -38,9 +38,11 @@ KEY_FILE_UPDATES_EMAIL_INTERVAL = "file_updates_email_interval"
KEY_FILE_UPDATES_LAST_EMAILED_TIME = "file_updates_last_emailed_time"
KEY_COLLABORATE_EMAIL_INTERVAL = 'collaborate_email_interval'
KEY_COLLABORATE_LAST_EMAILED_TIME = 'collaborate_last_emailed_time'
+KEY_LOGIN_EMAIL_INTERVAL = 'enable_login_email'
+KEY_PASSWD_UPDATE_EMAIL_INTERVAL = 'enable_password_update_email'
DEFAULT_COLLABORATE_EMAIL_INTERVAL = 3600
-
+DEFAULT_PWD_UPDATE_EMAIL_ENABLED = 1
class CryptoOptionNotSetError(Exception):
pass
@@ -346,6 +348,34 @@ class UserOptionsManager(models.Manager):
def unset_collaborate_last_emailed_time(self, username):
return self.unset_user_option(username, KEY_COLLABORATE_LAST_EMAILED_TIME)
+ def get_login_email_enable_status(self, username):
+ val = self.get_user_option(username, KEY_LOGIN_EMAIL_INTERVAL)
+ if not val:
+ return None
+ try:
+ return int(val)
+ except ValueError:
+ logger.error('Failed to convert string %s to int' % val)
+ return None
+
+ def set_login_email_enable_status(self, username, enable):
+ return self.set_user_option(username, KEY_LOGIN_EMAIL_INTERVAL,
+ str(enable))
+
+ def get_password_update_email_enable_status(self, username):
+ val = self.get_user_option(username, KEY_PASSWD_UPDATE_EMAIL_INTERVAL)
+ if not val:
+ return DEFAULT_PWD_UPDATE_EMAIL_ENABLED
+ try:
+ return int(val)
+ except ValueError:
+ logger.error('Failed to convert string %s to int' % val)
+ return None
+
+ def set_password_update_email_enable_status(self, username, enable):
+ return self.set_user_option(username, KEY_PASSWD_UPDATE_EMAIL_INTERVAL,
+ str(enable))
+
class UserOptions(models.Model):
email = LowerCaseCharField(max_length=255, db_index=True)
diff --git a/seahub/profile/templates/profile/set_profile_react.html b/seahub/profile/templates/profile/set_profile_react.html
index 4fd2dac780..d63a1951bd 100644
--- a/seahub/profile/templates/profile/set_profile_react.html
+++ b/seahub/profile/templates/profile/set_profile_react.html
@@ -58,6 +58,8 @@ window.app.pageOptions = {
fileUpdatesEmailInterval: {{ file_updates_email_interval }},
collaborateEmailInterval: {{ collaborate_email_interval }},
+ enablePasswordUpdateEmail: {{ enable_password_update_email }},
+ enableLoginEmail: {{ enable_login_email }},
twoFactorAuthEnabled: {% if two_factor_auth_enabled %} true {% else %} false {% endif %},
{% if two_factor_auth_enabled %}
diff --git a/seahub/profile/views.py b/seahub/profile/views.py
index 91b01d4a09..36a93d4076 100644
--- a/seahub/profile/views.py
+++ b/seahub/profile/views.py
@@ -90,6 +90,9 @@ def edit_profile(request):
file_updates_email_interval = file_updates_email_interval if file_updates_email_interval is not None else 0
collaborate_email_interval = UserOptions.objects.get_collaborate_email_interval(username)
collaborate_email_interval = collaborate_email_interval if collaborate_email_interval is not None else DEFAULT_COLLABORATE_EMAIL_INTERVAL
+ enable_login_email = UserOptions.objects.get_login_email_enable_status(username)
+ enable_login_email = enable_login_email if enable_login_email is not None else 0
+ enable_password_update_email = UserOptions.objects.get_password_update_email_enable_status(username)
if work_weixin_oauth_check():
enable_wechat_work = True
@@ -168,6 +171,8 @@ def edit_profile(request):
'ENABLE_UPDATE_USER_INFO': ENABLE_UPDATE_USER_INFO,
'file_updates_email_interval': file_updates_email_interval,
'collaborate_email_interval': collaborate_email_interval,
+ 'enable_password_update_email': enable_password_update_email,
+ 'enable_login_email': enable_login_email,
'social_next_page': reverse('edit_profile'),
'enable_wechat_work': enable_wechat_work,
'social_connected': social_connected,