User session tracking #93587
@ -257,3 +257,42 @@ def check_verification_payload(
|
|||||||
|
|
||||||
log.debug("verification OK")
|
log.debug("verification OK")
|
||||||
return VerificationResult.OK, payload
|
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.deconstruct import deconstructible
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
import oauth2_provider.models as oa2_models
|
import oauth2_provider.models as oa2_models
|
||||||
|
import user_agents
|
||||||
|
|
||||||
from . import fields
|
from . import fields
|
||||||
from . import hashers
|
from . import hashers
|
||||||
@ -682,3 +683,10 @@ class UserSession(models.Model):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'UserSession pk={self.pk} for {self.user}'
|
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.db.models.signals import m2m_changed, post_delete
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
|
||||||
from . import models
|
from . import email, models
|
||||||
import bid_main.utils as utils
|
import bid_main.utils as utils
|
||||||
import bid_main.file_utils
|
import bid_main.file_utils
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ def log_exception(sender, **kwargs):
|
|||||||
|
|
||||||
@receiver(user_logged_in)
|
@receiver(user_logged_in)
|
||||||
def process_new_login(sender, request, user, **kwargs):
|
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.
|
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:
|
if request_ip and user.current_login_ip != request_ip:
|
||||||
user.last_login_ip = F("current_login_ip")
|
user.last_login_ip = F("current_login_ip")
|
||||||
user.current_login_ip = request_ip
|
user.current_login_ip = request_ip
|
||||||
|
|
||||||
fields.update({"last_login_ip", "current_login_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)
|
user.save(update_fields=fields)
|
||||||
models.UserSession.update_or_create_from_request(request, user)
|
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"
|
sqlparse==0.5.0 ; python_version >= "3.8" and python_version < "4"
|
||||||
tornado==6.0.3 ; 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"
|
urllib3==1.25.11 ; python_version >= "3.8" and python_version < "4"
|
||||||
|
user-agents==2.2.0
|
||||||
uwsgi==2.0.23
|
uwsgi==2.0.23
|
||||||
wrapt==1.15.0 ; python_version >= "3.8" and python_version < "4"
|
wrapt==1.15.0 ; python_version >= "3.8" and python_version < "4"
|
||||||
zipp==0.6.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.