@cadriciel/module-sftp 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +2 -0
- package/README.md +25 -0
- package/dist/index.js +40 -0
- package/dist/service.js +225 -0
- package/package.json +17 -0
- package/src/index.ts +27 -0
- package/src/service.ts +209 -0
- package/tsconfig.json +15 -0
package/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# 📦 @cadriciel/module-sftp
|
|
2
|
+
|
|
3
|
+
Unified file management and SFTP integration for the Cadriciel framework.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **SFTPGo Integration**: Manage users and folders via SFTPGo.
|
|
8
|
+
- **Minio Integration**: S3-compatible object storage support.
|
|
9
|
+
- **Automated Provisioning**: Programmatically create SFTP users and manage access.
|
|
10
|
+
- **Unified API**: Abstract file operations across different storage backends.
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
import SftpModule from "@cadriciel/module-sftp";
|
|
16
|
+
|
|
17
|
+
kernel.load(SftpModule);
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Configuration
|
|
21
|
+
|
|
22
|
+
Requires environment variables for SFTPGo connection:
|
|
23
|
+
- `SFTPGO_URL`
|
|
24
|
+
- `SFTPGO_ADMIN_USER`
|
|
25
|
+
- `SFTPGO_ADMIN_PASSWORD`
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./service"), exports);
|
|
18
|
+
const service_1 = require("./service");
|
|
19
|
+
exports.default = {
|
|
20
|
+
name: 'sftp',
|
|
21
|
+
setup(ctx) {
|
|
22
|
+
if (ctx.logger) {
|
|
23
|
+
ctx.logger.log('📦 SFTP module loaded');
|
|
24
|
+
}
|
|
25
|
+
// Initialize service with config
|
|
26
|
+
const service = new service_1.SftpService(ctx.env || process.env);
|
|
27
|
+
if (ctx.services) {
|
|
28
|
+
ctx.services.set('sftp', service);
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
routes(ctx) {
|
|
32
|
+
const sftpService = ctx.services.get('sftp');
|
|
33
|
+
if (!sftpService)
|
|
34
|
+
return;
|
|
35
|
+
ctx.app.use('*', async (c, next) => {
|
|
36
|
+
c.set('sftp', sftpService);
|
|
37
|
+
await next();
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
};
|
package/dist/service.js
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.SftpService = void 0;
|
|
40
|
+
const axios_1 = __importDefault(require("axios"));
|
|
41
|
+
const Minio = __importStar(require("minio"));
|
|
42
|
+
class SftpService {
|
|
43
|
+
client;
|
|
44
|
+
baseURL;
|
|
45
|
+
adminUser;
|
|
46
|
+
adminPassword;
|
|
47
|
+
s3Config;
|
|
48
|
+
token;
|
|
49
|
+
tokenExpiresAt;
|
|
50
|
+
minioClient;
|
|
51
|
+
constructor(env) {
|
|
52
|
+
this.baseURL = env.SFTPGO_URL || '';
|
|
53
|
+
this.adminUser = env.SFTPGO_ADMIN_USER || '';
|
|
54
|
+
this.adminPassword = env.SFTPGO_ADMIN_PASSWORD;
|
|
55
|
+
this.s3Config = {
|
|
56
|
+
url: env.SFTPGO_S3_URL,
|
|
57
|
+
region: env.SFTPGO_S3_REGION || "us-east-1",
|
|
58
|
+
accessKey: env.SFTPGO_S3_ACCESS_KEY,
|
|
59
|
+
secretKey: env.SFTPGO_S3_SECRET_KEY,
|
|
60
|
+
bucket: env.SFTPGO_S3_BUCKET,
|
|
61
|
+
};
|
|
62
|
+
if (this.baseURL && this.adminUser && this.adminPassword) {
|
|
63
|
+
console.log(`✅ SftpGoService initialized with URL: ${this.baseURL}`);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
console.warn("⚠️ SftpGo configuration missing (SFTPGO_URL, SFTPGO_ADMIN_USER, SFTPGO_ADMIN_PASSWORD)");
|
|
67
|
+
}
|
|
68
|
+
this.client = axios_1.default.create({
|
|
69
|
+
baseURL: this.baseURL,
|
|
70
|
+
timeout: 10000,
|
|
71
|
+
});
|
|
72
|
+
if (this.s3Config.url && this.s3Config.accessKey && this.s3Config.secretKey) {
|
|
73
|
+
try {
|
|
74
|
+
const parsedUrl = new URL(this.s3Config.url);
|
|
75
|
+
this.minioClient = new Minio.Client({
|
|
76
|
+
endPoint: parsedUrl.hostname,
|
|
77
|
+
port: parseInt(parsedUrl.port) || (parsedUrl.protocol === "https:" ? 443 : 80),
|
|
78
|
+
useSSL: parsedUrl.protocol === "https:",
|
|
79
|
+
accessKey: this.s3Config.accessKey,
|
|
80
|
+
secretKey: this.s3Config.secretKey,
|
|
81
|
+
region: this.s3Config.region,
|
|
82
|
+
});
|
|
83
|
+
console.log(`✅ Minio client initialized for bucket: ${this.s3Config.bucket}`);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
console.error("❌ Error initializing Minio client:", error.message);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async ensureBucketExists() {
|
|
91
|
+
if (!this.minioClient || !this.s3Config.bucket)
|
|
92
|
+
return;
|
|
93
|
+
try {
|
|
94
|
+
const exists = await this.minioClient.bucketExists(this.s3Config.bucket);
|
|
95
|
+
if (!exists) {
|
|
96
|
+
console.log(`🪣 Creating S3 bucket: ${this.s3Config.bucket}...`);
|
|
97
|
+
await this.minioClient.makeBucket(this.s3Config.bucket, this.s3Config.region);
|
|
98
|
+
console.log(`✅ Bucket ${this.s3Config.bucket} created successfully.`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
console.error(`❌ Error checking/creating bucket ${this.s3Config.bucket}:`, error.message);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async ensureAuth() {
|
|
106
|
+
const now = Date.now();
|
|
107
|
+
const safetyMargin = 60 * 1000;
|
|
108
|
+
if (this.token && this.tokenExpiresAt && (this.tokenExpiresAt - now > safetyMargin)) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const auth = Buffer.from(`${this.adminUser}:${this.adminPassword}`).toString('base64');
|
|
113
|
+
const response = await this.client.get("/api/v2/token", {
|
|
114
|
+
headers: {
|
|
115
|
+
Authorization: `Basic ${auth}`
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
this.token = response.data.access_token;
|
|
119
|
+
// SFTPGo returns expires_at as ISO string date
|
|
120
|
+
this.tokenExpiresAt = new Date(response.data.expires_at).getTime();
|
|
121
|
+
// console.log(`✅ New SFTPGo token obtained`);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
console.error("❌ SFTPGo Authentication Error:", error.message);
|
|
125
|
+
throw new Error(`Cannot authenticate with SFTPGo: ${error.message}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async createUser(username, password) {
|
|
129
|
+
await this.ensureAuth();
|
|
130
|
+
let userPayload = {
|
|
131
|
+
username: username,
|
|
132
|
+
password: password,
|
|
133
|
+
status: 1, // active
|
|
134
|
+
permissions: {
|
|
135
|
+
"/": ["*"]
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
if (this.s3Config.bucket && this.minioClient) {
|
|
139
|
+
await this.ensureBucketExists();
|
|
140
|
+
let sftpGoEndpoint = this.s3Config.url;
|
|
141
|
+
if (sftpGoEndpoint.includes("localhost")) {
|
|
142
|
+
sftpGoEndpoint = sftpGoEndpoint.replace("localhost", "minio");
|
|
143
|
+
}
|
|
144
|
+
userPayload.filesystem = {
|
|
145
|
+
provider: 1, // S3
|
|
146
|
+
s3config: {
|
|
147
|
+
bucket: this.s3Config.bucket,
|
|
148
|
+
region: this.s3Config.region,
|
|
149
|
+
access_key: this.s3Config.accessKey,
|
|
150
|
+
access_secret: {
|
|
151
|
+
status: "Plain",
|
|
152
|
+
payload: this.s3Config.secretKey
|
|
153
|
+
},
|
|
154
|
+
endpoint: sftpGoEndpoint,
|
|
155
|
+
key_prefix: `${username}/`,
|
|
156
|
+
force_path_style: true
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
userPayload.home_dir = `/srv/sftpgo/data/${username}`;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
userPayload.home_dir = `/srv/sftpgo/data/${username}`;
|
|
163
|
+
userPayload.uid = 0;
|
|
164
|
+
userPayload.gid = 0;
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
const response = await this.client.post("/api/v2/users", userPayload, {
|
|
168
|
+
headers: { Authorization: `Bearer ${this.token}` }
|
|
169
|
+
});
|
|
170
|
+
console.log(`✅ SFTPGo user created: ${username}`);
|
|
171
|
+
return response.data;
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
if (error.response?.status === 409) {
|
|
175
|
+
console.log(`⚠️ User ${username} already exists, updating...`);
|
|
176
|
+
return this.updateUser(username, userPayload);
|
|
177
|
+
}
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async updateUser(username, userPayload) {
|
|
182
|
+
await this.ensureAuth();
|
|
183
|
+
const id = await this.getUserId(username);
|
|
184
|
+
if (id)
|
|
185
|
+
userPayload.id = id;
|
|
186
|
+
try {
|
|
187
|
+
const response = await this.client.put(`/api/v2/users/${id || username}`, userPayload, {
|
|
188
|
+
headers: { Authorization: `Bearer ${this.token}` }
|
|
189
|
+
});
|
|
190
|
+
console.log(`✅ SFTPGo user updated: ${username}`);
|
|
191
|
+
return response.data;
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
console.error(`❌ Error updating SFTPGo user ${username}:`, error.message);
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async getUserId(username) {
|
|
199
|
+
try {
|
|
200
|
+
const response = await this.client.get(`/api/v2/users/${username}`, {
|
|
201
|
+
headers: { Authorization: `Bearer ${this.token}` }
|
|
202
|
+
});
|
|
203
|
+
return response.data.id;
|
|
204
|
+
}
|
|
205
|
+
catch (e) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
async deleteUser(username) {
|
|
210
|
+
await this.ensureAuth();
|
|
211
|
+
try {
|
|
212
|
+
await this.client.delete(`/api/v2/users/${username}`, {
|
|
213
|
+
headers: { Authorization: `Bearer ${this.token}` }
|
|
214
|
+
});
|
|
215
|
+
console.log(`✅ SFTPGo user deleted: ${username}`);
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
if (error.response?.status === 404)
|
|
219
|
+
return;
|
|
220
|
+
console.error(`❌ Error deleting SFTPGo user ${username}:`, error.message);
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
exports.SftpService = SftpService;
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cadriciel/module-sftp",
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "SFTPGo integration module",
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@cadriciel/kernel": "workspace:*",
|
|
11
|
+
"axios": "^1.13.2",
|
|
12
|
+
"minio": "^8.0.6"
|
|
13
|
+
},
|
|
14
|
+
"peerDependencies": {
|
|
15
|
+
"typescript": "^5.0.0"
|
|
16
|
+
}
|
|
17
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export * from './service';
|
|
2
|
+
import { SftpService } from './service';
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
name: 'sftp',
|
|
6
|
+
setup(ctx: any) {
|
|
7
|
+
if (ctx.logger) {
|
|
8
|
+
ctx.logger.log('📦 SFTP module loaded');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Initialize service with config
|
|
12
|
+
const service = new SftpService(ctx.env || process.env);
|
|
13
|
+
|
|
14
|
+
if (ctx.services) {
|
|
15
|
+
ctx.services.set('sftp', service);
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
routes(ctx: any) {
|
|
19
|
+
const sftpService = ctx.services.get('sftp');
|
|
20
|
+
if (!sftpService) return;
|
|
21
|
+
|
|
22
|
+
ctx.app.use('*', async (c: any, next: any) => {
|
|
23
|
+
c.set('sftp', sftpService);
|
|
24
|
+
await next();
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
};
|
package/src/service.ts
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import axios, { AxiosInstance } from 'axios';
|
|
2
|
+
import * as Minio from 'minio';
|
|
3
|
+
|
|
4
|
+
export interface SftpUserPayload {
|
|
5
|
+
username: string;
|
|
6
|
+
password?: string;
|
|
7
|
+
status?: number;
|
|
8
|
+
permissions?: any;
|
|
9
|
+
home_dir?: string;
|
|
10
|
+
uid?: number;
|
|
11
|
+
gid?: number;
|
|
12
|
+
filesystem?: {
|
|
13
|
+
provider: number;
|
|
14
|
+
s3config?: any;
|
|
15
|
+
};
|
|
16
|
+
id?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class SftpService {
|
|
20
|
+
private client: AxiosInstance;
|
|
21
|
+
private baseURL: string;
|
|
22
|
+
private adminUser: string;
|
|
23
|
+
private adminPassword?: string;
|
|
24
|
+
private s3Config: any;
|
|
25
|
+
private token?: string;
|
|
26
|
+
private tokenExpiresAt?: number;
|
|
27
|
+
private minioClient?: Minio.Client;
|
|
28
|
+
|
|
29
|
+
constructor(env: any) {
|
|
30
|
+
this.baseURL = env.SFTPGO_URL || '';
|
|
31
|
+
this.adminUser = env.SFTPGO_ADMIN_USER || '';
|
|
32
|
+
this.adminPassword = env.SFTPGO_ADMIN_PASSWORD;
|
|
33
|
+
|
|
34
|
+
this.s3Config = {
|
|
35
|
+
url: env.SFTPGO_S3_URL,
|
|
36
|
+
region: env.SFTPGO_S3_REGION || "us-east-1",
|
|
37
|
+
accessKey: env.SFTPGO_S3_ACCESS_KEY,
|
|
38
|
+
secretKey: env.SFTPGO_S3_SECRET_KEY,
|
|
39
|
+
bucket: env.SFTPGO_S3_BUCKET,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
if (this.baseURL && this.adminUser && this.adminPassword) {
|
|
43
|
+
console.log(`✅ SftpGoService initialized with URL: ${this.baseURL}`);
|
|
44
|
+
} else {
|
|
45
|
+
console.warn("⚠️ SftpGo configuration missing (SFTPGO_URL, SFTPGO_ADMIN_USER, SFTPGO_ADMIN_PASSWORD)");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.client = axios.create({
|
|
49
|
+
baseURL: this.baseURL,
|
|
50
|
+
timeout: 10000,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (this.s3Config.url && this.s3Config.accessKey && this.s3Config.secretKey) {
|
|
54
|
+
try {
|
|
55
|
+
const parsedUrl = new URL(this.s3Config.url);
|
|
56
|
+
this.minioClient = new Minio.Client({
|
|
57
|
+
endPoint: parsedUrl.hostname,
|
|
58
|
+
port: parseInt(parsedUrl.port) || (parsedUrl.protocol === "https:" ? 443 : 80),
|
|
59
|
+
useSSL: parsedUrl.protocol === "https:",
|
|
60
|
+
accessKey: this.s3Config.accessKey,
|
|
61
|
+
secretKey: this.s3Config.secretKey,
|
|
62
|
+
region: this.s3Config.region,
|
|
63
|
+
});
|
|
64
|
+
console.log(`✅ Minio client initialized for bucket: ${this.s3Config.bucket}`);
|
|
65
|
+
} catch (error: any) {
|
|
66
|
+
console.error("❌ Error initializing Minio client:", error.message);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private async ensureBucketExists() {
|
|
72
|
+
if (!this.minioClient || !this.s3Config.bucket) return;
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const exists = await this.minioClient.bucketExists(this.s3Config.bucket);
|
|
76
|
+
if (!exists) {
|
|
77
|
+
console.log(`🪣 Creating S3 bucket: ${this.s3Config.bucket}...`);
|
|
78
|
+
await this.minioClient.makeBucket(this.s3Config.bucket, this.s3Config.region);
|
|
79
|
+
console.log(`✅ Bucket ${this.s3Config.bucket} created successfully.`);
|
|
80
|
+
}
|
|
81
|
+
} catch (error: any) {
|
|
82
|
+
console.error(`❌ Error checking/creating bucket ${this.s3Config.bucket}:`, error.message);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private async ensureAuth() {
|
|
87
|
+
const now = Date.now();
|
|
88
|
+
const safetyMargin = 60 * 1000;
|
|
89
|
+
|
|
90
|
+
if (this.token && this.tokenExpiresAt && (this.tokenExpiresAt - now > safetyMargin)) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const auth = Buffer.from(`${this.adminUser}:${this.adminPassword}`).toString('base64');
|
|
96
|
+
const response = await this.client.get("/api/v2/token", {
|
|
97
|
+
headers: {
|
|
98
|
+
Authorization: `Basic ${auth}`
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
this.token = response.data.access_token;
|
|
103
|
+
// SFTPGo returns expires_at as ISO string date
|
|
104
|
+
this.tokenExpiresAt = new Date(response.data.expires_at).getTime();
|
|
105
|
+
// console.log(`✅ New SFTPGo token obtained`);
|
|
106
|
+
} catch (error: any) {
|
|
107
|
+
console.error("❌ SFTPGo Authentication Error:", error.message);
|
|
108
|
+
throw new Error(`Cannot authenticate with SFTPGo: ${error.message}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async createUser(username: string, password?: string) {
|
|
113
|
+
await this.ensureAuth();
|
|
114
|
+
|
|
115
|
+
let userPayload: SftpUserPayload = {
|
|
116
|
+
username: username,
|
|
117
|
+
password: password,
|
|
118
|
+
status: 1, // active
|
|
119
|
+
permissions: {
|
|
120
|
+
"/": ["*"]
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
if (this.s3Config.bucket && this.minioClient) {
|
|
125
|
+
await this.ensureBucketExists();
|
|
126
|
+
let sftpGoEndpoint = this.s3Config.url;
|
|
127
|
+
if (sftpGoEndpoint.includes("localhost")) {
|
|
128
|
+
sftpGoEndpoint = sftpGoEndpoint.replace("localhost", "minio");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
userPayload.filesystem = {
|
|
132
|
+
provider: 1, // S3
|
|
133
|
+
s3config: {
|
|
134
|
+
bucket: this.s3Config.bucket,
|
|
135
|
+
region: this.s3Config.region,
|
|
136
|
+
access_key: this.s3Config.accessKey,
|
|
137
|
+
access_secret: {
|
|
138
|
+
status: "Plain",
|
|
139
|
+
payload: this.s3Config.secretKey
|
|
140
|
+
},
|
|
141
|
+
endpoint: sftpGoEndpoint,
|
|
142
|
+
key_prefix: `${username}/`,
|
|
143
|
+
force_path_style: true
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
userPayload.home_dir = `/srv/sftpgo/data/${username}`;
|
|
147
|
+
} else {
|
|
148
|
+
userPayload.home_dir = `/srv/sftpgo/data/${username}`;
|
|
149
|
+
userPayload.uid = 0;
|
|
150
|
+
userPayload.gid = 0;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
const response = await this.client.post("/api/v2/users", userPayload, {
|
|
155
|
+
headers: { Authorization: `Bearer ${this.token}` }
|
|
156
|
+
});
|
|
157
|
+
console.log(`✅ SFTPGo user created: ${username}`);
|
|
158
|
+
return response.data;
|
|
159
|
+
} catch (error: any) {
|
|
160
|
+
if (error.response?.status === 409) {
|
|
161
|
+
console.log(`⚠️ User ${username} already exists, updating...`);
|
|
162
|
+
return this.updateUser(username, userPayload);
|
|
163
|
+
}
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async updateUser(username: string, userPayload: SftpUserPayload) {
|
|
169
|
+
await this.ensureAuth();
|
|
170
|
+
const id = await this.getUserId(username);
|
|
171
|
+
if (id) userPayload.id = id;
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
const response = await this.client.put(`/api/v2/users/${id || username}`, userPayload, {
|
|
175
|
+
headers: { Authorization: `Bearer ${this.token}` }
|
|
176
|
+
});
|
|
177
|
+
console.log(`✅ SFTPGo user updated: ${username}`);
|
|
178
|
+
return response.data;
|
|
179
|
+
} catch (error: any) {
|
|
180
|
+
console.error(`❌ Error updating SFTPGo user ${username}:`, error.message);
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async getUserId(username: string): Promise<number | null> {
|
|
186
|
+
try {
|
|
187
|
+
const response = await this.client.get(`/api/v2/users/${username}`, {
|
|
188
|
+
headers: { Authorization: `Bearer ${this.token}` }
|
|
189
|
+
});
|
|
190
|
+
return response.data.id;
|
|
191
|
+
} catch (e) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async deleteUser(username: string) {
|
|
197
|
+
await this.ensureAuth();
|
|
198
|
+
try {
|
|
199
|
+
await this.client.delete(`/api/v2/users/${username}`, {
|
|
200
|
+
headers: { Authorization: `Bearer ${this.token}` }
|
|
201
|
+
});
|
|
202
|
+
console.log(`✅ SFTPGo user deleted: ${username}`);
|
|
203
|
+
} catch (error: any) {
|
|
204
|
+
if (error.response?.status === 404) return;
|
|
205
|
+
console.error(`❌ Error deleting SFTPGo user ${username}:`, error.message);
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "CommonJS",
|
|
5
|
+
"outDir": "./dist",
|
|
6
|
+
"rootDir": "./src",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true
|
|
11
|
+
},
|
|
12
|
+
"include": [
|
|
13
|
+
"src/**/*"
|
|
14
|
+
]
|
|
15
|
+
}
|