crewx 0.8.6-rc.2 → 0.8.6-rc.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/bin/crewx-lib.js +9 -1
- package/bin/crewx.js +22 -1
- package/dist-server/bootstrap/tls.js +7 -11
- package/dist-server/domain/auth/auth.controller.js +13 -11
- package/dist-server/domain/auth/guards/base-auth.guard.js +8 -7
- package/dist-server/domain/auth/session-store.js +26 -0
- package/package.json +9 -9
- package/packages/cli/package.json +1 -1
package/bin/crewx-lib.js
CHANGED
|
@@ -19,6 +19,8 @@ export function parseServeFlags(args) {
|
|
|
19
19
|
let token = undefined;
|
|
20
20
|
let tokenFile = undefined;
|
|
21
21
|
let noOpen = false;
|
|
22
|
+
let https = false;
|
|
23
|
+
let trust = false;
|
|
22
24
|
|
|
23
25
|
const consumed = new Set();
|
|
24
26
|
|
|
@@ -60,6 +62,12 @@ export function parseServeFlags(args) {
|
|
|
60
62
|
} else if (arg === '--no-open') {
|
|
61
63
|
noOpen = true;
|
|
62
64
|
consumed.add(i);
|
|
65
|
+
} else if (arg === '--https') {
|
|
66
|
+
https = true;
|
|
67
|
+
consumed.add(i);
|
|
68
|
+
} else if (arg === '--trust') {
|
|
69
|
+
trust = true;
|
|
70
|
+
consumed.add(i);
|
|
63
71
|
} else if (arg === '--help' || arg === '-h') {
|
|
64
72
|
return { help: true };
|
|
65
73
|
}
|
|
@@ -74,7 +82,7 @@ export function parseServeFlags(args) {
|
|
|
74
82
|
}
|
|
75
83
|
}
|
|
76
84
|
|
|
77
|
-
return { port, token, tokenFile, noOpen };
|
|
85
|
+
return { port, token, tokenFile, noOpen, https, trust };
|
|
78
86
|
}
|
|
79
87
|
|
|
80
88
|
/**
|
package/bin/crewx.js
CHANGED
|
@@ -94,6 +94,8 @@ function launchWeb(serveArgs = [], { openBrowser = false } = {}) {
|
|
|
94
94
|
' --port <N> Port to listen on (default: auto)\n' +
|
|
95
95
|
' --token <T> MCP bearer token\n' +
|
|
96
96
|
' --token-file <path> Read token from file\n' +
|
|
97
|
+
' --https Enable HTTPS with auto-generated certificate\n' +
|
|
98
|
+
' --trust Install CA to system trust store (requires --https)\n' +
|
|
97
99
|
' --no-open Do not open browser automatically\n' +
|
|
98
100
|
' --help, -h Show this help\n',
|
|
99
101
|
);
|
|
@@ -110,10 +112,21 @@ function launchWeb(serveArgs = [], { openBrowser = false } = {}) {
|
|
|
110
112
|
process.exit(flagsResult.error.startsWith('Unknown option') ? 2 : 1);
|
|
111
113
|
}
|
|
112
114
|
|
|
113
|
-
const { port, token, tokenFile, noOpen } = flagsResult;
|
|
115
|
+
const { port, token, tokenFile, noOpen, https, trust } = flagsResult;
|
|
114
116
|
|
|
115
117
|
const env = { ...process.env, NODE_ENV: 'production' }; // Force production — prevents users from bypassing FREE limits via env
|
|
116
118
|
|
|
119
|
+
if (https) {
|
|
120
|
+
env.CREWX_HTTPS = 'true';
|
|
121
|
+
}
|
|
122
|
+
if (trust) {
|
|
123
|
+
if (!https) {
|
|
124
|
+
console.error('❌ --trust requires --https');
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
env.CREWX_TRUST_CA = 'true';
|
|
128
|
+
}
|
|
129
|
+
|
|
117
130
|
if (port) {
|
|
118
131
|
env.PORT = String(port);
|
|
119
132
|
}
|
|
@@ -144,6 +157,14 @@ function launchWeb(serveArgs = [], { openBrowser = false } = {}) {
|
|
|
144
157
|
env.NODE_EXTRA_CA_CERTS = certPath;
|
|
145
158
|
}
|
|
146
159
|
|
|
160
|
+
if (https) {
|
|
161
|
+
console.log('🔒 HTTPS enabled with SowonLabs CA');
|
|
162
|
+
if (!trust) {
|
|
163
|
+
console.log('⚠️ Browser may show a certificate warning.');
|
|
164
|
+
console.log(' To remove warning: npx crewx serve --https --trust');
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
147
168
|
if (openBrowser && !noOpen) {
|
|
148
169
|
env.CREWX_OPEN_BROWSER = '1';
|
|
149
170
|
}
|
|
@@ -17,7 +17,7 @@ const CERT_PATH = (0, path_1.join)(TLS_DIR, 'cert.pem');
|
|
|
17
17
|
exports.CERT_PATH = CERT_PATH;
|
|
18
18
|
const KEY_PATH = (0, path_1.join)(TLS_DIR, 'key.pem');
|
|
19
19
|
async function resolveHttpsOptions() {
|
|
20
|
-
if (process.env.
|
|
20
|
+
if (process.env.CREWX_HTTPS !== 'true') {
|
|
21
21
|
return null;
|
|
22
22
|
}
|
|
23
23
|
if (process.env.TLS_CERT && process.env.TLS_KEY) {
|
|
@@ -37,7 +37,7 @@ async function resolveHttpsOptions() {
|
|
|
37
37
|
const server = generateServerCert(ca.cert, ca.key);
|
|
38
38
|
(0, fs_1.writeFileSync)(CERT_PATH, server.certPem, { mode: 0o644 });
|
|
39
39
|
(0, fs_1.writeFileSync)(KEY_PATH, server.keyPem, { mode: 0o600 });
|
|
40
|
-
if (ca.isNew) {
|
|
40
|
+
if (ca.isNew && process.env.CREWX_TRUST_CA === 'true') {
|
|
41
41
|
installCA(CA_CERT_PATH);
|
|
42
42
|
}
|
|
43
43
|
return {
|
|
@@ -109,13 +109,9 @@ function generateServerCert(caCert, caKey) {
|
|
|
109
109
|
};
|
|
110
110
|
}
|
|
111
111
|
function installCA(caCertPath) {
|
|
112
|
-
if (process.env.CREWX_TRUST_CA === '0') {
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
112
|
const platform = process.platform;
|
|
116
113
|
console.log('');
|
|
117
|
-
console.log('🔐
|
|
118
|
-
console.log(' 등록하지 않아도 HTTPS는 동작하지만, 브라우저에서 "주의 요함" 경고가 표시됩니다.');
|
|
114
|
+
console.log('🔐 Installing CA certificate to system trust store...');
|
|
119
115
|
console.log('');
|
|
120
116
|
try {
|
|
121
117
|
if (platform === 'win32') {
|
|
@@ -125,16 +121,16 @@ function installCA(caCertPath) {
|
|
|
125
121
|
(0, child_process_1.execSync)(`security add-trusted-cert -r trustRoot -k ~/Library/Keychains/login.keychain-db "${caCertPath}"`, { stdio: 'inherit' });
|
|
126
122
|
}
|
|
127
123
|
else {
|
|
128
|
-
console.log('
|
|
124
|
+
console.log(' Manual installation required on Linux:');
|
|
129
125
|
console.log(` sudo cp ${caCertPath} /usr/local/share/ca-certificates/crewx-ca.crt`);
|
|
130
126
|
console.log(' sudo update-ca-certificates');
|
|
131
127
|
return;
|
|
132
128
|
}
|
|
133
|
-
console.log('✅ SowonLabs CA
|
|
129
|
+
console.log('✅ SowonLabs CA installed. HTTPS will work without browser warnings.');
|
|
134
130
|
}
|
|
135
131
|
catch {
|
|
136
|
-
console.log('⚠️ CA
|
|
137
|
-
console.log(`
|
|
132
|
+
console.log('⚠️ CA installation failed. HTTPS still works, but browser will show a warning.');
|
|
133
|
+
console.log(` Manual install: ${caCertPath}`);
|
|
138
134
|
}
|
|
139
135
|
}
|
|
140
136
|
function randomSerialNumber() {
|
|
@@ -18,10 +18,10 @@ const common_1 = require("@nestjs/common");
|
|
|
18
18
|
const swagger_1 = require("@nestjs/swagger");
|
|
19
19
|
const class_validator_1 = require("class-validator");
|
|
20
20
|
const throttler_1 = require("@nestjs/throttler");
|
|
21
|
-
const mcp_constants_js_1 = require("../mcp/mcp.constants.js");
|
|
22
21
|
const auth_constants_js_1 = require("./auth.constants.js");
|
|
23
22
|
const public_decorator_js_1 = require("./decorators/public.decorator.js");
|
|
24
23
|
const ip_utils_js_1 = require("./utils/ip.utils.js");
|
|
24
|
+
const session_store_js_1 = require("./session-store.js");
|
|
25
25
|
class LoginDto {
|
|
26
26
|
username;
|
|
27
27
|
password;
|
|
@@ -38,11 +38,9 @@ __decorate([
|
|
|
38
38
|
], LoginDto.prototype, "password", void 0);
|
|
39
39
|
const COOKIE_NAME = 'crewx_token';
|
|
40
40
|
let AuthController = AuthController_1 = class AuthController {
|
|
41
|
-
mcpToken;
|
|
42
41
|
credentials;
|
|
43
42
|
logger = new common_1.Logger(AuthController_1.name);
|
|
44
|
-
constructor(
|
|
45
|
-
this.mcpToken = mcpToken;
|
|
43
|
+
constructor(credentials) {
|
|
46
44
|
this.credentials = credentials;
|
|
47
45
|
}
|
|
48
46
|
// AUTH.CHECK: Guard already passed at this point — always returns 200
|
|
@@ -66,7 +64,8 @@ let AuthController = AuthController_1 = class AuthController {
|
|
|
66
64
|
error: { code: 'INVALID_CREDENTIALS', message: 'Invalid username or password' },
|
|
67
65
|
});
|
|
68
66
|
}
|
|
69
|
-
|
|
67
|
+
const sessionId = (0, session_store_js_1.createSession)();
|
|
68
|
+
res.cookie(COOKIE_NAME, sessionId, {
|
|
70
69
|
httpOnly: true,
|
|
71
70
|
sameSite: 'strict',
|
|
72
71
|
secure: req.secure,
|
|
@@ -76,7 +75,10 @@ let AuthController = AuthController_1 = class AuthController {
|
|
|
76
75
|
return { success: true, data: { authenticated: true } };
|
|
77
76
|
}
|
|
78
77
|
// AUTH.LOGOUT: clear session cookie (BaseAuthGuard applied — not @Public)
|
|
79
|
-
logout(res) {
|
|
78
|
+
logout(req, res) {
|
|
79
|
+
const cookieSessionId = req.cookies?.[COOKIE_NAME];
|
|
80
|
+
if (cookieSessionId)
|
|
81
|
+
(0, session_store_js_1.destroySession)(cookieSessionId);
|
|
80
82
|
res.cookie(COOKIE_NAME, '', { httpOnly: true, maxAge: 0, path: '/' });
|
|
81
83
|
return { success: true, data: { authenticated: false } };
|
|
82
84
|
}
|
|
@@ -107,15 +109,15 @@ __decorate([
|
|
|
107
109
|
(0, swagger_1.ApiOperation)({ operationId: 'AUTH.LOGOUT', summary: 'Logout and clear session cookie' }),
|
|
108
110
|
(0, common_1.Post)('logout'),
|
|
109
111
|
(0, common_1.HttpCode)(200),
|
|
110
|
-
__param(0, (0, common_1.
|
|
112
|
+
__param(0, (0, common_1.Req)()),
|
|
113
|
+
__param(1, (0, common_1.Res)({ passthrough: true })),
|
|
111
114
|
__metadata("design:type", Function),
|
|
112
|
-
__metadata("design:paramtypes", [Object]),
|
|
115
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
113
116
|
__metadata("design:returntype", Object)
|
|
114
117
|
], AuthController.prototype, "logout", null);
|
|
115
118
|
exports.AuthController = AuthController = AuthController_1 = __decorate([
|
|
116
119
|
(0, swagger_1.ApiTags)('auth'),
|
|
117
120
|
(0, common_1.Controller)('auth'),
|
|
118
|
-
__param(0, (0, common_1.Inject)(
|
|
119
|
-
|
|
120
|
-
__metadata("design:paramtypes", [Object, Object])
|
|
121
|
+
__param(0, (0, common_1.Inject)(auth_constants_js_1.AUTH_CREDENTIALS)),
|
|
122
|
+
__metadata("design:paramtypes", [Object])
|
|
121
123
|
], AuthController);
|
|
@@ -18,6 +18,7 @@ const core_1 = require("@nestjs/core");
|
|
|
18
18
|
const mcp_constants_js_1 = require("../../mcp/mcp.constants.js");
|
|
19
19
|
const public_decorator_js_1 = require("../decorators/public.decorator.js");
|
|
20
20
|
const ip_utils_js_1 = require("../utils/ip.utils.js");
|
|
21
|
+
const session_store_js_1 = require("../session-store.js");
|
|
21
22
|
let BaseAuthGuard = class BaseAuthGuard {
|
|
22
23
|
reflector;
|
|
23
24
|
mcpToken;
|
|
@@ -43,15 +44,15 @@ let BaseAuthGuard = class BaseAuthGuard {
|
|
|
43
44
|
return (0, ip_utils_js_1.isLoopback)(ip);
|
|
44
45
|
}
|
|
45
46
|
validateToken(req) {
|
|
46
|
-
const
|
|
47
|
+
const cookieSessionId = req.cookies?.['crewx_token'];
|
|
47
48
|
const bearerToken = (0, ip_utils_js_1.extractBearerToken)(req);
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
|
|
49
|
+
if (cookieSessionId && (0, session_store_js_1.validateSession)(cookieSessionId)) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
if (bearerToken && (0, ip_utils_js_1.safeCompare)(bearerToken, this.mcpToken.token)) {
|
|
53
|
+
return true;
|
|
53
54
|
}
|
|
54
|
-
|
|
55
|
+
throw new common_1.UnauthorizedException('Authentication required');
|
|
55
56
|
}
|
|
56
57
|
};
|
|
57
58
|
exports.BaseAuthGuard = BaseAuthGuard;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createSession = createSession;
|
|
4
|
+
exports.validateSession = validateSession;
|
|
5
|
+
exports.destroySession = destroySession;
|
|
6
|
+
const crypto_1 = require("crypto");
|
|
7
|
+
const sessions = new Map();
|
|
8
|
+
const SESSION_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days (matches cookie maxAge)
|
|
9
|
+
function createSession() {
|
|
10
|
+
const sessionId = (0, crypto_1.randomBytes)(32).toString('hex');
|
|
11
|
+
sessions.set(sessionId, { createdAt: Date.now() });
|
|
12
|
+
return sessionId;
|
|
13
|
+
}
|
|
14
|
+
function validateSession(sessionId) {
|
|
15
|
+
const session = sessions.get(sessionId);
|
|
16
|
+
if (!session)
|
|
17
|
+
return false;
|
|
18
|
+
if (Date.now() - session.createdAt > SESSION_MAX_AGE_MS) {
|
|
19
|
+
sessions.delete(sessionId);
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
function destroySession(sessionId) {
|
|
25
|
+
sessions.delete(sessionId);
|
|
26
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crewx",
|
|
3
|
-
"version": "0.8.6-rc.
|
|
3
|
+
"version": "0.8.6-rc.4",
|
|
4
4
|
"description": "CrewX — AI agent team dashboard with Electron UI and CLI (Web + Electron + Global CLI)",
|
|
5
5
|
"main": "server.js",
|
|
6
6
|
"bin": {
|
|
@@ -68,17 +68,17 @@
|
|
|
68
68
|
"wink-nlp-utils": "^2.1.0",
|
|
69
69
|
"yargs": "^17.7.0",
|
|
70
70
|
"zod": "^3.22.0",
|
|
71
|
-
"@crewx/cli": "0.8.6-rc.
|
|
72
|
-
"@crewx/cron": "0.1.8",
|
|
71
|
+
"@crewx/cli": "0.8.6-rc.4",
|
|
73
72
|
"@crewx/doc": "0.1.8",
|
|
74
|
-
"@crewx/
|
|
73
|
+
"@crewx/cron": "0.1.8",
|
|
75
74
|
"@crewx/knowledge-core": "0.1.6",
|
|
76
|
-
"@crewx/
|
|
77
|
-
"@crewx/
|
|
78
|
-
"@crewx/wbs": "0.1.9",
|
|
75
|
+
"@crewx/memory": "0.1.10",
|
|
76
|
+
"@crewx/sdk": "0.8.6-rc.5",
|
|
79
77
|
"@crewx/search": "0.1.9",
|
|
80
|
-
"@crewx/
|
|
81
|
-
"@crewx/
|
|
78
|
+
"@crewx/skill": "0.1.8",
|
|
79
|
+
"@crewx/wbs": "0.1.9",
|
|
80
|
+
"@crewx/shared": "0.0.5",
|
|
81
|
+
"@crewx/workflow": "0.3.18"
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
84
84
|
"@ccusage/codex": "^0.0.1",
|