remove CORS and add CSRF token

This commit is contained in:
Blake Blackshear 2023-10-05 17:33:54 -05:00
parent 62a5cb87be
commit 86576889b1
18 changed files with 1064 additions and 333 deletions

View File

@ -93,10 +93,6 @@ http {
secure_token $args;
secure_token_types application/vnd.apple.mpegurl;
add_header Access-Control-Allow-Headers '*';
add_header Access-Control-Expose-Headers 'Server,range,Content-Length,Content-Range';
add_header Access-Control-Allow-Methods 'GET, HEAD, OPTIONS';
add_header Access-Control-Allow-Origin '*';
add_header Cache-Control "no-store";
expires off;
}
@ -104,16 +100,6 @@ http {
location /stream/ {
add_header Cache-Control "no-store";
expires off;
add_header 'Access-Control-Allow-Origin' "$http_origin" always;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Expose-Headers' 'Content-Length';
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' "$http_origin";
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
types {
application/dash+xml mpd;
@ -126,16 +112,6 @@ http {
}
location /clips/ {
add_header 'Access-Control-Allow-Origin' "$http_origin" always;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Expose-Headers' 'Content-Length';
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' "$http_origin";
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
types {
video/mp4 mp4;
@ -152,17 +128,6 @@ http {
}
location /recordings/ {
add_header 'Access-Control-Allow-Origin' "$http_origin" always;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Expose-Headers' 'Content-Length';
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' "$http_origin";
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
types {
video/mp4 mp4;
}
@ -173,17 +138,6 @@ http {
}
location /exports/ {
add_header 'Access-Control-Allow-Origin' "$http_origin" always;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Expose-Headers' 'Content-Length';
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' "$http_origin";
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
types {
video/mp4 mp4;
}
@ -235,8 +189,6 @@ http {
}
location ~* /api/.*\.(jpg|jpeg|png)$ {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
rewrite ^/api/(.*)$ $1 break;
proxy_pass http://frigate_api;
proxy_pass_request_headers on;
@ -248,10 +200,6 @@ http {
location /api/ {
add_header Cache-Control "no-store";
expires off;
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
proxy_pass http://frigate_api/;
proxy_pass_request_headers on;
proxy_set_header Host $host;

View File

@ -155,10 +155,6 @@ cd web && npm install
cd web && npm run dev
```
#### 3a. Run the development server against a non-local instance
To run the development server against a non-local instance, you will need to modify the API_HOST default return in `web/src/env.js`.
#### 4. Making changes
The Web UI is built using [Vite](https://vitejs.dev/), [Preact](https://preactjs.com), and [Tailwind CSS](https://tailwindcss.com).

View File

@ -74,6 +74,13 @@ def create_app(
):
app = Flask(__name__)
@app.before_request
def check_csrf():
if request.method in ["GET", "HEAD", "OPTIONS", "TRACE"]:
pass
if "origin" in request.headers and "x-csrf-token" not in request.headers:
return jsonify({"success": False, "message": "Missing CSRF header"}), 401
@app.before_request
def _db_connect():
if database.is_closed():

View File

@ -20,7 +20,6 @@
},
"ignorePatterns": ["*.d.ts"],
"rules": {
"indent": ["error", 2, { "SwitchCase": 1 }],
"comma-dangle": [
"error",
{

View File

@ -1,4 +1,5 @@
{
"printWidth": 120,
"trailingComma": "es5",
"singleQuote": true
}

1290
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -45,6 +45,7 @@
"eslint-config-preact": "^1.3.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-jest": "^27.2.3",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-vitest-globals": "^1.4.0",
"fake-indexeddb": "^4.0.1",
"jsdom": "^22.0.0",

View File

@ -1,2 +1 @@
import { API_HOST } from '../env';
export const baseUrl = API_HOST || `${window.location.protocol}//${window.location.host}${window.baseUrl || '/'}`;
export const baseUrl = `${window.location.protocol}//${window.location.host}${window.baseUrl || '/'}`;

View File

@ -5,6 +5,9 @@ import { WsProvider } from './ws';
import axios from 'axios';
axios.defaults.baseURL = `${baseUrl}api/`;
axios.defaults.headers.common = {
'X-CSRF-TOKEN': 1,
};
export function ApiProvider({ children, options }) {
return (

View File

@ -58,7 +58,7 @@ export default function CameraImage({ camera, onload, searchParams = '', stretch
if (!config || scaledHeight === 0 || !canvasRef.current) {
return;
}
img.src = `${apiHost}/api/${name}/latest.jpg?h=${scaledHeight}${searchParams ? `&${searchParams}` : ''}`;
img.src = `${apiHost}api/${name}/latest.jpg?h=${scaledHeight}${searchParams ? `&${searchParams}` : ''}`;
}, [apiHost, canvasRef, name, img, searchParams, scaledHeight, config]);
return (

View File

@ -56,10 +56,10 @@ export const HistoryVideo = ({
}
video.src({
src: `${apiHost}/vod/event/${id}/master.m3u8`,
src: `${apiHost}vod/event/${id}/master.m3u8`,
type: 'application/vnd.apple.mpegurl',
});
video.poster(`${apiHost}/api/events/${id}/snapshot.jpg`);
video.poster(`${apiHost}api/events/${id}/snapshot.jpg`);
if (videoIsPlaying) {
video.play();
}

View File

@ -153,7 +153,7 @@ export function EventCard({ camera, event }) {
<Link className="" href={`/recording/${camera}/${format(start, 'yyyy-MM-dd/HH/mm/ss')}`}>
<div className="flex flex-row mb-2">
<div className="w-28 mr-4">
<img className="antialiased" loading="lazy" src={`${apiHost}/api/events/${event.id}/thumbnail.jpg`} />
<img className="antialiased" loading="lazy" src={`${apiHost}api/events/${event.id}/thumbnail.jpg`} />
</div>
<div className="flex flex-row w-full border-b">
<div className="w-full text-gray-700 font-semibold relative pt-0">

View File

@ -1,2 +0,0 @@
export const ENV = import.meta.env.MODE;
export const API_HOST = ENV === 'production' ? '' : 'http://localhost:5000/';

View File

@ -212,7 +212,7 @@ export default function Camera({ camera }) {
key={objectType}
header={objectType}
href={`/events?cameras=${camera}&labels=${encodeURIComponent(objectType)}`}
media={<img src={`${apiHost}/api/${camera}/${encodeURIComponent(objectType)}/thumbnail.jpg`} />}
media={<img src={`${apiHost}api/${camera}/${encodeURIComponent(objectType)}/thumbnail.jpg`} />}
/>
))}
</div>

View File

@ -336,7 +336,7 @@ ${Object.keys(objectMaskPoints)
<div className="space-y-4">
<div className="relative">
<img ref={imageRef} src={`${apiHost}/api/${camera}/latest.jpg`} />
<img ref={imageRef} src={`${apiHost}api/${camera}/latest.jpg`} />
<EditableMask
onChange={handleUpdateEditable}
points={'subkey' in editing ? editing.set[editing.key][editing.subkey] : editing.set[editing.key]}

View File

@ -74,7 +74,7 @@ export default function Config() {
format: true,
schemas: [
{
uri: `${apiHost}/api/config/schema.json`,
uri: `${apiHost}api/config/schema.json`,
fileMatch: [String(modelUri)],
},
],

View File

@ -402,7 +402,7 @@ export default function Events({ path, ...props }) {
icon={Snapshot}
label="Download Snapshot"
value="snapshot"
href={`${apiHost}/api/events/${downloadEvent.id}/snapshot.jpg?download=true`}
href={`${apiHost}api/events/${downloadEvent.id}/snapshot.jpg?download=true`}
download
/>
)}
@ -411,7 +411,7 @@ export default function Events({ path, ...props }) {
icon={Clip}
label="Download Clip"
value="clip"
href={`${apiHost}/api/events/${downloadEvent.id}/clip.mp4?download=true`}
href={`${apiHost}api/events/${downloadEvent.id}/clip.mp4?download=true`}
download
/>
)}
@ -492,7 +492,7 @@ export default function Events({ path, ...props }) {
<img
className="flex-grow-0"
src={`${apiHost}/api/events/${plusSubmitEvent.id}/snapshot.jpg`}
src={`${apiHost}api/events/${plusSubmitEvent.id}/snapshot.jpg`}
alt={`${plusSubmitEvent.label}`}
/>
@ -619,7 +619,7 @@ export default function Events({ path, ...props }) {
<div
className="relative rounded-l flex-initial min-w-[125px] h-[125px] bg-contain bg-no-repeat bg-center"
style={{
'background-image': `url(${apiHost}/api/events/${event.id}/thumbnail.jpg)`,
'background-image': `url(${apiHost}api/events/${event.id}/thumbnail.jpg)`,
}}
>
<StarRecording
@ -776,8 +776,8 @@ export default function Events({ path, ...props }) {
className="flex-grow-0"
src={
event.has_snapshot
? `${apiHost}/api/events/${event.id}/snapshot.jpg`
: `${apiHost}/api/events/${event.id}/thumbnail.jpg`
? `${apiHost}api/events/${event.id}/snapshot.jpg`
: `${apiHost}api/events/${event.id}/thumbnail.jpg`
}
alt={`${event.label} at ${((event?.data?.top_score || event.top_score) * 100).toFixed(
0

View File

@ -9,6 +9,13 @@ export default defineConfig({
define: {
'import.meta.vitest': 'undefined',
},
server: {
proxy: {
'/api': {
target: 'http://localhost:5000'
}
}
},
plugins: [
preact(),
monacoEditorPlugin.default({