User session tracking #93587
@ -257,3 +257,42 @@ def check_verification_payload(
|
||||
|
||||
log.debug("verification OK")
|
||||
return VerificationResult.OK, payload
|
||||
|
||||
|
||||
def send_new_user_session(session):
|
||||
if not hasattr(session, 'user'):
|
||||
log.error('programming error: called for a session without a user')
|
||||
return False
|
||||
user = session.user
|
||||
|
||||
# sending only a text/plain email to reduce the room for look-alike phishing emails
|
||||
email_body_txt, subject = construct_new_user_session(session)
|
||||
|
||||
email = user.email
|
||||
try:
|
||||
send_mail(
|
||||
subject,
|
||||
message=email_body_txt,
|
||||
from_email=None, # just use the configured default From-address.
|
||||
recipient_list=[email],
|
||||
fail_silently=False,
|
||||
)
|
||||
except (smtplib.SMTPException, OSError):
|
||||
Oleg-Komarov marked this conversation as resolved
Outdated
|
||||
log.exception("failed to send a new user session email for account %s", user.pk)
|
||||
return False
|
||||
log.info("sent a new user session email for account %s", user.pk)
|
||||
return True
|
||||
|
||||
|
||||
def construct_new_user_session(session):
|
||||
context = {
|
||||
"session": session,
|
||||
"user": session.user,
|
||||
"subject": "Blender ID new sign-in",
|
||||
}
|
||||
|
||||
email_body_txt = loader.render_to_string(
|
||||
"bid_main/emails/new_user_session.txt", context
|
||||
)
|
||||
|
||||
return email_body_txt, context["subject"]
|
||||
|
@ -19,6 +19,7 @@ from django.utils import timezone
|
||||
from django.utils.deconstruct import deconstructible
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
import oauth2_provider.models as oa2_models
|
||||
import user_agents
|
||||
|
||||
from . import fields
|
||||
from . import hashers
|
||||
@ -682,3 +683,10 @@ class UserSession(models.Model):
|
||||
|
||||
def __str__(self):
|
||||
return f'UserSession pk={self.pk} for {self.user}'
|
||||
|
||||
@property
|
||||
def device(self):
|
||||
if self.user_agent:
|
||||
return user_agents.parse(self.user_agent)
|
||||
else:
|
||||
return None
|
||||
Oleg-Komarov marked this conversation as resolved
Outdated
Anna Sirota
commented
superfluous else superfluous else
|
||||
|
@ -6,7 +6,7 @@ from django.db.models import F
|
||||
from django.db.models.signals import m2m_changed, post_delete
|
||||
from django.dispatch import receiver
|
||||
|
||||
from . import models
|
||||
from . import email, models
|
||||
import bid_main.utils as utils
|
||||
import bid_main.file_utils
|
||||
|
||||
@ -20,7 +20,7 @@ def log_exception(sender, **kwargs):
|
||||
|
||||
@receiver(user_logged_in)
|
||||
def process_new_login(sender, request, user, **kwargs):
|
||||
"""Updates user fields upon login.
|
||||
"""Updates user fields and creates a UserSession upon login. Sends and email if IP is new.
|
||||
|
||||
Only saves specific fields, so that the webhook trigger knows what changed.
|
||||
"""
|
||||
@ -33,8 +33,11 @@ def process_new_login(sender, request, user, **kwargs):
|
||||
if request_ip and user.current_login_ip != request_ip:
|
||||
user.last_login_ip = F("current_login_ip")
|
||||
user.current_login_ip = request_ip
|
||||
|
||||
fields.update({"last_login_ip", "current_login_ip"})
|
||||
try:
|
||||
email.send_new_user_session(request.session.create_model_instance({}))
|
||||
except Exception:
|
||||
log.exception('failed to send a new user session email')
|
||||
Oleg-Komarov marked this conversation as resolved
Anna Sirota
commented
should be a call of a task should be a call of a task
|
||||
|
||||
user.save(update_fields=fields)
|
||||
models.UserSession.update_or_create_from_request(request, user)
|
||||
|
15
bid_main/templates/bid_main/emails/new_user_session.txt
Normal file
15
bid_main/templates/bid_main/emails/new_user_session.txt
Normal file
@ -0,0 +1,15 @@
|
||||
{% autoescape off %}
|
||||
Dear {{ user.full_name|default:user.email }}!
|
||||
|
||||
A new sign-in for your Blender ID account {{ user.email }}
|
||||
|
||||
IP address: {{ session.ip }}
|
||||
Device: {{ session.device }}
|
||||
|
||||
If this was you, you can ignore this message.
|
||||
If this wasn't you, please change or reset your password.
|
||||
|
||||
--
|
||||
Kind regards,
|
||||
The Blender Web Team
|
||||
{% endautoescape %}
|
@ -51,6 +51,7 @@ sorl-thumbnail==12.7.0 ; python_version >= "3.8" and python_version < "4"
|
||||
sqlparse==0.5.0 ; python_version >= "3.8" and python_version < "4"
|
||||
tornado==6.0.3 ; python_version >= "3.8" and python_version < "4"
|
||||
urllib3==1.25.11 ; python_version >= "3.8" and python_version < "4"
|
||||
user-agents==2.2.0
|
||||
uwsgi==2.0.23
|
||||
wrapt==1.15.0 ; python_version >= "3.8" and python_version < "4"
|
||||
zipp==0.6.0 ; python_version >= "3.8" and python_version < "4"
|
||||
|
Loading…
Reference in New Issue
Block a user
might be better to make this a background task instead of wrapping into except.