1
0
forked from finn/site

mgt c10 checkpoint with debug

This commit is contained in:
finn 2024-08-05 00:59:01 -07:00
parent ed9df4db6f
commit 3d1f21ffcb
9 changed files with 133 additions and 25 deletions

View File

@ -1,5 +1,6 @@
from flask import render_template
from flask_mail import Message from flask_mail import Message
from app import mail from app import mail, app
def send_email(subject, sender, recipients, text_body, html_body): def send_email(subject, sender, recipients, text_body, html_body):
msg = Message(subject, sender=sender, recipients=recipients) msg = Message(subject, sender=sender, recipients=recipients)
@ -7,3 +8,15 @@ def send_email(subject, sender, recipients, text_body, html_body):
msg.html = html_body msg.html = html_body
mail.send(msg) mail.send(msg)
def send_password_reset_email(user):
token = user.get_reset_password_token()
hostname = app.config['REAL_HOSTNAME']
send_email('[Blog] Reset Password',
sender=app.config['ADMINS'][0],
recipients=[user.email],
text_body=render_template('email/reset_password.txt', hostname=hostname, user=user, token=token),
html_body=render_template('email/reset_password.html', hostname=hostname, user=user, token=token))

View File

@ -17,17 +17,20 @@ class RegistrationForm(FlaskForm):
password = PasswordField('Password', validators=[DataRequired()]) password = PasswordField('Password', validators=[DataRequired()])
password2 = PasswordField('Repeat Password', validators=[DataRequired()]) password2 = PasswordField('Repeat Password', validators=[DataRequired()])
submit = SubmitField('Register') submit = SubmitField('Register')
def validate_username(self, username): def validate_username(self, username):
user = db.session.scalar(sa.select(User).where(User.username == username.data)) user = db.session.scalar(sa.select(User).where(User.username == username.data))
if user is not None: if user is not None:
raise ValidationError('Please use a different username.') raise ValidationError('Please use a different username.')
def validate_email(self, email): def validate_email(self, email):
user = db.session.scalar(sa.select(User).where(User.email == email.data)) user = db.session.scalar(sa.select(User).where(User.email == email.data))
if user is not None: if user is not None:
raise ValidationError('Please use a different email address.') raise ValidationError('Please use a different email address.')
class ResetPasswordForm(FlaskForm):
password = PasswordField('Password', validators=[DataRequired()])
password2 = PasswordField('Repeat Password', validators=[DataRequired()])
submit = SubmitField('Request Reset')
class EditProfileForm(FlaskForm): class EditProfileForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()]) username = StringField('Username', validators=[DataRequired()])
about_me = TextAreaField('About me', validators=[Length(min=0, max=140)]) about_me = TextAreaField('About me', validators=[Length(min=0, max=140)])

View File

@ -1,14 +1,17 @@
import os
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Optional from typing import Optional
import sqlalchemy as sa import sqlalchemy as sa
import sqlalchemy.orm as so import sqlalchemy.orm as so
from werkzeug.security import generate_password_hash, check_password_hash from werkzeug.security import generate_password_hash, check_password_hash
import pydenticon, hashlib, base64 import os, pydenticon, hashlib, base64, jwt
from time import time
from app import db, login from app import db, login, app
from flask_login import UserMixin from flask_login import UserMixin
#debug
import sys
followers = sa.Table( followers = sa.Table(
'followers', 'followers',
db.metadata, db.metadata,
@ -39,15 +42,31 @@ class User(UserMixin, db.Model):
self.password_hash = generate_password_hash(password) self.password_hash = generate_password_hash(password)
def check_password(self, password): def check_password(self, password):
return check_password_hash(self.password_hash, password) return check_password_hash(self.password_hash, password)
def get_reset_password_token(self, expires_in=600):
token = jwt.encode({'reset_password': self.id, 'exp': time() + expires_in},
app.config['SECRET_KEY'], algorithm='HS256')
return token
@staticmethod
def verify_reset_password_token(token):
try:
id = jwt.decode(token,
app.config['SECRET_KEY'], algorithms='HS256')['reset_password']
except:
return
return db.session.get(User, id)
def gen_avatar(self, write_png=True): def gen_avatar(self, write_png=True):
foreground = [ "rgb(45,79,255)", foreground = ['#ACE1AF',
"rgb(254,180,44)", '#ACC4E1',
"rgb(226,121,234)", '#E1ACDE',
"rgb(30,179,253)", '#E1CAAC',
"rgb(232,77,65)", '#AFFF00',
"rgb(49,203,115)", '#00FFCF',
"rgb(141,69,170)" ] '#5000FF',
background = "rgb(22,22,22)" '#FF0030']
background = '#151515'
digest = hashlib.md5(self.email.lower().encode('utf-8')).hexdigest() digest = hashlib.md5(self.email.lower().encode('utf-8')).hexdigest()
basedir = os.path.abspath(os.path.dirname(__file__)) basedir = os.path.abspath(os.path.dirname(__file__))

View File

@ -5,9 +5,12 @@ from datetime import datetime, timezone
import sqlalchemy as sa import sqlalchemy as sa
from app import app, db from app import app, db
from app.forms import LoginForm, RegistrationForm, EditProfileForm, EmptyForm, PostForm, ResetPasswordRequestForm from app.forms import LoginForm, RegistrationForm, EditProfileForm, EmptyForm, PostForm, ResetPasswordRequestForm, ResetPasswordForm
from app.models import User, Post from app.models import User, Post
#from app.email import send_password_reset_email from app.email import send_password_reset_email
#debug:
import sys
@app.before_request @app.before_request
def before_request(): def before_request():
@ -84,6 +87,25 @@ def register():
return redirect(url_for('login')) return redirect(url_for('login'))
return render_template('register.html', title='Register', form=form) return render_template('register.html', title='Register', form=form)
@app.route('/reset_password/<token>', methods=['GET', 'POST'])
def reset_password(token):
if current_user.is_authenticated:
print('rp user is authed', file=sys.stderr)
return redirect(url_for('index'))
user = User.verify_reset_password_token(token)
if not user:
print('rp not user', file=sys.stderr)
return redirect(url_for('index'))
form = ResetPasswordForm()
if form.validate_on_submit():
print('rp validated', file=sys.stderr)
user.set_password(form.password.data)
db.session.commit()
flash('Your password has been reset.')
return redirect(url_for('login'))
return render_template('reset_password.html', form=form)
@app.route('/user/<username>') @app.route('/user/<username>')
@login_required @login_required
def user(username): def user(username):
@ -153,11 +175,14 @@ def unfollow(username):
@app.route('/reset_password_request', methods=['GET', 'POST']) @app.route('/reset_password_request', methods=['GET', 'POST'])
def reset_password_request(): def reset_password_request():
if current_user.is_authenticated: if current_user.is_authenticated:
print('rpr user is authed', file=sys.stderr)
return redirect(url_for('index')) return redirect(url_for('index'))
form = ResetPasswordRequestForm() form = ResetPasswordRequestForm()
if form.validate_on_submit(): if form.validate_on_submit():
print('rpr form validated', file=sys.stderr)
user = db.session.scalar(sa.select(User).where(User.email == form.email.data)) user = db.session.scalar(sa.select(User).where(User.email == form.email.data))
if user: if user:
print('rpr if user', file=sys.stderr)
send_password_reset_email(user) send_password_reset_email(user)
flash('Password reset sent.') flash('Password reset sent.')
return redirect(url_for('login')) return redirect(url_for('login'))

View File

@ -0,0 +1,10 @@
<!doctype html>
<html>
<body>
<p>User {{ user.username }} requested password reset.</p>
<p>Reset link:</p>
<p><a href="{{ hostname }}{{ url_for('reset_password', token=token) }}">click here</a>
<p>If you did not request this, ignore this message.</p>
</body>
</html>

View File

@ -0,0 +1,7 @@
User {{ user.username }} requested password reset.
Reset link follows.
{{ hostname }}{{ url_for('reset_password', token=token) }}
If you did not request this, ignore this message.

View File

@ -0,0 +1,26 @@
{% extends "base.html" %}
{% block content %}
<h1>Reset Your Password</h1>
<form action="" method="post">
{{ form.hidden_tag() }}
<p>
{{ form.password.label }}
{{ form.password(size=32) }}
{% for error in form.password.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>
{{ form.password2.label }}
{{ form.password2(size=32) }}
{% for error in form.password2.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>{{ form.submit() }}</p>
</form>
{% endblock %}

View File

@ -4,9 +4,9 @@ basedir = os.path.abspath(os.path.dirname(__file__))
# Remove or fallbacks for prod # Remove or fallbacks for prod
class Config: class Config:
SECRET_KEY = os.environ.get('FLASK_SECRET_KEY') or 'flasksk' SECRET_KEY = os.environ.get('DOTENV_FLASK_SECRET_KEY')
#SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'zapp.db') #SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'zapp.db')
SQLALCHEMY_DATABASE_URI = 'mariadb+mariadbconnector://flasku:' + os.environ.get('MYSQL_PASSWORD') + '@db:3306/flask' SQLALCHEMY_DATABASE_URI = 'mariadb+mariadbconnector://flasku:' + os.environ.get('DOTENV_MYSQL_PASSWORD') + '@db:3306/flask'
MAIL_SERVER = 'pmb' MAIL_SERVER = 'pmb'
#MAIL_SERVER = '' #MAIL_SERVER = ''
@ -14,8 +14,10 @@ class Config:
MAIL_USE_TLS = False MAIL_USE_TLS = False
MAIL_USERNAME = '' MAIL_USERNAME = ''
MAIL_PASSWORD = '' MAIL_PASSWORD = ''
ADMINS = [os.environ.get('ADMIN_EMAIL')] ADMINS = [os.environ.get('DOTENV_ADMIN_EMAIL')]
FROM_ADDRESS = os.environ.get('FROM_ADDRESS') FROM_ADDRESS = os.environ.get('DOTENV_FROM_ADDRESS')
REAL_HOSTNAME = os.environ.get('DOTENV_REAL_HOSTNAME')
DC_LOGGING = True DC_LOGGING = True
POSTS_PER_PAGE=5 POSTS_PER_PAGE=5

View File

@ -37,11 +37,14 @@ services:
environment: environment:
- MYSQL_USER=flasku - MYSQL_USER=flasku
#- MYSQL_PASSWORD=flaskp #- MYSQL_PASSWORD=flaskp
- MYSQL_PASSWORD=${DOTENV_MYSQL_FLASK_PASSWORD} - DOTENV_MYSQL_PASSWORD=${DOTENV_MYSQL_FLASK_PASSWORD}
- TOKEN_I=${DOTENV_TOKEN_I} - DOTENV_FLASK_SECRET_KEY=${FLASK_SECRET_KEY}
- TOKEN_C=${DOTENV_TOKEN_C} - DOTENV_TOKEN_I=${FLASK_TOKEN_I}
- ADMIN_EMAIL=${ADMIN_EMAIL} - DOTENV_TOKEN_C=${FLASK_TOKEN_C}
- FROM_ADDRESS=${GITEA_MAIL_FROM} - DOTENV_ADMIN_EMAIL=${FLASK_ADMIN_EMAIL}
- DOTENV_FROM_ADDRESS=${FLASK_MAIL_FROM}
- DOTENV_JWT_PHRASE=${FLASK_JWT_PHRASE}
- DOTENV_REAL_HOSTNAME=${FLASK_REAL_HOSTNAME}
#ports: #ports:
# - 8000:8000 # - 8000:8000
expose: expose: