added ws authentication
This commit is contained in:
@@ -29,7 +29,10 @@ class RunCommand extends Component
|
|||||||
}
|
}
|
||||||
|
|
||||||
return $server->definedResources()
|
return $server->definedResources()
|
||||||
->filter(fn ($resource) => str_starts_with($resource->status, 'running:'))
|
->filter(function ($resource) {
|
||||||
|
$status = method_exists($resource, 'realStatus') ? $resource->realStatus() : (method_exists($resource, 'status') ? $resource->status() : 'exited');
|
||||||
|
return str_starts_with($status, 'running:');
|
||||||
|
})
|
||||||
->map(function ($resource) use ($server) {
|
->map(function ($resource) use ($server) {
|
||||||
$container_name = $resource->uuid;
|
$container_name = $resource->uuid;
|
||||||
|
|
||||||
|
|||||||
32
package-lock.json
generated
32
package-lock.json
generated
@@ -10,6 +10,8 @@
|
|||||||
"@xterm/addon-fit": "^0.10.0",
|
"@xterm/addon-fit": "^0.10.0",
|
||||||
"@xterm/xterm": "^5.5.0",
|
"@xterm/xterm": "^5.5.0",
|
||||||
"alpinejs": "3.14.0",
|
"alpinejs": "3.14.0",
|
||||||
|
"cookie": "^0.6.0",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
"ioredis": "5.4.1",
|
"ioredis": "5.4.1",
|
||||||
"node-pty": "^1.0.0",
|
"node-pty": "^1.0.0",
|
||||||
"tailwindcss-scrollbar": "0.1.0",
|
"tailwindcss-scrollbar": "0.1.0",
|
||||||
@@ -18,7 +20,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "4.5.1",
|
"@vitejs/plugin-vue": "4.5.1",
|
||||||
"autoprefixer": "10.4.19",
|
"autoprefixer": "10.4.19",
|
||||||
"axios": "1.7.2",
|
"axios": "^1.7.4",
|
||||||
"laravel-echo": "1.16.1",
|
"laravel-echo": "1.16.1",
|
||||||
"laravel-vite-plugin": "0.8.1",
|
"laravel-vite-plugin": "0.8.1",
|
||||||
"postcss": "8.4.38",
|
"postcss": "8.4.38",
|
||||||
@@ -783,10 +785,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "1.7.2",
|
"version": "1.7.4",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz",
|
||||||
"integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==",
|
"integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.6",
|
"follow-redirects": "^1.15.6",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
@@ -956,6 +959,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
|
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/cookie": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cssesc": {
|
"node_modules/cssesc": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||||
@@ -1016,6 +1028,18 @@
|
|||||||
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
||||||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
|
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/dotenv": {
|
||||||
|
"version": "16.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
|
||||||
|
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://dotenvx.com"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.4.692",
|
"version": "1.4.692",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.692.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.692.tgz",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "4.5.1",
|
"@vitejs/plugin-vue": "4.5.1",
|
||||||
"autoprefixer": "10.4.19",
|
"autoprefixer": "10.4.19",
|
||||||
"axios": "1.7.2",
|
"axios": "^1.7.4",
|
||||||
"laravel-echo": "1.16.1",
|
"laravel-echo": "1.16.1",
|
||||||
"laravel-vite-plugin": "0.8.1",
|
"laravel-vite-plugin": "0.8.1",
|
||||||
"postcss": "8.4.38",
|
"postcss": "8.4.38",
|
||||||
@@ -23,6 +23,8 @@
|
|||||||
"@xterm/addon-fit": "^0.10.0",
|
"@xterm/addon-fit": "^0.10.0",
|
||||||
"@xterm/xterm": "^5.5.0",
|
"@xterm/xterm": "^5.5.0",
|
||||||
"alpinejs": "3.14.0",
|
"alpinejs": "3.14.0",
|
||||||
|
"cookie": "^0.6.0",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
"ioredis": "5.4.1",
|
"ioredis": "5.4.1",
|
||||||
"node-pty": "^1.0.0",
|
"node-pty": "^1.0.0",
|
||||||
"tailwindcss-scrollbar": "0.1.0",
|
"tailwindcss-scrollbar": "0.1.0",
|
||||||
|
|||||||
@@ -34,8 +34,9 @@
|
|||||||
|
|
||||||
function initializeWebSocket() {
|
function initializeWebSocket() {
|
||||||
if (!socket || socket.readyState === WebSocket.CLOSED) {
|
if (!socket || socket.readyState === WebSocket.CLOSED) {
|
||||||
|
const url = "{{ str_replace(['http://', 'https://'], '', config('app.url')) }}" || window.location.hostname;
|
||||||
socket = new WebSocket((window.location.protocol === 'https:' ? 'wss://' : 'ws://') +
|
socket = new WebSocket((window.location.protocol === 'https:' ? 'wss://' : 'ws://') +
|
||||||
"{{ str_replace(['http://', 'https://'], '', config('app.url')) }}" +
|
url +
|
||||||
':6002/terminal');
|
':6002/terminal');
|
||||||
socket.onmessage = handleSocketMessage;
|
socket.onmessage = handleSocketMessage;
|
||||||
socket.onerror = (e) => {
|
socket.onerror = (e) => {
|
||||||
|
|||||||
@@ -154,6 +154,12 @@ Route::middleware(['auth', 'verified'])->group(function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Route::get('/command-center', CommandCenterIndex::class)->name('command-center');
|
Route::get('/command-center', CommandCenterIndex::class)->name('command-center');
|
||||||
|
Route::post('/terminal/auth', function () {
|
||||||
|
if (auth()->check()) {
|
||||||
|
return response()->json(['authenticated' => true], 200);
|
||||||
|
}
|
||||||
|
return response()->json(['authenticated' => false], 401);
|
||||||
|
})->name('terminal.auth');
|
||||||
|
|
||||||
Route::prefix('invitations')->group(function () {
|
Route::prefix('invitations')->group(function () {
|
||||||
Route::get('/{uuid}', [Controller::class, 'accept_invitation'])->name('team.invitation.accept');
|
Route::get('/{uuid}', [Controller::class, 'accept_invitation'])->name('team.invitation.accept');
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { WebSocketServer } from 'ws';
|
import { WebSocketServer } from 'ws';
|
||||||
import http from 'http';
|
import http from 'http';
|
||||||
import pty from 'node-pty';
|
import pty from 'node-pty';
|
||||||
|
import axios from 'axios';
|
||||||
|
import cookie from 'cookie';
|
||||||
|
import 'dotenv/config'
|
||||||
|
|
||||||
const server = http.createServer((req, res) => {
|
const server = http.createServer((req, res) => {
|
||||||
if (req.url === '/ready') {
|
if (req.url === '/ready') {
|
||||||
@@ -12,7 +15,45 @@ const server = http.createServer((req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const wss = new WebSocketServer({ server, path: '/terminal' });
|
const verifyClient = async (info, callback) => {
|
||||||
|
const cookies = cookie.parse(info.req.headers.cookie || '');
|
||||||
|
const origin = new URL(info.origin);
|
||||||
|
const protocol = origin.protocol;
|
||||||
|
const xsrfToken = cookies['XSRF-TOKEN'];
|
||||||
|
|
||||||
|
// Generate session cookie name based on APP_NAME
|
||||||
|
const appName = process.env.APP_NAME || 'laravel';
|
||||||
|
const sessionCookieName = `${appName.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase()}_session`;
|
||||||
|
const laravelSession = cookies[sessionCookieName];
|
||||||
|
|
||||||
|
// Verify presence of required tokens
|
||||||
|
if (!laravelSession || !xsrfToken) {
|
||||||
|
return callback(false, 401, 'Unauthorized: Missing required tokens');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Authenticate with Laravel backend
|
||||||
|
const response = await axios.post(`${protocol}//coolify/terminal/auth`, null, {
|
||||||
|
headers: {
|
||||||
|
'Cookie': `${sessionCookieName}=${laravelSession}`,
|
||||||
|
'X-XSRF-TOKEN': xsrfToken
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status === 200) {
|
||||||
|
// Authentication successful
|
||||||
|
callback(true);
|
||||||
|
} else {
|
||||||
|
callback(false, 401, 'Unauthorized: Invalid credentials');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Authentication error:', error.message);
|
||||||
|
callback(false, 500, 'Internal Server Error');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const wss = new WebSocketServer({ server, path: '/terminal', verifyClient: verifyClient });
|
||||||
const userSessions = new Map();
|
const userSessions = new Map();
|
||||||
|
|
||||||
wss.on('connection', (ws) => {
|
wss.on('connection', (ws) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user