@cadriciel/module-sftp 0.2.0 → 0.3.2

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/package.json CHANGED
@@ -1,23 +1,31 @@
1
1
  {
2
2
  "name": "@cadriciel/module-sftp",
3
- "version": "0.2.0",
4
- "description": "SFTPGo integration module",
3
+ "version": "0.3.2",
4
+ "description": "SFTPGo integration module for Cadriciel",
5
+ "type": "module",
5
6
  "main": "dist/index.js",
6
7
  "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
7
11
  "scripts": {
8
- "build": "tsc"
12
+ "build": "tsc",
13
+ "prepublishOnly": "npm run build"
9
14
  },
10
15
  "dependencies": {
11
- "@cadriciel/kernel": "workspace:*",
16
+ "@cadriciel/kernel": "^0.3.2",
12
17
  "axios": "^1.7.0"
13
18
  },
14
19
  "peerDependencies": {
15
- "@cadriciel/module-s3": "^1.0.0",
16
- "typescript": "^5.0.0"
20
+ "@cadriciel/module-s3": "^0.3.2"
17
21
  },
18
22
  "peerDependenciesMeta": {
19
23
  "@cadriciel/module-s3": {
20
24
  "optional": true
21
25
  }
26
+ },
27
+ "devDependencies": {
28
+ "typescript": "^5.0.0",
29
+ "@types/node": "^20.11.0"
22
30
  }
23
- }
31
+ }
@@ -1,2 +0,0 @@
1
-
2
- $ tsc
package/src/index.ts DELETED
@@ -1,29 +0,0 @@
1
- import type { KernelModule, KernelContext } from '@cadriciel/kernel';
2
- import { SftpService } from './service.js';
3
-
4
- export default {
5
- name: 'sftp',
6
- dependsOn: ['s3'],
7
- order: 8,
8
-
9
- setup(ctx: KernelContext) {
10
- ctx.logger?.log('📦 SFTP module loaded');
11
-
12
- // Get S3Service if available
13
- const s3Service = ctx.services?.get('s3') as any;
14
-
15
- // Initialize service with config, logger, and optional S3Service
16
- const service = new SftpService(ctx.env || process.env, ctx.logger, s3Service);
17
-
18
- ctx.services?.set('sftp', service);
19
- },
20
- routes(ctx: KernelContext) {
21
- const sftpService = ctx.services.get('sftp') as SftpService;
22
- if (!sftpService) return;
23
-
24
- ctx.app.use('*', async (c, next) => {
25
- (c as any).set('sftp', sftpService);
26
- await next();
27
- });
28
- }
29
- };
package/src/service.ts DELETED
@@ -1,223 +0,0 @@
1
- import axios, { AxiosInstance } from 'axios';
2
-
3
- // S3Service interface (matches @cadriciel/module-s3)
4
- export interface S3Service {
5
- ensureBucket(bucket: string): Promise<void>;
6
- }
7
-
8
- export interface SftpServiceLogger {
9
- log(message: string, ...args: any[]): void;
10
- info(message: string, ...args: any[]): void;
11
- warn(message: string, ...args: any[]): void;
12
- error(message: string, ...args: any[]): void;
13
- }
14
-
15
- export interface SftpUserPayload {
16
- username: string;
17
- password?: string;
18
- status?: number;
19
- permissions?: any;
20
- home_dir?: string;
21
- uid?: number;
22
- gid?: number;
23
- filesystem?: {
24
- provider: number;
25
- s3config?: any;
26
- };
27
- id?: number;
28
- }
29
-
30
- export interface SftpS3Config {
31
- url: string;
32
- region: string;
33
- accessKey: string;
34
- secretKey: string;
35
- bucket: string;
36
- }
37
-
38
- export class SftpService {
39
- private client: AxiosInstance;
40
- private baseURL: string;
41
- private adminUser: string;
42
- private adminPassword?: string;
43
- private s3Config?: SftpS3Config;
44
- private token?: string;
45
- private tokenExpiresAt?: number;
46
- private s3Service?: S3Service;
47
- private logger?: SftpServiceLogger;
48
-
49
- constructor(
50
- env: any,
51
- logger?: SftpServiceLogger,
52
- s3Service?: S3Service
53
- ) {
54
- this.logger = logger;
55
- this.s3Service = s3Service;
56
- this.baseURL = env.CADRICIEL_SFTPGO_URL || '';
57
- this.adminUser = env.CADRICIEL_SFTPGO_ADMIN_USER || '';
58
- this.adminPassword = env.CADRICIEL_SFTPGO_ADMIN_PASSWORD;
59
-
60
- // S3 config for SFTPGo users (may differ from main S3)
61
- if (env.CADRICIEL_SFTPGO_S3_URL && env.CADRICIEL_SFTPGO_S3_ACCESS_KEY) {
62
- this.s3Config = {
63
- url: env.CADRICIEL_SFTPGO_S3_URL,
64
- region: env.CADRICIEL_SFTPGO_S3_REGION || "us-east-1",
65
- accessKey: env.CADRICIEL_SFTPGO_S3_ACCESS_KEY,
66
- secretKey: env.CADRICIEL_SFTPGO_S3_SECRET_KEY,
67
- bucket: env.CADRICIEL_SFTPGO_S3_BUCKET,
68
- };
69
- }
70
-
71
- if (this.baseURL && this.adminUser && this.adminPassword) {
72
- this.logger?.info(`SftpGoService initialized with URL: ${this.baseURL}`);
73
- } else {
74
- this.logger?.warn("SftpGo configuration missing (CADRICIEL_SFTPGO_URL, CADRICIEL_SFTPGO_ADMIN_USER, CADRICIEL_SFTPGO_ADMIN_PASSWORD)");
75
- }
76
-
77
- this.client = axios.create({
78
- baseURL: this.baseURL,
79
- timeout: 10000,
80
- });
81
-
82
- if (this.s3Config) {
83
- this.logger?.info(`SFTP S3 backend configured for bucket: ${this.s3Config.bucket}`);
84
- }
85
- }
86
-
87
- private async ensureBucketExists(): Promise<void> {
88
- if (!this.s3Config) return;
89
-
90
- // Use S3Service if available, otherwise skip (SFTPGo will handle it)
91
- if (this.s3Service) {
92
- try {
93
- await this.s3Service.ensureBucket(this.s3Config.bucket);
94
- } catch (error: any) {
95
- this.logger?.error(`Error ensuring bucket ${this.s3Config.bucket}:`, error.message);
96
- }
97
- }
98
- }
99
-
100
- private async ensureAuth(): Promise<void> {
101
- const now = Date.now();
102
- const safetyMargin = 60 * 1000;
103
-
104
- if (this.token && this.tokenExpiresAt && (this.tokenExpiresAt - now > safetyMargin)) {
105
- return;
106
- }
107
-
108
- try {
109
- const auth = Buffer.from(`${this.adminUser}:${this.adminPassword}`).toString('base64');
110
- const response = await this.client.get("/api/v2/token", {
111
- headers: {
112
- Authorization: `Basic ${auth}`
113
- }
114
- });
115
-
116
- this.token = response.data.access_token;
117
- this.tokenExpiresAt = new Date(response.data.expires_at).getTime();
118
- } catch (error: any) {
119
- this.logger?.error("SFTPGo Authentication Error:", error.message);
120
- throw new Error(`Cannot authenticate with SFTPGo: ${error.message}`);
121
- }
122
- }
123
-
124
- async createUser(username: string, password?: string): Promise<any> {
125
- await this.ensureAuth();
126
-
127
- let userPayload: SftpUserPayload = {
128
- username: username,
129
- password: password,
130
- status: 1,
131
- permissions: {
132
- "/": ["*"]
133
- }
134
- };
135
-
136
- if (this.s3Config) {
137
- await this.ensureBucketExists();
138
-
139
- // Replace localhost with minio for Docker networking
140
- let sftpGoEndpoint = this.s3Config.url;
141
- if (sftpGoEndpoint.includes("localhost")) {
142
- sftpGoEndpoint = sftpGoEndpoint.replace("localhost", "minio");
143
- }
144
-
145
- userPayload.filesystem = {
146
- provider: 1, // S3
147
- s3config: {
148
- bucket: this.s3Config.bucket,
149
- region: this.s3Config.region,
150
- access_key: this.s3Config.accessKey,
151
- access_secret: {
152
- status: "Plain",
153
- payload: this.s3Config.secretKey
154
- },
155
- endpoint: sftpGoEndpoint,
156
- key_prefix: `${username}/`,
157
- force_path_style: true
158
- }
159
- };
160
- userPayload.home_dir = `/srv/sftpgo/data/${username}`;
161
- } else {
162
- userPayload.home_dir = `/srv/sftpgo/data/${username}`;
163
- userPayload.uid = 0;
164
- userPayload.gid = 0;
165
- }
166
-
167
- try {
168
- const response = await this.client.post("/api/v2/users", userPayload, {
169
- headers: { Authorization: `Bearer ${this.token}` }
170
- });
171
- this.logger?.info(`SFTPGo user created: ${username}`);
172
- return response.data;
173
- } catch (error: any) {
174
- if (error.response?.status === 409) {
175
- this.logger?.info(`User ${username} already exists, updating...`);
176
- return this.updateUser(username, userPayload);
177
- }
178
- throw error;
179
- }
180
- }
181
-
182
- async updateUser(username: string, userPayload: SftpUserPayload): Promise<any> {
183
- await this.ensureAuth();
184
- const id = await this.getUserId(username);
185
- if (id) userPayload.id = id;
186
-
187
- try {
188
- const response = await this.client.put(`/api/v2/users/${id || username}`, userPayload, {
189
- headers: { Authorization: `Bearer ${this.token}` }
190
- });
191
- this.logger?.info(`SFTPGo user updated: ${username}`);
192
- return response.data;
193
- } catch (error: any) {
194
- this.logger?.error(`Error updating SFTPGo user ${username}:`, error.message);
195
- throw error;
196
- }
197
- }
198
-
199
- async getUserId(username: string): Promise<number | null> {
200
- try {
201
- const response = await this.client.get(`/api/v2/users/${username}`, {
202
- headers: { Authorization: `Bearer ${this.token}` }
203
- });
204
- return response.data.id;
205
- } catch (e) {
206
- return null;
207
- }
208
- }
209
-
210
- async deleteUser(username: string): Promise<void> {
211
- await this.ensureAuth();
212
- try {
213
- await this.client.delete(`/api/v2/users/${username}`, {
214
- headers: { Authorization: `Bearer ${this.token}` }
215
- });
216
- this.logger?.info(`SFTPGo user deleted: ${username}`);
217
- } catch (error: any) {
218
- if (error.response?.status === 404) return;
219
- this.logger?.error(`Error deleting SFTPGo user ${username}:`, error.message);
220
- throw error;
221
- }
222
- }
223
- }
package/tsconfig.json DELETED
@@ -1,17 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "ESNext",
5
- "moduleResolution": "Node",
6
- "outDir": "./dist",
7
- "rootDir": "./src",
8
- "strict": true,
9
- "declaration": true,
10
- "esModuleInterop": true,
11
- "skipLibCheck": true,
12
- "forceConsistentCasingInFileNames": true
13
- },
14
- "include": [
15
- "src/**/*"
16
- ]
17
- }