diff --git a/app/__init__.py b/app/__init__.py
index f589d4f9..f057191c 100644
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -16,7 +16,6 @@
from flask import *
-from flask_user import *
from flask_gravatar import Gravatar
import flask_menu as menu
from flask_mail import Mail
@@ -24,6 +23,7 @@ from flask_github import GitHub
from flask_wtf.csrf import CSRFProtect
from flask_flatpages import FlatPages
from flask_babel import Babel
+from flask_login import logout_user, current_user
import os, redis
app = Flask(__name__, static_folder="public/static")
@@ -64,13 +64,10 @@ init_app(app)
# def get_locale():
# return request.accept_languages.best_match(app.config['LANGUAGES'].keys())
-from . import models, tasks, template_filters
-
+from . import models, tasks, template_filters, usermgr
from .blueprints import create_blueprints
create_blueprints(app)
-from flask_login import logout_user
-
@app.route("/uploads/")
def send_upload(path):
return send_from_directory(app.config['UPLOAD_DIR'], path)
@@ -88,7 +85,7 @@ def check_for_ban():
if current_user.rank == models.UserRank.BANNED:
flash("You have been banned.", "danger")
logout_user()
- return redirect(url_for('user.login'))
+ return redirect(url_for('users.login'))
elif current_user.rank == models.UserRank.NOT_JOINED:
current_user.rank = models.UserRank.MEMBER
models.db.session.commit()
diff --git a/app/blueprints/admin/admin.py b/app/blueprints/admin/admin.py
index 7280ab97..f1104a83 100644
--- a/app/blueprints/admin/admin.py
+++ b/app/blueprints/admin/admin.py
@@ -19,7 +19,7 @@ import os
from celery import group
from flask import *
-from flask_user import *
+from flask_login import current_user
from flask_wtf import FlaskForm
from wtforms import *
diff --git a/app/blueprints/admin/tagseditor.py b/app/blueprints/admin/tagseditor.py
index 2919d4dc..0aa6776e 100644
--- a/app/blueprints/admin/tagseditor.py
+++ b/app/blueprints/admin/tagseditor.py
@@ -16,7 +16,7 @@
from flask import *
-from flask_user import *
+from flask_login import current_user, login_required
from flask_wtf import FlaskForm
from wtforms import *
from wtforms.validators import *
diff --git a/app/blueprints/api/endpoints.py b/app/blueprints/api/endpoints.py
index 3145954d..8afc2af3 100644
--- a/app/blueprints/api/endpoints.py
+++ b/app/blueprints/api/endpoints.py
@@ -16,7 +16,7 @@
from flask import *
-from flask_user import *
+from flask_login import current_user, login_required
from . import bp
from .auth import is_api_authd
from .support import error, handleCreateRelease
diff --git a/app/blueprints/api/tokens.py b/app/blueprints/api/tokens.py
index 2150efc4..ef145eaa 100644
--- a/app/blueprints/api/tokens.py
+++ b/app/blueprints/api/tokens.py
@@ -16,7 +16,7 @@
from flask import render_template, redirect, request, session, url_for, abort
-from flask_user import login_required, current_user
+from flask_login import login_required, current_user
from flask_wtf import FlaskForm
from wtforms import *
from wtforms.ext.sqlalchemy.fields import QuerySelectField
diff --git a/app/blueprints/github/__init__.py b/app/blueprints/github/__init__.py
index f4b764f8..fec824ea 100644
--- a/app/blueprints/github/__init__.py
+++ b/app/blueprints/github/__init__.py
@@ -19,7 +19,7 @@ from flask import Blueprint
bp = Blueprint("github", __name__)
from flask import redirect, url_for, request, flash, abort, render_template, jsonify, current_app
-from flask_user import current_user, login_required
+from flask_login import current_user, login_required
from sqlalchemy import func, or_, and_
from app import github, csrf
from app.models import db, User, APIToken, Package, Permission
@@ -46,7 +46,7 @@ def callback(oauth_token):
next_url = request.args.get("next")
if oauth_token is None:
flash("Authorization failed [err=gh-oauth-login-failed]", "danger")
- return redirect(url_for("user.login"))
+ return redirect(url_for("users.login"))
# Get Github username
url = "https://api.github.com/user"
@@ -79,7 +79,7 @@ def callback(oauth_token):
return redirect(next_url or url_for("homepage.home"))
else:
flash("Authorization failed [err=gh-login-failed]", "danger")
- return redirect(url_for("user.login"))
+ return redirect(url_for("users.login"))
@bp.route("/github/webhook/", methods=["POST"])
diff --git a/app/blueprints/metapackages/__init__.py b/app/blueprints/metapackages/__init__.py
index abaf9436..8882b874 100644
--- a/app/blueprints/metapackages/__init__.py
+++ b/app/blueprints/metapackages/__init__.py
@@ -16,10 +16,11 @@
from flask import *
+from sqlalchemy import func
+from app.models import MetaPackage, Package, db, Dependency, PackageState, ForumTopic
bp = Blueprint("metapackages", __name__)
-from app.models import *
@bp.route("/metapackages/")
def list_all():
@@ -29,6 +30,7 @@ def list_all():
.group_by(MetaPackage.id).all()
return render_template("metapackages/list.html", mpackages=mpackages)
+
@bp.route("/metapackages//")
def view(name):
mpackage = MetaPackage.query.filter_by(name=name).first()
diff --git a/app/blueprints/notifications/__init__.py b/app/blueprints/notifications/__init__.py
index ba3e1c68..6e8cce4b 100644
--- a/app/blueprints/notifications/__init__.py
+++ b/app/blueprints/notifications/__init__.py
@@ -16,7 +16,7 @@
from flask import Blueprint, render_template, redirect, url_for
-from flask_user import current_user, login_required
+from flask_login import current_user, login_required
from app.models import db, Notification
bp = Blueprint("notifications", __name__)
diff --git a/app/blueprints/packages/packages.py b/app/blueprints/packages/packages.py
index 0f39b553..44a6512b 100644
--- a/app/blueprints/packages/packages.py
+++ b/app/blueprints/packages/packages.py
@@ -21,6 +21,7 @@ import flask_menu as menu
from celery import uuid
from flask import render_template
from flask_wtf import FlaskForm
+from flask_login import login_required
from sqlalchemy import or_, func
from sqlalchemy.orm import joinedload, subqueryload
from wtforms import *
diff --git a/app/blueprints/packages/releases.py b/app/blueprints/packages/releases.py
index 2c667327..68635d14 100644
--- a/app/blueprints/packages/releases.py
+++ b/app/blueprints/packages/releases.py
@@ -18,6 +18,7 @@
from celery import uuid
from flask import *
from flask_wtf import FlaskForm
+from flask_login import login_required
from wtforms import *
from wtforms.ext.sqlalchemy.fields import QuerySelectField
from wtforms.validators import *
diff --git a/app/blueprints/packages/reviews.py b/app/blueprints/packages/reviews.py
index a52d6175..9fe8bc0b 100644
--- a/app/blueprints/packages/reviews.py
+++ b/app/blueprints/packages/reviews.py
@@ -17,7 +17,7 @@
from . import bp
from flask import *
-from flask_user import *
+from flask_login import current_user, login_required
from flask_wtf import FlaskForm
from wtforms import *
from wtforms.validators import *
diff --git a/app/blueprints/packages/screenshots.py b/app/blueprints/packages/screenshots.py
index b9ceb811..c177307e 100644
--- a/app/blueprints/packages/screenshots.py
+++ b/app/blueprints/packages/screenshots.py
@@ -17,6 +17,7 @@
from flask import *
from flask_wtf import FlaskForm
+from flask_login import login_required
from wtforms import *
from wtforms.validators import *
diff --git a/app/blueprints/tasks/__init__.py b/app/blueprints/tasks/__init__.py
index 838718b4..c1cdfff6 100644
--- a/app/blueprints/tasks/__init__.py
+++ b/app/blueprints/tasks/__init__.py
@@ -16,6 +16,7 @@
from flask import *
+from flask_login import login_required
from app import csrf
from app.tasks import celery
diff --git a/app/blueprints/threads/__init__.py b/app/blueprints/threads/__init__.py
index a0819382..bf387180 100644
--- a/app/blueprints/threads/__init__.py
+++ b/app/blueprints/threads/__init__.py
@@ -19,7 +19,7 @@ from flask import *
bp = Blueprint("threads", __name__)
-from flask_user import *
+from flask_login import current_user, login_required
from app.models import *
from app.utils import addNotification, isYes, addAuditLog
diff --git a/app/blueprints/todo/__init__.py b/app/blueprints/todo/__init__.py
index 9051e358..230cf26a 100644
--- a/app/blueprints/todo/__init__.py
+++ b/app/blueprints/todo/__init__.py
@@ -15,7 +15,7 @@
# along with this program. If not, see .
from flask import *
-from flask_user import *
+from flask_login import current_user, login_required
from sqlalchemy import or_
from app.models import *
diff --git a/app/blueprints/users/__init__.py b/app/blueprints/users/__init__.py
index 2e2f965a..3a2814ee 100644
--- a/app/blueprints/users/__init__.py
+++ b/app/blueprints/users/__init__.py
@@ -2,4 +2,4 @@ from flask import Blueprint
bp = Blueprint("users", __name__)
-from . import profile, claim
+from . import profile, claim, account
diff --git a/app/blueprints/users/account.py b/app/blueprints/users/account.py
new file mode 100644
index 00000000..10afbfed
--- /dev/null
+++ b/app/blueprints/users/account.py
@@ -0,0 +1,167 @@
+# ContentDB
+# Copyright (C) 2020 rubenwardy
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+
+from flask import *
+from flask_login import current_user, login_required, logout_user, login_user
+from flask_wtf import FlaskForm
+from sqlalchemy import or_
+from wtforms import *
+from wtforms.validators import *
+
+from app.models import *
+from app.tasks.emails import sendVerifyEmail
+from app.utils import randomString, make_flask_login_password, is_safe_url, check_password_hash
+from . import bp
+
+
+class LoginForm(FlaskForm):
+ username = StringField("Username or email", [InputRequired()])
+ password = PasswordField("Password", [InputRequired(), Length(6, 100)])
+ remember_me = BooleanField("Remember me")
+ submit = SubmitField("Login")
+
+
+@bp.route("/user/login/", methods=["GET", "POST"])
+def login():
+ form = LoginForm(request.form)
+ if form.validate_on_submit():
+ username = form.username.data.strip()
+ user = User.query.filter(or_(User.username==username, User.email==username)).first()
+ if user is None:
+ err = "User {} does not exist".format(username)
+
+ elif not check_password_hash(user.password, form.password.data):
+ err = "Incorrect password. Did you set one?"
+
+ else:
+ login_user(user)
+ flash("Logged in successfully.")
+
+ next = request.args.get("r")
+ if next and not is_safe_url(next):
+ abort(400)
+
+ return redirect(next or url_for("homepage.home"))
+
+ if err:
+ # The existence of a username is public, but emails are not
+ if "@" in username:
+ flash("Incorrect email or password", "danger")
+ else:
+ flash(err, "error")
+
+
+ return render_template("users/login.html", form=form)
+
+
+@bp.route("/user/logout/", methods=["GET", "POST"])
+def logout():
+ logout_user()
+ return redirect(url_for("homepage.home"))
+
+
+class RegisterForm(FlaskForm):
+ username = StringField("Username", [InputRequired()])
+ email = StringField("Email", [InputRequired(), Email()])
+ password = PasswordField("Password", [InputRequired(), Length(6, 100)])
+ submit = SubmitField("Register")
+
+
+@bp.route("/user/register/", methods=["GET", "POST"])
+def register():
+ form = RegisterForm(request.form)
+ return render_template("users/register.html", form=form)
+
+
+@bp.route("/user/forgot-password/", methods=["GET", "POST"])
+def forgot_password():
+ return "Forgot password page"
+
+
+class SetPasswordForm(FlaskForm):
+ email = StringField("Email", [Optional(), Email()])
+ password = PasswordField("New password", [InputRequired(), Length(8, 100)])
+ password2 = PasswordField("Verify password", [InputRequired(), Length(8, 100)])
+ submit = SubmitField("Save")
+
+
+@bp.route("/user/change-password/", methods=["GET", "POST"])
+@login_required
+def change_password():
+ return "change"
+
+
+@bp.route("/user/set-password/", methods=["GET", "POST"])
+@login_required
+def set_password():
+ if current_user.hasPassword():
+ return redirect(url_for("users.change_password"))
+
+ form = SetPasswordForm(request.form)
+ if current_user.email is None:
+ form.email.validators = [InputRequired(), Email()]
+
+ if request.method == "POST" and form.validate():
+ one = form.password.data
+ two = form.password2.data
+ if one == two:
+ # Hash password
+ hashed_password = make_flask_login_password(form.password.data)
+
+ # Change password
+ current_user.password = hashed_password
+ db.session.commit()
+
+ # Prepare one-time system message
+ flash('Your password has been changed successfully.', 'success')
+
+ newEmail = form["email"].data
+ if newEmail != current_user.email and newEmail.strip() != "":
+ token = randomString(32)
+
+ ver = UserEmailVerification()
+ ver.user = current_user
+ ver.token = token
+ ver.email = newEmail
+ db.session.add(ver)
+ db.session.commit()
+
+ task = sendVerifyEmail.delay(newEmail, token)
+ return redirect(url_for("tasks.check", id=task.id, r=url_for("users.profile", username=current_user.username)))
+ else:
+ return redirect(url_for("users.login"))
+ else:
+ flash("Passwords do not match", "danger")
+
+ return render_template("users/set_password.html", form=form, optional=request.args.get("optional"))
+
+
+@bp.route("/users/verify/")
+def verify_email():
+ token = request.args.get("token")
+ ver = UserEmailVerification.query.filter_by(token=token).first()
+ if ver is None:
+ flash("Unknown verification token!", "danger")
+ else:
+ ver.user.email = ver.email
+ db.session.delete(ver)
+ db.session.commit()
+
+ if current_user.is_authenticated:
+ return redirect(url_for("users.profile", username=current_user.username))
+ else:
+ return redirect(url_for("homepage.home"))
diff --git a/app/blueprints/users/profile.py b/app/blueprints/users/profile.py
index 9d3275ed..df4100c3 100644
--- a/app/blueprints/users/profile.py
+++ b/app/blueprints/users/profile.py
@@ -16,7 +16,7 @@
from flask import *
-from flask_user import signals, current_user, user_manager, login_required
+from flask_login import current_user, login_required
from flask_wtf import FlaskForm
from sqlalchemy import func
from wtforms import *
@@ -26,7 +26,7 @@ from app.markdown import render_markdown
from app.models import *
from app.tasks.emails import sendVerifyEmail, sendEmailRaw
from app.tasks.forumtasks import checkForumAccount
-from app.utils import randomString, rank_required, nonEmptyOrNone, addAuditLog
+from app.utils import randomString, rank_required, nonEmptyOrNone, addAuditLog, make_flask_login_password
from . import bp
@@ -182,79 +182,3 @@ def send_email(username):
return redirect(url_for("tasks.check", id=task.id, r=next_url))
return render_template("users/send_email.html", form=form)
-
-
-
-class SetPasswordForm(FlaskForm):
- email = StringField("Email", [Optional(), Email()])
- password = PasswordField("New password", [InputRequired(), Length(2, 100)])
- password2 = PasswordField("Verify password", [InputRequired(), Length(2, 100)])
- submit = SubmitField("Save")
-
-@bp.route("/user/set-password/", methods=["GET", "POST"])
-@login_required
-def set_password():
- if current_user.hasPassword():
- return redirect(url_for("user.change_password"))
-
- form = SetPasswordForm(request.form)
- if current_user.email is None:
- form.email.validators = [InputRequired(), Email()]
-
- if request.method == "POST" and form.validate():
- one = form.password.data
- two = form.password2.data
- if one == two:
- # Hash password
- hashed_password = user_manager.hash_password(form.password.data)
-
- # Change password
- current_user.password = hashed_password
- db.session.commit()
-
- # Send 'password_changed' email
- if user_manager.USER_ENABLE_EMAIL and current_user.email:
- user_manager.email_manager.send_password_changed_email(current_user)
-
- # Send password_changed signal
- signals.user_changed_password.send(current_app._get_current_object(), user=current_user)
-
- # Prepare one-time system message
- flash('Your password has been changed successfully.', 'success')
-
- newEmail = form["email"].data
- if newEmail != current_user.email and newEmail.strip() != "":
- token = randomString(32)
-
- ver = UserEmailVerification()
- ver.user = current_user
- ver.token = token
- ver.email = newEmail
- db.session.add(ver)
- db.session.commit()
-
- task = sendVerifyEmail.delay(newEmail, token)
- return redirect(url_for("tasks.check", id=task.id, r=url_for("users.profile", username=current_user.username)))
- else:
- return redirect(url_for("user.login"))
- else:
- flash("Passwords do not match", "danger")
-
- return render_template("users/set_password.html", form=form, optional=request.args.get("optional"))
-
-
-@bp.route("/users/verify/")
-def verify_email():
- token = request.args.get("token")
- ver = UserEmailVerification.query.filter_by(token=token).first()
- if ver is None:
- flash("Unknown verification token!", "danger")
- else:
- ver.user.email = ver.email
- db.session.delete(ver)
- db.session.commit()
-
- if current_user.is_authenticated:
- return redirect(url_for("users.profile", username=current_user.username))
- else:
- return redirect(url_for("homepage.home"))
diff --git a/app/default_data.py b/app/default_data.py
index 2b7a2562..508b2a77 100644
--- a/app/default_data.py
+++ b/app/default_data.py
@@ -1,11 +1,11 @@
from .models import *
-from .utils import make_flask_user_password
+from .utils import make_flask_login_password
def populate(session):
admin_user = User("rubenwardy")
- admin_user.active = True
- admin_user.password = make_flask_user_password("tuckfrump")
+ admin_user.is_active = True
+ admin_user.password = make_flask_login_password("tuckfrump")
admin_user.github_username = "rubenwardy"
admin_user.forums_username = "rubenwardy"
admin_user.rank = UserRank.ADMIN
diff --git a/app/models.py b/app/models.py
index cb699cd1..cd49588b 100644
--- a/app/models.py
+++ b/app/models.py
@@ -22,11 +22,11 @@ from urllib.parse import urlparse
from flask import url_for
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy, BaseQuery
-from flask_user import UserManager, UserMixin
from sqlalchemy_searchable import SearchQueryMixin, make_searchable
from sqlalchemy_utils.types import TSVectorType
-from app import app, gravatar
+from .usermgr import UserMixin, login_manager
+from . import app, gravatar
# Initialise database
db = SQLAlchemy(app)
@@ -138,6 +138,9 @@ class User(db.Model, UserMixin):
password = db.Column(db.String(255), nullable=False, server_default="")
reset_password_token = db.Column(db.String(100), nullable=False, server_default="")
+ def get_id(self):
+ return self.username
+
rank = db.Column(db.Enum(UserRank))
# Account linking
@@ -153,7 +156,7 @@ class User(db.Model, UserMixin):
# User information
profile_pic = db.Column(db.String(255), nullable=True, server_default=None)
- active = db.Column("is_active", db.Boolean, nullable=False, server_default="0")
+ is_active = db.Column("is_active", db.Boolean, nullable=False, server_default="0")
display_name = db.Column(db.String(100), nullable=False, default=display_name_default)
# Links
@@ -174,7 +177,7 @@ class User(db.Model, UserMixin):
self.username = username
self.email_confirmed_at = datetime.datetime.now() - datetime.timedelta(days=6000)
self.display_name = username
- self.active = active
+ self.is_active = active
self.email = email
self.password = password
self.rank = UserRank.NOT_JOINED
@@ -718,7 +721,7 @@ class Package(db.Model):
def getSetStateURL(self, state):
if type(state) == str:
- state = PackageState[perm]
+ state = PackageState[state]
elif type(state) != PackageState:
raise Exception("Unknown state given to Package.canMoveToState()")
@@ -1474,10 +1477,11 @@ class ForumTopic(db.Model):
raise Exception("Permission {} is not related to topics".format(perm.name))
-# Setup Flask-User
-user_manager = UserManager(app, db, User)
-
if app.config.get("LOG_SQL"):
import logging
logging.basicConfig()
logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
+
+@login_manager.user_loader
+def load_user(user_id):
+ return User.query.filter_by(username=user_id).first()
diff --git a/app/template_filters.py b/app/template_filters.py
index 93b72038..230daa22 100644
--- a/app/template_filters.py
+++ b/app/template_filters.py
@@ -1,7 +1,7 @@
from . import app
from .models import Permission, Package, PackageState, PackageRelease
from .utils import abs_url_for, url_set_query
-from flask_user import current_user
+from flask_login import current_user
from flask_babel import format_timedelta
from urllib.parse import urlparse
diff --git a/app/templates/base.html b/app/templates/base.html
index fabe2f61..970b489b 100644
--- a/app/templates/base.html
+++ b/app/templates/base.html
@@ -131,11 +131,11 @@
{{ _("License Editor") }}
{% endif %}
{% endif %}
- {{ _("Sign out") }}
+ {{ _("Sign out") }}
{% else %}
- {{ _("Sign in") }}
+ {{ _("Sign in") }}
{% endif %}
diff --git a/app/templates/users/claim.html b/app/templates/users/claim.html
index 75b6cdce..b1815c35 100644
--- a/app/templates/users/claim.html
+++ b/app/templates/users/claim.html
@@ -126,7 +126,7 @@ Creating an Account
options.
- Register
+ Register
diff --git a/app/templates/flask_user/login.html b/app/templates/users/login.html
similarity index 56%
rename from app/templates/flask_user/login.html
rename to app/templates/users/login.html
index 87f54286..96a7651e 100644
--- a/app/templates/flask_user/login.html
+++ b/app/templates/users/login.html
@@ -8,33 +8,21 @@ Sign in
- {% from "flask_user/_macros.html" import render_field, render_checkbox_field, render_submit_field %}
+ {% from "macros/forms.html" import render_field, render_checkbox_field, render_submit_field %}
- {% from "flask_user/_macros.html" import render_field, render_checkbox_field, render_submit_field %}
GitHub
@@ -67,7 +52,6 @@ Sign in