Switch from Authlib to pyJWT

This commit is contained in:
Patrick Jentsch 2022-07-18 17:10:09 +02:00
parent 12bba0564f
commit 1de9fce854
3 changed files with 74 additions and 48 deletions

View File

@ -91,7 +91,7 @@ def register():
db.session.rollback() db.session.rollback()
flash('Internal Server Error', category='error') flash('Internal Server Error', category='error')
abort(500) abort(500)
token = user.generate_confirmation_token() token = user.generate_confirm_user_token()
msg = create_message( msg = create_message(
user.email, user.email,
'Confirm Your Account', 'Confirm Your Account',
@ -115,7 +115,7 @@ def register():
def confirm(token): def confirm(token):
if current_user.confirmed: if current_user.confirmed:
return redirect(url_for('main.dashboard')) return redirect(url_for('main.dashboard'))
if current_user.confirm(token): if current_user.confirm_user(token):
db.session.commit() db.session.commit()
flash('You have confirmed your account') flash('You have confirmed your account')
return redirect(url_for('main.dashboard')) return redirect(url_for('main.dashboard'))
@ -139,7 +139,7 @@ def unconfirmed():
@bp.route('/confirm') @bp.route('/confirm')
@login_required @login_required
def resend_confirmation(): def resend_confirmation():
token = current_user.generate_confirmation_token() token = current_user.generate_confirm_user_token()
msg = create_message( msg = create_message(
current_user.email, current_user.email,
'Confirm Your Account', 'Confirm Your Account',
@ -160,7 +160,7 @@ def reset_password_request():
if form.validate_on_submit(): if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data.lower()).first() user = User.query.filter_by(email=form.email.data.lower()).first()
if user is not None: if user is not None:
token = user.generate_reset_token() token = user.generate_password_reset_token()
msg = create_message( msg = create_message(
user.email, user.email,
'Reset Your Password', 'Reset Your Password',

View File

@ -1,7 +1,6 @@
from app import db, hashids, login, mail, socketio from app import db, login, mail, socketio
from app.converters.vrt import normalize_vrt_file from app.converters.vrt import normalize_vrt_file
from app.email import create_message from app.email import create_message
from authlib.jose import jwt, JoseError
from datetime import datetime, timedelta from datetime import datetime, timedelta
from enum import Enum, IntEnum from enum import Enum, IntEnum
from flask import current_app, url_for from flask import current_app, url_for
@ -12,10 +11,10 @@ from tqdm import tqdm
from werkzeug.security import generate_password_hash, check_password_hash from werkzeug.security import generate_password_hash, check_password_hash
import base64 import base64
import json import json
import jwt
import os import os
import requests import requests
import shutil import shutil
import time
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
import yaml import yaml
@ -213,6 +212,29 @@ class Role(HashidMixin, db.Model):
db.session.commit() db.session.commit()
class Token(db.Model):
__tablename__ = 'tokens'
# Primary key
id = db.Column(db.Integer, primary_key=True)
# Foreign keys
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
# Fields
access_token = db.Column(db.String(64), nullable=False, index=True)
access_expiration = db.Column(db.DateTime, nullable=False)
refresh_token = db.Column(db.String(64), nullable=False, index=True)
refresh_expiration = db.Column(db.DateTime, nullable=False)
# def generate(self):
# header = {'alg': 'HS256', 'exp': int(time.time()) + expiration}
# payload = {'confirm': self.hashid}
# return jwt.encode(header, payload, current_app.config['SECRET_KEY'])
# self.access_token = secrets.token_urlsafe()
# self.access_expiration = datetime.utcnow() + \
# timedelta(minutes=current_app.config['ACCESS_TOKEN_MINUTES'])
# self.refresh_token = secrets.token_urlsafe()
# self.refresh_expiration = datetime.utcnow() + \
# timedelta(days=current_app.config['REFRESH_TOKEN_DAYS'])
class User(HashidMixin, UserMixin, db.Model): class User(HashidMixin, UserMixin, db.Model):
__tablename__ = 'users' __tablename__ = 'users'
# Primary key # Primary key
@ -292,17 +314,20 @@ class User(HashidMixin, UserMixin, db.Model):
def can(self, permission): def can(self, permission):
return self.role.has_permission(permission) return self.role.has_permission(permission)
def confirm(self, token): def confirm_user(self, token):
# s = TimedJSONWebSignatureSerializer(current_app.config['SECRET_KEY'])
# try:
# data = s.loads(token.encode('utf-8'))
# except BadSignature:
# return False
try: try:
data = jwt.decode(token, current_app.config['SECRET_KEY']) payload = jwt.decode(
except JoseError: token,
current_app.config['SECRET_KEY'],
algorithms=['HS256'],
issuer=current_app.config['SERVER_NAME'],
options={'require': ['exp', 'iat', 'iss', 'purpose', 'sub']}
)
except jwt.PyJWTError:
return False return False
if data.get('confirm') != self.hashid: if payload.get('purpose') != 'confirm_user':
return False
if payload.get('sub') != self.id:
return False return False
self.confirmed = True self.confirmed = True
db.session.add(self) db.session.add(self)
@ -312,22 +337,27 @@ class User(HashidMixin, UserMixin, db.Model):
shutil.rmtree(self.path, ignore_errors=True) shutil.rmtree(self.path, ignore_errors=True)
db.session.delete(self) db.session.delete(self)
def generate_confirmation_token(self, expiration=3600): def generate_confirm_user_token(self, expiration=3600):
# s = TimedJSONWebSignatureSerializer( utc_now = datetime.utcnow()
# current_app.config['SECRET_KEY'], expiration) payload = {
# return s.dumps({'confirm': self.hashid}).decode('utf-8') 'exp': utc_now + timedelta(seconds=expiration),
header = {'alg': 'HS256', 'exp': int(time.time()) + expiration} 'iat': utc_now,
payload = {'confirm': self.hashid} 'iss': current_app.config['SERVER_NAME'],
return jwt.encode(header, payload, current_app.config['SECRET_KEY']) 'purpose': 'confirm_user',
'sub': self.id
}
return jwt.encode(payload, current_app.config['SECRET_KEY'], algorithm='HS256')
def generate_password_reset_token(self, expiration=3600):
def generate_reset_token(self, expiration=3600): utc_now = datetime.utcnow()
# s = TimedJSONWebSignatureSerializer( payload = {
# current_app.config['SECRET_KEY'], expiration) 'exp': utc_now + timedelta(seconds=expiration),
# return s.dumps({'reset': self.hashid}).decode('utf-8') 'iat': utc_now,
header = {'alg': 'HS256', 'exp': int(time.time()) + expiration} 'iss': current_app.config['SERVER_NAME'],
payload = {'reset': self.hashid} 'purpose': 'reset_password',
return jwt.encode(header, payload, current_app.config['SECRET_KEY']) 'sub': self.id
}
return jwt.encode(payload, current_app.config['SECRET_KEY'], algorithm='HS256')
def get_token(self, expires_in=3600): def get_token(self, expires_in=3600):
now = datetime.utcnow() now = datetime.utcnow()
@ -410,25 +440,21 @@ class User(HashidMixin, UserMixin, db.Model):
@staticmethod @staticmethod
def reset_password(token, new_password): def reset_password(token, new_password):
# s = TimedJSONWebSignatureSerializer(current_app.config['SECRET_KEY'])
# try:
# data = s.loads(token.encode('utf-8'))
# except BadSignature:
# return False
# user = User.query.get(data.get('reset'))
# if user is None:
# return False
# user.password = new_password
# db.session.add(user)
# return True
try: try:
data = jwt.decode(token, current_app.config['SECRET_KEY']) payload = jwt.decode(
except JoseError: token,
current_app.config['SECRET_KEY'],
algorithms=['HS256'],
issuer=current_app.config['SERVER_NAME'],
options={'require': ['exp', 'iat', 'iss', 'purpose', 'sub']}
)
except jwt.PyJWTError:
return False return False
user_hashid = data.get('reset') if payload.get('purpose') != 'reset_password':
if user_hashid is None: return False
user_id = payload.get('sub')
if user_id is None:
return False return False
user_id = hashids.decode(user_hashid)
user = User.query.get(user_id) user = User.query.get(user_id)
if user is None: if user is None:
return False return False

View File

@ -1,4 +1,3 @@
Authlib
cqi cqi
docker docker
eventlet eventlet
@ -18,6 +17,7 @@ Flask-WTF
hiredis hiredis
MarkupSafe==2.0.1 MarkupSafe==2.0.1
psycopg2 psycopg2
PyJWT
pyScss pyScss
python-dotenv python-dotenv
pyyaml pyyaml