diff --git a/server/src/auth.ts b/server/src/auth.ts new file mode 100644 index 0000000..f7e21b7 --- /dev/null +++ b/server/src/auth.ts @@ -0,0 +1,14 @@ +import { Request } from 'express' +import { createHash } from 'crypto' + +export function isAuthorized (expectedSha: string, request: Request) { + if (request.headers && request.headers.authorization) { + const { authorization } = request.headers + const [algo, b64] = authorization.split(' ') + if (algo.toLowerCase() === 'basic') { + const sha = createHash('sha256').update(Buffer.from(b64, 'base64').toString()).digest('hex') + return sha === expectedSha + } + } + return false +} diff --git a/server/src/index.ts b/server/src/index.ts index 9280696..0c46198 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,6 +1,10 @@ import { Server } from './server' import { Keyboard } from './keyboard' +if (!process.env.AUTH_SHA) { + throw new Error('AUTH_SHA was not set.') +} + const port = Number(process.env.PORT || 3000) const keyboard = new Keyboard({ @@ -9,7 +13,8 @@ const keyboard = new Keyboard({ }) const server = new Server({ - mjpegUrl: process.env.MJPEG_URL || 'http://127.0.0.1:9000/stream/video.mjpeg' + mjpegUrl: process.env.MJPEG_URL || 'http://127.0.0.1:9000/stream/video.mjpeg', + authSha: process.env.AUTH_SHA }) server.on('ps2-command', (ps2Command) => { diff --git a/server/src/server.ts b/server/src/server.ts index 0ef9544..8a4a4dd 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -4,30 +4,55 @@ import path from 'path' import { CommandFrame } from 'common' import { EventEmitter } from 'events' import http from 'http' +import { isAuthorized } from './auth' let streamCounter = 0 +const unauthorizedHttpMessage = 'HTTP/1.1 401 Unauthorized\r\nwww-authenticate: basic\r\n\r\n' + export class Server extends EventEmitter { mjpegUrl: string + authSha: string + app: express.Application - constructor(opts: { mjpegUrl: string }) { + constructor(opts: { mjpegUrl: string, authSha: string }) { super() this.mjpegUrl = opts.mjpegUrl - } + this.authSha = opts.authSha - listen(port: number) { - const app = express() - // app.use(authMiddleware) - app.use(express.static(path.join(__dirname, '../../frontend/dist'))) + const app = this.app = express() + app.set('x-powered-by', false) + + app.use(this.authMiddleware) + + app.use(express.static(path.join(__dirname, '../../frontend/dist'))) app.get('/video.mjpeg', this.handleVideo) + } - const server = app.listen(port, () => console.log(`pilo listening on port ${port}`)) - const webSocketServer = new WebSocketServer({ httpServer: server }) + listen(port: number) { + const server = this.app.listen(port, () => console.log(`pilo listening on port ${port}`)) + const webSocketServer = new WebSocketServer() webSocketServer.on('command', (data: CommandFrame) => { this.emit(data.type, data.ps2Command) }) + + server.on('upgrade', (request, socket, head) => { + if (isAuthorized(this.authSha, request)) { + return webSocketServer.handleUpgrade(request, socket, head) + } + + socket.end(unauthorizedHttpMessage) + }) + } + + authMiddleware = (req: express.Request, res: express.Response, next) => { + if (isAuthorized(this.authSha, req)) { + return next() + } + + res.connection.end(unauthorizedHttpMessage) } handleVideo = (req, res) => { diff --git a/server/src/websocket-server.ts b/server/src/websocket-server.ts index 652c9ee..0e8e20c 100644 --- a/server/src/websocket-server.ts +++ b/server/src/websocket-server.ts @@ -1,19 +1,18 @@ import * as ws from 'ws' -import * as http from 'http' import { CommandFrame } from 'common' import { EventEmitter } from 'events' let connectionCounter = 0 export class WebSocketServer extends EventEmitter { - constructor(opts: { - httpServer: http.Server - }) { + wss: ws.Server + + constructor() { super() - const server = new ws.Server({ server: opts.httpServer }) + const wss = this.wss = new ws.Server({ noServer: true }) - server.on('connection', (socket) => { + wss.on('connection', (socket) => { const connectionId = connectionCounter++ console.log(`Websocket connection received (connectionId: ${connectionId})`) @@ -29,4 +28,10 @@ export class WebSocketServer extends EventEmitter { }) }) } + + handleUpgrade = (request, socket, head) => { + this.wss.handleUpgrade(request, socket, head, function done(ws) { + this.wss.emit('connection', ws) + }) + } }