admin add local user page

This commit is contained in:
rimu 2024-02-28 05:04:07 +13:00
parent d1c4400967
commit ffad1c4b6e
4 changed files with 178 additions and 2 deletions

View file

@ -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)])

View file

@ -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/<int:user_id>/delete', methods=['GET'])
@login_required
@permission_required('administer all users')

View file

@ -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 %}
<div class="row">
<div class="col">
{% include 'admin/_nav.html' %}
</div>
</div>
<div class="row">
<div class="col col-login mx-auto">
<h3>{{ _('Add new user') }}</h3>
<form method="post" enctype="multipart/form-data" id="add_local_user_form">
{{ form.csrf_token() }}
{{ render_field(form.user_name) }}
{{ render_field(form.email) }}
{{ render_field(form.password) }}
{{ render_field(form.password2) }}
{{ render_field(form.about) }}
{{ render_field(form.matrix_user_id) }}
{% if user.avatar_id %}
<img class="user_icon_big rounded-circle" src="{{ user.avatar_image() }}" width="120" height="120" />
{% endif %}
{{ render_field(form.profile_file) }}
<small class="field_hint">Provide a square image that looks good when small.</small>
{% if user.cover_id %}
<a href="{{ user.cover_image() }}"><img class="user_icon_big" src="{{ user.cover_image() }}" style="width: 300px; height: auto;" /></a>
{% endif %}
{{ render_field(form.banner_file) }}
<small class="field_hint">Provide a wide image - letterbox orientation.</small>
{{ render_field(form.bot) }}
{{ render_field(form.verified) }}
{{ render_field(form.banned) }}
{{ render_field(form.newsletter) }}
{{ render_field(form.nsfw) }}
{{ render_field(form.nsfl) }}
{{ render_field(form.submit) }}
</form>
</div>
</div>
{% endblock %}

View file

@ -14,13 +14,14 @@
<div class="row">
<div class="col">
<a class="btn btn-primary" href="{{ url_for('admin.admin_users_add') }}" style="float: right;">{{ _('Add local user') }}</a>
<form method="get">
<input type="search" name="search" value="{{ search }}">
<input type="radio" name="local_remote" value="local" id="local_remote_local" {{ 'checked' if local_remote == 'local' }}><label for="local_remote_local"> Local</label>
<input type="radio" name="local_remote" value="remote" id="local_remote_remote" {{ 'checked' if local_remote == 'remote' }}><label for="local_remote_remote"> Remote</label>
<input type="submit" name="submit" value="Search" class="btn btn-primary">
</form>
<table class="table table-striped">
<table class="table table-striped mt-1">
<tr>
<th>Name</th>
<th>Local/Remote</th>