Skip to content
Permalink
Browse files Browse the repository at this point in the history
Merge pull request from GHSA-36gx-9q6h-g429
Advisory fix 1 - notify user that his account is locked through email
  • Loading branch information
frankcorneliusmartin committed Feb 28, 2023
2 parents 6a525dc + 53ffcd1 commit ab4381c
Show file tree
Hide file tree
Showing 6 changed files with 372 additions and 157 deletions.
2 changes: 1 addition & 1 deletion vantage6-server/vantage6/server/mail_service.py
Expand Up @@ -20,7 +20,7 @@ def send_async_email(self, app, msg):
with app.app_context():
try:
self.mail.send(msg)
except ConnectionRefusedError as e:
except Exception as e:
log.error("Mailserver error!")
log.debug(e)

Expand Down
6 changes: 1 addition & 5 deletions vantage6-server/vantage6/server/model/user.py
Expand Up @@ -130,11 +130,7 @@ def is_blocked(self, max_failed_attempts: int,
if has_max_attempts and td_last_login < td_max_blocked:
minutes_remaining = \
(td_max_blocked - td_last_login).seconds // 60 + 1
return True, (
f"Your account is blocked for the next {minutes_remaining} "
"minutes due to failed login attempts. Please wait or "
"reactivate your account via email."
)
return True, minutes_remaining
else:
return False, None

Expand Down
63 changes: 59 additions & 4 deletions vantage6-server/vantage6/server/resource/token.py
Expand Up @@ -2,13 +2,12 @@
"""
Resources below '/<api_base>/token'
"""
from __future__ import print_function, unicode_literals

import logging
import datetime as dt
import pyotp

from typing import Union
from flask import request, g
from flask import request, g, render_template
from flask_jwt_extended import (
jwt_required,
create_access_token,
Expand Down Expand Up @@ -166,6 +165,62 @@ def post(self):
log.info(f"Succesfull login from {username}")
return ret, HTTPStatus.OK, {'jwt-token': token}

def user_login(self, username: str, password: str) -> Union[dict, db.User]:
"""Returns user a message in case of failed login attempt."""
log.info(f"Trying to login '{username}'")
failed_login_msg = "Failed to login"
if db.User.username_exists(username):
user = db.User.get_by_username(username)
pw_policy = self.config.get('password_policy', {})
max_failed_attempts = pw_policy.get('max_failed_attempts', 5)
inactivation_time = pw_policy.get('inactivation_minutes', 15)

is_blocked, min_rem = user.is_blocked(max_failed_attempts,
inactivation_time)
if is_blocked:
self.notify_user_blocked(user, max_failed_attempts, min_rem)
return {"msg": failed_login_msg}, HTTPStatus.UNAUTHORIZED
elif user.check_password(password):
user.failed_login_attempts = 0
user.save()
return user, HTTPStatus.OK
else:
# update the number of failed login attempts
user.failed_login_attempts = 1 \
if (
not user.failed_login_attempts or
user.failed_login_attempts >= max_failed_attempts
) else user.failed_login_attempts + 1
user.last_login_attempt = dt.datetime.now()
user.save()

return {"msg": failed_login_msg}, HTTPStatus.UNAUTHORIZED

def notify_user_blocked(self, user: db.User, max_n_attempts: int,
min_rem: int) -> None:
"""Sends an email to the user when his or her account is locked"""
if not user.email:
log.warning(f'User {user.username} is locked, but does not have'
'an email registered. So no message has been sent.')

log.info(f'User {user.username} is locked')

template_vars = {'firstname': user.firstname,
'number_of_allowed_attempts': max_n_attempts,
'ip': request.access_route[-1],
'time': dt.datetime.now(dt.timezone.utc),
'time_remaining': min_rem}

self.mail.send_email(
"Your account has been temporary suspended",
sender="support@vantage6.ai",
recipients=[user.email],
text_body=render_template("mail/blocked_account.txt",
**template_vars),
html_body=render_template("mail/blocked_account.html",
**template_vars)
)

@staticmethod
def validate_2fa_token(user: User, mfa_code: Union[int, str]) -> bool:
"""
Expand Down Expand Up @@ -197,7 +252,7 @@ def post(self):
---
description: >-
Allows node to sign in using a unique API key. If the login is
successful this returns a dictionairy with access and refresh tokens
successful this returns a dictionary with access and refresh tokens
for the node as well as a node_url and a refresh_url.
requestBody:
Expand Down
157 changes: 157 additions & 0 deletions vantage6-server/vantage6/server/templates/mail/blocked_account.html
@@ -0,0 +1,157 @@
<!--
HTML Email Starter Kit
Documentation: https://github.com/timothylong/html-email-starter-kit
-->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width" initial-scale="1">
<!--[if !mso]>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<![endif]-->
<meta name="x-apple-disable-message-reformatting">
<title></title>
<!--[if mso]>
<style>
* { font-family: sans-serif !important; }
</style>
<![endif]-->
<!--[if !mso]><!-->
<!-- Insert font reference, e.g. <link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700" rel="stylesheet"> -->
<!--<![endif]-->
<style>
*,
*:after,
*:before {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
* {
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
html,
body,
.document {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-size: 1.05em;
width: 100% !important;
height: 100% !important;
margin: 0;
padding: 0;
}
body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
}
div[style*="margin: 16px 0"] {
margin: 0 !important;
}
table,
td {
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
}
table {
border-spacing: 0;
border-collapse: collapse;
table-layout: fixed;
margin: 0 auto;
}
img {
-ms-interpolation-mode: bicubic;
max-width: 100%;
border: 0;
}
*[x-apple-data-detectors] {
color: inherit !important;
text-decoration: none !important;
}
.x-gmail-data-detectors,
.x-gmail-data-detectors *,
.aBn {
border-bottom: 0 !important;
cursor: default !important;
}
.btn {
-webkit-transition: all 200ms ease;
transition: all 200ms ease;
}
.btn:hover {
background-color: dodgerblue;
}
@media screen and (max-width: 750px) {
.container {
width: 100%;
margin: auto;
}
.stack {
display: block;
width: 100%;
max-width: 100%;
}
}
</style>
</head>
<body>
<div style="display: none; max-height: 0px; overflow: hidden;">
<!-- Preheader message here -->
</div>
<div style="display: none; max-height: 0px; overflow: hidden;">&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;</div>
<table style="background-color: #fff;" role="presentation" aria-hidden="true" cellspacing="0" cellpadding="0" border="0" align="center" class="document">
<tr>
<td valign="top">
<table role="presentation" aria-hidden="true" cellspacing="0" cellpadding="0" border="0" align="center" width="750" class="container">
<tr>
<td>
<table role="presentation" aria-hidden="true" cellspacing="0" cellpadding="0" border="0" align="center" width="100%">
<tr>
<td width=250></td>
<td style="padding:20px;">
<img src="https://raw.githubusercontent.com/IKNL/guidelines/master/resources/logos/vantage6.png" width=250>
</td>
<td width=250></td>
</tr>
<tr style="background-color:#f9f9f9;" width="100%">
<td colspan=3 style="padding:40px; color:#0f497b;">

Dear <strong>{{firstname}}</strong>,

<p>
After {{number_of_allowed_attempts}} failed login attempts your user account has been blocked.<br/>
<ul>
<li>Source IP: {{ip}}</li>
<li>Last attempt at: {{time}} (UTC)</li>
</ul>
</p>
<p>
Your account will be unlocked in {{time_remaining}} minutes. Please wait or reactivate your account via email.
</p>
<p>Sincerely, <br/>vantage6 Support Team</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
<table role="presentation" aria-hidden="true" cellspacing="0" cellpadding="0" border="0" align="center" width="750" class="container">
<tr style="font-size: .6em;">
<td style="padding-top:20px; padding-bottom: 20px;">
<a style="color:#0f497b; font-weight: bold;" href="https://vantage6.ai">vantage6.ai</a>
</td>
<td style="padding-top:20px; padding-bottom: 20px;text-align: center;">
<a style="color:#0f497b; font-weight: bold;" href="https://discord.gg/yAyFf6Y">Join our Discord channel</a>
</td >
<td style="padding-top:20px; padding-bottom: 20px;text-align: right;">
<a style="color:#0f497b; font-weight: bold;" href="mailto:support@vantage6.ai">support@vantage6.ai</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
10 changes: 10 additions & 0 deletions vantage6-server/vantage6/server/templates/mail/blocked_account.txt
@@ -0,0 +1,10 @@
Dear {{firstname}},

After {{number_of_allowed_attempts}} failed login attempts your user account has been blocked.

Source IP: {{ip}}
Last attempt at: {{time}} (UTC)

Your account will be unlocked in {{time_remaining}} minutes. Please wait or reactivate your account via email.

Sincerely, vantage6 Support Team

0 comments on commit ab4381c

Please sign in to comment.