Merge pull request #884 from simple-login/ac-login-metric

Send newrelic events on login and register
This commit is contained in:
Son Nguyen Kim 2022-04-11 17:52:52 +02:00 committed by GitHub
commit 0a9c103ad1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 69 additions and 1 deletions

View file

@ -19,6 +19,7 @@ from app.email_utils import (
send_email, send_email,
render, render,
) )
from app.events.auth_event import LoginEvent, RegisterEvent
from app.extensions import limiter from app.extensions import limiter
from app.log import LOG from app.log import LOG
from app.models import User, ApiKey, SocialAuth, AccountActivation from app.models import User, ApiKey, SocialAuth, AccountActivation
@ -55,16 +56,20 @@ def auth_login():
user = User.filter_by(email=email).first() user = User.filter_by(email=email).first()
if not user or not user.check_password(password): if not user or not user.check_password(password):
LoginEvent(LoginEvent.ActionType.failed, LoginEvent.Source.api).send()
return jsonify(error="Email or password incorrect"), 400 return jsonify(error="Email or password incorrect"), 400
elif user.disabled: elif user.disabled:
LoginEvent(LoginEvent.ActionType.disabled_login, LoginEvent.Source.api).send()
return jsonify(error="Account disabled"), 400 return jsonify(error="Account disabled"), 400
elif not user.activated: elif not user.activated:
LoginEvent(LoginEvent.ActionType.not_activated, LoginEvent.Source.api).send()
return jsonify(error="Account not activated"), 422 return jsonify(error="Account not activated"), 422
elif user.fido_enabled(): elif user.fido_enabled():
# allow user who has TOTP enabled to continue using the mobile app # allow user who has TOTP enabled to continue using the mobile app
if not user.enable_otp: if not user.enable_otp:
return jsonify(error="Currently we don't support FIDO on mobile yet"), 403 return jsonify(error="Currently we don't support FIDO on mobile yet"), 403
LoginEvent(LoginEvent.ActionType.success, LoginEvent.Source.api).send()
return jsonify(**auth_payload(user, device)), 200 return jsonify(**auth_payload(user, device)), 200
@ -88,14 +93,20 @@ def auth_register():
password = data.get("password") password = data.get("password")
if DISABLE_REGISTRATION: if DISABLE_REGISTRATION:
RegisterEvent(RegisterEvent.ActionType.failed, RegisterEvent.Source.api).send()
return jsonify(error="registration is closed"), 400 return jsonify(error="registration is closed"), 400
if not email_can_be_used_as_mailbox(email) or personal_email_already_used(email): if not email_can_be_used_as_mailbox(email) or personal_email_already_used(email):
RegisterEvent(
RegisterEvent.ActionType.invalid_email, RegisterEvent.Source.api
).send()
return jsonify(error=f"cannot use {email} as personal inbox"), 400 return jsonify(error=f"cannot use {email} as personal inbox"), 400
if not password or len(password) < 8: if not password or len(password) < 8:
RegisterEvent(RegisterEvent.ActionType.failed, RegisterEvent.Source.api).send()
return jsonify(error="password too short"), 400 return jsonify(error="password too short"), 400
if len(password) > 100: if len(password) > 100:
RegisterEvent(RegisterEvent.ActionType.failed, RegisterEvent.Source.api).send()
return jsonify(error="password too long"), 400 return jsonify(error="password too long"), 400
LOG.d("create user %s", email) LOG.d("create user %s", email)
@ -114,6 +125,7 @@ def auth_register():
render("transactional/code-activation.html", code=code), render("transactional/code-activation.html", code=code),
) )
RegisterEvent(RegisterEvent.ActionType.success, RegisterEvent.Source.api).send()
return jsonify(msg="User needs to confirm their account"), 200 return jsonify(msg="User needs to confirm their account"), 200

View file

@ -5,6 +5,7 @@ from wtforms import StringField, validators
from app.auth.base import auth_bp from app.auth.base import auth_bp
from app.auth.views.login_utils import after_login from app.auth.views.login_utils import after_login
from app.events.auth_event import LoginEvent
from app.extensions import limiter from app.extensions import limiter
from app.log import LOG from app.log import LOG
from app.models import User from app.models import User
@ -43,18 +44,22 @@ def login():
g.deduct_limit = True g.deduct_limit = True
form.password.data = None form.password.data = None
flash("Email or password incorrect", "error") flash("Email or password incorrect", "error")
LoginEvent(LoginEvent.ActionType.failed).send()
elif user.disabled: elif user.disabled:
flash( flash(
"Your account is disabled. Please contact SimpleLogin team to re-enable your account.", "Your account is disabled. Please contact SimpleLogin team to re-enable your account.",
"error", "error",
) )
LoginEvent(LoginEvent.ActionType.disabled_login).send()
elif not user.activated: elif not user.activated:
show_resend_activation = True show_resend_activation = True
flash( flash(
"Please check your inbox for the activation email. You can also have this email re-sent", "Please check your inbox for the activation email. You can also have this email re-sent",
"error", "error",
) )
LoginEvent(LoginEvent.ActionType.not_activated).send()
else: else:
LoginEvent(LoginEvent.ActionType.success).send()
return after_login(user, next_url) return after_login(user, next_url)
return render_template( return render_template(

View file

@ -13,6 +13,7 @@ from app.email_utils import (
email_can_be_used_as_mailbox, email_can_be_used_as_mailbox,
personal_email_already_used, personal_email_already_used,
) )
from app.events.auth_event import RegisterEvent
from app.log import LOG from app.log import LOG
from app.models import User, ActivationCode from app.models import User, ActivationCode
from app.utils import random_string, encode_url, sanitize_email from app.utils import random_string, encode_url, sanitize_email
@ -60,6 +61,7 @@ def register():
hcaptcha_res, hcaptcha_res,
) )
flash("Wrong Captcha", "error") flash("Wrong Captcha", "error")
RegisterEvent(RegisterEvent.ActionType.catpcha_failed).send()
return render_template( return render_template(
"auth/register.html", "auth/register.html",
form=form, form=form,
@ -70,10 +72,11 @@ def register():
email = sanitize_email(form.email.data) email = sanitize_email(form.email.data)
if not email_can_be_used_as_mailbox(email): if not email_can_be_used_as_mailbox(email):
flash("You cannot use this email address as your personal inbox.", "error") flash("You cannot use this email address as your personal inbox.", "error")
RegisterEvent(RegisterEvent.ActionType.email_in_use).send()
else: else:
if personal_email_already_used(email): if personal_email_already_used(email):
flash(f"Email {email} already used", "error") flash(f"Email {email} already used", "error")
RegisterEvent(RegisterEvent.ActionType.email_in_use).send()
else: else:
LOG.d("create user %s", email) LOG.d("create user %s", email)
user = User.create( user = User.create(
@ -86,8 +89,10 @@ def register():
try: try:
send_activation_email(user, next_url) send_activation_email(user, next_url)
RegisterEvent(RegisterEvent.ActionType.success).send()
except Exception: except Exception:
flash("Invalid email, are you sure the email is correct?", "error") flash("Invalid email, are you sure the email is correct?", "error")
RegisterEvent(RegisterEvent.ActionType.invalid_email).send()
return redirect(url_for("auth.register")) return redirect(url_for("auth.register"))
return render_template("auth/register_waiting_activation.html") return render_template("auth/register_waiting_activation.html")

46
app/events/auth_event.py Normal file
View file

@ -0,0 +1,46 @@
import newrelic
from app.models import EnumE
class LoginEvent:
class ActionType(EnumE):
success = 0
failed = 1
disabled_login = 2
not_activated = 3
class Source(EnumE):
web = 0
api = 1
def __init__(self, action: ActionType, source: Source = Source.web):
self.action = action
self.source = source
def send(self):
newrelic.agent.record_custom_event(
"LoginEvent", {"action": self.action, "source": self.source}
)
class RegisterEvent:
class ActionType(EnumE):
success = 0
failed = 1
catpcha_failed = 2
email_in_use = 3
invalid_email = 4
class Source(EnumE):
web = 0
api = 1
def __init__(self, action: ActionType, source: Source = Source.web):
self.action = action
self.source = source
def send(self):
newrelic.agent.record_custom_event(
"RegisterEvent", {"action": self.action, "source": self.source}
)