mirror of
https://github.com/koush/scrypted.git
synced 2026-02-09 00:39:56 +00:00
server: add support for CORS API access
This commit is contained in:
4
server/package-lock.json
generated
4
server/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/server",
|
||||
"version": "0.5.13",
|
||||
"version": "0.6.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/server",
|
||||
"version": "0.5.13",
|
||||
"version": "0.6.1",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/server",
|
||||
"version": "0.5.13",
|
||||
"version": "0.6.1",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { HttpRequest } from '@scrypted/types';
|
||||
import bodyParser from 'body-parser';
|
||||
import { Request, Response, Router } from 'express';
|
||||
import { ServerResponse, IncomingHttpHeaders } from 'http';
|
||||
import { IncomingHttpHeaders, IncomingMessage, ServerResponse } from 'http';
|
||||
import WebSocket, { Server as WebSocketServer } from "ws";
|
||||
|
||||
export function isConnectionUpgrade(headers: IncomingHttpHeaders) {
|
||||
@@ -40,6 +40,8 @@ export abstract class PluginHttp<T> {
|
||||
abstract handleRequestEndpoint(req: Request, res: Response, endpointRequest: HttpRequest, pluginData: T): void;
|
||||
abstract getEndpointPluginData(req: Request, endpoint: string, isUpgrade: boolean, isEngineIOEndpoint: boolean): Promise<T>;
|
||||
abstract handleWebSocket(endpoint: string, httpRequest: HttpRequest, ws: WebSocket, pluginData: T): Promise<void>;
|
||||
abstract addAccessControlHeaders(req: IncomingMessage, res: ServerResponse): void;
|
||||
abstract checkUpgrade(req: Request, res: Response, pluginData: T): void;
|
||||
|
||||
async endpointHandler(req: Request, res: Response, isPublicEndpoint: boolean, isEngineIOEndpoint: boolean,
|
||||
handler: (req: Request, res: Response, endpointRequest: HttpRequest, pluginData: T) => void) {
|
||||
@@ -59,28 +61,45 @@ export abstract class PluginHttp<T> {
|
||||
}
|
||||
};
|
||||
|
||||
const { owner, pkg } = req.params;
|
||||
let endpoint = pkg;
|
||||
if (owner)
|
||||
endpoint = `@${owner}/${endpoint}`;
|
||||
const pluginData = await this.getEndpointPluginData(req, endpoint, isUpgrade, isEngineIOEndpoint);
|
||||
|
||||
if (!pluginData) {
|
||||
end(404, `Not Found (plugin or device "${endpoint}" not found)`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isEngineIOEndpoint && isUpgrade) {
|
||||
this.checkUpgrade(req, res, pluginData);
|
||||
}
|
||||
|
||||
if (isEngineIOEndpoint && !isUpgrade) {
|
||||
console.log('req', req.method, req.url, req.headers);
|
||||
this.addAccessControlHeaders(req, res);
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
||||
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With, Access-Control-Request-Method');
|
||||
}
|
||||
|
||||
if (isEngineIOEndpoint && req.method === 'OPTIONS') {
|
||||
res.send(204);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!isPublicEndpoint && !res.locals.username) {
|
||||
end(401, 'Not Authorized');
|
||||
console.log('rejected request', isPublicEndpoint, res.locals.username, req.originalUrl)
|
||||
return;
|
||||
}
|
||||
|
||||
const { owner, pkg } = req.params;
|
||||
let endpoint = pkg;
|
||||
if (owner)
|
||||
endpoint = `@${owner}/${endpoint}`;
|
||||
|
||||
if (isUpgrade && req.headers.upgrade?.toLowerCase() !== 'websocket') {
|
||||
end(404, 'Not Found (unknown upgrade protocol)');
|
||||
return;
|
||||
}
|
||||
|
||||
const pluginData = await this.getEndpointPluginData(req, endpoint, isUpgrade, isEngineIOEndpoint);
|
||||
if (!pluginData) {
|
||||
end(404, `Not Found (plugin or device "${endpoint}" not found)`);
|
||||
return;
|
||||
}
|
||||
|
||||
let rootPath = `/endpoint/${endpoint}`;
|
||||
if (isPublicEndpoint)
|
||||
rootPath += '/public'
|
||||
@@ -98,10 +117,6 @@ export abstract class PluginHttp<T> {
|
||||
aclId: res.locals.aclId,
|
||||
};
|
||||
|
||||
if (isEngineIOEndpoint && !isUpgrade && isPublicEndpoint) {
|
||||
res.header("Access-Control-Allow-Origin", '*');
|
||||
}
|
||||
|
||||
if (!isEngineIOEndpoint && isUpgrade) {
|
||||
try {
|
||||
this.wss.handleUpgrade(req, req.socket, (req as any).upgradeHead, async (ws) => {
|
||||
|
||||
@@ -4,10 +4,12 @@ import axios from 'axios';
|
||||
import * as io from 'engine.io';
|
||||
import { once } from 'events';
|
||||
import express, { Request, Response } from 'express';
|
||||
import { ParamsDictionary } from 'express-serve-static-core';
|
||||
import http, { ServerResponse } from 'http';
|
||||
import https from 'https';
|
||||
import type { spawn as ptySpawn } from 'node-pty-prebuilt-multiarch';
|
||||
import path from 'path';
|
||||
import { ParsedQs } from 'qs';
|
||||
import rimraf from 'rimraf';
|
||||
import semver from 'semver';
|
||||
import { PassThrough } from 'stream';
|
||||
@@ -155,11 +157,23 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
|
||||
}, 60 * 60 * 1000);
|
||||
}
|
||||
|
||||
checkUpgrade(req: express.Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>, res: express.Response<any, Record<string, any>>, pluginData: HttpPluginData): void {
|
||||
// pluginData.pluginHost.io.
|
||||
const { sid } = req.query;
|
||||
const client = (pluginData.pluginHost.io as any).clients[sid as string];
|
||||
if (client) {
|
||||
res.locals.username = 'existing-io-session';
|
||||
}
|
||||
}
|
||||
|
||||
addAccessControlHeaders(req: http.IncomingMessage, res: http.ServerResponse) {
|
||||
res.setHeader('Vary', 'Origin,Referer');
|
||||
const header = this.getAccessControlAllowOrigin(req.headers);
|
||||
if (header)
|
||||
if (header) {
|
||||
res.setHeader('Access-Control-Allow-Origin', header);
|
||||
res.setHeader("Access-Control-Allow-Credentials", "true");
|
||||
res.setHeader('Access-Control-Allow-Private-Network', 'true');
|
||||
}
|
||||
}
|
||||
|
||||
getAccessControlAllowOrigin(headers: http.IncomingHttpHeaders) {
|
||||
|
||||
@@ -439,7 +439,7 @@ async function start() {
|
||||
const { username, password, change_password, maxAge: maxAgeRequested } = req.body;
|
||||
const timestamp = Date.now();
|
||||
const maxAge = parseInt(maxAgeRequested) || ONE_DAY_MILLISECONDS;
|
||||
const addresses = getHostAddresses(true, true).map(address => `https://${address}:${SCRYPTED_SECURE_PORT}`);
|
||||
const addresses = ((await scrypted.addressSettings.getLocalAddresses()) || getHostAddresses(true, true)).map(address => `https://${address}:${SCRYPTED_SECURE_PORT}`);
|
||||
|
||||
if (hasLogin) {
|
||||
const user = await db.tryGet(ScryptedUser, username);
|
||||
@@ -520,14 +520,13 @@ async function start() {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
app.get('/login', async (req, res) => {
|
||||
await checkResetLogin();
|
||||
|
||||
scrypted.addAccessControlHeaders(req, res);
|
||||
const hostname = os.hostname()?.split('.')?.[0];
|
||||
|
||||
const addresses = getHostAddresses(true, true).map(address => `https://${address}:${SCRYPTED_SECURE_PORT}`);
|
||||
const addresses = ((await scrypted.addressSettings.getLocalAddresses()) || getHostAddresses(true, true)).map(address => `https://${address}:${SCRYPTED_SECURE_PORT}`);
|
||||
if (req.protocol === 'https' && req.headers.authorization) {
|
||||
const username = await new Promise(resolve => {
|
||||
const basicChecker = basicAuth.check((req) => {
|
||||
|
||||
Reference in New Issue
Block a user