Finalize mail settings admin UI and Mailrise compatibility
This commit is contained in:
@@ -1,13 +1,88 @@
|
||||
|
||||
|
||||
from flask import Blueprint, render_template, request, flash, redirect, url_for, jsonify, current_app
|
||||
from flask_mail import Message
|
||||
from flask_login import login_required, current_user
|
||||
from functools import wraps
|
||||
from datetime import datetime, timedelta
|
||||
from sqlalchemy import func, desc
|
||||
import secrets
|
||||
from app.routes.mail import mail
|
||||
from app.extensions import db
|
||||
from app.models import User, Post, PostImage, GPXFile, Comment, Like, PageView
|
||||
from app.models import User, Post, PostImage, GPXFile, Comment, Like, PageView, MailSettings, SentEmail
|
||||
|
||||
admin = Blueprint('admin', __name__, url_prefix='/admin')
|
||||
|
||||
def admin_required(f):
|
||||
"""Decorator to require admin access"""
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
if not current_user.is_authenticated or not current_user.is_admin:
|
||||
flash('Admin access required.', 'error')
|
||||
return redirect(url_for('auth.login'))
|
||||
return f(*args, **kwargs)
|
||||
return decorated_function
|
||||
|
||||
@admin.route('/mail-settings', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def mail_settings():
|
||||
settings = MailSettings.query.first()
|
||||
if request.method == 'POST':
|
||||
enabled = bool(request.form.get('enabled'))
|
||||
provider = request.form.get('provider')
|
||||
server = request.form.get('server')
|
||||
port = int(request.form.get('port') or 0)
|
||||
use_tls = bool(request.form.get('use_tls'))
|
||||
username = request.form.get('username')
|
||||
password = request.form.get('password')
|
||||
default_sender = request.form.get('default_sender')
|
||||
if not settings:
|
||||
settings = MailSettings()
|
||||
db.session.add(settings)
|
||||
settings.enabled = enabled
|
||||
settings.provider = provider
|
||||
settings.server = server
|
||||
settings.port = port
|
||||
settings.use_tls = use_tls
|
||||
settings.username = username
|
||||
settings.password = password
|
||||
settings.default_sender = default_sender
|
||||
db.session.commit()
|
||||
flash('Mail settings updated.', 'success')
|
||||
sent_emails = SentEmail.query.order_by(SentEmail.sent_at.desc()).limit(50).all()
|
||||
return render_template('admin/mail_settings.html', settings=settings, sent_emails=sent_emails)
|
||||
|
||||
|
||||
## Duplicate imports and Blueprint definitions removed
|
||||
|
||||
# Password reset token generator (simple, for demonstration)
|
||||
def generate_reset_token(user):
|
||||
# In production, use itsdangerous or Flask-Security for secure tokens
|
||||
return secrets.token_urlsafe(32)
|
||||
|
||||
# Admin: Send password reset email to user
|
||||
@admin.route('/users/<int:user_id>/reset-password', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def reset_user_password(user_id):
|
||||
user = User.query.get_or_404(user_id)
|
||||
token = generate_reset_token(user)
|
||||
# In production, save token to DB or cache, and validate on reset
|
||||
reset_url = url_for('auth.reset_password', token=token, _external=True)
|
||||
msg = Message(
|
||||
subject="Password Reset Request",
|
||||
recipients=[user.email],
|
||||
body=f"Hello {user.nickname},\n\nAn admin has requested a password reset for your account. Click the link below to reset your password:\n{reset_url}\n\nIf you did not request this, please ignore this email."
|
||||
)
|
||||
try:
|
||||
mail.send(msg)
|
||||
flash(f"Password reset email sent to {user.email}.", "success")
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"Error sending reset email: {e}")
|
||||
flash(f"Failed to send password reset email: {e}", "danger")
|
||||
return redirect(url_for('admin.user_detail', user_id=user.id))
|
||||
|
||||
def admin_required(f):
|
||||
"""Decorator to require admin access"""
|
||||
@wraps(f)
|
||||
@@ -98,7 +173,7 @@ def dashboard():
|
||||
def posts():
|
||||
"""Admin post management - review posts"""
|
||||
page = request.args.get('page', 1, type=int)
|
||||
status = request.args.get('status', 'pending') # pending, published, all
|
||||
status = request.args.get('status', 'all') # pending, published, all
|
||||
|
||||
query = Post.query
|
||||
|
||||
|
||||
@@ -3,8 +3,12 @@ from flask_login import login_user, logout_user, login_required, current_user
|
||||
from werkzeug.security import check_password_hash
|
||||
from app.models import User, db
|
||||
from app.forms import LoginForm, RegisterForm, ForgotPasswordForm
|
||||
from app.routes.reset_password import RequestResetForm, ResetPasswordForm
|
||||
from flask_mail import Message
|
||||
from app.routes.mail import mail
|
||||
from app.utils.token import generate_reset_token, verify_reset_token
|
||||
import re
|
||||
|
||||
from app.forms import LoginForm, RegisterForm, ForgotPasswordForm
|
||||
auth = Blueprint('auth', __name__)
|
||||
|
||||
@auth.route('/login', methods=['GET', 'POST'])
|
||||
@@ -83,18 +87,44 @@ def forgot_password():
|
||||
"""Forgot password page"""
|
||||
if current_user.is_authenticated:
|
||||
return redirect(url_for('main.index'))
|
||||
|
||||
form = ForgotPasswordForm()
|
||||
form = RequestResetForm()
|
||||
if form.validate_on_submit():
|
||||
user = User.query.filter_by(email=form.email.data).first()
|
||||
if user:
|
||||
# TODO: Implement email sending for password reset
|
||||
flash('If an account with that email exists, we\'ve sent password reset instructions.', 'info')
|
||||
else:
|
||||
flash('If an account with that email exists, we\'ve sent password reset instructions.', 'info')
|
||||
token = generate_reset_token(user.email)
|
||||
reset_url = url_for('auth.reset_password', token=token, _external=True)
|
||||
msg = Message(
|
||||
subject="Password Reset Request",
|
||||
recipients=[user.email],
|
||||
body=f"Hello {user.nickname},\n\nTo reset your password, click the link below:\n{reset_url}\n\nIf you did not request this, please ignore this email."
|
||||
)
|
||||
try:
|
||||
mail.send(msg)
|
||||
except Exception as e:
|
||||
flash(f"Failed to send reset email: {e}", "danger")
|
||||
flash('If an account with that email exists, we\'ve sent password reset instructions.', 'info')
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
return render_template('auth/forgot_password.html', form=form)
|
||||
# Password reset route
|
||||
@auth.route('/reset-password/<token>', methods=['GET', 'POST'])
|
||||
def reset_password(token):
|
||||
if current_user.is_authenticated:
|
||||
return redirect(url_for('main.index'))
|
||||
email = verify_reset_token(token)
|
||||
if not email:
|
||||
flash('Invalid or expired reset link.', 'danger')
|
||||
return redirect(url_for('auth.forgot_password'))
|
||||
user = User.query.filter_by(email=email).first()
|
||||
if not user:
|
||||
flash('Invalid or expired reset link.', 'danger')
|
||||
return redirect(url_for('auth.forgot_password'))
|
||||
form = ResetPasswordForm()
|
||||
if form.validate_on_submit():
|
||||
user.set_password(form.password.data)
|
||||
db.session.commit()
|
||||
flash('Your password has been reset. You can now log in.', 'success')
|
||||
return redirect(url_for('auth.login'))
|
||||
return render_template('auth/reset_password.html', form=form)
|
||||
|
||||
def is_valid_password(password):
|
||||
"""Validate password strength"""
|
||||
|
||||
2
app/routes/mail.py
Normal file
2
app/routes/mail.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from flask_mail import Mail
|
||||
mail = Mail()
|
||||
12
app/routes/reset_password.py
Normal file
12
app/routes/reset_password.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, PasswordField, SubmitField
|
||||
from wtforms.validators import DataRequired, Email, EqualTo, Length
|
||||
|
||||
class RequestResetForm(FlaskForm):
|
||||
email = StringField('Email', validators=[DataRequired(), Email()])
|
||||
submit = SubmitField('Request Password Reset')
|
||||
|
||||
class ResetPasswordForm(FlaskForm):
|
||||
password = PasswordField('New Password', validators=[DataRequired(), Length(min=6)])
|
||||
confirm_password = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')])
|
||||
submit = SubmitField('Reset Password')
|
||||
Reference in New Issue
Block a user