--- /dev/null
+export * from './my-account-danger-zone.component'
--- /dev/null
+<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>
\ No newline at end of file
--- /dev/null
+@import '_variables';
+@import '_mixins';
+
+.delete-me {
+ font-size: 15px;
+
+ button {
+ @include peertube-button;
+ @include grey-button;
+ }
+}
\ No newline at end of file
--- /dev/null
+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)
+ )
+ }
+}
<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>
\ No newline at end of file
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: [
MyAccountVideoChannelCreateComponent,
MyAccountVideoChannelUpdateComponent,
ActorAvatarInfoComponent,
- MyAccountVideoImportsComponent
+ MyAccountVideoImportsComponent,
+ MyAccountDangerZoneComponent
],
exports: [
)
}
+ 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'
usersVideoRatingValidator
} from '../../middlewares'
import {
+ deleteMeValidator,
usersAskResetPasswordValidator,
usersResetPasswordValidator,
videoImportsSortValidator,
authenticate,
asyncMiddleware(getUserInformation)
)
+usersRouter.delete('/me',
+ authenticate,
+ asyncMiddleware(deleteMeValidator),
+ asyncMiddleware(deleteMe)
+)
usersRouter.get('/me/video-quota-used',
authenticate,
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()
}
]
+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'),
export {
usersAddValidator,
+ deleteMeValidator,
usersRegisterValidator,
usersRemoveValidator,
usersUpdateValidator,
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'
})
})
+ 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 = {
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'
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 ])
.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'
registerUser,
getMyUserInformation,
getMyUserVideoRating,
+ deleteMe,
getMyUserVideoQuotaUsed,
getUsersList,
getUsersListPaginationAndSort,