mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Add API key authentication.
We don't have any sensitive data, but we do access the file system. On the off chance that some phishing website decides to try to use Liberation as an attack vector, prevent access to the API by unauthorized applications. An API key is generated at each program start and passed to the front end via the QWebChannel.
This commit is contained in:
parent
09457d8aab
commit
77d29e314c
@ -1,7 +1,8 @@
|
|||||||
from fastapi import FastAPI
|
from fastapi import Depends, FastAPI
|
||||||
|
|
||||||
from . import debuggeometries, eventstream
|
from . import debuggeometries, eventstream
|
||||||
|
from .security import ApiKeyManager
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI(dependencies=[Depends(ApiKeyManager.verify)])
|
||||||
app.include_router(debuggeometries.router)
|
app.include_router(debuggeometries.router)
|
||||||
app.include_router(eventstream.router)
|
app.include_router(eventstream.router)
|
||||||
|
|||||||
15
game/server/security.py
Normal file
15
game/server/security.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import secrets
|
||||||
|
|
||||||
|
from fastapi import HTTPException, Security, status
|
||||||
|
from fastapi.security import APIKeyHeader
|
||||||
|
|
||||||
|
API_KEY_HEADER = APIKeyHeader(name="X-API-Key")
|
||||||
|
|
||||||
|
|
||||||
|
class ApiKeyManager:
|
||||||
|
KEY = secrets.token_urlsafe()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def verify(cls, api_key_header: str = Security(API_KEY_HEADER)) -> None:
|
||||||
|
if api_key_header != cls.KEY:
|
||||||
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||||
@ -10,6 +10,7 @@ from game import Game
|
|||||||
from game.ato.airtaaskingorder import AirTaskingOrder
|
from game.ato.airtaaskingorder import AirTaskingOrder
|
||||||
from game.profiling import logged_duration
|
from game.profiling import logged_duration
|
||||||
from game.server.leaflet import LeafletLatLon
|
from game.server.leaflet import LeafletLatLon
|
||||||
|
from game.server.security import ApiKeyManager
|
||||||
from game.theater import (
|
from game.theater import (
|
||||||
ConflictTheater,
|
ConflictTheater,
|
||||||
)
|
)
|
||||||
@ -46,6 +47,7 @@ from .unculledzonejs import UnculledZone
|
|||||||
class MapModel(QObject):
|
class MapModel(QObject):
|
||||||
cleared = Signal()
|
cleared = Signal()
|
||||||
|
|
||||||
|
apiKeyChanged = Signal(str)
|
||||||
mapCenterChanged = Signal(list)
|
mapCenterChanged = Signal(list)
|
||||||
controlPointsChanged = Signal()
|
controlPointsChanged = Signal()
|
||||||
groundObjectsChanged = Signal()
|
groundObjectsChanged = Signal()
|
||||||
@ -187,6 +189,10 @@ class MapModel(QObject):
|
|||||||
self._map_center = [ll.latitude, ll.longitude]
|
self._map_center = [ll.latitude, ll.longitude]
|
||||||
self.mapCenterChanged.emit(self._map_center)
|
self.mapCenterChanged.emit(self._map_center)
|
||||||
|
|
||||||
|
@Property(str, notify=apiKeyChanged)
|
||||||
|
def apiKey(self) -> str:
|
||||||
|
return ApiKeyManager.KEY
|
||||||
|
|
||||||
@Property(list, notify=mapCenterChanged)
|
@Property(list, notify=mapCenterChanged)
|
||||||
def mapCenter(self) -> LeafletLatLon:
|
def mapCenter(self) -> LeafletLatLon:
|
||||||
return self._map_center
|
return self._map_center
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import qt_ui.uiconstants as CONST
|
|||||||
from game import Game, VERSION, persistency
|
from game import Game, VERSION, persistency
|
||||||
from game.debriefing import Debriefing
|
from game.debriefing import Debriefing
|
||||||
from game.server import EventStream
|
from game.server import EventStream
|
||||||
|
from game.server.security import ApiKeyManager
|
||||||
from qt_ui import liberation_install
|
from qt_ui import liberation_install
|
||||||
from qt_ui.dialogs import Dialog
|
from qt_ui.dialogs import Dialog
|
||||||
from qt_ui.models import GameModel
|
from qt_ui.models import GameModel
|
||||||
@ -88,6 +89,8 @@ class QLiberationWindow(QMainWindow):
|
|||||||
else:
|
else:
|
||||||
self.onGameGenerated(self.game)
|
self.onGameGenerated(self.game)
|
||||||
|
|
||||||
|
logging.debug(f"API Key: {ApiKeyManager.KEY}")
|
||||||
|
|
||||||
def initUi(self):
|
def initUi(self):
|
||||||
hbox = QSplitter(Qt.Horizontal)
|
hbox = QSplitter(Qt.Horizontal)
|
||||||
vbox = QSplitter(Qt.Vertical)
|
vbox = QSplitter(Qt.Vertical)
|
||||||
|
|||||||
@ -3,10 +3,15 @@ const ENABLE_EXPENSIVE_DEBUG_TOOLS = false;
|
|||||||
const HTTP_BACKEND = "http://[::1]:5000";
|
const HTTP_BACKEND = "http://[::1]:5000";
|
||||||
const WS_BACKEND = "ws://[::1]:5000/eventstream";
|
const WS_BACKEND = "ws://[::1]:5000/eventstream";
|
||||||
|
|
||||||
|
// Uniquely generated at startup and passed to use by the QWebChannel.
|
||||||
|
var API_KEY = null;
|
||||||
|
|
||||||
function getJson(endpoint) {
|
function getJson(endpoint) {
|
||||||
return fetch(`${HTTP_BACKEND}${endpoint}`).then((response) =>
|
return fetch(`${HTTP_BACKEND}${endpoint}`, {
|
||||||
response.json()
|
headers: {
|
||||||
);
|
"X-API-Key": API_KEY,
|
||||||
|
},
|
||||||
|
}).then((response) => response.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
const Colors = Object.freeze({
|
const Colors = Object.freeze({
|
||||||
@ -356,6 +361,7 @@ new QWebChannel(qt.webChannelTransport, function (channel) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
game = channel.objects.game;
|
game = channel.objects.game;
|
||||||
|
API_KEY = game.apiKey;
|
||||||
drawInitialMap();
|
drawInitialMap();
|
||||||
game.cleared.connect(clearAllLayers);
|
game.cleared.connect(clearAllLayers);
|
||||||
game.mapCenterChanged.connect(recenterMap);
|
game.mapCenterChanged.connect(recenterMap);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user