Skip to content

Commit e060477

Browse files
authored
fix: not ask for password when deleting account (#498)
1 parent a1ab441 commit e060477

File tree

4 files changed

+61
-52
lines changed

4 files changed

+61
-52
lines changed

lms/static/js/student_account/AccountsClient.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
import 'whatwg-fetch';
22
import Cookies from 'js-cookie';
33

4-
const deactivate = (password) => fetch('/api/user/v1/accounts/deactivate_logout/', {
4+
const deactivate = () => fetch('/api/user/v1/accounts/deactivate_logout/', {
55
method: 'POST',
66
credentials: 'same-origin',
77
headers: {
88
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
99
'X-CSRFToken': Cookies.get('csrftoken'),
1010
},
1111
// URLSearchParams + polyfill doesn't work in IE11
12-
body: `password=${encodeURIComponent(password)}`,
1312
}).then((response) => {
1413
if (response.ok) {
1514
return response;

lms/static/js/student_account/components/StudentAccountDeletion.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export class StudentAccountDeletion extends React.Component {
4949
const showError = socialAuthConnected || !isActive;
5050

5151
const socialAuthError = StringUtils.interpolate(
52-
gettext('Before proceeding, please {htmlStart}unlink all social media accounts{htmlEnd}.'),
52+
gettext('Before proceeding, please {htmlStart}unlink all linked accounts{htmlEnd}.'),
5353
{
5454
htmlStart: '<a href="https://support.edx.org/hc/en-us/articles/207206067" rel="noopener" target="_blank">',
5555
htmlEnd: '</a>',

lms/static/js/student_account/components/StudentAccountDeletionModal.jsx

Lines changed: 50 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,18 @@ class StudentAccountDeletionConfirmationModal extends React.Component {
1313
super(props);
1414

1515
this.deleteAccount = this.deleteAccount.bind(this);
16-
this.handlePasswordInputChange = this.handlePasswordInputChange.bind(this);
17-
this.passwordFieldValidation = this.passwordFieldValidation.bind(this);
16+
this.handleConfirmationInputChange = this.handleConfirmationInputChange.bind(this);
17+
this.confirmationFieldValidation = this.confirmationFieldValidation.bind(this);
1818
this.handleConfirmationModalClose = this.handleConfirmationModalClose.bind(this);
19+
20+
// The text user needs to type to confirm deletion
21+
// TODO: This should be stored in a config file
22+
this.confirmationText = 'DELETE_MY_ACCOUNT';
23+
1924
this.state = {
20-
password: '',
21-
passwordSubmitted: false,
22-
passwordValid: true,
25+
confirmationInput: '',
26+
confirmationSubmitted: false,
27+
confirmationValid: true,
2328
validationMessage: '',
2429
validationErrorDetails: '',
2530
accountQueuedForDeletion: false,
@@ -36,13 +41,13 @@ class StudentAccountDeletionConfirmationModal extends React.Component {
3641

3742
deleteAccount() {
3843
return this.setState(
39-
{ passwordSubmitted: true },
44+
{ confirmationSubmitted: true },
4045
() => (
41-
deactivate(this.state.password)
46+
deactivate()
4247
.then(() => this.setState({
4348
accountQueuedForDeletion: true,
4449
responseError: false,
45-
passwordSubmitted: false,
50+
confirmationSubmitted: false,
4651
validationMessage: '',
4752
validationErrorDetails: '',
4853
}))
@@ -52,41 +57,51 @@ class StudentAccountDeletionConfirmationModal extends React.Component {
5257
}
5358

5459
failedSubmission(error) {
55-
const title = error.message === '403' ? gettext('Password is incorrect') : gettext('Unable to delete account');
56-
const body = error.message === '403' ? gettext('Please re-enter your password.') : gettext('Sorry, there was an error trying to process your request. Please try again later.');
60+
const title = gettext('Unable to delete account');
61+
const body = gettext('Sorry, there was an error trying to process your request. Please try again later.');
5762

5863
this.setState({
59-
passwordSubmitted: false,
64+
confirmationSubmitted: false,
6065
responseError: true,
61-
passwordValid: false,
66+
confirmationValid: false,
6267
validationMessage: title,
6368
validationErrorDetails: body,
6469
});
6570
}
6671

67-
handlePasswordInputChange(value) {
68-
this.setState({ password: value });
72+
handleConfirmationInputChange(value) {
73+
this.setState({ confirmationInput: value });
74+
this.confirmationFieldValidation(value);
6975
}
7076

71-
passwordFieldValidation(value) {
72-
let feedback = { passwordValid: true };
77+
confirmationFieldValidation(value) {
78+
let feedback = { confirmationValid: true };
7379

7480
if (value.length < 1) {
7581
feedback = {
76-
passwordValid: false,
77-
validationMessage: gettext('A Password is required'),
82+
confirmationValid: false,
83+
validationMessage: gettext('Confirmation text is required'),
7884
validationErrorDetails: '',
7985
};
86+
} else if (value !== this.confirmationText) {
87+
feedback = {
88+
confirmationValid: false,
89+
validationMessage: gettext('Confirmation text does not match'),
90+
validationErrorDetails: StringUtils.interpolate(
91+
gettext('Please type "{confirmationText}" exactly as shown.'),
92+
{ confirmationText: this.confirmationText }
93+
),
94+
};
8095
}
8196

8297
this.setState(feedback);
8398
}
8499

85100
renderConfirmationModal() {
86101
const {
87-
passwordValid,
88-
password,
89-
passwordSubmitted,
102+
confirmationValid,
103+
confirmationInput,
104+
confirmationSubmitted,
90105
responseError,
91106
validationErrorDetails,
92107
validationMessage,
@@ -123,6 +138,10 @@ class StudentAccountDeletionConfirmationModal extends React.Component {
123138
},
124139
);
125140

141+
const confirmationInstructions = StringUtils.interpolate(
142+
gettext('If you still wish to continue and delete your account, please type "{confirmationText}" in the box below:'),
143+
{ confirmationText: this.confirmationText }
144+
);
126145

127146
return (
128147
<div className="delete-confirmation-wrapper">
@@ -172,18 +191,18 @@ class StudentAccountDeletionConfirmationModal extends React.Component {
172191
dismissible={false}
173192
open
174193
/>
175-
<p className="next-steps">{ gettext('If you still wish to continue and delete your account, please enter your account password:') }</p>
194+
<p className="next-steps">{ confirmationInstructions }</p>
176195
<InputText
177-
name="confirm-password"
178-
label="Password"
179-
type="password"
180-
className={['confirm-password-input']}
181-
onBlur={this.passwordFieldValidation}
182-
isValid={passwordValid}
196+
name="confirm-deletion"
197+
type="text"
198+
className={['confirm-deletion-input']}
199+
onBlur={this.confirmationFieldValidation}
200+
isValid={confirmationValid}
183201
validationMessage={validationMessage}
184-
onChange={this.handlePasswordInputChange}
185-
autoComplete="new-password"
202+
onChange={this.handleConfirmationInputChange}
203+
autoComplete="off"
186204
themes={['danger']}
205+
placeholder={gettext('Type confirmation text here')}
187206
/>
188207
</div>
189208
)}
@@ -192,7 +211,7 @@ class StudentAccountDeletionConfirmationModal extends React.Component {
192211
<Button
193212
label={gettext('Yes, Delete')}
194213
onClick={this.deleteAccount}
195-
disabled={password.length === 0 || passwordSubmitted}
214+
disabled={confirmationInput.length === 0 || !confirmationValid || confirmationSubmitted}
196215
/>,
197216
]}
198217
/>

openedx/core/djangoapps/user_api/accounts/views.py

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -488,37 +488,29 @@ def post(self, request, username):
488488
class DeactivateLogoutView(APIView):
489489
"""
490490
POST /api/user/v1/accounts/deactivate_logout/
491-
{
492-
"password": "example_password",
493-
}
491+
{}
494492
495493
**POST Parameters**
496494
497-
A POST request must include the following parameter.
498-
499-
* password: Required. The current password of the user being deactivated.
495+
A POST request with an empty body is sufficient for authenticated users.
500496
501497
**POST Response Values**
502498
503-
If the request does not specify a username or submits a username
504-
for a non-existent user, the request returns an HTTP 404 "Not Found"
505-
response.
506-
507-
If a user who is not a superuser tries to deactivate a user,
508-
the request returns an HTTP 403 "Forbidden" response.
499+
If the user is not authenticated, the request returns an HTTP 401
500+
"Unauthorized" response.
509501
510502
If the specified user is successfully deactivated, the request
511503
returns an HTTP 204 "No Content" response.
512504
513505
If an unanticipated error occurs, the request returns an
514506
HTTP 500 "Internal Server Error" response.
515507
516-
Allows an LMS user to take the following actions:
508+
Allows an authenticated LMS user to take the following actions:
517509
- Change the user's password permanently to Django's unusable password
518510
- Log the user out
519511
- Create a row in the retirement table for that user
520512
"""
521-
authentication_classes = (JwtAuthentication, SessionAuthentication,)
513+
authentication_classes = (SessionAuthentication,)
522514
permission_classes = (permissions.IsAuthenticated,)
523515

524516
def post(self, request):
@@ -530,10 +522,9 @@ def post(self, request):
530522
"""
531523
user_model = get_user_model()
532524
try:
533-
# Get the username from the request and check that it exists
534-
verify_user_password_response = self._verify_user_password(request)
535-
if verify_user_password_response.status_code != status.HTTP_204_NO_CONTENT:
536-
return verify_user_password_response
525+
# Since we're removing password verification, we only need to ensure
526+
# TODO: make password verfication conditional depending on wether the user account was created through 3rd party auth or not.
527+
# the user is authenticated (handled by permission_classes)
537528
with transaction.atomic():
538529
user_email = request.user.email
539530
create_retirement_request_and_deactivate_account(request.user)

0 commit comments

Comments
 (0)