Add ability to delete our account

This commit is contained in:
Chocobozzz 2018-08-08 10:55:27 +02:00
parent a031ab0b9b
commit 92b9d60c00
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
12 changed files with 144 additions and 4 deletions

View File

@ -0,0 +1 @@
export * from './my-account-danger-zone.component'

View File

@ -0,0 +1,5 @@
<div class="delete-me">
<p>Once you delete your account, there is no going back. Please be certain.</p>
<button (click)="deleteMe()">Delete your account</button>
</div>

View File

@ -0,0 +1,11 @@
@import '_variables';
@import '_mixins';
.delete-me {
font-size: 15px;
button {
@include peertube-button;
@include grey-button;
}
}

View File

@ -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)
)
}
}

View File

@ -14,3 +14,6 @@
<div i18n class="account-title">Video settings</div>
<my-account-video-settings [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-video-settings>
<div i18n class="account-title">Danger zone</div>
<my-account-danger-zone [user]="user"></my-account-danger-zone>

View File

@ -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: [

View File

@ -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'

View File

@ -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()

View File

@ -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,

View File

@ -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 = {

View File

@ -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 ])

View File

@ -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,