mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 19:36:56 -08:00
account approval process
This commit is contained in:
parent
da9e77cbd7
commit
2f3f8b6155
20 changed files with 319 additions and 82 deletions
|
@ -1,7 +1,7 @@
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
from flask import request, flash, json, url_for, current_app, redirect
|
from flask import request, flash, json, url_for, current_app, redirect, g
|
||||||
from flask_login import login_required, current_user
|
from flask_login import login_required, current_user
|
||||||
from flask_babel import _
|
from flask_babel import _
|
||||||
from sqlalchemy import text, desc
|
from sqlalchemy import text, desc
|
||||||
|
@ -15,9 +15,9 @@ from app.admin.forms import FederationForm, SiteMiscForm, SiteProfileForm, EditC
|
||||||
from app.admin.util import unsubscribe_from_everything_then_delete, unsubscribe_from_community
|
from app.admin.util import unsubscribe_from_everything_then_delete, unsubscribe_from_community
|
||||||
from app.community.util import save_icon_file, save_banner_file
|
from app.community.util import save_icon_file, save_banner_file
|
||||||
from app.models import AllowedInstances, BannedInstances, ActivityPubLog, utcnow, Site, Community, CommunityMember, \
|
from app.models import AllowedInstances, BannedInstances, ActivityPubLog, utcnow, Site, Community, CommunityMember, \
|
||||||
User, Instance, File, Report, Topic
|
User, Instance, File, Report, Topic, UserRegistration
|
||||||
from app.utils import render_template, permission_required, set_setting, get_setting, gibberish, markdown_to_html, \
|
from app.utils import render_template, permission_required, set_setting, get_setting, gibberish, markdown_to_html, \
|
||||||
moderating_communities, joined_communities
|
moderating_communities, joined_communities, finalize_user_setup
|
||||||
from app.admin import bp
|
from app.admin import bp
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,7 +26,8 @@ from app.admin import bp
|
||||||
@permission_required('change instance settings')
|
@permission_required('change instance settings')
|
||||||
def admin_home():
|
def admin_home():
|
||||||
return render_template('admin/home.html', title=_('Admin'), moderating_communities=moderating_communities(current_user.get_id()),
|
return render_template('admin/home.html', title=_('Admin'), moderating_communities=moderating_communities(current_user.get_id()),
|
||||||
joined_communities=joined_communities(current_user.get_id()))
|
joined_communities=joined_communities(current_user.get_id()),
|
||||||
|
site=g.site)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/site', methods=['GET', 'POST'])
|
@bp.route('/site', methods=['GET', 'POST'])
|
||||||
|
@ -54,7 +55,8 @@ def admin_site():
|
||||||
form.legal_information.data = site.legal_information
|
form.legal_information.data = site.legal_information
|
||||||
return render_template('admin/site.html', title=_('Site profile'), form=form,
|
return render_template('admin/site.html', title=_('Site profile'), form=form,
|
||||||
moderating_communities=moderating_communities(current_user.get_id()),
|
moderating_communities=moderating_communities(current_user.get_id()),
|
||||||
joined_communities=joined_communities(current_user.get_id())
|
joined_communities=joined_communities(current_user.get_id()),
|
||||||
|
site=g.site
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -95,7 +97,8 @@ def admin_misc():
|
||||||
form.log_activitypub_json.data = site.log_activitypub_json
|
form.log_activitypub_json.data = site.log_activitypub_json
|
||||||
return render_template('admin/misc.html', title=_('Misc settings'), form=form,
|
return render_template('admin/misc.html', title=_('Misc settings'), form=form,
|
||||||
moderating_communities=moderating_communities(current_user.get_id()),
|
moderating_communities=moderating_communities(current_user.get_id()),
|
||||||
joined_communities=joined_communities(current_user.get_id())
|
joined_communities=joined_communities(current_user.get_id()),
|
||||||
|
site=g.site
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -135,7 +138,8 @@ def admin_federation():
|
||||||
|
|
||||||
return render_template('admin/federation.html', title=_('Federation settings'), form=form,
|
return render_template('admin/federation.html', title=_('Federation settings'), form=form,
|
||||||
moderating_communities=moderating_communities(current_user.get_id()),
|
moderating_communities=moderating_communities(current_user.get_id()),
|
||||||
joined_communities=joined_communities(current_user.get_id())
|
joined_communities=joined_communities(current_user.get_id()),
|
||||||
|
site=g.site
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -155,7 +159,8 @@ def admin_activities():
|
||||||
prev_url = url_for('admin.admin_activities', page=activities.prev_num) if activities.has_prev and page != 1 else None
|
prev_url = url_for('admin.admin_activities', page=activities.prev_num) if activities.has_prev and page != 1 else None
|
||||||
|
|
||||||
return render_template('admin/activities.html', title=_('ActivityPub Log'), next_url=next_url, prev_url=prev_url,
|
return render_template('admin/activities.html', title=_('ActivityPub Log'), next_url=next_url, prev_url=prev_url,
|
||||||
activities=activities)
|
activities=activities,
|
||||||
|
site=g.site)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/activity_json/<int:activity_id>')
|
@bp.route('/activity_json/<int:activity_id>')
|
||||||
|
@ -164,7 +169,9 @@ def admin_activities():
|
||||||
def activity_json(activity_id):
|
def activity_json(activity_id):
|
||||||
activity = ActivityPubLog.query.get_or_404(activity_id)
|
activity = ActivityPubLog.query.get_or_404(activity_id)
|
||||||
return render_template('admin/activity_json.html', title=_('Activity JSON'),
|
return render_template('admin/activity_json.html', title=_('Activity JSON'),
|
||||||
activity_json_data=json.loads(activity.activity_json), activity=activity, current_app=current_app)
|
activity_json_data=json.loads(activity.activity_json), activity=activity,
|
||||||
|
current_app=current_app,
|
||||||
|
site=g.site)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/activity_json/<int:activity_id>/replay')
|
@bp.route('/activity_json/<int:activity_id>/replay')
|
||||||
|
@ -198,7 +205,8 @@ def admin_communities():
|
||||||
|
|
||||||
return render_template('admin/communities.html', title=_('Communities'), next_url=next_url, prev_url=prev_url,
|
return render_template('admin/communities.html', title=_('Communities'), next_url=next_url, prev_url=prev_url,
|
||||||
communities=communities, moderating_communities=moderating_communities(current_user.get_id()),
|
communities=communities, moderating_communities=moderating_communities(current_user.get_id()),
|
||||||
joined_communities=joined_communities(current_user.get_id()))
|
joined_communities=joined_communities(current_user.get_id()),
|
||||||
|
site=g.site)
|
||||||
|
|
||||||
|
|
||||||
def topics_for_form():
|
def topics_for_form():
|
||||||
|
@ -280,7 +288,8 @@ def admin_community_edit(community_id):
|
||||||
form.default_layout.data = community.default_layout
|
form.default_layout.data = community.default_layout
|
||||||
return render_template('admin/edit_community.html', title=_('Edit community'), form=form, community=community,
|
return render_template('admin/edit_community.html', title=_('Edit community'), form=form, community=community,
|
||||||
moderating_communities=moderating_communities(current_user.get_id()),
|
moderating_communities=moderating_communities(current_user.get_id()),
|
||||||
joined_communities=joined_communities(current_user.get_id())
|
joined_communities=joined_communities(current_user.get_id()),
|
||||||
|
site=g.site
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -332,7 +341,8 @@ def admin_topics():
|
||||||
topics = Topic.query.order_by(Topic.name).all()
|
topics = Topic.query.order_by(Topic.name).all()
|
||||||
return render_template('admin/topics.html', title=_('Topics'), topics=topics,
|
return render_template('admin/topics.html', title=_('Topics'), topics=topics,
|
||||||
moderating_communities=moderating_communities(current_user.get_id()),
|
moderating_communities=moderating_communities(current_user.get_id()),
|
||||||
joined_communities=joined_communities(current_user.get_id())
|
joined_communities=joined_communities(current_user.get_id()),
|
||||||
|
site=g.site
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -356,7 +366,8 @@ def admin_topic_add():
|
||||||
|
|
||||||
return render_template('admin/edit_topic.html', title=_('Add topic'), form=form,
|
return render_template('admin/edit_topic.html', title=_('Add topic'), form=form,
|
||||||
moderating_communities=moderating_communities(current_user.get_id()),
|
moderating_communities=moderating_communities(current_user.get_id()),
|
||||||
joined_communities=joined_communities(current_user.get_id())
|
joined_communities=joined_communities(current_user.get_id()),
|
||||||
|
site=g.site
|
||||||
)
|
)
|
||||||
|
|
||||||
@bp.route('/topic/<int:topic_id>/edit', methods=['GET', 'POST'])
|
@bp.route('/topic/<int:topic_id>/edit', methods=['GET', 'POST'])
|
||||||
|
@ -381,7 +392,8 @@ def admin_topic_edit(topic_id):
|
||||||
form.machine_name.data = topic.machine_name
|
form.machine_name.data = topic.machine_name
|
||||||
return render_template('admin/edit_topic.html', title=_('Edit topic'), form=form, topic=topic,
|
return render_template('admin/edit_topic.html', title=_('Edit topic'), form=form, topic=topic,
|
||||||
moderating_communities=moderating_communities(current_user.get_id()),
|
moderating_communities=moderating_communities(current_user.get_id()),
|
||||||
joined_communities=joined_communities(current_user.get_id())
|
joined_communities=joined_communities(current_user.get_id()),
|
||||||
|
site=g.site
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -425,10 +437,42 @@ def admin_users():
|
||||||
return render_template('admin/users.html', title=_('Users'), next_url=next_url, prev_url=prev_url, users=users,
|
return render_template('admin/users.html', title=_('Users'), next_url=next_url, prev_url=prev_url, users=users,
|
||||||
local_remote=local_remote, search=search,
|
local_remote=local_remote, search=search,
|
||||||
moderating_communities=moderating_communities(current_user.get_id()),
|
moderating_communities=moderating_communities(current_user.get_id()),
|
||||||
joined_communities=joined_communities(current_user.get_id())
|
joined_communities=joined_communities(current_user.get_id()),
|
||||||
|
site=g.site
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/approve_registrations', methods=['GET'])
|
||||||
|
@login_required
|
||||||
|
@permission_required('approve registrations')
|
||||||
|
def admin_approve_registrations():
|
||||||
|
registrations = UserRegistration.query.filter_by(status=0).order_by(UserRegistration.created_at).all()
|
||||||
|
recently_approved = UserRegistration.query.filter_by(status=1).order_by(desc(UserRegistration.approved_at)).limit(30)
|
||||||
|
return render_template('admin/approve_registrations.html',
|
||||||
|
registrations=registrations,
|
||||||
|
recently_approved=recently_approved,
|
||||||
|
site=g.site)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/approve_registrations/<int:user_id>/approve', methods=['GET'])
|
||||||
|
@login_required
|
||||||
|
@permission_required('approve registrations')
|
||||||
|
def admin_approve_registrations_approve(user_id):
|
||||||
|
user = User.query.get_or_404(user_id)
|
||||||
|
registration = UserRegistration.query.filter_by(status=0, user_id=user_id).first()
|
||||||
|
if registration:
|
||||||
|
registration.status = 1
|
||||||
|
registration.approved_at = utcnow()
|
||||||
|
registration.approved_by = current_user.id
|
||||||
|
db.session.commit()
|
||||||
|
if user.verified:
|
||||||
|
finalize_user_setup(user, True)
|
||||||
|
|
||||||
|
flash(_('Registration approved.'))
|
||||||
|
|
||||||
|
return redirect(url_for('admin.admin_approve_registrations'))
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/user/<int:user_id>/edit', methods=['GET', 'POST'])
|
@bp.route('/user/<int:user_id>/edit', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('administer all users')
|
@permission_required('administer all users')
|
||||||
|
@ -493,7 +537,8 @@ def admin_user_edit(user_id):
|
||||||
|
|
||||||
return render_template('admin/edit_user.html', title=_('Edit user'), form=form, user=user,
|
return render_template('admin/edit_user.html', title=_('Edit user'), form=form, user=user,
|
||||||
moderating_communities=moderating_communities(current_user.get_id()),
|
moderating_communities=moderating_communities(current_user.get_id()),
|
||||||
joined_communities=joined_communities(current_user.get_id())
|
joined_communities=joined_communities(current_user.get_id()),
|
||||||
|
site=g.site
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -540,5 +585,6 @@ def admin_reports():
|
||||||
return render_template('admin/reports.html', title=_('Reports'), next_url=next_url, prev_url=prev_url, reports=reports,
|
return render_template('admin/reports.html', title=_('Reports'), next_url=next_url, prev_url=prev_url, reports=reports,
|
||||||
local_remote=local_remote, search=search,
|
local_remote=local_remote, search=search,
|
||||||
moderating_communities=moderating_communities(current_user.get_id()),
|
moderating_communities=moderating_communities(current_user.get_id()),
|
||||||
joined_communities=joined_communities(current_user.get_id())
|
joined_communities=joined_communities(current_user.get_id()),
|
||||||
|
site=g.site
|
||||||
)
|
)
|
|
@ -1,30 +0,0 @@
|
||||||
from flask import render_template, current_app
|
|
||||||
from flask_babel import _
|
|
||||||
from app.email import send_email
|
|
||||||
|
|
||||||
|
|
||||||
def send_password_reset_email(user):
|
|
||||||
token = user.get_reset_password_token()
|
|
||||||
send_email(_('[PieFed] Reset Your Password'),
|
|
||||||
sender='PieFed <rimu@chorebuster.net>',
|
|
||||||
recipients=[user.email],
|
|
||||||
text_body=render_template('email/reset_password.txt',
|
|
||||||
user=user, token=token),
|
|
||||||
html_body=render_template('email/reset_password.html',
|
|
||||||
user=user, token=token))
|
|
||||||
|
|
||||||
|
|
||||||
def send_welcome_email(user):
|
|
||||||
send_email(_('Welcome to PieFed'),
|
|
||||||
sender='PieFed <rimu@chorebuster.net>',
|
|
||||||
recipients=[user.email],
|
|
||||||
text_body=render_template('email/welcome.txt', user=user),
|
|
||||||
html_body=render_template('email/welcome.html', user=user))
|
|
||||||
|
|
||||||
|
|
||||||
def send_verification_email(user):
|
|
||||||
send_email(_('Please verify your email address'),
|
|
||||||
sender='PieFed <rimu@chorebuster.net>',
|
|
||||||
recipients=[user.email],
|
|
||||||
text_body=render_template('email/verification.txt', user=user),
|
|
||||||
html_body=render_template('email/verification.html', user=user))
|
|
|
@ -20,6 +20,7 @@ class RegistrationForm(FlaskForm):
|
||||||
password2 = PasswordField(
|
password2 = PasswordField(
|
||||||
_l('Repeat password'), validators=[DataRequired(),
|
_l('Repeat password'), validators=[DataRequired(),
|
||||||
EqualTo('password')])
|
EqualTo('password')])
|
||||||
|
question = StringField(_('Why would you like to join this site?'), validators=[DataRequired(), Length(min=1, max=512)])
|
||||||
recaptcha = RecaptchaField()
|
recaptcha = RecaptchaField()
|
||||||
|
|
||||||
submit = SubmitField(_l('Register'))
|
submit = SubmitField(_l('Register'))
|
||||||
|
|
|
@ -3,14 +3,16 @@ from flask import redirect, url_for, flash, request, make_response, session, Mar
|
||||||
from werkzeug.urls import url_parse
|
from werkzeug.urls import url_parse
|
||||||
from flask_login import login_user, logout_user, current_user
|
from flask_login import login_user, logout_user, current_user
|
||||||
from flask_babel import _
|
from flask_babel import _
|
||||||
|
from wtforms import Label
|
||||||
|
|
||||||
from app import db, cache
|
from app import db, cache
|
||||||
from app.auth import bp
|
from app.auth import bp
|
||||||
from app.auth.forms import LoginForm, RegistrationForm, ResetPasswordRequestForm, ResetPasswordForm
|
from app.auth.forms import LoginForm, RegistrationForm, ResetPasswordRequestForm, ResetPasswordForm
|
||||||
from app.auth.util import random_token, normalize_utf
|
from app.auth.util import random_token, normalize_utf
|
||||||
from app.models import User, utcnow, IpBan
|
from app.email import send_verification_email, send_password_reset_email
|
||||||
from app.auth.email import send_password_reset_email, send_welcome_email, send_verification_email
|
from app.models import User, utcnow, IpBan, UserRegistration
|
||||||
from app.activitypub.signature import RsaKeys
|
from app.utils import render_template, ip_address, user_ip_banned, user_cookie_banned, banned_ip_addresses, \
|
||||||
from app.utils import render_template, ip_address, user_ip_banned, user_cookie_banned, banned_ip_addresses
|
finalize_user_setup
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/login', methods=['GET', 'POST'])
|
@bp.route('/login', methods=['GET', 'POST'])
|
||||||
|
@ -51,6 +53,8 @@ def login():
|
||||||
# Set a cookie so we have another way to track banned people
|
# Set a cookie so we have another way to track banned people
|
||||||
response.set_cookie('sesion', '17489047567495', expires=datetime(year=2099, month=12, day=30))
|
response.set_cookie('sesion', '17489047567495', expires=datetime(year=2099, month=12, day=30))
|
||||||
return response
|
return response
|
||||||
|
if user.waiting_for_approval():
|
||||||
|
return redirect(url_for('auth.please_wait'))
|
||||||
login_user(user, remember=True)
|
login_user(user, remember=True)
|
||||||
current_user.last_seen = utcnow()
|
current_user.last_seen = utcnow()
|
||||||
current_user.ip_address = ip_address()
|
current_user.ip_address = ip_address()
|
||||||
|
@ -84,6 +88,8 @@ def register():
|
||||||
if current_user.is_authenticated:
|
if current_user.is_authenticated:
|
||||||
return redirect(url_for('main.index'))
|
return redirect(url_for('main.index'))
|
||||||
form = RegistrationForm()
|
form = RegistrationForm()
|
||||||
|
if g.site.registration_mode != 'RequireApplication':
|
||||||
|
form.question.validators = ()
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
if form.email.data == '': # ignore any registration where the email field is filled out. spam prevention
|
if form.email.data == '': # ignore any registration where the email field is filled out. spam prevention
|
||||||
if form.real_email.data.lower().startswith('postmaster@') or form.real_email.data.lower().startswith('abuse@') or \
|
if form.real_email.data.lower().startswith('postmaster@') or form.real_email.data.lower().startswith('abuse@') or \
|
||||||
|
@ -104,20 +110,37 @@ def register():
|
||||||
user.set_password(form.password.data)
|
user.set_password(form.password.data)
|
||||||
db.session.add(user)
|
db.session.add(user)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
login_user(user, remember=True)
|
|
||||||
send_welcome_email(user)
|
|
||||||
send_verification_email(user)
|
send_verification_email(user)
|
||||||
|
|
||||||
if current_app.config['MODE'] == 'development':
|
if current_app.config['MODE'] == 'development':
|
||||||
current_app.logger.info('Verify account:' + url_for('auth.verify_email', token=user.verification_token, _external=True))
|
current_app.logger.info('Verify account:' + url_for('auth.verify_email', token=user.verification_token, _external=True))
|
||||||
|
if g.site.registration_mode == 'RequireApplication':
|
||||||
flash(_('Great, you are now a registered user!'))
|
application = UserRegistration(user_id=user.id, answer=form.question.data)
|
||||||
|
db.session.add(application)
|
||||||
|
db.session.commit()
|
||||||
|
return redirect(url_for('auth.please_wait'))
|
||||||
|
else:
|
||||||
|
return redirect(url_for('auth.check_email'))
|
||||||
|
|
||||||
resp = make_response(redirect(url_for('topic.choose_topics')))
|
resp = make_response(redirect(url_for('topic.choose_topics')))
|
||||||
if user_ip_banned():
|
if user_ip_banned():
|
||||||
resp.set_cookie('sesion', '17489047567495', expires=datetime(year=2099, month=12, day=30))
|
resp.set_cookie('sesion', '17489047567495', expires=datetime(year=2099, month=12, day=30))
|
||||||
return resp
|
return resp
|
||||||
return render_template('auth/register.html', title=_('Register'), form=form, site=g.site)
|
else:
|
||||||
|
if g.site.registration_mode == 'RequireApplication' and g.site.application_question != '':
|
||||||
|
form.question.label = Label('question', g.site.application_question)
|
||||||
|
if g.site.registration_mode != 'RequireApplication':
|
||||||
|
del form.question
|
||||||
|
return render_template('auth/register.html', title=_('Register'), form=form, site=g.site)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/please_wait', methods=['GET'])
|
||||||
|
def please_wait():
|
||||||
|
return render_template('auth/please_wait.html', title=_('Account under review'), site=g.site)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/check_email', methods=['GET'])
|
||||||
|
def check_email():
|
||||||
|
return render_template('auth/check_email.html', title=_('Check your email'), site=g.site)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/reset_password_request', methods=['GET', 'POST'])
|
@bp.route('/reset_password_request', methods=['GET', 'POST'])
|
||||||
|
@ -168,21 +191,21 @@ def verify_email(token):
|
||||||
if user.verified: # guard against users double-clicking the link in the email
|
if user.verified: # guard against users double-clicking the link in the email
|
||||||
return redirect(url_for('main.index'))
|
return redirect(url_for('main.index'))
|
||||||
user.verified = True
|
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.commit()
|
db.session.commit()
|
||||||
flash(_('Thank you for verifying your email address.'))
|
if not user.waiting_for_approval():
|
||||||
|
finalize_user_setup(user)
|
||||||
|
else:
|
||||||
|
flash(_('Thank you for verifying your email address.'))
|
||||||
else:
|
else:
|
||||||
flash(_('Email address validation failed.'), 'error')
|
flash(_('Email address validation failed.'), 'error')
|
||||||
if len(user.communities()) == 0:
|
if user.waiting_for_approval():
|
||||||
return redirect(url_for('topic.choose_topics'))
|
return redirect(url_for('auth.please_wait'))
|
||||||
else:
|
else:
|
||||||
return redirect(url_for('main.index'))
|
login_user(user, remember=True)
|
||||||
|
if len(user.communities()) == 0:
|
||||||
|
return redirect(url_for('topic.choose_topics'))
|
||||||
|
else:
|
||||||
|
return redirect(url_for('main.index'))
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/validation_required')
|
@bp.route('/validation_required')
|
||||||
|
|
|
@ -9,8 +9,8 @@ import click
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from app.activitypub.signature import RsaKeys
|
from app.activitypub.signature import RsaKeys
|
||||||
from app.auth.email import send_verification_email
|
|
||||||
from app.auth.util import random_token
|
from app.auth.util import random_token
|
||||||
|
from app.email import send_verification_email
|
||||||
from app.models import Settings, BannedInstances, Interest, Role, User, RolePermission, Domain, ActivityPubLog, \
|
from app.models import Settings, BannedInstances, Interest, Role, User, RolePermission, Domain, ActivityPubLog, \
|
||||||
utcnow, Site, Instance
|
utcnow, Site, Instance
|
||||||
from app.utils import file_get_contents, retrieve_block_list
|
from app.utils import file_get_contents, retrieve_block_list
|
||||||
|
|
30
app/email.py
30
app/email.py
|
@ -1,6 +1,6 @@
|
||||||
from flask import current_app, render_template, escape
|
from flask import current_app, render_template, escape
|
||||||
from app import db, celery
|
from app import db, celery
|
||||||
from flask_babel import _, lazy_gettext as _l # todo: set the locale based on account_id so that _() works
|
from flask_babel import _, lazy_gettext as _l # todo: set the locale based on account_id so that _() works
|
||||||
import boto3
|
import boto3
|
||||||
from botocore.exceptions import ClientError
|
from botocore.exceptions import ClientError
|
||||||
from typing import List
|
from typing import List
|
||||||
|
@ -9,6 +9,34 @@ AWS_REGION = "ap-southeast-2"
|
||||||
CHARSET = "UTF-8"
|
CHARSET = "UTF-8"
|
||||||
|
|
||||||
|
|
||||||
|
def send_password_reset_email(user):
|
||||||
|
token = user.get_reset_password_token()
|
||||||
|
send_email(_('[PieFed] Reset Your Password'),
|
||||||
|
sender='PieFed <rimu@chorebuster.net>',
|
||||||
|
recipients=[user.email],
|
||||||
|
text_body=render_template('email/reset_password.txt',
|
||||||
|
user=user, token=token),
|
||||||
|
html_body=render_template('email/reset_password.html',
|
||||||
|
user=user, token=token))
|
||||||
|
|
||||||
|
|
||||||
|
def send_verification_email(user):
|
||||||
|
send_email(_('Please verify your email address'),
|
||||||
|
sender='PieFed <rimu@chorebuster.net>',
|
||||||
|
recipients=[user.email],
|
||||||
|
text_body=render_template('email/verification.txt', user=user),
|
||||||
|
html_body=render_template('email/verification.html', user=user))
|
||||||
|
|
||||||
|
|
||||||
|
def send_welcome_email(user, application_required):
|
||||||
|
subject = _('Your application has been approved - welcome to PieFed') if application_required else _('Welcome to PieFed')
|
||||||
|
send_email(subject,
|
||||||
|
sender='PieFed <rimu@chorebuster.net>',
|
||||||
|
recipients=[user.email],
|
||||||
|
text_body=render_template('email/welcome.txt', user=user, application_required=application_required),
|
||||||
|
html_body=render_template('email/welcome.html', user=user, application_required=application_required))
|
||||||
|
|
||||||
|
|
||||||
@celery.task
|
@celery.task
|
||||||
def send_async_email(subject, sender, recipients, text_body, html_body, reply_to):
|
def send_async_email(subject, sender, recipients, text_body, html_body, reply_to):
|
||||||
if type(recipients) == str:
|
if type(recipients) == str:
|
||||||
|
|
|
@ -447,6 +447,10 @@ class User(UserMixin, db.Model):
|
||||||
def is_local(self):
|
def is_local(self):
|
||||||
return self.ap_id is None or self.ap_profile_id.startswith('https://' + current_app.config['SERVER_NAME'])
|
return self.ap_id is None or self.ap_profile_id.startswith('https://' + current_app.config['SERVER_NAME'])
|
||||||
|
|
||||||
|
def waiting_for_approval(self):
|
||||||
|
application = UserRegistration.query.filter_by(user_id=self.id, status=0).first()
|
||||||
|
return application is not None
|
||||||
|
|
||||||
@cache.memoize(timeout=30)
|
@cache.memoize(timeout=30)
|
||||||
def is_admin(self):
|
def is_admin(self):
|
||||||
for role in self.roles:
|
for role in self.roles:
|
||||||
|
@ -581,6 +585,8 @@ class User(UserMixin, db.Model):
|
||||||
file.delete_from_disk()
|
file.delete_from_disk()
|
||||||
self.avatar_id = None
|
self.avatar_id = None
|
||||||
db.session.delete(file)
|
db.session.delete(file)
|
||||||
|
if self.waiting_for_approval():
|
||||||
|
db.session.query(UserRegistration).filter(UserRegistration.user_id == self.id).delete()
|
||||||
|
|
||||||
def purge_content(self):
|
def purge_content(self):
|
||||||
files = File.query.join(Post).filter(Post.user_id == self.id).all()
|
files = File.query.join(Post).filter(Post.user_id == self.id).all()
|
||||||
|
@ -861,6 +867,17 @@ class UserFollowRequest(db.Model):
|
||||||
follow_id = db.Column(db.Integer, db.ForeignKey('user.id'))
|
follow_id = db.Column(db.Integer, db.ForeignKey('user.id'))
|
||||||
|
|
||||||
|
|
||||||
|
class UserRegistration(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), index=True)
|
||||||
|
answer = db.Column(db.String(512))
|
||||||
|
status = db.Column(db.Integer, default=0, index=True) # 0 = unapproved, 1 = approved
|
||||||
|
created_at = db.Column(db.DateTime, default=utcnow)
|
||||||
|
approved_at = db.Column(db.DateTime)
|
||||||
|
approved_by = db.Column(db.Integer, db.ForeignKey('user.id'))
|
||||||
|
user = db.relationship('User', foreign_keys=[user_id], lazy='joined')
|
||||||
|
|
||||||
|
|
||||||
class PostVote(db.Model):
|
class PostVote(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), index=True)
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), index=True)
|
||||||
|
@ -980,7 +997,7 @@ class Site(db.Model):
|
||||||
enable_nsfl = db.Column(db.Boolean, default=False)
|
enable_nsfl = db.Column(db.Boolean, default=False)
|
||||||
community_creation_admin_only = db.Column(db.Boolean, default=False)
|
community_creation_admin_only = db.Column(db.Boolean, default=False)
|
||||||
reports_email_admins = db.Column(db.Boolean, default=True)
|
reports_email_admins = db.Column(db.Boolean, default=True)
|
||||||
registration_mode = db.Column(db.String(20), default='Closed')
|
registration_mode = db.Column(db.String(20), default='Closed') # possible values: Open, RequireApplication, Closed
|
||||||
application_question = db.Column(db.Text, default='')
|
application_question = db.Column(db.Text, default='')
|
||||||
allow_or_block_list = db.Column(db.Integer, default=2) # 1 = allow list, 2 = block list
|
allow_or_block_list = db.Column(db.Integer, default=2) # 1 = allow list, 2 = block list
|
||||||
allowlist = db.Column(db.Text, default='')
|
allowlist = db.Column(db.Text, default='')
|
||||||
|
|
|
@ -598,7 +598,7 @@ fieldset legend {
|
||||||
}
|
}
|
||||||
.post_list .post_teaser .utilities_row a {
|
.post_list .post_teaser .utilities_row a {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 44px;
|
min-width: 44px;
|
||||||
}
|
}
|
||||||
.post_list .post_teaser .utilities_row .preview_image, .post_list .post_teaser .utilities_row .post_options {
|
.post_list .post_teaser .utilities_row .preview_image, .post_list .post_teaser .utilities_row .post_options {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
@ -221,7 +221,7 @@ nav, etc which are used site-wide */
|
||||||
.utilities_row {
|
.utilities_row {
|
||||||
a {
|
a {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 44px;
|
min-width: 44px;
|
||||||
}
|
}
|
||||||
.preview_image, .post_options {
|
.preview_image, .post_options {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
@ -4,7 +4,10 @@
|
||||||
<a href="{{ url_for('admin.admin_misc') }}">{{ _('Misc settings') }}</a> |
|
<a href="{{ url_for('admin.admin_misc') }}">{{ _('Misc settings') }}</a> |
|
||||||
<a href="{{ url_for('admin.admin_communities') }}">{{ _('Communities') }}</a> |
|
<a href="{{ url_for('admin.admin_communities') }}">{{ _('Communities') }}</a> |
|
||||||
<a href="{{ url_for('admin.admin_topics') }}">{{ _('Topics') }}</a> |
|
<a href="{{ url_for('admin.admin_topics') }}">{{ _('Topics') }}</a> |
|
||||||
<a href="{{ url_for('admin.admin_users') }}">{{ _('Users') }}</a> |
|
<a href="{{ url_for('admin.admin_users', local_remote='local') }}">{{ _('Users') }}</a> |
|
||||||
|
{% if site.registration_mode == 'RequireApplication' %}
|
||||||
|
<a href="{{ url_for('admin.admin_approve_registrations') }}">{{ _('Registration applications') }}</a> |
|
||||||
|
{% endif %}
|
||||||
<a href="{{ url_for('admin.admin_reports') }}">{{ _('Moderation') }}</a> |
|
<a href="{{ url_for('admin.admin_reports') }}">{{ _('Moderation') }}</a> |
|
||||||
<a href="{{ url_for('admin.admin_federation') }}">{{ _('Federation') }}</a> |
|
<a href="{{ url_for('admin.admin_federation') }}">{{ _('Federation') }}</a> |
|
||||||
<a href="{{ url_for('admin.admin_activities') }}">{{ _('Activities') }}</a>
|
<a href="{{ url_for('admin.admin_activities') }}">{{ _('Activities') }}</a>
|
||||||
|
|
49
app/templates/admin/approve_registrations.html
Normal file
49
app/templates/admin/approve_registrations.html
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% from 'bootstrap/form.html' import render_form %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
{% include 'admin/_nav.html' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
{% if registrations %}
|
||||||
|
<p>{{ _('When registering, people are asked "%(question)s".', question=site.application_question) }} </p>
|
||||||
|
<form method="get">
|
||||||
|
<input type="search" name="search" value="{{ search }}">
|
||||||
|
</form>
|
||||||
|
<table class="table table-striped">
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Email</th>
|
||||||
|
<th>Email verifed</th>
|
||||||
|
<th>Answer</th>
|
||||||
|
<th>Applied</th>
|
||||||
|
<th>IP</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
{% for registration in registrations %}
|
||||||
|
<tr>
|
||||||
|
<td><img src="{{ registration.user.avatar_thumbnail() }}" class="community_icon rounded-circle" loading="lazy" />
|
||||||
|
{{ registration.user.display_name() }}</td>
|
||||||
|
<td><a href="mailto:{{ registration.user.email }}">{{ registration.user.email }}</a></td>
|
||||||
|
<td>{{ '<span class="green">✓</span>'|safe if registration.user.verified else '<span class="red">✗</span>'|safe }}</td>
|
||||||
|
<td>{{ registration.answer }}</td>
|
||||||
|
<td>{{ moment(registration.created_at).fromNow() }}</td>
|
||||||
|
<td>{{ registration.user.ip_address if registration.user.ip_address }} </td>
|
||||||
|
<td><a href="{{ url_for('admin.admin_approve_registrations_approve', user_id=registration.user.id) }}" class="btn btn-sm btn-primary">{{ _('Approve') }}</a>
|
||||||
|
<a href="/u/{{ registration.user.link() }}">{{ _('View') }}</a> |
|
||||||
|
<a href="{{ url_for('admin.admin_user_delete', user_id=registration.user.id) }}" class="confirm_first">{{ _('Delete') }}</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<p>{{ _('No one is waiting to be approved.') }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -33,7 +33,12 @@
|
||||||
<td><img src="{{ user.avatar_thumbnail() }}" class="community_icon rounded-circle" loading="lazy" />
|
<td><img src="{{ user.avatar_thumbnail() }}" class="community_icon rounded-circle" loading="lazy" />
|
||||||
{{ user.display_name() }}</td>
|
{{ user.display_name() }}</td>
|
||||||
<td>{{ 'Local' if user.is_local() else 'Remote' }}</td>
|
<td>{{ 'Local' if user.is_local() else 'Remote' }}</td>
|
||||||
<td>{{ user.last_seen }}</td>
|
<td>{% if request.args.get('local_remote', '') == 'local' %}
|
||||||
|
{{ moment(user.last_seen).fromNow() }}
|
||||||
|
{% else %}
|
||||||
|
{{ user.last_seen }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
<td>{{ user.attitude * 100 if user.attitude != 1 }}</td>
|
<td>{{ user.attitude * 100 if user.attitude != 1 }}</td>
|
||||||
<td>{{ 'R ' + str(user.reputation) if user.reputation }}</td>
|
<td>{{ 'R ' + str(user.reputation) if user.reputation }}</td>
|
||||||
<td>{{ '<span class="red">Banned</span>'|safe if user.banned }} </td>
|
<td>{{ '<span class="red">Banned</span>'|safe if user.banned }} </td>
|
||||||
|
|
15
app/templates/auth/check_email.html
Normal file
15
app/templates/auth/check_email.html
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-login mx-auto">
|
||||||
|
<div class="card mt-5">
|
||||||
|
<div class="card-body p-6">
|
||||||
|
<div class="card-title text-center">{{ _('Check your email') }}</div>
|
||||||
|
<p>{{ _('We sent you an email containing a link that you need to click to enable your account.') }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
15
app/templates/auth/please_wait.html
Normal file
15
app/templates/auth/please_wait.html
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-login mx-auto">
|
||||||
|
<div class="card mt-5">
|
||||||
|
<div class="card-body p-6">
|
||||||
|
<div class="card-title text-center">{{ _('Thanks for registering') }}</div>
|
||||||
|
<p>{{ _('We are reviewing your application and will email you once it has been accepted.') }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -3,7 +3,7 @@
|
||||||
<p>I'm Rimu, the founder of PieFed, and I'm super grateful for your sign-up.</p>
|
<p>I'm Rimu, the founder of PieFed, and I'm super grateful for your sign-up.</p>
|
||||||
<p>As PieFed is still in an early development phase, your thoughts and ideas mean the world to me. You might stumble upon a
|
<p>As PieFed is still in an early development phase, your thoughts and ideas mean the world to me. You might stumble upon a
|
||||||
few hiccups or notice some missing bits here and there — that's totally expected. Just hit reply to this email and
|
few hiccups or notice some missing bits here and there — that's totally expected. Just hit reply to this email and
|
||||||
let me know what's up. Screenshots, if you've got 'em, will make a world of difference!</p>
|
let me know what's up. Screenshots, if you've got 'em, will be very helpful!</p>
|
||||||
<p>If you are technically-minded, reporting issues on the <a href="https://codeberg.org/rimu/pyfedi/issues">Codeberg
|
<p>If you are technically-minded, reporting issues on the <a href="https://codeberg.org/rimu/pyfedi/issues">Codeberg
|
||||||
issue tracker</a> would be awesome. And hey, if Python's
|
issue tracker</a> would be awesome. And hey, if Python's
|
||||||
your jam and you're keen to pitch in, <a href="https://join.piefed.social/">check out the project site</a> or
|
your jam and you're keen to pitch in, <a href="https://join.piefed.social/">check out the project site</a> or
|
||||||
|
|
|
@ -12,14 +12,14 @@
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
<h1 class="mt-2">{{ _('Edit profile of %(name)s', name=user.user_name) }}</h1>
|
<h1 class="mt-2">{{ _('Edit profile of %(name)s', name=user.user_name) }}</h1>
|
||||||
<form method='post' enctype="multipart/form-data" role="form">
|
<form method='post' enctype="multipart/form-data" role="form" autocomplete="off">
|
||||||
{{ form.csrf_token() }}
|
{{ form.csrf_token() }}
|
||||||
{{ render_field(form.title) }}
|
{{ render_field(form.title) }}
|
||||||
{{ render_field(form.email) }}
|
{{ render_field(form.email) }}
|
||||||
{{ render_field(form.password_field) }}
|
{{ render_field(form.password_field) }}
|
||||||
<hr />
|
<hr />
|
||||||
{{ render_field(form.about) }}
|
{{ render_field(form.about) }}
|
||||||
{{ render_field(form.matrix_user_id) }}
|
{{ render_field(form.matrixuserid) }}
|
||||||
<small class="field_hint">e.g. @something:matrix.org. Include leading @ and use : before server</small>
|
<small class="field_hint">e.g. @something:matrix.org. Include leading @ and use : before server</small>
|
||||||
{{ render_field(form.profile_file) }}
|
{{ render_field(form.profile_file) }}
|
||||||
<small class="field_hint">Provide a square image that looks good when small.</small>
|
<small class="field_hint">Provide a square image that looks good when small.</small>
|
||||||
|
|
|
@ -13,9 +13,9 @@ class ProfileForm(FlaskForm):
|
||||||
title = StringField(_l('Display name'), validators=[Optional(), Length(max=255)])
|
title = StringField(_l('Display name'), validators=[Optional(), Length(max=255)])
|
||||||
email = EmailField(_l('Email address'), validators=[Email(), DataRequired(), Length(min=5, max=255)])
|
email = EmailField(_l('Email address'), validators=[Email(), DataRequired(), Length(min=5, max=255)])
|
||||||
password_field = PasswordField(_l('Set new password'), validators=[Optional(), Length(min=1, max=50)],
|
password_field = PasswordField(_l('Set new password'), validators=[Optional(), Length(min=1, max=50)],
|
||||||
render_kw={"autocomplete": 'Off'})
|
render_kw={"autocomplete": 'new-password'})
|
||||||
about = TextAreaField(_l('Bio'), validators=[Optional(), Length(min=3, max=5000)])
|
about = TextAreaField(_l('Bio'), validators=[Optional(), Length(min=3, max=5000)])
|
||||||
matrix_user_id = StringField(_l('Matrix User ID'), validators=[Optional(), Length(max=255)], render_kw={'autocomplete': 'off'})
|
matrixuserid = StringField(_l('Matrix User ID'), validators=[Optional(), Length(max=255)], render_kw={'autocomplete': 'off'})
|
||||||
profile_file = FileField(_('Avatar image'))
|
profile_file = FileField(_('Avatar image'))
|
||||||
banner_file = FileField(_('Top banner image'))
|
banner_file = FileField(_('Top banner image'))
|
||||||
bot = BooleanField(_l('This profile is a bot'))
|
bot = BooleanField(_l('This profile is a bot'))
|
||||||
|
@ -25,6 +25,10 @@ class ProfileForm(FlaskForm):
|
||||||
if current_user.another_account_using_email(field.data):
|
if current_user.another_account_using_email(field.data):
|
||||||
raise ValidationError(_l('That email address is already in use by another account'))
|
raise ValidationError(_l('That email address is already in use by another account'))
|
||||||
|
|
||||||
|
def validate_matrix_user_id(self, matrix_user_id):
|
||||||
|
if not matrix_user_id.data.strip().startswith('@'):
|
||||||
|
raise ValidationError(_('Matrix user ids start with @'))
|
||||||
|
|
||||||
|
|
||||||
class SettingsForm(FlaskForm):
|
class SettingsForm(FlaskForm):
|
||||||
newsletter = BooleanField(_l('Subscribe to email newsletter'))
|
newsletter = BooleanField(_l('Subscribe to email newsletter'))
|
||||||
|
|
|
@ -96,7 +96,7 @@ def edit_profile(actor):
|
||||||
current_user.set_password(form.password_field.data)
|
current_user.set_password(form.password_field.data)
|
||||||
current_user.about = form.about.data
|
current_user.about = form.about.data
|
||||||
current_user.about_html = markdown_to_html(form.about.data)
|
current_user.about_html = markdown_to_html(form.about.data)
|
||||||
current_user.matrix_user_id = form.matrix_user_id.data
|
current_user.matrix_user_id = form.matrixuserid.data
|
||||||
current_user.bot = form.bot.data
|
current_user.bot = form.bot.data
|
||||||
profile_file = request.files['profile_file']
|
profile_file = request.files['profile_file']
|
||||||
if profile_file and profile_file.filename != '':
|
if profile_file and profile_file.filename != '':
|
||||||
|
@ -135,7 +135,7 @@ def edit_profile(actor):
|
||||||
form.title.data = current_user.title
|
form.title.data = current_user.title
|
||||||
form.email.data = current_user.email
|
form.email.data = current_user.email
|
||||||
form.about.data = current_user.about
|
form.about.data = current_user.about
|
||||||
form.matrix_user_id.data = current_user.matrix_user_id
|
form.matrixuserid.data = current_user.matrix_user_id
|
||||||
form.password_field.data = ''
|
form.password_field.data = ''
|
||||||
|
|
||||||
return render_template('user/edit_profile.html', title=_('Edit profile'), form=form, user=current_user,
|
return render_template('user/edit_profile.html', title=_('Edit profile'), form=form, user=current_user,
|
||||||
|
|
14
app/utils.py
14
app/utils.py
|
@ -22,6 +22,7 @@ from wtforms.widgets import Select, html_params, ListWidget, CheckboxInput
|
||||||
from app import db, cache
|
from app import db, cache
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from app.email import send_welcome_email
|
||||||
from app.models import Settings, Domain, Instance, BannedInstances, User, Community, DomainBlock, ActivityPubLog, IpBan, \
|
from app.models import Settings, Domain, Instance, BannedInstances, User, Community, DomainBlock, ActivityPubLog, IpBan, \
|
||||||
Site, Post, PostReply, utcnow, Filter, CommunityMember
|
Site, Post, PostReply, utcnow, Filter, CommunityMember
|
||||||
|
|
||||||
|
@ -562,6 +563,19 @@ def joined_communities(user_id):
|
||||||
filter(CommunityMember.user_id == user_id).order_by(Community.title).all()
|
filter(CommunityMember.user_id == user_id).order_by(Community.title).all()
|
||||||
|
|
||||||
|
|
||||||
|
def finalize_user_setup(user, application_required=False):
|
||||||
|
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.commit()
|
||||||
|
send_welcome_email(user, application_required)
|
||||||
|
|
||||||
|
|
||||||
# All the following post/comment ranking math is explained at https://medium.com/hacking-and-gonzo/how-reddit-ranking-algorithms-work-ef111e33d0d9
|
# All the following post/comment ranking math is explained at https://medium.com/hacking-and-gonzo/how-reddit-ranking-algorithms-work-ef111e33d0d9
|
||||||
epoch = datetime(1970, 1, 1)
|
epoch = datetime(1970, 1, 1)
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
"""user registration processing
|
||||||
|
|
||||||
|
Revision ID: 3fb833e34c75
|
||||||
|
Revises: 52e8d73b69ba
|
||||||
|
Create Date: 2024-02-02 11:56:44.895871
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '3fb833e34c75'
|
||||||
|
down_revision = '52e8d73b69ba'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('user_registration',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('user_id', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('answer', sa.String(length=512), nullable=True),
|
||||||
|
sa.Column('status', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('approved_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('approved_by', sa.Integer(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['approved_by'], ['user.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
with op.batch_alter_table('user_registration', schema=None) as batch_op:
|
||||||
|
batch_op.create_index(batch_op.f('ix_user_registration_status'), ['status'], unique=False)
|
||||||
|
batch_op.create_index(batch_op.f('ix_user_registration_user_id'), ['user_id'], unique=False)
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('user_registration', schema=None) as batch_op:
|
||||||
|
batch_op.drop_index(batch_op.f('ix_user_registration_user_id'))
|
||||||
|
batch_op.drop_index(batch_op.f('ix_user_registration_status'))
|
||||||
|
|
||||||
|
op.drop_table('user_registration')
|
||||||
|
# ### end Alembic commands ###
|
Loading…
Add table
Reference in a new issue