From ffad1c4b6eb8ed8d9089faa4fcc477f076f98ad0 Mon Sep 17 00:00:00 2001 From: rimu <3310831+rimu@users.noreply.github.com> Date: Wed, 28 Feb 2024 05:04:07 +1300 Subject: [PATCH] admin add local user page --- app/admin/forms.py | 62 +++++++++++++++++++++++++++ app/admin/routes.py | 69 ++++++++++++++++++++++++++++++- app/templates/admin/add_user.html | 46 +++++++++++++++++++++ app/templates/admin/users.html | 3 +- 4 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 app/templates/admin/add_user.html diff --git a/app/admin/forms.py b/app/admin/forms.py index 68066e44..004525bf 100644 --- a/app/admin/forms.py +++ b/app/admin/forms.py @@ -1,10 +1,13 @@ from flask_wtf import FlaskForm from flask_wtf.file import FileRequired, FileAllowed +from sqlalchemy import func from wtforms import StringField, PasswordField, SubmitField, HiddenField, BooleanField, TextAreaField, SelectField, \ FileField, IntegerField from wtforms.validators import ValidationError, DataRequired, Email, EqualTo, Length, Optional from flask_babel import _, lazy_gettext as _l +from app.models import Community, User + class SiteProfileForm(FlaskForm): name = StringField(_l('Name')) @@ -96,6 +99,65 @@ class EditTopicForm(FlaskForm): submit = SubmitField(_l('Save')) +class AddUserForm(FlaskForm): + user_name = StringField(_l('User name'), validators=[DataRequired()], + render_kw={'autofocus': True, 'autocomplete': 'off'}) + email = StringField(_l('Email address'), validators=[Optional(), Length(max=255)]) + password = PasswordField(_l('Password'), validators=[DataRequired(), Length(min=8, max=50)], + render_kw={'autocomplete': 'new-password'}) + password2 = PasswordField(_l('Repeat password'), validators=[DataRequired(), EqualTo('password')]) + about = TextAreaField(_l('Bio'), validators=[Optional(), Length(min=3, max=5000)]) + matrix_user_id = StringField(_l('Matrix User ID'), validators=[Optional(), Length(max=255)]) + profile_file = FileField(_l('Avatar image')) + banner_file = FileField(_l('Top banner image')) + bot = BooleanField(_l('This profile is a bot')) + verified = BooleanField(_l('Email address is verified')) + banned = BooleanField(_l('Banned')) + newsletter = BooleanField(_l('Subscribe to email newsletter')) + ignore_bots = BooleanField(_l('Hide posts by bots')) + nsfw = BooleanField(_l('Show NSFW posts')) + nsfl = BooleanField(_l('Show NSFL posts')) + submit = SubmitField(_l('Save')) + + def validate_email(self, email): + user = User.query.filter(func.lower(User.email) == func.lower(email.data.strip())).first() + if user is not None: + raise ValidationError(_l('An account with this email address already exists.')) + + def validate_user_name(self, user_name): + if '@' in user_name.data: + raise ValidationError(_l('User names cannot contain @.')) + user = User.query.filter(func.lower(User.user_name) == func.lower(user_name.data.strip())).filter_by(ap_id=None).first() + if user is not None: + if user.deleted: + raise ValidationError(_l('This username was used in the past and cannot be reused.')) + else: + raise ValidationError(_l('An account with this user name already exists.')) + community = Community.query.filter(func.lower(Community.name) == func.lower(user_name.data.strip())).first() + if community is not None: + raise ValidationError(_l('A community with this name exists so it cannot be used for a user.')) + + def validate_password(self, password): + if not password.data: + return + password.data = password.data.strip() + if password.data == 'password' or password.data == '12345678' or password.data == '1234567890': + raise ValidationError(_l('This password is too common.')) + + first_char = password.data[0] # the first character in the string + + all_the_same = True + # Compare all characters to the first character + for char in password.data: + if char != first_char: + all_the_same = False + if all_the_same: + raise ValidationError(_l('This password is not secure.')) + + if password.data == 'password' or password.data == '12345678' or password.data == '1234567890': + raise ValidationError(_l('This password is too common.')) + + class EditUserForm(FlaskForm): about = TextAreaField(_l('Bio'), validators=[Optional(), Length(min=3, max=5000)]) email = StringField(_l('Email address'), validators=[Optional(), Length(max=255)]) diff --git a/app/admin/routes.py b/app/admin/routes.py index e2e148b8..77f22043 100644 --- a/app/admin/routes.py +++ b/app/admin/routes.py @@ -11,7 +11,7 @@ from app.activitypub.routes import process_inbox_request, process_delete_request from app.activitypub.signature import post_request from app.activitypub.util import default_context from app.admin.forms import FederationForm, SiteMiscForm, SiteProfileForm, EditCommunityForm, EditUserForm, \ - EditTopicForm, SendNewsletterForm + EditTopicForm, SendNewsletterForm, AddUserForm from app.admin.util import unsubscribe_from_everything_then_delete, unsubscribe_from_community, send_newsletter from app.community.util import save_icon_file, save_banner_file from app.models import AllowedInstances, BannedInstances, ActivityPubLog, utcnow, Site, Community, CommunityMember, \ @@ -581,6 +581,73 @@ def admin_user_edit(user_id): ) +@bp.route('/users/add', methods=['GET', 'POST']) +@login_required +@permission_required('administer all users') +def admin_users_add(): + form = AddUserForm() + user = User() + if form.validate_on_submit(): + user.user_name = form.user_name.data + user.set_password(form.password.data) + user.about = form.about.data + user.email = form.email.data + user.about_html = markdown_to_html(form.about.data) + user.matrix_user_id = form.matrix_user_id.data + user.bot = form.bot.data + profile_file = request.files['profile_file'] + if profile_file and profile_file.filename != '': + # remove old avatar + if user.avatar_id: + file = File.query.get(user.avatar_id) + file.delete_from_disk() + user.avatar_id = None + db.session.delete(file) + + # add new avatar + file = save_icon_file(profile_file, 'users') + if file: + user.avatar = file + banner_file = request.files['banner_file'] + if banner_file and banner_file.filename != '': + # remove old cover + if user.cover_id: + file = File.query.get(user.cover_id) + file.delete_from_disk() + user.cover_id = None + db.session.delete(file) + + # add new cover + file = save_banner_file(banner_file, 'users') + if file: + user.cover = file + user.newsletter = form.newsletter.data + user.ignore_bots = form.ignore_bots.data + user.show_nsfw = form.nsfw.data + user.show_nsfl = form.nsfl.data + + from app.activitypub.signature import RsaKeys + user.verified = True + user.last_seen = utcnow() + private_key, public_key = RsaKeys.generate_keypair() + user.private_key = private_key + user.public_key = public_key + user.ap_profile_id = f"https://{current_app.config['SERVER_NAME']}/u/{user.user_name}" + user.ap_public_url = f"https://{current_app.config['SERVER_NAME']}/u/{user.user_name}" + user.ap_inbox_url = f"https://{current_app.config['SERVER_NAME']}/u/{user.user_name}/inbox" + db.session.add(user) + db.session.commit() + + flash(_('User added')) + return redirect(url_for('admin.admin_users', local_remote='local')) + + return render_template('admin/add_user.html', title=_('Add user'), form=form, user=user, + moderating_communities=moderating_communities(current_user.get_id()), + joined_communities=joined_communities(current_user.get_id()), + site=g.site + ) + + @bp.route('/user//delete', methods=['GET']) @login_required @permission_required('administer all users') diff --git a/app/templates/admin/add_user.html b/app/templates/admin/add_user.html new file mode 100644 index 00000000..5b83db59 --- /dev/null +++ b/app/templates/admin/add_user.html @@ -0,0 +1,46 @@ +{% if theme() and file_exists('app/templates/themes/' + theme() + '/base.html') %} + {% extends 'themes/' + theme() + '/base.html' %} +{% else %} + {% extends "base.html" %} +{% endif %} %} +{% from 'bootstrap/form.html' import render_field %} + +{% block app_content %} +
+
+ {% include 'admin/_nav.html' %} +
+
+ +
+ +
+{% endblock %} \ No newline at end of file diff --git a/app/templates/admin/users.html b/app/templates/admin/users.html index 2fc23539..4758a34f 100644 --- a/app/templates/admin/users.html +++ b/app/templates/admin/users.html @@ -14,13 +14,14 @@
+ {{ _('Add local user') }}
- +
Name Local/Remote