Skip to content

Commit

Permalink
Gran actualización de seguridad
Browse files Browse the repository at this point in the history
  • Loading branch information
livrasand committed Oct 17, 2024
1 parent 97544ac commit 7801842
Show file tree
Hide file tree
Showing 19 changed files with 1,613 additions and 1,005 deletions.
73 changes: 62 additions & 11 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@
from dotenv import load_dotenv
import pyotp
import qrcode
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Email
from flask_wtf.csrf import CSRFProtect

load_dotenv()
app = Flask(__name__)
Expand All @@ -38,7 +44,7 @@
app.config['MAIL_USE_TLS'] = True
app.config['MAIL_USERNAME'] = 'noresponder.kha@gmail.com'
app.config['MAIL_PASSWORD'] = 'sdlj izlj wpix ipsn'
app.config['MAIL_DEFAULT_SENDER'] = ('Join KHA', 'noresponder.kha@gmail.com')
app.config['MAIL_DEFAULT_SENDER'] = ('Kingdom Hall Attendant', 'noresponder.kha@gmail.com')

mail = Mail(app)
babel = Babel(app)
Expand All @@ -47,6 +53,26 @@

secret = pyotp.random_base32()

# Configura el Limiter
limiter = Limiter(
get_remote_address,
app=app,
default_limits=["200 per day", "50 per hour"]
)

# Configura CSRFProtect
csrf = CSRFProtect(app)

class LoginForm(FlaskForm):
email = StringField('Correo electrónico', validators=[DataRequired(), Email()])
password = PasswordField('Contraseña', validators=[DataRequired()])

class Verify2FAForm(FlaskForm):
codigo = StringField('Authentication code', validators=[DataRequired()])

class RegistrationForm(FlaskForm):
email = StringField('Email', validators=[DataRequired(), Email()])

#@babel.localeselector
def get_locale():
return session.get('language')
Expand Down Expand Up @@ -120,6 +146,10 @@ def update_last_login(user_id):
def welcome():
return render_template('welcome.html')

@app.route('/security')
def security():
return render_template('security.html')

#@app.errorhandler(Exception)
#def handle_exception(e):
# code = 404
Expand All @@ -135,9 +165,10 @@ def faq():
def sitemap():
return render_template('sitemap.xml')

@app.route('/login')
@app.route('/login', methods=['GET', 'POST'])
def login():
return render_template('login.html')
form = LoginForm()
return render_template('login.html', form=form)

@app.route('/login-desktop-client')
def login_desktop():
Expand Down Expand Up @@ -2086,8 +2117,12 @@ def inject_user_info():
return dict(nombre=g.get('nombre', 'Nombre'), apellidos=g.get('apellidos', 'Apellidos'), email=g.get('user_email', 'usuario@correo.com'))

@app.route('/accessing', methods=['POST'])
@limiter.limit("1 per minute")
def accessing():
email = request.form.get('email')
form = LoginForm()
if form.validate_on_submit():
email = form.email.data
password = form.password.data
if not email:
flash('El campo de correo electrónico es obligatorio.')
return redirect(url_for('login'))
Expand Down Expand Up @@ -2127,7 +2162,7 @@ def accessing():
img = qrcode.make(uri)

# Guardar el código QR en el sistema
qr_folder_path = os.path.join('kha', 'static', 'qrcodes') # Asegúrate de que apunte a la carpeta correcta
qr_folder_path = os.path.join('kha', 'static', 'qrcodes') # Ruta para PythonAnywhere
os.makedirs(qr_folder_path, exist_ok=True) # Crear la carpeta si no existe
qr_code_path = os.path.join(qr_folder_path, f'{email}_qr.png')
img.save(qr_code_path)
Expand All @@ -2147,8 +2182,10 @@ def accessing():
conn.close() # Cerrar conexión en caso de error
flash('Correo o contraseña incorrectos.')
return redirect(url_for('login'))
return render_template('login.html', form=form)

@app.route('/activate_2fa', methods=['GET', 'POST'])
@limiter.limit("1 per minute")
def activate_2fa():
if request.method == 'POST':
email = session.get('temp_user_email') # Recuperar el correo del usuario temporal
Expand All @@ -2164,7 +2201,7 @@ def activate_2fa():
img = qrcode.make(uri)

# Definir la ruta del código QR y crear la carpeta si no existe
qr_folder_path = os.path.join('kha', 'static', 'qrcodes') # Asegúrate de que apunte a la carpeta correcta
qr_folder_path = os.path.join('kha', 'static', 'qrcodes') # Ruta para PythonAnywhere
os.makedirs(qr_folder_path, exist_ok=True) # Crear la carpeta si no existe
qr_code_path = os.path.join(qr_folder_path, f'{email}_qr.png')
img.save(qr_code_path)
Expand All @@ -2182,9 +2219,11 @@ def activate_2fa():
return render_template('activate_2fa.html')

@app.route('/verify_2fa', methods=['GET', 'POST'])
@limiter.limit("2 per minute")
def verify_2fa():
if request.method == 'POST':
codigo_ingresado = request.form['codigo']
form = Verify2FAForm()
if form.validate_on_submit():
codigo_ingresado = form.codigo.data
user_id = session.get('temp_user_id') # Recuperar el ID de usuario temporal
email = session.get('temp_user_email') # Recuperar el email temporal

Expand Down Expand Up @@ -2228,17 +2267,19 @@ def verify_2fa():
flash('Error al verificar el código 2FA.')

conn.close()
return render_template('verify_2fa.html')
return render_template('verify_2fa.html', form=form)

@app.route('/signup')
def signup():
return render_template('signup.html')
form = LoginForm()
return render_template('signup.html', form=form)

@app.route('/forgot')
def forgot():
return render_template('forgot.html')

@app.route('/recovery', methods=['POST'])
@limiter.limit("1 per minute")
def recovery():
email = request.form['email']

Expand All @@ -2264,9 +2305,12 @@ def recovery():
return redirect(url_for('forgot'))

@app.route('/register', methods=['GET', 'POST'])
@limiter.limit("1 per minute")
def register():
form = RegistrationForm()
if form.validate_on_submit():
if request.method == 'POST':
email = request.form['email']
email = form.email.data

# Validar el email
if not re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', email):
Expand Down Expand Up @@ -2336,8 +2380,10 @@ def register():
return redirect(url_for('register'))

return render_template('signup.html')
return render_template('signup.html')

@app.route('/register/sent', methods=['GET', 'POST'])
@limiter.limit("1 per minute")
def register_sent():
email = request.args.get('email') # Obtener el correo de los parámetros de la URL
qr_code_path = os.path.join('static', 'qrcodes', f'{email}_qr.png')
Expand All @@ -2354,6 +2400,7 @@ def log_in_system():
return render_template('log-in-system.html')

@app.route('/resend_token/<email>', methods=['GET', 'POST'])
@limiter.limit("1 per minute")
def resend_token(email):
if request.method == 'POST' or request.method == 'GET':
token = secrets.token_urlsafe(16)
Expand Down Expand Up @@ -4036,5 +4083,9 @@ def eliminar_all_ava():
# Redirigir a la página original después de la eliminación
return redirect(url_for('audio_video_acomodadores'))

# Manejo de excepciones para límites de tasa
@app.errorhandler(429)
def ratelimit_handler(e):
return jsonify(error="Se ha detectado un comportamiento inusual en tus solicitudes. Por favor, intenta nuevamente más tarde. Si estás intentando eludir nuestras medidas de seguridad, ten en cuenta que hemos registrado tus datos. Para contribuir a la seguridad de nuestra plataforma, te invitamos a reportar cualquier vulnerabilidad y recibir el reconocimiento correspondiente."), 429
if __name__ == '__main__':
app.run(debug=True) #or False
Binary file modified requirements.txt
Binary file not shown.
1 change: 1 addition & 0 deletions static/pricing-cover.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added static/pricing-gradient-50.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added static/pricing-gradient-75.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added static/pricing-gradient.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 4 additions & 6 deletions static/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -13730,11 +13730,6 @@ th, td {
vertical-align: middle;
}

tbody tr:nth-child(2n) {
background: rgba(255, 255, 255, 0.3); /* Fondo semitransparente */
backdrop-filter: blur(15px); /* Efecto de desenfoque */
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); /* Sombra suave */
}


th {
Expand Down Expand Up @@ -13771,7 +13766,10 @@ button:focus {
font-family: 'YouTube Sans';
}
.jwIcon {
color: #f56900;
background: linear-gradient(to right, #6d92cc, #8a64e5);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
@media screen and (min-width: 600px) {
.css-dYcOXC {
Expand Down
18 changes: 11 additions & 7 deletions templates/activate_2fa.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,20 @@
<title>Activar 2FA - Kingdom Hall Attendant</title>
</head>
<body>
<a class="styles_module_wtLink__f24f44fb styles_module_wtLink_Utility__f24f44fb styles_module_wtLink_Default__f24f44fb styles_module_wtLink_Light__f24f44fb styles_module_wtBodyMedium__f24f44fb styles_module_logoLink__8a4eaeaf styles_module_dark__8a4eaeaf Nav_navTopLogo__LpSb7" href="https://www.getkha.org/" >
<img src="{{ url_for('static', filename='images/313010479-cfab1393-8ae1-4b3f-9895-7022272f1262.jpeg') }}" style="width:50px;border-radius: 25%;"></a>
{% include 'nav.html' %}
<div class="container">
<center>
<svg height="32" aria-hidden="true" viewBox="0 0 24 24" version="1.1" width="32" data-view-component="true" fill="#59636e" class="octicon octicon-device-mobile color-fg-muted">
<path d="M10.25 5.25A.75.75 0 0 1 11 4.5h2A.75.75 0 0 1 13 6h-2a.75.75 0 0 1-.75-.75ZM12 19.5a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"></path>
<path d="M4 2.75C4 1.784 4.784 1 5.75 1h12.5c.966 0 1.75.784 1.75 1.75v18.5A1.75 1.75 0 0 1 18.25 23H5.75A1.75 1.75 0 0 1 4 21.25Zm1.75-.25a.25.25 0 0 0-.25.25v18.5c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25V2.75a.25.25 0 0 0-.25-.25Z"></path>
</svg>
</center>
<h1 style="font-size: 24px;font-weight: 300;letter-spacing: -0.5px;">Activate Two-factor authentication</h1>

<nav id="wt-top-navigation-n_XG1HoVuvytluhKz8jtu" class="styles_module_container__05ad3f7f Nav_globalNavContainer__BzVy1"><div class="styles_module_menuGroup__a75ea064"><div class="styles_module_menuBar__a75ea064 styles_module_buttonsSection__33aa50d8"><a class="styles_module_wtLink__f24f44fb styles_module_wtLink_Navigational__f24f44fb styles_module_wtLink_Neutral__f24f44fb styles_module_wtLink_Light__f24f44fb styles_module_wtLabelMediumSemi__f24f44fb styles_module_navLink__0a0665ea" href="/login" data-prevent-routing="true" data-testid="Iniciar sesión">Iniciar sesión</a><a class="styles_module_wtButton__38691ab2 styles_module_wtButton_XSmall__38691ab2 styles_module_wtButtonPrimaryNeutral_Light__38691ab2" href="/signup" data-testid="Registrarse">Registrarse</a></div></div></nav>
<div class="container">
<h1>Active la Autenticación 2FA</h1>
{% if qr_code_path %}
<p style="max-width: 450px;">Escanea el código QR utilizando Google Authenticator, Microsoft Authenticator o cualquier aplicación de autenticación de tu elección compatible con códigos de un solo uso (TOTP) para activar la verificación en dos pasos (2FA) y mejorar la seguridad de tu cuenta:</p>
<img src="{{ url_for('static', filename='qrcodes/' + qr_code_path.split('/')[-1]) }}" alt="Código QR" style="width: 150px;">

<img src="{{ url_for('static', filename='qrcodes/' + qr_code_path.split('/')[-1]) }}" alt="Código QR" style="width: 150px;"> <!-- Linea para servidor -->
<p>Después de escanear, <a href="/login" style="margin-top: 20px;color: rgb(41, 126, 255); cursor: pointer;">Inicia sesión nuevamente</a>.</p>
{% else %}
<p>Error al generar el código QR. Por favor, inténtalo de nuevo.</p>
Expand Down
38 changes: 1 addition & 37 deletions templates/faq.html
Original file line number Diff line number Diff line change
Expand Up @@ -2481,43 +2481,7 @@

<body>
<div class="container" style='background-image: url("https://cdn.wetransfer.com/_next/static/media/PricingBackground.74fe38a7.svg");background-size: 100%;background-repeat: no-repeat;'>
<a class="styles_module_wtLink__f24f44fb styles_module_wtLink_Utility__f24f44fb styles_module_wtLink_Default__f24f44fb styles_module_wtLink_Light__f24f44fb styles_module_wtBodyMedium__f24f44fb styles_module_logoLink__8a4eaeaf styles_module_dark__8a4eaeaf Nav_navTopLogo__LpSb7" href="https://www.getkha.org/" >
<img src="{{ url_for('static', filename='images/313010479-cfab1393-8ae1-4b3f-9895-7022272f1262.jpeg') }}" style="width:50px;border-radius: 25%;"></a>

<nav id="wt-top-navigation-n_XG1HoVuvytluhKz8jtu" class="styles_module_container__05ad3f7f Nav_globalNavContainer__BzVy1"><div class="styles_module_menuGroup__a75ea064"><div class="styles_module_menuBar__a75ea064"><div class="styles_module_fullMenu__a75ea064" aria-hidden="false"><a class="styles_module_wtLink__f24f44fb styles_module_wtLink_Navigational__f24f44fb styles_module_wtLink_Neutral__f24f44fb styles_module_wtLink_Light__f24f44fb styles_module_wtLabelMediumSemi__f24f44fb styles_module_navLink__0a0665ea" href="/manifesto">Manifesto</a><a class="styles_module_wtLink__f24f44fb styles_module_wtLink_Navigational__f24f44fb styles_module_wtLink_Neutral__f24f44fb styles_module_wtLink_Light__f24f44fb styles_module_wtLabelMediumSemi__f24f44fb styles_module_navLink__0a0665ea" href="/faq">FAQ</a><a class="styles_module_wtLink__f24f44fb styles_module_wtLink_Navigational__f24f44fb styles_module_wtLink_Neutral__f24f44fb styles_module_wtLink_Light__f24f44fb styles_module_wtLabelMediumSemi__f24f44fb styles_module_navLink__0a0665ea" href="https://github.com/livrasand/Kingdom-Hall-Attendant">Source code</a></div></div><div class="styles_module_menuBar__a75ea064 styles_module_buttonsSection__33aa50d8"><a class="styles_module_wtLink__f24f44fb styles_module_wtLink_Navigational__f24f44fb styles_module_wtLink_Neutral__f24f44fb styles_module_wtLink_Light__f24f44fb styles_module_wtLabelMediumSemi__f24f44fb styles_module_navLink__0a0665ea" href="/login" data-prevent-routing="true" data-testid="Iniciar sesión">Iniciar sesión</a><a class="styles_module_wtButton__38691ab2 styles_module_wtButton_XSmall__38691ab2 styles_module_wtButtonPrimaryNeutral_Light__38691ab2" href="/signup" data-testid="Registrarse">Registrarse</a></div></div></nav>
<!-- Menú móvil -->
<nav id="mobile-navigation" class="styles_module_container__05ad3f7f Nav_globalNavContainer__BzVy1">
<div class="styles_module_menuGroup__a75ea064">
<div class="styles_module_menuBar__a75ea064 styles_module_buttonsSection__33aa50d8">
<a id="menu-toggle" class="styles_module_wtButton__38691ab2 styles_module_wtButton_XSmall__38691ab2 styles_module_wtButtonPrimaryNeutral_Light__38691ab2" data-testid="Menu toggle" style="height: 100%;margin:0;">
<svg width="18" height="18" viewBox="0 0 18 18">
<polyline id="globalnav-menutrigger-bread-bottom" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" points="2 12, 16 12" class="globalnav-menutrigger-bread globalnav-menutrigger-bread-bottom">
<animate id="globalnav-anim-menutrigger-bread-bottom-open" attributeName="points" keyTimes="0;0.5;1" dur="0.24s" begin="indefinite" fill="freeze" calcMode="spline" keySplines="0.42, 0, 1, 1;0, 0, 0.58, 1" values=" 2 12, 16 12; 2 9, 16 9; 3.5 15, 15 3.5"></animate>
<animate id="globalnav-anim-menutrigger-bread-bottom-close" attributeName="points" keyTimes="0;0.5;1" dur="0.24s" begin="indefinite" fill="freeze" calcMode="spline" keySplines="0.42, 0, 1, 1;0, 0, 0.58, 1" values=" 3.5 15, 15 3.5; 2 9, 16 9; 2 12, 16 12"></animate>
</polyline>
<polyline id="globalnav-menutrigger-bread-top" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" points="2 5, 16 5" class="globalnav-menutrigger-bread globalnav-menutrigger-bread-top">
<animate id="globalnav-anim-menutrigger-bread-top-open" attributeName="points" keyTimes="0;0.5;1" dur="0.24s" begin="indefinite" fill="freeze" calcMode="spline" keySplines="0.42, 0, 1, 1;0, 0, 0.58, 1" values=" 2 5, 16 5; 2 9, 16 9; 3.5 3.5, 15 15"></animate>
<animate id="globalnav-anim-menutrigger-bread-top-close" attributeName="points" keyTimes="0;0.5;1" dur="0.24s" begin="indefinite" fill="freeze" calcMode="spline" keySplines="0.42, 0, 1, 1;0, 0, 0.58, 1" values=" 3.5 3.5, 15 15; 2 9, 16 9; 2 5, 16 5"></animate>
</polyline>
</svg>
</a>
</div>
</div>
</nav>

<div id="fullscreen-menu" class="fullscreen-menu">
<div class="fullscreen-curtain"></div>
<div class="fullscreen-menu-content">
<button class="close-menu" id="close-menu"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="#fff" style="margin-left: -4px;">
<path d="M 19.990234 2.9863281 A 1.0001 1.0001 0 0 0 19.292969 3.2929688 L 12 10.585938 L 4.7070312 3.2929688 A 1.0001 1.0001 0 0 0 3.9902344 2.9902344 A 1.0001 1.0001 0 0 0 3.2929688 4.7070312 L 10.585938 12 L 3.2929688 19.292969 A 1.0001 1.0001 0 1 0 4.7070312 20.707031 L 12 13.414062 L 19.292969 20.707031 A 1.0001 1.0001 0 1 0 20.707031 19.292969 L 13.414062 12 L 20.707031 4.7070312 A 1.0001 1.0001 0 0 0 19.990234 2.9863281 z"></path>
</svg></button>
<a class="styles_module_wtLink__f24f44fb styles_module_wtLink_Navigational__f24f44fb styles_module_wtLink_Neutral__f24f44fb styles_module_wtLink_Light__f24f44fb styles_module_wtLabelMediumSemi__f24f44fb styles_module_navLink__0a0665ea" href="/manifesto" data-prevent-routing="true" data-testid="Manifesto" style="color: #e8e8ed;">Manifesto</a>
<a class="styles_module_wtLink__f24f44fb styles_module_wtLink_Navigational__f24f44fb styles_module_wtLink_Neutral__f24f44fb styles_module_wtLink_Light__f24f44fb styles_module_wtLabelMediumSemi__f24f44fb styles_module_navLink__0a0665ea" href="/faq" data-prevent-routing="true" data-testid="FAQ" style="color: #e8e8ed;">FAQ</a>
<a class="styles_module_wtLink__f24f44fb styles_module_wtLink_Navigational__f24f44fb styles_module_wtLink_Neutral__f24f44fb styles_module_wtLink_Light__f24f44fb styles_module_wtLabelMediumSemi__f24f44fb styles_module_navLink__0a0665ea" href="https://github.com/livrasand/Kingdom-Hall-Attendant" data-prevent-routing="true" data-testid="Source code" style="color: #e8e8ed;">Source code</a>
<a class="styles_module_wtLink__f24f44fb styles_module_wtLink_Navigational__f24f44fb styles_module_wtLink_Neutral__f24f44fb styles_module_wtLink_Light__f24f44fb styles_module_wtLabelMediumSemi__f24f44fb styles_module_navLink__0a0665ea" href="/login" data-prevent-routing="true" data-testid="Iniciar sesión" style="color: #e8e8ed;">Iniciar sesión</a>
<a class="styles_module_wtLink__f24f44fb styles_module_wtLink_Navigational__f24f44fb styles_module_wtLink_Neutral__f24f44fb styles_module_wtLink_Light__f24f44fb styles_module_wtLabelMediumSemi__f24f44fb styles_module_navLink__0a0665ea" href="/signup" data-prevent-routing="true" data-testid="Registrarse" style="color: #e8e8ed;">Registrarse</a>
</div>
</div>
{% include 'nav.html' %}

<div id="footer" style="padding:2%;border-radius: 28px;margin-top: 60px;">
<div class="">
Expand Down
Loading

0 comments on commit 7801842

Please sign in to comment.