5
5
*/
6
6
7
7
import { injectable , inject } from "inversify" ;
8
- import { UserDB , WorkspaceDB , TeamDB , ProjectDB } from "@gitpod/gitpod-db/lib" ;
8
+ import { WorkspaceDB , TeamDB , ProjectDB } from "@gitpod/gitpod-db/lib" ;
9
9
import { User , Workspace } from "@gitpod/gitpod-protocol" ;
10
10
import { StorageClient } from "../storage/storage-client" ;
11
11
import { log } from "@gitpod/gitpod-protocol/lib/util/logging" ;
12
12
import { StopWorkspacePolicy } from "@gitpod/ws-manager/lib" ;
13
13
import { AuthProviderService } from "../auth/auth-provider-service" ;
14
- import { IAnalyticsWriter } from "@gitpod/gitpod-protocol/lib/analytics" ;
15
14
import { WorkspaceService } from "../workspace/workspace-service" ;
16
- import { Authorizer } from "../authorization/authorizer " ;
17
- import { ApplicationError , ErrorCodes } from "@gitpod/gitpod-protocol /lib/messaging/error " ;
15
+ import { UserService } from "./user-service " ;
16
+ import { TransactionalContext } from "@gitpod/gitpod-db /lib/typeorm/transactional-db-impl " ;
18
17
19
18
@injectable ( )
20
19
export class UserDeletionService {
21
20
constructor (
22
- @inject ( UserDB ) private readonly db : UserDB ,
21
+ @inject ( UserService ) private readonly userService : UserService ,
23
22
@inject ( WorkspaceDB ) private readonly workspaceDb : WorkspaceDB ,
24
23
@inject ( TeamDB ) private readonly teamDb : TeamDB ,
25
24
@inject ( ProjectDB ) private readonly projectDb : ProjectDB ,
26
25
@inject ( StorageClient ) private readonly storageClient : StorageClient ,
27
26
@inject ( WorkspaceService ) private readonly workspaceService : WorkspaceService ,
28
27
@inject ( AuthProviderService ) private readonly authProviderService : AuthProviderService ,
29
- @inject ( IAnalyticsWriter ) private readonly analytics : IAnalyticsWriter ,
30
- @inject ( Authorizer ) private readonly authorizer : Authorizer ,
31
- ) { }
32
-
33
- /**
34
- * This method deletes a User logically. The contract here is that after running this method without receiving an
35
- * error, the system does not contain any data that is relatable to the actual person in the sense of the GDPR.
36
- * To guarantee that, but also maintain traceability
37
- * we anonymize data that might contain user related/relatable data and keep the entities itself (incl. ids).
38
- */
39
- async deleteUser ( userId : string , targetUserId : string ) : Promise < void > {
40
- await this . authorizer . checkPermissionOnUser ( userId , "delete" , targetUserId ) ;
41
- const user = await this . db . findUserById ( targetUserId ) ;
42
- if ( ! user ) {
43
- throw new ApplicationError ( ErrorCodes . NOT_FOUND , `No user with id ${ targetUserId } found!` ) ;
44
- }
45
-
46
- if ( user . markedDeleted === true ) {
47
- log . debug ( { userId : targetUserId } , "Is deleted but markDeleted already set. Continuing." ) ;
48
- }
28
+ ) {
29
+ this . userService . onDeleteUser ( async ( subjectId , user , ctx ) => {
30
+ await this . contributeToDeleteUser ( subjectId , user , ctx ) ;
31
+ } ) ;
32
+ }
49
33
34
+ private async contributeToDeleteUser ( userId : string , user : User , ctx : TransactionalContext ) : Promise < void > {
50
35
// Stop all workspaces
51
36
await this . workspaceService . stopRunningWorkspacesForUser (
52
37
{ } ,
@@ -62,70 +47,20 @@ export class UserDeletionService {
62
47
try {
63
48
await this . authProviderService . deleteAuthProvider ( provider ) ;
64
49
} catch ( error ) {
65
- log . error ( { userId : targetUserId } , "Failed to delete user's auth provider." , error ) ;
50
+ log . error ( { userId : user . id } , "Failed to delete user's auth provider." , error ) ;
66
51
}
67
52
}
68
53
69
- // User
70
- await this . db . transaction ( async ( db ) => {
71
- this . anonymizeUser ( user ) ;
72
- this . deleteIdentities ( user ) ;
73
- await this . deleteTokens ( db , user ) ;
74
- user . lastVerificationTime = undefined ;
75
- user . markedDeleted = true ;
76
- await db . storeUser ( user ) ;
77
- } ) ;
78
-
79
54
await Promise . all ( [
80
55
// Workspace
81
- this . anonymizeAllWorkspaces ( targetUserId ) ,
56
+ this . anonymizeAllWorkspaces ( user . id ) ,
82
57
// Bucket
83
- this . deleteUserBucket ( targetUserId ) ,
58
+ this . deleteUserBucket ( user . id ) ,
84
59
// Teams owned only by this user
85
- this . deleteSoleOwnedTeams ( targetUserId ) ,
60
+ this . deleteSoleOwnedTeams ( user . id ) ,
86
61
// Team memberships
87
- this . deleteTeamMemberships ( targetUserId ) ,
62
+ this . deleteTeamMemberships ( user . id ) ,
88
63
] ) ;
89
-
90
- // Track the deletion Event for Analytics Purposes
91
- this . analytics . track ( {
92
- userId : user . id ,
93
- event : "deletion" ,
94
- properties : {
95
- deleted_at : new Date ( ) . toISOString ( ) ,
96
- } ,
97
- } ) ;
98
- this . analytics . identify ( {
99
- userId : user . id ,
100
- traits : {
101
- github_slug : "deleted-user" ,
102
- gitlab_slug : "deleted-user" ,
103
- bitbucket_slug : "deleted-user" ,
104
- email : "deleted-user" ,
105
- full_name : "deleted-user" ,
106
- name : "deleted-user" ,
107
- } ,
108
- } ) ;
109
- }
110
-
111
- private anonymizeUser ( user : User ) {
112
- user . avatarUrl = "deleted-avatarUrl" ;
113
- user . fullName = "deleted-fullName" ;
114
- user . name = "deleted-Name" ;
115
- if ( user . verificationPhoneNumber ) {
116
- user . verificationPhoneNumber = "deleted-phoneNumber" ;
117
- }
118
- }
119
-
120
- private deleteIdentities ( user : User ) {
121
- for ( const identity of user . identities ) {
122
- identity . deleted = true ; // This triggers the HARD DELETION of the identity
123
- }
124
- }
125
-
126
- private async deleteTokens ( db : UserDB , user : User ) {
127
- const tokenDeletions = user . identities . map ( ( identity ) => db . deleteTokens ( identity ) ) ;
128
- await Promise . all ( tokenDeletions ) ;
129
64
}
130
65
131
66
private async anonymizeAllWorkspaces ( userId : string ) {
0 commit comments