aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-danger-zone/index.ts1
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.html5
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.scss11
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.ts46
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-settings.component.html3
-rw-r--r--client/src/app/+my-account/my-account.module.ts4
-rw-r--r--client/src/app/shared/users/user.service.ts10
-rw-r--r--server/controllers/api/users.ts18
-rw-r--r--server/middlewares/validators/users.ts14
-rw-r--r--server/tests/api/check-params/users.ts8
-rw-r--r--server/tests/api/users/users.ts17
-rw-r--r--server/tests/utils/users/users.ts11
12 files changed, 144 insertions, 4 deletions
diff --git a/client/src/app/+my-account/my-account-settings/my-account-danger-zone/index.ts b/client/src/app/+my-account/my-account-settings/my-account-danger-zone/index.ts
new file mode 100644
index 000000000..88a39bb97
--- /dev/null
+++ b/client/src/app/+my-account/my-account-settings/my-account-danger-zone/index.ts
@@ -0,0 +1 @@
export * from './my-account-danger-zone.component'
diff --git a/client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.html b/client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.html
new file mode 100644
index 000000000..8caff972f
--- /dev/null
+++ b/client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.html
@@ -0,0 +1,5 @@
1<div class="delete-me">
2 <p>Once you delete your account, there is no going back. Please be certain.</p>
3
4 <button (click)="deleteMe()">Delete your account</button>
5</div> \ No newline at end of file
diff --git a/client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.scss b/client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.scss
new file mode 100644
index 000000000..0ca310468
--- /dev/null
+++ b/client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.scss
@@ -0,0 +1,11 @@
1@import '_variables';
2@import '_mixins';
3
4.delete-me {
5 font-size: 15px;
6
7 button {
8 @include peertube-button;
9 @include grey-button;
10 }
11} \ No newline at end of file
diff --git a/client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.ts b/client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.ts
new file mode 100644
index 000000000..63a121f64
--- /dev/null
+++ b/client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.ts
@@ -0,0 +1,46 @@
1import { Component, Input } from '@angular/core'
2import { NotificationsService } from 'angular2-notifications'
3import { AuthService, ConfirmService, RedirectService } from '../../../core'
4import { UserService } from '../../../shared'
5import { I18n } from '@ngx-translate/i18n-polyfill'
6import { User } from '@app/shared'
7
8@Component({
9 selector: 'my-account-danger-zone',
10 templateUrl: './my-account-danger-zone.component.html',
11 styleUrls: [ './my-account-danger-zone.component.scss' ]
12})
13export class MyAccountDangerZoneComponent {
14 @Input() user: User = null
15
16 constructor (
17 private authService: AuthService,
18 private notificationsService: NotificationsService,
19 private userService: UserService,
20 private confirmService: ConfirmService,
21 private redirectService: RedirectService,
22 private i18n: I18n
23 ) { }
24
25 async deleteMe () {
26 const res = await this.confirmService.confirmWithInput(
27 this.i18n('Are you sure you want to delete your account? This will delete all you data, including channels, videos etc.'),
28 this.i18n('Type your username to confirm'),
29 this.user.username,
30 this.i18n('Delete your account'),
31 this.i18n('Delete my account')
32 )
33 if (res === false) return
34
35 this.userService.deleteMe().subscribe(
36 () => {
37 this.notificationsService.success(this.i18n('Success'), this.i18n('Your account is deleted.'))
38
39 this.authService.logout()
40 this.redirectService.redirectToHomepage()
41 },
42
43 err => this.notificationsService.error(this.i18n('Error'), err.message)
44 )
45 }
46}
diff --git a/client/src/app/+my-account/my-account-settings/my-account-settings.component.html b/client/src/app/+my-account/my-account-settings/my-account-settings.component.html
index ff08cb777..c7e23cd1f 100644
--- a/client/src/app/+my-account/my-account-settings/my-account-settings.component.html
+++ b/client/src/app/+my-account/my-account-settings/my-account-settings.component.html
@@ -14,3 +14,6 @@
14 14
15<div i18n class="account-title">Video settings</div> 15<div i18n class="account-title">Video settings</div>
16<my-account-video-settings [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-video-settings> 16<my-account-video-settings [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-video-settings>
17
18<div i18n class="account-title">Danger zone</div>
19<my-account-danger-zone [user]="user"></my-account-danger-zone> \ No newline at end of file
diff --git a/client/src/app/+my-account/my-account.module.ts b/client/src/app/+my-account/my-account.module.ts
index 5403ab649..29b49e8d9 100644
--- a/client/src/app/+my-account/my-account.module.ts
+++ b/client/src/app/+my-account/my-account.module.ts
@@ -13,6 +13,7 @@ import { MyAccountVideoChannelCreateComponent } from '@app/+my-account/my-accoun
13import { MyAccountVideoChannelUpdateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-update.component' 13import { MyAccountVideoChannelUpdateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-update.component'
14import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component' 14import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component'
15import { MyAccountVideoImportsComponent } from '@app/+my-account/my-account-video-imports/my-account-video-imports.component' 15import { MyAccountVideoImportsComponent } from '@app/+my-account/my-account-video-imports/my-account-video-imports.component'
16import { MyAccountDangerZoneComponent } from '@app/+my-account/my-account-settings/my-account-danger-zone'
16 17
17@NgModule({ 18@NgModule({
18 imports: [ 19 imports: [
@@ -32,7 +33,8 @@ import { MyAccountVideoImportsComponent } from '@app/+my-account/my-account-vide
32 MyAccountVideoChannelCreateComponent, 33 MyAccountVideoChannelCreateComponent,
33 MyAccountVideoChannelUpdateComponent, 34 MyAccountVideoChannelUpdateComponent,
34 ActorAvatarInfoComponent, 35 ActorAvatarInfoComponent,
35 MyAccountVideoImportsComponent 36 MyAccountVideoImportsComponent,
37 MyAccountDangerZoneComponent
36 ], 38 ],
37 39
38 exports: [ 40 exports: [
diff --git a/client/src/app/shared/users/user.service.ts b/client/src/app/shared/users/user.service.ts
index 365a21df7..e6dc3dbf8 100644
--- a/client/src/app/shared/users/user.service.ts
+++ b/client/src/app/shared/users/user.service.ts
@@ -39,6 +39,16 @@ export class UserService {
39 ) 39 )
40 } 40 }
41 41
42 deleteMe () {
43 const url = UserService.BASE_USERS_URL + 'me'
44
45 return this.authHttp.delete(url)
46 .pipe(
47 map(this.restExtractor.extractDataBool),
48 catchError(err => this.restExtractor.handleError(err))
49 )
50 }
51
42 changeAvatar (avatarForm: FormData) { 52 changeAvatar (avatarForm: FormData) {
43 const url = UserService.BASE_USERS_URL + 'me/avatar/pick' 53 const url = UserService.BASE_USERS_URL + 'me/avatar/pick'
44 54
diff --git a/server/controllers/api/users.ts b/server/controllers/api/users.ts
index 36bf0e0fe..3d2586c3a 100644
--- a/server/controllers/api/users.ts
+++ b/server/controllers/api/users.ts
@@ -30,6 +30,7 @@ import {
30 usersVideoRatingValidator 30 usersVideoRatingValidator
31} from '../../middlewares' 31} from '../../middlewares'
32import { 32import {
33 deleteMeValidator,
33 usersAskResetPasswordValidator, 34 usersAskResetPasswordValidator,
34 usersResetPasswordValidator, 35 usersResetPasswordValidator,
35 videoImportsSortValidator, 36 videoImportsSortValidator,
@@ -62,6 +63,11 @@ usersRouter.get('/me',
62 authenticate, 63 authenticate,
63 asyncMiddleware(getUserInformation) 64 asyncMiddleware(getUserInformation)
64) 65)
66usersRouter.delete('/me',
67 authenticate,
68 asyncMiddleware(deleteMeValidator),
69 asyncMiddleware(deleteMe)
70)
65 71
66usersRouter.get('/me/video-quota-used', 72usersRouter.get('/me/video-quota-used',
67 authenticate, 73 authenticate,
@@ -296,8 +302,18 @@ async function listUsers (req: express.Request, res: express.Response, next: exp
296 return res.json(getFormattedObjects(resultList.data, resultList.total)) 302 return res.json(getFormattedObjects(resultList.data, resultList.total))
297} 303}
298 304
305async function deleteMe (req: express.Request, res: express.Response) {
306 const user: UserModel = res.locals.oauth.token.User
307
308 await user.destroy()
309
310 auditLogger.delete(res.locals.oauth.token.User.Account.Actor.getIdentifier(), new UserAuditView(user.toFormattedJSON()))
311
312 return res.sendStatus(204)
313}
314
299async function removeUser (req: express.Request, res: express.Response, next: express.NextFunction) { 315async function removeUser (req: express.Request, res: express.Response, next: express.NextFunction) {
300 const user = await UserModel.loadById(req.params.id) 316 const user: UserModel = res.locals.user
301 317
302 await user.destroy() 318 await user.destroy()
303 319
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts
index 8ca9763a1..3c207c81f 100644
--- a/server/middlewares/validators/users.ts
+++ b/server/middlewares/validators/users.ts
@@ -74,6 +74,19 @@ const usersRemoveValidator = [
74 } 74 }
75] 75]
76 76
77const deleteMeValidator = [
78 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
79 const user: UserModel = res.locals.oauth.token.User
80 if (user.username === 'root') {
81 return res.status(400)
82 .send({ error: 'You cannot delete your root account.' })
83 .end()
84 }
85
86 return next()
87 }
88]
89
77const usersUpdateValidator = [ 90const usersUpdateValidator = [
78 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'), 91 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
79 body('email').optional().isEmail().withMessage('Should have a valid email attribute'), 92 body('email').optional().isEmail().withMessage('Should have a valid email attribute'),
@@ -215,6 +228,7 @@ const usersResetPasswordValidator = [
215 228
216export { 229export {
217 usersAddValidator, 230 usersAddValidator,
231 deleteMeValidator,
218 usersRegisterValidator, 232 usersRegisterValidator,
219 usersRemoveValidator, 233 usersRemoveValidator,
220 usersUpdateValidator, 234 usersUpdateValidator,
diff --git a/server/tests/api/check-params/users.ts b/server/tests/api/check-params/users.ts
index 62faabc54..60165ae22 100644
--- a/server/tests/api/check-params/users.ts
+++ b/server/tests/api/check-params/users.ts
@@ -8,7 +8,7 @@ import { UserRole, VideoImport, VideoImportState } from '../../../../shared'
8import { 8import {
9 createUser, flushTests, getMyUserInformation, getMyUserVideoRating, getUsersList, immutableAssign, killallServers, makeGetRequest, 9 createUser, flushTests, getMyUserInformation, getMyUserVideoRating, getUsersList, immutableAssign, killallServers, makeGetRequest,
10 makePostBodyRequest, makeUploadRequest, makePutBodyRequest, registerUser, removeUser, runServer, ServerInfo, setAccessTokensToServers, 10 makePostBodyRequest, makeUploadRequest, makePutBodyRequest, registerUser, removeUser, runServer, ServerInfo, setAccessTokensToServers,
11 updateUser, uploadVideo, userLogin 11 updateUser, uploadVideo, userLogin, deleteMe
12} from '../../utils' 12} from '../../utils'
13import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params' 13import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
14import { getMagnetURI, getMyVideoImports, getYoutubeVideoUrl, importVideo } from '../../utils/videos/video-imports' 14import { getMagnetURI, getMyVideoImports, getYoutubeVideoUrl, importVideo } from '../../utils/videos/video-imports'
@@ -469,6 +469,12 @@ describe('Test users API validators', function () {
469 }) 469 })
470 }) 470 })
471 471
472 describe('When deleting our account', function () {
473 it('Should fail with with the root account', async function () {
474 await deleteMe(server.url, server.accessToken, 400)
475 })
476 })
477
472 describe('When register a new user', function () { 478 describe('When register a new user', function () {
473 const registrationPath = path + '/register' 479 const registrationPath = path + '/register'
474 const baseCorrectParams = { 480 const baseCorrectParams = {
diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts
index 1ea599859..c9e8eb6f9 100644
--- a/server/tests/api/users/users.ts
+++ b/server/tests/api/users/users.ts
@@ -6,7 +6,8 @@ import { UserRole } from '../../../../shared/index'
6import { 6import {
7 createUser, flushTests, getBlacklistedVideosList, getMyUserInformation, getMyUserVideoQuotaUsed, getMyUserVideoRating, 7 createUser, flushTests, getBlacklistedVideosList, getMyUserInformation, getMyUserVideoQuotaUsed, getMyUserVideoRating,
8 getUserInformation, getUsersList, getUsersListPaginationAndSort, getVideosList, killallServers, login, makePutBodyRequest, rateVideo, 8 getUserInformation, getUsersList, getUsersListPaginationAndSort, getVideosList, killallServers, login, makePutBodyRequest, rateVideo,
9 registerUser, removeUser, removeVideo, runServer, ServerInfo, testImage, updateMyAvatar, updateMyUser, updateUser, uploadVideo, userLogin 9 registerUser, removeUser, removeVideo, runServer, ServerInfo, testImage, updateMyAvatar, updateMyUser, updateUser, uploadVideo, userLogin,
10 deleteMe
10} from '../../utils/index' 11} from '../../utils/index'
11import { follow } from '../../utils/server/follows' 12import { follow } from '../../utils/server/follows'
12import { setAccessTokensToServers } from '../../utils/users/login' 13import { setAccessTokensToServers } from '../../utils/users/login'
@@ -478,6 +479,20 @@ describe('Test users', function () {
478 expect(user.videoQuota).to.equal(5 * 1024 * 1024) 479 expect(user.videoQuota).to.equal(5 * 1024 * 1024)
479 }) 480 })
480 481
482 it('Should remove me', async function () {
483 {
484 const res = await getUsersList(server.url, server.accessToken)
485 expect(res.body.data.find(u => u.username === 'user_15')).to.not.be.undefined
486 }
487
488 await deleteMe(server.url, accessToken)
489
490 {
491 const res = await getUsersList(server.url, server.accessToken)
492 expect(res.body.data.find(u => u.username === 'user_15')).to.be.undefined
493 }
494 })
495
481 after(async function () { 496 after(async function () {
482 killallServers([ server ]) 497 killallServers([ server ])
483 498
diff --git a/server/tests/utils/users/users.ts b/server/tests/utils/users/users.ts
index 37b15f64a..e24e721bd 100644
--- a/server/tests/utils/users/users.ts
+++ b/server/tests/utils/users/users.ts
@@ -56,6 +56,16 @@ function getMyUserInformation (url: string, accessToken: string, specialStatus =
56 .expect('Content-Type', /json/) 56 .expect('Content-Type', /json/)
57} 57}
58 58
59function deleteMe (url: string, accessToken: string, specialStatus = 204) {
60 const path = '/api/v1/users/me'
61
62 return request(url)
63 .delete(path)
64 .set('Accept', 'application/json')
65 .set('Authorization', 'Bearer ' + accessToken)
66 .expect(specialStatus)
67}
68
59function getMyUserVideoQuotaUsed (url: string, accessToken: string, specialStatus = 200) { 69function getMyUserVideoQuotaUsed (url: string, accessToken: string, specialStatus = 200) {
60 const path = '/api/v1/users/me/video-quota-used' 70 const path = '/api/v1/users/me/video-quota-used'
61 71
@@ -216,6 +226,7 @@ export {
216 registerUser, 226 registerUser,
217 getMyUserInformation, 227 getMyUserInformation,
218 getMyUserVideoRating, 228 getMyUserVideoRating,
229 deleteMe,
219 getMyUserVideoQuotaUsed, 230 getMyUserVideoQuotaUsed,
220 getUsersList, 231 getUsersList,
221 getUsersListPaginationAndSort, 232 getUsersListPaginationAndSort,