@@ -15,7 +15,6 @@ import type { ICredentialsEncrypted } from 'n8n-workflow';
15
15
import { ApplicationError , jsonParse } from 'n8n-workflow' ;
16
16
import { UM_FIX_INSTRUCTION } from '@/constants' ;
17
17
import { UserRepository } from '@db/repositories/user.repository' ;
18
- import { SharedCredentialsRepository } from '@/databases/repositories/sharedCredentials.repository' ;
19
18
20
19
export class ImportCredentialsCommand extends BaseCommand {
21
20
static description = 'Import credentials' ;
@@ -65,87 +64,103 @@ export class ImportCredentialsCommand extends BaseCommand {
65
64
}
66
65
}
67
66
68
- let totalImported = 0 ;
69
-
70
- const cipher = Container . get ( Cipher ) ;
71
67
const user = flags . userId ? await this . getAssignee ( flags . userId ) : await this . getOwner ( ) ;
72
68
73
- if ( flags . separate ) {
74
- let { input : inputPath } = flags ;
69
+ const credentials = await this . readCredentials ( flags . input , flags . separate ) ;
70
+
71
+ await Db . getConnection ( ) . transaction ( async ( transactionManager ) => {
72
+ this . transactionManager = transactionManager ;
73
+
74
+ const result = await this . checkRelations ( credentials , flags . userId ) ;
75
75
76
- if ( process . platform === 'win32' ) {
77
- inputPath = inputPath . replace ( / \\ / g , '/' ) ;
76
+ if ( ! result . success ) {
77
+ throw new ApplicationError ( result . message ) ;
78
78
}
79
79
80
- const files = await glob ( '*.json' , {
81
- cwd : inputPath ,
82
- absolute : true ,
83
- } ) ;
80
+ for ( const credential of credentials ) {
81
+ await this . storeCredential ( credential , user ) ;
82
+ }
83
+ } ) ;
84
84
85
- totalImported = files . length ;
86
-
87
- await Db . getConnection ( ) . transaction ( async ( transactionManager ) => {
88
- this . transactionManager = transactionManager ;
89
- for ( const file of files ) {
90
- const credential = jsonParse < ICredentialsEncrypted > (
91
- fs . readFileSync ( file , { encoding : 'utf8' } ) ,
92
- ) ;
93
-
94
- if ( credential . id && flags . userId ) {
95
- const credentialOwner = await this . getCredentialOwner ( credential . id ) ;
96
-
97
- if ( credentialOwner && credentialOwner !== flags . userId ) {
98
- throw new ApplicationError (
99
- `The credential with id "${ credential . id } " is already owned by the user with the id "${ credentialOwner } ". It can't be re-owned by the user with the id "${ flags . userId } "` ,
100
- ) ;
101
- }
102
- }
103
-
104
- if ( typeof credential . data === 'object' ) {
105
- // plain data / decrypted input. Should be encrypted first.
106
- credential . data = cipher . encrypt ( credential . data ) ;
107
- }
108
- await this . storeCredential ( credential , user ) ;
109
- }
110
- } ) ;
85
+ this . reportSuccess ( credentials . length ) ;
86
+ }
111
87
112
- this . reportSuccess ( totalImported ) ;
113
- return ;
88
+ private async checkRelations ( credentials : ICredentialsEncrypted [ ] , userId ?: string ) {
89
+ if ( ! userId ) {
90
+ return {
91
+ success : true as const ,
92
+ message : undefined ,
93
+ } ;
114
94
}
115
95
116
- const credentials = jsonParse < ICredentialsEncrypted [ ] > (
117
- fs . readFileSync ( flags . input , { encoding : 'utf8' } ) ,
118
- ) ;
96
+ for ( const credential of credentials ) {
97
+ if ( credential . id === undefined ) {
98
+ continue ;
99
+ }
119
100
120
- totalImported = credentials . length ;
101
+ if ( ! ( await this . credentialExists ( credential . id ) ) ) {
102
+ continue ;
103
+ }
121
104
122
- if ( ! Array . isArray ( credentials ) ) {
123
- throw new ApplicationError (
124
- 'File does not seem to contain credentials. Make sure the credentials are contained in an array.' ,
125
- ) ;
105
+ const ownerId = await this . getCredentialOwner ( credential . id ) ;
106
+ if ( ! ownerId ) {
107
+ continue ;
108
+ }
109
+
110
+ if ( ownerId !== userId ) {
111
+ return {
112
+ success : false as const ,
113
+ 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 } "` ,
114
+ } ;
115
+ }
126
116
}
127
117
128
- await Db . getConnection ( ) . transaction ( async ( transactionManager ) => {
129
- this . transactionManager = transactionManager ;
130
- for ( const credential of credentials ) {
131
- if ( credential . id && flags . userId ) {
132
- const credentialOwner = await this . getCredentialOwner ( credential . id ) ;
133
- if ( credentialOwner && credentialOwner !== flags . userId ) {
134
- throw new ApplicationError (
135
- `The credential with id "${ credential . id } " is already owned by the user with the id "${ credentialOwner } ". It can't be re-owned by the user with the id "${ flags . userId } "` ,
136
- ) ;
137
- }
138
- }
118
+ return {
119
+ success : true as const ,
120
+ message : undefined ,
121
+ } ;
122
+ }
139
123
140
- if ( typeof credential . data === 'object' ) {
141
- // plain data / decrypted input. Should be encrypted first.
142
- credential . data = cipher . encrypt ( credential . data ) ;
143
- }
144
- await this . storeCredential ( credential , user ) ;
124
+ private async readCredentials ( path : string , separate : boolean ) : Promise < ICredentialsEncrypted [ ] > {
125
+ const cipher = Container . get ( Cipher ) ;
126
+
127
+ if ( process . platform === 'win32' ) {
128
+ path = path . replace ( / \\ / g, '/' ) ;
129
+ }
130
+
131
+ let credentials : ICredentialsEncrypted [ ] ;
132
+
133
+ if ( separate ) {
134
+ const files = await glob ( '*.json' , {
135
+ cwd : path ,
136
+ absolute : true ,
137
+ } ) ;
138
+
139
+ credentials = files . map ( ( file ) =>
140
+ jsonParse < ICredentialsEncrypted > ( fs . readFileSync ( file , { encoding : 'utf8' } ) ) ,
141
+ ) ;
142
+ } else {
143
+ const credentialsUnchecked = jsonParse < ICredentialsEncrypted [ ] > (
144
+ fs . readFileSync ( path , { encoding : 'utf8' } ) ,
145
+ ) ;
146
+
147
+ if ( ! Array . isArray ( credentialsUnchecked ) ) {
148
+ throw new ApplicationError (
149
+ 'File does not seem to contain credentials. Make sure the credentials are contained in an array.' ,
150
+ ) ;
145
151
}
146
- } ) ;
147
152
148
- this . reportSuccess ( totalImported ) ;
153
+ credentials = credentialsUnchecked ;
154
+ }
155
+
156
+ return credentials . map ( ( credential ) => {
157
+ if ( typeof credential . data === 'object' ) {
158
+ // plain data / decrypted input. Should be encrypted first.
159
+ credential . data = cipher . encrypt ( credential . data ) ;
160
+ }
161
+
162
+ return credential ;
163
+ } ) ;
149
164
}
150
165
151
166
async catch ( error : Error ) {
@@ -202,11 +217,15 @@ export class ImportCredentialsCommand extends BaseCommand {
202
217
}
203
218
204
219
private async getCredentialOwner ( credentialsId : string ) {
205
- const sharedCredential = await Container . get ( SharedCredentialsRepository ) . findOneBy ( {
220
+ const sharedCredential = await this . transactionManager . findOneBy ( SharedCredentials , {
206
221
credentialsId,
207
222
role : 'credential:owner' ,
208
223
} ) ;
209
224
210
225
return sharedCredential ?. userId ;
211
226
}
227
+
228
+ private async credentialExists ( credentialId : string ) {
229
+ return await this . transactionManager . existsBy ( CredentialsEntity , { id : credentialId } ) ;
230
+ }
212
231
}
0 commit comments