1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
|
"""
flask_security.change_username
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Flask-Security Change Username module
:copyright: (c) 2025-2025 by J. Christopher Wagner (jwag).
:license: MIT, see LICENSE for more details.
Allow user to change their username.
This is really just for when username is used as an authenticating
identity (and therefore has to be unique).
The basic feature allows an authenticated user to change their username
to any available username. Normalization and validation take place
using username_util.
This doesn't offer any defense against username enumeration by another user or
preventing a user from constantly changing their username. Applications
could use the UsernameUtil.check_username() method to implement this.
Think about: username is normally considered 'public' however as an identity
should we follow a change protocol more like email - require a confirmation from
the registered email.
"""
from __future__ import annotations
import typing as t
from flask import after_this_request, request
from flask import current_app
from flask_login import current_user
from wtforms import Field, SubmitField
from .decorators import auth_required
from .forms import (
Form,
build_form_from_request,
get_form_field_label,
)
from .proxies import _security, _datastore
from .quart_compat import get_quart_status
from .signals import username_changed
from .utils import (
base_render_json,
config_value as cv,
do_flash,
get_message,
get_url,
send_mail,
view_commit,
)
if t.TYPE_CHECKING: # pragma: no cover
from flask.typing import ResponseValue
if get_quart_status(): # pragma: no cover
from quart import redirect
else:
from flask import redirect
class ChangeUsernameForm(Form):
"""Change Username Form.
There is a single element - 'username'
that will be validated by calling :meth:`.UsernameUtil.validate`
then verified to be unique in the DB.
username field value injected at init_app time with build_username_field()
"""
username: t.ClassVar[Field]
submit = SubmitField(label=get_form_field_label("submit"))
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@auth_required(
lambda: cv("API_ENABLED_METHODS"),
within=lambda: cv("FRESHNESS"),
grace=lambda: cv("FRESHNESS_GRACE_PERIOD"),
)
def change_username() -> ResponseValue:
"""Start Change Username for an existing authenticated user"""
payload: dict[str, t.Any]
form: ChangeUsernameForm = t.cast(
ChangeUsernameForm, build_form_from_request("change_username_form")
)
if form.validate_on_submit():
# simple - just change username
form.user = current_user
after_this_request(view_commit)
update_username(form.user, form.username.data)
if _security._want_json(request):
return base_render_json(form)
do_flash(*get_message("USERNAME_CHANGE"))
return redirect(
get_url(cv("POST_CHANGE_USERNAME_VIEW")) or get_url(cv("POST_LOGIN_VIEW"))
)
if _security._want_json(request):
form.user = current_user
payload = dict(current_username=current_user.username)
return base_render_json(form, additional=payload)
return _security.render_template(
cv("CHANGE_USERNAME_TEMPLATE"),
change_username_form=form,
current_username=current_user.username,
**_security._run_ctx_processor("change_username"),
)
def update_username(user, new_username):
old_username = user.username
user.username = new_username
_datastore.put(user)
_send_username_changed_notice(user)
username_changed.send(
current_app._get_current_object(),
_async_wrapper=current_app.ensure_sync,
user=user,
old_username=old_username,
)
def _send_username_changed_notice(user):
"""Sends the username changed notice email for the specified user.
:param user: The user to send the notice to
"""
if cv("SEND_USERNAME_CHANGE_EMAIL"):
subject = cv("EMAIL_SUBJECT_USERNAME_CHANGE_NOTICE")
send_mail(subject, user.email, "change_username_notice", user=user)
|