Add ability to delete our account
This commit is contained in:
parent
a031ab0b9b
commit
92b9d60c00
|
@ -0,0 +1 @@
|
|||
export * from './my-account-danger-zone.component'
|
|
@ -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>
|
|
@ -0,0 +1,11 @@
|
|||
@import '_variables';
|
||||
@import '_mixins';
|
||||
|
||||
.delete-me {
|
||||
font-size: 15px;
|
||||
|
||||
button {
|
||||
@include peertube-button;
|
||||
@include grey-button;
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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: [
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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 ])
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue