From 92b9d60c00432c58d6184f3683bdb14a0300a3c6 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 8 Aug 2018 10:55:27 +0200 Subject: [PATCH] Add ability to delete our account --- .../my-account-danger-zone/index.ts | 1 + .../my-account-danger-zone.component.html | 5 ++ .../my-account-danger-zone.component.scss | 11 +++++ .../my-account-danger-zone.component.ts | 46 +++++++++++++++++++ .../my-account-settings.component.html | 3 ++ .../src/app/+my-account/my-account.module.ts | 4 +- client/src/app/shared/users/user.service.ts | 10 ++++ server/controllers/api/users.ts | 18 +++++++- server/middlewares/validators/users.ts | 14 ++++++ server/tests/api/check-params/users.ts | 8 +++- server/tests/api/users/users.ts | 17 ++++++- server/tests/utils/users/users.ts | 11 +++++ 12 files changed, 144 insertions(+), 4 deletions(-) create mode 100644 client/src/app/+my-account/my-account-settings/my-account-danger-zone/index.ts create mode 100644 client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.html create mode 100644 client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.scss create mode 100644 client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.ts 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 @@ +
+

Once you delete your account, there is no going back. Please be certain.

+ + +
\ 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 @@ +@import '_variables'; +@import '_mixins'; + +.delete-me { + font-size: 15px; + + button { + @include peertube-button; + @include grey-button; + } +} \ 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 @@ +import { Component, Input } from '@angular/core' +import { NotificationsService } from 'angular2-notifications' +import { AuthService, ConfirmService, RedirectService } from '../../../core' +import { UserService } from '../../../shared' +import { I18n } from '@ngx-translate/i18n-polyfill' +import { User } from '@app/shared' + +@Component({ + selector: 'my-account-danger-zone', + templateUrl: './my-account-danger-zone.component.html', + styleUrls: [ './my-account-danger-zone.component.scss' ] +}) +export class MyAccountDangerZoneComponent { + @Input() user: User = null + + constructor ( + private authService: AuthService, + private notificationsService: NotificationsService, + private userService: UserService, + private confirmService: ConfirmService, + private redirectService: RedirectService, + private i18n: I18n + ) { } + + async deleteMe () { + const res = await this.confirmService.confirmWithInput( + this.i18n('Are you sure you want to delete your account? This will delete all you data, including channels, videos etc.'), + this.i18n('Type your username to confirm'), + this.user.username, + this.i18n('Delete your account'), + this.i18n('Delete my account') + ) + if (res === false) return + + this.userService.deleteMe().subscribe( + () => { + this.notificationsService.success(this.i18n('Success'), this.i18n('Your account is deleted.')) + + this.authService.logout() + this.redirectService.redirectToHomepage() + }, + + err => this.notificationsService.error(this.i18n('Error'), err.message) + ) + } +} 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 @@
Video settings
+ +
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 import { MyAccountVideoChannelUpdateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-update.component' import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component' import { MyAccountVideoImportsComponent } from '@app/+my-account/my-account-video-imports/my-account-video-imports.component' +import { MyAccountDangerZoneComponent } from '@app/+my-account/my-account-settings/my-account-danger-zone' @NgModule({ imports: [ @@ -32,7 +33,8 @@ import { MyAccountVideoImportsComponent } from '@app/+my-account/my-account-vide MyAccountVideoChannelCreateComponent, MyAccountVideoChannelUpdateComponent, ActorAvatarInfoComponent, - MyAccountVideoImportsComponent + MyAccountVideoImportsComponent, + MyAccountDangerZoneComponent ], 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 { ) } + deleteMe () { + const url = UserService.BASE_USERS_URL + 'me' + + return this.authHttp.delete(url) + .pipe( + map(this.restExtractor.extractDataBool), + catchError(err => this.restExtractor.handleError(err)) + ) + } + changeAvatar (avatarForm: FormData) { const url = UserService.BASE_USERS_URL + 'me/avatar/pick' 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 { usersVideoRatingValidator } from '../../middlewares' import { + deleteMeValidator, usersAskResetPasswordValidator, usersResetPasswordValidator, videoImportsSortValidator, @@ -62,6 +63,11 @@ usersRouter.get('/me', authenticate, asyncMiddleware(getUserInformation) ) +usersRouter.delete('/me', + authenticate, + asyncMiddleware(deleteMeValidator), + asyncMiddleware(deleteMe) +) usersRouter.get('/me/video-quota-used', authenticate, @@ -296,8 +302,18 @@ async function listUsers (req: express.Request, res: express.Response, next: exp return res.json(getFormattedObjects(resultList.data, resultList.total)) } +async function deleteMe (req: express.Request, res: express.Response) { + const user: UserModel = res.locals.oauth.token.User + + await user.destroy() + + auditLogger.delete(res.locals.oauth.token.User.Account.Actor.getIdentifier(), new UserAuditView(user.toFormattedJSON())) + + return res.sendStatus(204) +} + async function removeUser (req: express.Request, res: express.Response, next: express.NextFunction) { - const user = await UserModel.loadById(req.params.id) + const user: UserModel = res.locals.user await user.destroy() 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 = [ } ] +const deleteMeValidator = [ + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + const user: UserModel = res.locals.oauth.token.User + if (user.username === 'root') { + return res.status(400) + .send({ error: 'You cannot delete your root account.' }) + .end() + } + + return next() + } +] + const usersUpdateValidator = [ param('id').isInt().not().isEmpty().withMessage('Should have a valid id'), body('email').optional().isEmail().withMessage('Should have a valid email attribute'), @@ -215,6 +228,7 @@ const usersResetPasswordValidator = [ export { usersAddValidator, + deleteMeValidator, usersRegisterValidator, usersRemoveValidator, 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' import { createUser, flushTests, getMyUserInformation, getMyUserVideoRating, getUsersList, immutableAssign, killallServers, makeGetRequest, makePostBodyRequest, makeUploadRequest, makePutBodyRequest, registerUser, removeUser, runServer, ServerInfo, setAccessTokensToServers, - updateUser, uploadVideo, userLogin + updateUser, uploadVideo, userLogin, deleteMe } from '../../utils' import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params' import { getMagnetURI, getMyVideoImports, getYoutubeVideoUrl, importVideo } from '../../utils/videos/video-imports' @@ -469,6 +469,12 @@ describe('Test users API validators', function () { }) }) + describe('When deleting our account', function () { + it('Should fail with with the root account', async function () { + await deleteMe(server.url, server.accessToken, 400) + }) + }) + describe('When register a new user', function () { const registrationPath = path + '/register' 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' import { createUser, flushTests, getBlacklistedVideosList, getMyUserInformation, getMyUserVideoQuotaUsed, getMyUserVideoRating, getUserInformation, getUsersList, getUsersListPaginationAndSort, getVideosList, killallServers, login, makePutBodyRequest, rateVideo, - registerUser, removeUser, removeVideo, runServer, ServerInfo, testImage, updateMyAvatar, updateMyUser, updateUser, uploadVideo, userLogin + registerUser, removeUser, removeVideo, runServer, ServerInfo, testImage, updateMyAvatar, updateMyUser, updateUser, uploadVideo, userLogin, + deleteMe } from '../../utils/index' import { follow } from '../../utils/server/follows' import { setAccessTokensToServers } from '../../utils/users/login' @@ -478,6 +479,20 @@ describe('Test users', function () { expect(user.videoQuota).to.equal(5 * 1024 * 1024) }) + it('Should remove me', async function () { + { + const res = await getUsersList(server.url, server.accessToken) + expect(res.body.data.find(u => u.username === 'user_15')).to.not.be.undefined + } + + await deleteMe(server.url, accessToken) + + { + const res = await getUsersList(server.url, server.accessToken) + expect(res.body.data.find(u => u.username === 'user_15')).to.be.undefined + } + }) + after(async function () { killallServers([ server ]) 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 = .expect('Content-Type', /json/) } +function deleteMe (url: string, accessToken: string, specialStatus = 204) { + const path = '/api/v1/users/me' + + return request(url) + .delete(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .expect(specialStatus) +} + function getMyUserVideoQuotaUsed (url: string, accessToken: string, specialStatus = 200) { const path = '/api/v1/users/me/video-quota-used' @@ -216,6 +226,7 @@ export { registerUser, getMyUserInformation, getMyUserVideoRating, + deleteMe, getMyUserVideoQuotaUsed, getUsersList, getUsersListPaginationAndSort, -- 2.41.0