1
- import os from 'node:os' ;
2
1
import { writeFile , chmod , readFile } from 'node:fs/promises' ;
3
2
import Container , { Service } from 'typedi' ;
4
3
import { SourceControlPreferences } from './types/sourceControlPreferences' ;
5
4
import type { ValidationError } from 'class-validator' ;
6
5
import { validate } from 'class-validator' ;
7
- import { writeFile as fsWriteFile , rm as fsRm } from 'fs/promises' ;
8
- import {
9
- generateSshKeyPair ,
10
- isSourceControlLicensed ,
11
- sourceControlFoldersExistCheck ,
12
- } from './sourceControlHelper.ee' ;
6
+ import { rm as fsRm } from 'fs/promises' ;
7
+ import { generateSshKeyPair , isSourceControlLicensed } from './sourceControlHelper.ee' ;
13
8
import { Cipher , InstanceSettings } from 'n8n-core' ;
14
9
import { ApplicationError , jsonParse } from 'n8n-workflow' ;
15
10
import {
@@ -35,7 +30,7 @@ export class SourceControlPreferencesService {
35
30
readonly gitFolder : string ;
36
31
37
32
constructor (
38
- instanceSettings : InstanceSettings ,
33
+ private readonly instanceSettings : InstanceSettings ,
39
34
private readonly logger : Logger ,
40
35
private readonly cipher : Cipher ,
41
36
) {
@@ -82,33 +77,29 @@ export class SourceControlPreferencesService {
82
77
private async getPrivateKeyFromDatabase ( ) {
83
78
const dbKeyPair = await this . getKeyPairFromDatabase ( ) ;
84
79
85
- if ( ! dbKeyPair ) return null ;
80
+ if ( ! dbKeyPair ) throw new ApplicationError ( 'Failed to find key pair in database' ) ;
86
81
87
82
return this . cipher . decrypt ( dbKeyPair . encryptedPrivateKey ) ;
88
83
}
89
84
90
85
private async getPublicKeyFromDatabase ( ) {
91
86
const dbKeyPair = await this . getKeyPairFromDatabase ( ) ;
92
87
93
- if ( ! dbKeyPair ) return null ;
88
+ if ( ! dbKeyPair ) throw new ApplicationError ( 'Failed to find key pair in database' ) ;
94
89
95
90
return dbKeyPair . publicKey ;
96
91
}
97
92
98
93
async getPrivateKeyPath ( ) {
99
94
const dbPrivateKey = await this . getPrivateKeyFromDatabase ( ) ;
100
95
101
- if ( dbPrivateKey ) {
102
- const tempFilePath = path . join ( os . tmpdir ( ) , 'ssh_private_key_temp' ) ;
103
-
104
- await writeFile ( tempFilePath , dbPrivateKey ) ;
96
+ const tempFilePath = path . join ( this . instanceSettings . n8nFolder , 'ssh_private_key_temp' ) ;
105
97
106
- await chmod ( tempFilePath , 0o600 ) ;
98
+ await writeFile ( tempFilePath , dbPrivateKey ) ;
107
99
108
- return tempFilePath ;
109
- }
100
+ await chmod ( tempFilePath , 0o600 ) ;
110
101
111
- return this . sshKeyName ; // fall back to key in filesystem
102
+ return tempFilePath ;
112
103
}
113
104
114
105
async getPublicKey ( ) {
@@ -136,33 +127,16 @@ export class SourceControlPreferencesService {
136
127
}
137
128
138
129
/**
139
- * Will generate an ed25519 key pair and save it to the database and the file system
140
- * Note: this will overwrite any existing key pair
130
+ * Generate an SSH key pair and write it to the database, overwriting any existing key pair.
141
131
*/
142
132
async generateAndSaveKeyPair ( keyPairType ?: KeyPairType ) : Promise < SourceControlPreferences > {
143
- sourceControlFoldersExistCheck ( [ this . gitFolder , this . sshFolder ] ) ;
144
133
if ( ! keyPairType ) {
145
134
keyPairType =
146
135
this . getPreferences ( ) . keyGeneratorType ??
147
136
( config . get ( 'sourceControl.defaultKeyPairType' ) as KeyPairType ) ??
148
137
'ed25519' ;
149
138
}
150
139
const keyPair = await generateSshKeyPair ( keyPairType ) ;
151
- if ( keyPair . publicKey && keyPair . privateKey ) {
152
- try {
153
- await fsWriteFile ( this . sshKeyName + '.pub' , keyPair . publicKey , {
154
- encoding : 'utf8' ,
155
- mode : 0o666 ,
156
- } ) ;
157
- await fsWriteFile ( this . sshKeyName , keyPair . privateKey , { encoding : 'utf8' , mode : 0o600 } ) ;
158
- } catch ( error ) {
159
- throw new ApplicationError ( 'Failed to save key pair to disk' , { cause : error } ) ;
160
- }
161
- }
162
- // update preferences only after generating key pair to prevent endless loop
163
- if ( keyPairType !== this . getPreferences ( ) . keyGeneratorType ) {
164
- await this . setPreferences ( { keyGeneratorType : keyPairType } ) ;
165
- }
166
140
167
141
try {
168
142
await Container . get ( SettingsRepository ) . save ( {
@@ -177,6 +151,11 @@ export class SourceControlPreferencesService {
177
151
throw new ApplicationError ( 'Failed to write key pair to database' , { cause : error } ) ;
178
152
}
179
153
154
+ // update preferences only after generating key pair to prevent endless loop
155
+ if ( keyPairType !== this . getPreferences ( ) . keyGeneratorType ) {
156
+ await this . setPreferences ( { keyGeneratorType : keyPairType } ) ;
157
+ }
158
+
180
159
return this . getPreferences ( ) ;
181
160
}
182
161
@@ -223,6 +202,10 @@ export class SourceControlPreferencesService {
223
202
preferences : Partial < SourceControlPreferences > ,
224
203
saveToDb = true ,
225
204
) : Promise < SourceControlPreferences > {
205
+ const noKeyPair = ( await this . getKeyPairFromDatabase ( ) ) === null ;
206
+
207
+ if ( noKeyPair ) await this . generateAndSaveKeyPair ( ) ;
208
+
226
209
this . sourceControlPreferences = preferences ;
227
210
if ( saveToDb ) {
228
211
const settingsValue = JSON . stringify ( this . _sourceControlPreferences ) ;
0 commit comments