fa-mcp-sdk 0.4.29 → 0.4.32

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.
@@ -0,0 +1,191 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Generate JWT token for MCP server authentication.
4
+ *
5
+ * Usage:
6
+ * node scripts/generate-jwt.js -u <username> -ttl <duration> [-s <service>] [-p <params>]
7
+ *
8
+ * Options:
9
+ * -u, --username Username (required). ENV: JWT_PAYLOAD_USERNAME
10
+ * -ttl Token lifetime: <N>s | <N>m | <N>d | <N>y (required). ENV: JWT_TTL
11
+ * -s, --service-name Service name (optional). ENV: JWT_PAYLOAD_SERVICE_NAME
12
+ * -p, --params Extra payload "key=value;key=value" (optional). ENV: JWT_PAYLOAD_PARAMS
13
+ *
14
+ * The encryptKey is read from config: webServer.auth.jwtToken.encryptKey
15
+ */
16
+
17
+ import crypto from 'crypto';
18
+ import { readFileSync } from 'fs';
19
+ import { fileURLToPath } from 'url';
20
+ import { dirname, resolve } from 'path';
21
+ import configModule from 'config';
22
+
23
+ // ── CLI argument parsing ────────────────────────────────────────────
24
+
25
+ function getArg (shortFlag, longFlag) {
26
+ const args = process.argv.slice(2);
27
+ for (let i = 0; i < args.length; i++) {
28
+ if (args[i] === shortFlag || args[i] === longFlag) {
29
+ return args[i + 1] || '';
30
+ }
31
+ }
32
+ return undefined;
33
+ }
34
+
35
+ const username = getArg('-u', '--username') ?? process.env.JWT_PAYLOAD_USERNAME;
36
+ const ttlRaw = getArg('-ttl', '-ttl') ?? process.env.JWT_TTL;
37
+ const service = getArg('-s', '--service-name') ?? process.env.JWT_PAYLOAD_SERVICE_NAME;
38
+ const paramsRaw = getArg('-p', '--params') ?? process.env.JWT_PAYLOAD_PARAMS;
39
+
40
+ // ── Validation ──────────────────────────────────────────────────────
41
+
42
+ if (!username || !username.trim()) {
43
+ console.error('Error: username is required (-u / --username or ENV JWT_PAYLOAD_USERNAME)');
44
+ process.exit(1);
45
+ }
46
+
47
+ if (!ttlRaw || !ttlRaw.trim()) {
48
+ console.error('Error: TTL is required (-ttl or ENV JWT_TTL). Format: <N>s | <N>m | <N>d | <N>y');
49
+ process.exit(1);
50
+ }
51
+
52
+ const ttlMatch = /^(\d+)([smdy])$/.exec(ttlRaw.trim());
53
+ if (!ttlMatch) {
54
+ console.error(`Error: invalid TTL format "${ttlRaw}". Expected: <N>s | <N>m | <N>d | <N>y`);
55
+ process.exit(1);
56
+ }
57
+
58
+ const ttlValue = parseInt(ttlMatch[1], 10);
59
+ const ttlUnit = ttlMatch[2];
60
+
61
+ if (ttlValue <= 0) {
62
+ console.error('Error: TTL value must be greater than 0');
63
+ process.exit(1);
64
+ }
65
+
66
+ const TTL_MULTIPLIERS = { s: 1, m: 60, d: 86400, y: 31536000 };
67
+ const liveTimeSec = ttlValue * TTL_MULTIPLIERS[ttlUnit];
68
+
69
+ // ── Config ──────────────────────────────────────────────────────────
70
+
71
+ let encryptKey;
72
+ try {
73
+ encryptKey = configModule.get('webServer.auth.jwtToken.encryptKey');
74
+ } catch {
75
+ // config key not found
76
+ }
77
+
78
+ if (!encryptKey || String(encryptKey).trim() === '' || encryptKey === '***') {
79
+ console.error('Error: webServer.auth.jwtToken.encryptKey is not configured or has a placeholder value.');
80
+ console.error('Set it in config/local.yaml or via ENV WS_TOKEN_ENCRYPT_KEY');
81
+ process.exit(1);
82
+ }
83
+
84
+ // ── Encryption (mirrors src/core/auth/jwt.ts) ───────────────────────
85
+
86
+ const ALGORITHM = 'aes-256-ctr';
87
+ const KEY = crypto
88
+ .createHash('sha256')
89
+ .update(String(encryptKey))
90
+ .digest('base64')
91
+ .substring(0, 32);
92
+
93
+ function encrypt (text) {
94
+ const buffer = Buffer.from(text);
95
+ const iv = crypto.randomBytes(16);
96
+ const cipher = crypto.createCipheriv(ALGORITHM, KEY, iv);
97
+ const encryptedBuf = Buffer.concat([iv, cipher.update(buffer), cipher.final()]);
98
+ return encryptedBuf.toString('hex');
99
+ }
100
+
101
+ // ── Auto-detect service name if checkMCPName is enabled ─────────────
102
+
103
+ let effectiveService = service;
104
+
105
+ if ((!effectiveService || !effectiveService.trim())) {
106
+ let checkMCPName = false;
107
+ try {
108
+ checkMCPName = configModule.get('webServer.auth.jwtToken.checkMCPName');
109
+ } catch {
110
+ // config key not found
111
+ }
112
+ if (checkMCPName) {
113
+ // 1) Try SERVICE_NAME from .env
114
+ if (process.env.SERVICE_NAME && process.env.SERVICE_NAME.trim()) {
115
+ effectiveService = process.env.SERVICE_NAME.trim();
116
+ } else {
117
+ // 2) Fallback to package.json name
118
+ try {
119
+ const __filename = fileURLToPath(import.meta.url);
120
+ const __dirname = dirname(__filename);
121
+ const pkgPath = resolve(__dirname, '..', 'package.json');
122
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
123
+ if (pkg.name) {
124
+ effectiveService = pkg.name;
125
+ }
126
+ } catch {
127
+ // package.json not found or unreadable
128
+ }
129
+ }
130
+ }
131
+ }
132
+
133
+ // ── Build payload ───────────────────────────────────────────────────
134
+
135
+ const payload = {};
136
+ payload.user = username.trim().toLowerCase();
137
+
138
+ if (effectiveService && effectiveService.trim()) {
139
+ payload.service = effectiveService.trim();
140
+ }
141
+
142
+ // Parse extra params: "key1=value1;key2=value2"
143
+ if (paramsRaw && paramsRaw.trim()) {
144
+ const pairs = paramsRaw.trim().split(';');
145
+ for (const pair of pairs) {
146
+ const eqIdx = pair.indexOf('=');
147
+ if (eqIdx <= 0) {
148
+ console.error(`Error: invalid param format "${pair}". Expected "key=value"`);
149
+ process.exit(1);
150
+ }
151
+ const key = pair.substring(0, eqIdx).trim();
152
+ const value = pair.substring(eqIdx + 1).trim();
153
+ if (!key) {
154
+ console.error(`Error: empty key in param "${pair}"`);
155
+ process.exit(1);
156
+ }
157
+ payload[key] = value;
158
+ }
159
+ }
160
+
161
+ const expire = Date.now() + (liveTimeSec * 1000);
162
+ payload.expire = expire;
163
+ payload.iat = new Date().toISOString();
164
+
165
+ // ── Generate token ──────────────────────────────────────────────────
166
+
167
+ const token = `${expire}.${encrypt(JSON.stringify(payload))}`;
168
+
169
+ console.log('');
170
+ console.log('JWT Token generated successfully');
171
+ console.log('─'.repeat(50));
172
+ console.log(` User: ${payload.user}`);
173
+ if (payload.service) {
174
+ console.log(` Service: ${payload.service}`);
175
+ }
176
+ console.log(` TTL: ${ttlRaw} (${liveTimeSec} seconds)`);
177
+ console.log(` Expires: ${new Date(expire).toISOString()}`);
178
+ if (Object.keys(payload).filter((k) => !['user', 'service', 'expire', 'iat'].includes(k)).length) {
179
+ const extra = Object.entries(payload)
180
+ .filter(([k]) => !['user', 'service', 'expire', 'iat'].includes(k))
181
+ .map(([k, v]) => `${k}=${v}`)
182
+ .join('; ');
183
+ console.log(` Params: ${extra}`);
184
+ }
185
+ console.log('─'.repeat(50));
186
+ console.log('');
187
+ console.log(token);
188
+ console.log('');
189
+ console.log('__PAYLOAD_JSON__');
190
+ console.log(JSON.stringify({ ...payload, ttl: ttlRaw, expire_iso: new Date(expire).toISOString() }));
191
+ console.log('__END_PAYLOAD_JSON__');