@@ -64,67 +64,25 @@ export class ImportCredentialsCommand extends BaseCommand {
64
64
}
65
65
}
66
66
67
- let totalImported = 0 ;
68
-
69
- const cipher = Container . get ( Cipher ) ;
70
67
const user = flags . userId ? await this . getAssignee ( flags . userId ) : await this . getOwner ( ) ;
71
68
72
- if ( flags . separate ) {
73
- let { input : inputPath } = flags ;
74
-
75
- if ( process . platform === 'win32' ) {
76
- inputPath = inputPath . replace ( / \\ / g, '/' ) ;
77
- }
78
-
79
- const files = await glob ( '*.json' , {
80
- cwd : inputPath ,
81
- absolute : true ,
82
- } ) ;
83
-
84
- totalImported = files . length ;
85
-
86
- await Db . getConnection ( ) . transaction ( async ( transactionManager ) => {
87
- this . transactionManager = transactionManager ;
88
- for ( const file of files ) {
89
- const credential = jsonParse < ICredentialsEncrypted > (
90
- fs . readFileSync ( file , { encoding : 'utf8' } ) ,
91
- ) ;
92
- if ( typeof credential . data === 'object' ) {
93
- // plain data / decrypted input. Should be encrypted first.
94
- credential . data = cipher . encrypt ( credential . data ) ;
95
- }
96
- await this . storeCredential ( credential , user ) ;
97
- }
98
- } ) ;
69
+ const credentials = await this . readCredentials ( flags . input , flags . separate ) ;
99
70
100
- this . reportSuccess ( totalImported ) ;
101
- return ;
102
- }
103
-
104
- const credentials = jsonParse < ICredentialsEncrypted [ ] > (
105
- fs . readFileSync ( flags . input , { encoding : 'utf8' } ) ,
106
- ) ;
71
+ await Db . getConnection ( ) . transaction ( async ( transactionManager ) => {
72
+ this . transactionManager = transactionManager ;
107
73
108
- totalImported = credentials . length ;
74
+ const result = await this . checkRelations ( credentials , flags . userId ) ;
109
75
110
- if ( ! Array . isArray ( credentials ) ) {
111
- throw new ApplicationError (
112
- 'File does not seem to contain credentials. Make sure the credentials are contained in an array.' ,
113
- ) ;
114
- }
76
+ if ( ! result . success ) {
77
+ throw new ApplicationError ( result . message ) ;
78
+ }
115
79
116
- await Db . getConnection ( ) . transaction ( async ( transactionManager ) => {
117
- this . transactionManager = transactionManager ;
118
80
for ( const credential of credentials ) {
119
- if ( typeof credential . data === 'object' ) {
120
- // plain data / decrypted input. Should be encrypted first.
121
- credential . data = cipher . encrypt ( credential . data ) ;
122
- }
123
81
await this . storeCredential ( credential , user ) ;
124
82
}
125
83
} ) ;
126
84
127
- this . reportSuccess ( totalImported ) ;
85
+ this . reportSuccess ( credentials . length ) ;
128
86
}
129
87
130
88
async catch ( error : Error ) {
@@ -142,15 +100,23 @@ export class ImportCredentialsCommand extends BaseCommand {
142
100
143
101
private async storeCredential ( credential : Partial < CredentialsEntity > , user : User ) {
144
102
const result = await this . transactionManager . upsert ( CredentialsEntity , credential , [ 'id' ] ) ;
145
- await this . transactionManager . upsert (
146
- SharedCredentials ,
147
- {
148
- credentialsId : result . identifiers [ 0 ] . id as string ,
149
- userId : user . id ,
150
- role : 'credential:owner' ,
151
- } ,
152
- [ 'credentialsId' , 'userId' ] ,
153
- ) ;
103
+
104
+ const sharingExists = await this . transactionManager . existsBy ( SharedCredentials , {
105
+ credentialsId : credential . id ,
106
+ role : 'credential:owner' ,
107
+ } ) ;
108
+
109
+ if ( ! sharingExists ) {
110
+ await this . transactionManager . upsert (
111
+ SharedCredentials ,
112
+ {
113
+ credentialsId : result . identifiers [ 0 ] . id as string ,
114
+ userId : user . id ,
115
+ role : 'credential:owner' ,
116
+ } ,
117
+ [ 'credentialsId' , 'userId' ] ,
118
+ ) ;
119
+ }
154
120
}
155
121
156
122
private async getOwner ( ) {
@@ -162,6 +128,84 @@ export class ImportCredentialsCommand extends BaseCommand {
162
128
return owner ;
163
129
}
164
130
131
+ private async checkRelations ( credentials : ICredentialsEncrypted [ ] , userId ?: string ) {
132
+ if ( ! userId ) {
133
+ return {
134
+ success : true as const ,
135
+ message : undefined ,
136
+ } ;
137
+ }
138
+
139
+ for ( const credential of credentials ) {
140
+ if ( credential . id === undefined ) {
141
+ continue ;
142
+ }
143
+
144
+ if ( ! ( await this . credentialExists ( credential . id ) ) ) {
145
+ continue ;
146
+ }
147
+
148
+ const ownerId = await this . getCredentialOwner ( credential . id ) ;
149
+ if ( ! ownerId ) {
150
+ continue ;
151
+ }
152
+
153
+ if ( ownerId !== userId ) {
154
+ return {
155
+ success : false as const ,
156
+ message : `The credential with id "${ credential . id } " is already owned by the user with the id "${ ownerId } ". It can't be re-owned by the user with the id "${ userId } "` ,
157
+ } ;
158
+ }
159
+ }
160
+
161
+ return {
162
+ success : true as const ,
163
+ message : undefined ,
164
+ } ;
165
+ }
166
+
167
+ private async readCredentials ( path : string , separate : boolean ) : Promise < ICredentialsEncrypted [ ] > {
168
+ const cipher = Container . get ( Cipher ) ;
169
+
170
+ if ( process . platform === 'win32' ) {
171
+ path = path . replace ( / \\ / g, '/' ) ;
172
+ }
173
+
174
+ let credentials : ICredentialsEncrypted [ ] ;
175
+
176
+ if ( separate ) {
177
+ const files = await glob ( '*.json' , {
178
+ cwd : path ,
179
+ absolute : true ,
180
+ } ) ;
181
+
182
+ credentials = files . map ( ( file ) =>
183
+ jsonParse < ICredentialsEncrypted > ( fs . readFileSync ( file , { encoding : 'utf8' } ) ) ,
184
+ ) ;
185
+ } else {
186
+ const credentialsUnchecked = jsonParse < ICredentialsEncrypted [ ] > (
187
+ fs . readFileSync ( path , { encoding : 'utf8' } ) ,
188
+ ) ;
189
+
190
+ if ( ! Array . isArray ( credentialsUnchecked ) ) {
191
+ throw new ApplicationError (
192
+ 'File does not seem to contain credentials. Make sure the credentials are contained in an array.' ,
193
+ ) ;
194
+ }
195
+
196
+ credentials = credentialsUnchecked ;
197
+ }
198
+
199
+ return credentials . map ( ( credential ) => {
200
+ if ( typeof credential . data === 'object' ) {
201
+ // plain data / decrypted input. Should be encrypted first.
202
+ credential . data = cipher . encrypt ( credential . data ) ;
203
+ }
204
+
205
+ return credential ;
206
+ } ) ;
207
+ }
208
+
165
209
private async getAssignee ( userId : string ) {
166
210
const user = await Container . get ( UserRepository ) . findOneBy ( { id : userId } ) ;
167
211
@@ -171,4 +215,17 @@ export class ImportCredentialsCommand extends BaseCommand {
171
215
172
216
return user ;
173
217
}
218
+
219
+ private async getCredentialOwner ( credentialsId : string ) {
220
+ const sharedCredential = await this . transactionManager . findOneBy ( SharedCredentials , {
221
+ credentialsId,
222
+ role : 'credential:owner' ,
223
+ } ) ;
224
+
225
+ return sharedCredential ?. userId ;
226
+ }
227
+
228
+ private async credentialExists ( credentialId : string ) {
229
+ return await this . transactionManager . existsBy ( CredentialsEntity , { id : credentialId } ) ;
230
+ }
174
231
}
0 commit comments