Merge branch '292-change-email' into 'develop'
Fix #292: Users can now update their email address Closes #292 See merge request funkwhale/funkwhale!1191
This commit is contained in:
commit
36a6a0018e
|
@ -6,6 +6,7 @@ from django.utils.translation import gettext_lazy as _
|
|||
|
||||
from django.contrib import auth
|
||||
|
||||
from allauth.account import models as allauth_models
|
||||
from rest_auth.serializers import PasswordResetSerializer as PRS
|
||||
from rest_auth.registration.serializers import RegisterSerializer as RS, get_adapter
|
||||
from rest_framework import serializers
|
||||
|
@ -288,3 +289,29 @@ class LoginSerializer(serializers.Serializer):
|
|||
|
||||
def save(self, request):
|
||||
return auth.login(request, self.validated_data)
|
||||
|
||||
|
||||
class UserChangeEmailSerializer(serializers.Serializer):
|
||||
password = serializers.CharField()
|
||||
email = serializers.EmailField()
|
||||
|
||||
def validate_password(self, value):
|
||||
if not self.instance.check_password(value):
|
||||
raise serializers.ValidationError("Invalid password")
|
||||
|
||||
def validate_email(self, value):
|
||||
if (
|
||||
allauth_models.EmailAddress.objects.filter(email__iexact=value)
|
||||
.exclude(user=self.context["user"])
|
||||
.exists()
|
||||
):
|
||||
raise serializers.ValidationError("This email address is already in use")
|
||||
return value
|
||||
|
||||
def save(self, request):
|
||||
current, _ = allauth_models.EmailAddress.objects.get_or_create(
|
||||
user=request.user,
|
||||
email=request.user.email,
|
||||
defaults={"verified": False, "primary": True},
|
||||
)
|
||||
current.change(request, self.validated_data["email"], confirm=True)
|
||||
|
|
|
@ -111,6 +111,22 @@ class UserViewSet(mixins.UpdateModelMixin, viewsets.GenericViewSet):
|
|||
data = {"subsonic_api_token": self.request.user.subsonic_api_token}
|
||||
return Response(data)
|
||||
|
||||
@action(
|
||||
methods=["post"],
|
||||
required_scope="security",
|
||||
url_path="change-email",
|
||||
detail=False,
|
||||
)
|
||||
def change_email(self, request, *args, **kwargs):
|
||||
if not self.request.user.is_authenticated:
|
||||
return Response(status=403)
|
||||
serializer = serializers.UserChangeEmailSerializer(
|
||||
request.user, data=request.data, context={"user": request.user}
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save(request)
|
||||
return Response(status=204)
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
if not self.request.user.username == kwargs.get("username"):
|
||||
return Response(status=403)
|
||||
|
|
|
@ -568,3 +568,26 @@ def test_update_settings(logged_in_api_client, factories):
|
|||
logged_in_api_client.user.refresh_from_db()
|
||||
|
||||
assert logged_in_api_client.user.settings == {"foo": "bar", "theme": "dark"}
|
||||
|
||||
|
||||
def test_user_change_email_requires_valid_password(logged_in_api_client):
|
||||
url = reverse("api:v1:users:users-change-email")
|
||||
payload = {"password": "invalid", "email": "test@new.email"}
|
||||
response = logged_in_api_client.post(url, payload)
|
||||
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
def test_user_change_email(logged_in_api_client, mocker, mailoutbox):
|
||||
user = logged_in_api_client.user
|
||||
user.set_password("mypassword")
|
||||
url = reverse("api:v1:users:users-change-email")
|
||||
payload = {"password": "mypassword", "email": "test@new.email"}
|
||||
response = logged_in_api_client.post(url, payload)
|
||||
|
||||
address = user.emailaddress_set.latest("id")
|
||||
|
||||
assert address.email == payload["email"]
|
||||
assert address.verified is False
|
||||
assert response.status_code == 204
|
||||
assert len(mailoutbox) == 1
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Users can now update their email address (#292)
|
|
@ -310,6 +310,53 @@ paths:
|
|||
application/json:
|
||||
schema:
|
||||
$ref: "./api/definitions.yml#/Me"
|
||||
delete:
|
||||
summary: Delete the user account performing the request
|
||||
tags:
|
||||
- "Auth and security"
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
confirm:
|
||||
type: "boolean"
|
||||
description: "Must be set to true, to avoid accidental deletion"
|
||||
password:
|
||||
type: "string"
|
||||
description: "The current password of the account"
|
||||
responses:
|
||||
200:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "./api/definitions.yml#/Me"
|
||||
/api/v1/users/users/change-email/:
|
||||
post:
|
||||
summary: Update the email address associated with a user account
|
||||
tags:
|
||||
- "Auth and security"
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
email:
|
||||
type: "string"
|
||||
format: "email"
|
||||
password:
|
||||
type: "string"
|
||||
description: "The current password of the account"
|
||||
responses:
|
||||
200:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "./api/definitions.yml#/Me"
|
||||
|
||||
/api/v1/rate-limit/:
|
||||
get:
|
||||
|
|
|
@ -270,6 +270,38 @@
|
|||
<translate translate-context="Content/Settings/Button.Label">Manage plugins</translate>
|
||||
</router-link>
|
||||
</section>
|
||||
<section class="ui text container">
|
||||
<div class="ui hidden divider"></div>
|
||||
<h2 class="ui header">
|
||||
<i class="comment icon"></i>
|
||||
<div class="content">
|
||||
<translate translate-context="*/*/Button.Label">Change my email address</translate>
|
||||
</div>
|
||||
</h2>
|
||||
<p>
|
||||
<translate translate-context="Content/Settings/Paragraph'">Change the email address associated with your account. We will send a confirmation to the new address.</translate>
|
||||
</p>
|
||||
<p>
|
||||
<translate :translate-params="{email: $store.state.auth.profile.email}" translate-context="Content/Settings/Paragraph'">Your current email address is %{ email }.</translate>
|
||||
</p>
|
||||
<form class="ui form" @submit.prevent="changeEmail">
|
||||
<div v-if="changeEmailErrors.length > 0" role="alert" class="ui negative message">
|
||||
<h4 class="header"><translate translate-context="Content/Settings/Error message.Title">We cannot change your email address</translate></h4>
|
||||
<ul class="list">
|
||||
<li v-for="error in changeEmailErrors">{{ error }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="new-email"><translate translate-context="*/*/*">New email</translate></label>
|
||||
<input id="new-email" required v-model="newEmail" type="email" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="current-password-field-email"><translate translate-context="*/*/*">Password</translate></label>
|
||||
<password-input field-id="current-password-field-email" required v-model="emailPassword" />
|
||||
</div>
|
||||
<button type="submit" class="ui button"><translate translate-context="*/*/*">Update</translate></button>
|
||||
</form>
|
||||
</section>
|
||||
<section class="ui text container">
|
||||
<div class="ui hidden divider"></div>
|
||||
<h2 class="ui header">
|
||||
|
@ -339,6 +371,10 @@ export default {
|
|||
isLoading: false,
|
||||
isLoadingAvatar: false,
|
||||
isDeletingAccount: false,
|
||||
changeEmailErrors: [],
|
||||
isChangingEmail: false,
|
||||
newEmail: null,
|
||||
emailPassword: null,
|
||||
accountDeleteErrors: [],
|
||||
avatarErrors: [],
|
||||
apps: [],
|
||||
|
@ -519,6 +555,33 @@ export default {
|
|||
}
|
||||
)
|
||||
},
|
||||
|
||||
changeEmail() {
|
||||
this.isChangingEmail = true
|
||||
this.changeEmailErrors = []
|
||||
let self = this
|
||||
let payload = {
|
||||
password: this.emailPassword,
|
||||
email: this.newEmail,
|
||||
}
|
||||
axios.post(`users/users/change-email/`, payload)
|
||||
.then(
|
||||
response => {
|
||||
self.isChangingEmail = false
|
||||
self.newEmail = null
|
||||
self.emailPassword = null
|
||||
let msg = self.$pgettext('*/Auth/Message', 'Your email has been changed, please check your inbox for our confirmation message.')
|
||||
self.$store.commit('ui/addMessage', {
|
||||
content: msg,
|
||||
date: new Date()
|
||||
})
|
||||
},
|
||||
error => {
|
||||
self.isChangingEmail = false
|
||||
self.changeEmailErrors = error.backendErrors
|
||||
}
|
||||
)
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
labels() {
|
||||
|
|
Loading…
Reference in New Issue