Skip to content

Commit

Permalink
Merge pull request #1468 from rommapp/master
Browse files Browse the repository at this point in the history
v3.7.1
  • Loading branch information
gantoine authored Jan 11, 2025
2 parents 8994fe1 + ddb51f6 commit 2e2707a
Show file tree
Hide file tree
Showing 74 changed files with 3,977 additions and 2,351 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/typecheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,8 @@ jobs:
- name: Run typecheck
run: npm run typecheck
working-directory: frontend

- name: Lockfile lint
run: |
[ -z "$(jq -r '.packages | to_entries[] | select((.key | contains("node_modules")) and (.value | has("resolved") | not)) | .key' < package-lock.json)" ]
working-directory: frontend
1 change: 0 additions & 1 deletion .trunk/trunk.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ lint:
- yaml
- css
- postcss
- sass
- html
- markdown
- json
Expand Down
2 changes: 2 additions & 0 deletions backend/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def str_to_bool(value: str) -> bool:
DISABLE_DOWNLOAD_ENDPOINT_AUTH = str_to_bool(
os.environ.get("DISABLE_DOWNLOAD_ENDPOINT_AUTH", "false")
)
DISABLE_USERPASS_LOGIN = str_to_bool(os.environ.get("DISABLE_USERPASS_LOGIN", "false"))

# OIDC
OIDC_ENABLED: Final = str_to_bool(os.environ.get("OIDC_ENABLED", "false"))
Expand All @@ -81,6 +82,7 @@ def str_to_bool(value: str) -> bool:
OIDC_CLIENT_SECRET: Final = os.environ.get("OIDC_CLIENT_SECRET", "")
OIDC_REDIRECT_URI: Final = os.environ.get("OIDC_REDIRECT_URI", "")
OIDC_SERVER_APPLICATION_URL: Final = os.environ.get("OIDC_SERVER_APPLICATION_URL", "")
OIDC_TLS_CACERTFILE: Final = os.environ.get("OIDC_TLS_CACERTFILE", None)

# SCANS
SCAN_TIMEOUT: Final = int(os.environ.get("SCAN_TIMEOUT", 60 * 60 * 4)) # 4 hours
Expand Down
13 changes: 10 additions & 3 deletions backend/decorators/auth.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
from typing import Any

from authlib.integrations.starlette_client import OAuth
from authlib.oidc.discovery import get_well_known_url
from config import (
OIDC_CLIENT_ID,
OIDC_CLIENT_SECRET,
OIDC_ENABLED,
OIDC_PROVIDER,
OIDC_REDIRECT_URI,
OIDC_SERVER_APPLICATION_URL,
OIDC_TLS_CACERTFILE,
)
from fastapi import Security
from fastapi.security.http import HTTPBasic
from fastapi.security.oauth2 import OAuth2PasswordBearer
from fastapi.types import DecoratedCallable
from handler.auth.base_handler import (
from handler.auth.constants import (
DEFAULT_SCOPES_MAP,
FULL_SCOPES_MAP,
WRITE_SCOPES_MAP,
Expand Down Expand Up @@ -49,8 +51,13 @@
name="openid",
client_id=config.get("OIDC_CLIENT_ID"),
client_secret=config.get("OIDC_CLIENT_SECRET"),
server_metadata_url=f'{config.get("OIDC_SERVER_APPLICATION_URL")}/.well-known/openid-configuration',
client_kwargs={"scope": "openid profile email"},
server_metadata_url=get_well_known_url(
config.get("OIDC_SERVER_APPLICATION_URL"), external=True
),
client_kwargs={
"scope": "openid profile email",
"verify": OIDC_TLS_CACERTFILE,
},
)


Expand Down
6 changes: 5 additions & 1 deletion backend/endpoints/auth.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from datetime import datetime, timedelta, timezone
from typing import Annotated, Final

from config import OIDC_ENABLED, OIDC_REDIRECT_URI
from config import DISABLE_USERPASS_LOGIN, OIDC_ENABLED, OIDC_REDIRECT_URI
from decorators.auth import oauth
from endpoints.forms.identity import OAuth2RequestForm
from endpoints.responses import MessageResponse
Expand All @@ -11,6 +11,7 @@
OIDCDisabledException,
OIDCNotConfiguredException,
UserDisabledException,
UserPassDisabledException,
)
from fastapi import Depends, HTTPException, Request, status
from fastapi.responses import RedirectResponse
Expand Down Expand Up @@ -45,6 +46,9 @@ def login(
MessageResponse: Standard message response
"""

if DISABLE_USERPASS_LOGIN:
raise UserPassDisabledException

user = auth_handler.authenticate_user(credentials.username, credentials.password)
if not user:
raise AuthCredentialsException
Expand Down
2 changes: 1 addition & 1 deletion backend/endpoints/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
CollectionPermissionError,
)
from fastapi import Request, UploadFile
from handler.auth.base_handler import Scope
from handler.auth.constants import Scope
from handler.database import db_collection_handler
from handler.filesystem import fs_resource_handler
from handler.filesystem.base_handler import CoverSize
Expand Down
2 changes: 1 addition & 1 deletion backend/endpoints/configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
ConfigNotWritableException,
)
from fastapi import HTTPException, Request, status
from handler.auth.base_handler import Scope
from handler.auth.constants import Scope
from logger.logger import log
from utils.router import APIRouter

Expand Down
2 changes: 1 addition & 1 deletion backend/endpoints/feeds.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
WebrcadeFeedSchema,
)
from fastapi import Request
from handler.auth.base_handler import Scope
from handler.auth.constants import Scope
from handler.database import db_platform_handler, db_rom_handler
from handler.metadata import meta_igdb_handler
from handler.metadata.base_hander import SWITCH_TITLEDB_REGEX
Expand Down
2 changes: 1 addition & 1 deletion backend/endpoints/firmware.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from endpoints.responses.firmware import AddFirmwareResponse, FirmwareSchema
from fastapi import File, HTTPException, Request, UploadFile, status
from fastapi.responses import FileResponse
from handler.auth.base_handler import Scope
from handler.auth.constants import Scope
from handler.database import db_firmware_handler, db_platform_handler
from handler.filesystem import fs_firmware_handler
from handler.scan_handler import scan_firmware
Expand Down
6 changes: 5 additions & 1 deletion backend/endpoints/heartbeat.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from config import (
DISABLE_EMULATOR_JS,
DISABLE_RUFFLE_RS,
DISABLE_USERPASS_LOGIN,
ENABLE_RESCAN_ON_FILESYSTEM_CHANGE,
ENABLE_SCHEDULED_RESCAN,
ENABLE_SCHEDULED_UPDATE_SWITCH_TITLEDB,
Expand Down Expand Up @@ -68,7 +69,10 @@ def heartbeat() -> HeartbeatResponse:
"DISABLE_EMULATOR_JS": DISABLE_EMULATOR_JS,
"DISABLE_RUFFLE_RS": DISABLE_RUFFLE_RS,
},
"FRONTEND": {"UPLOAD_TIMEOUT": UPLOAD_TIMEOUT},
"FRONTEND": {
"UPLOAD_TIMEOUT": UPLOAD_TIMEOUT,
"DISABLE_USERPASS_LOGIN": DISABLE_USERPASS_LOGIN,
},
"OIDC": {
"ENABLED": OIDC_ENABLED,
"PROVIDER": OIDC_PROVIDER,
Expand Down
2 changes: 1 addition & 1 deletion backend/endpoints/platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from exceptions.endpoint_exceptions import PlatformNotFoundInDatabaseException
from exceptions.fs_exceptions import PlatformAlreadyExistsException
from fastapi import Request
from handler.auth.base_handler import Scope
from handler.auth.constants import Scope
from handler.database import db_platform_handler
from handler.filesystem import fs_platform_handler
from handler.metadata.igdb_handler import IGDB_PLATFORM_LIST
Expand Down
2 changes: 1 addition & 1 deletion backend/endpoints/raw.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from decorators.auth import protected_route
from fastapi import Request
from fastapi.responses import FileResponse
from handler.auth.base_handler import Scope
from handler.auth.constants import Scope
from utils.router import APIRouter

router = APIRouter()
Expand Down
2 changes: 1 addition & 1 deletion backend/endpoints/responses/assets.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from datetime import datetime
from typing import TypedDict

from pydantic import BaseModel
from .base import BaseModel


class BaseAsset(BaseModel):
Expand Down
18 changes: 18 additions & 0 deletions backend/endpoints/responses/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from datetime import datetime, timezone

from pydantic import BaseModel as PydanticBaseModel
from pydantic import ConfigDict


class BaseModel(PydanticBaseModel):
"""Ensures all datetime fields include UTC timezone"""

model_config = ConfigDict(
json_encoders={
datetime: lambda dt: (
dt.isoformat()
if dt.tzinfo
else dt.replace(tzinfo=timezone.utc).isoformat()
)
}
)
3 changes: 2 additions & 1 deletion backend/endpoints/responses/collection.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from datetime import datetime

from models.collection import Collection
from pydantic import BaseModel

from .base import BaseModel


class CollectionSchema(BaseModel):
Expand Down
2 changes: 1 addition & 1 deletion backend/endpoints/responses/firmware.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from datetime import datetime
from typing import TypedDict

from pydantic import BaseModel
from .base import BaseModel


class FirmwareSchema(BaseModel):
Expand Down
1 change: 1 addition & 0 deletions backend/endpoints/responses/heartbeat.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class EmulationDict(TypedDict):

class FrontendDict(TypedDict):
UPLOAD_TIMEOUT: int
DISABLE_USERPASS_LOGIN: bool


class OIDCDict(TypedDict):
Expand Down
3 changes: 2 additions & 1 deletion backend/endpoints/responses/identity.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from datetime import datetime

from models.user import Role
from pydantic import BaseModel

from .base import BaseModel


class UserSchema(BaseModel):
Expand Down
3 changes: 2 additions & 1 deletion backend/endpoints/responses/platform.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from datetime import datetime

from models.platform import DEFAULT_COVER_ASPECT_RATIO
from pydantic import BaseModel, Field, computed_field
from pydantic import Field, computed_field

from .base import BaseModel
from .firmware import FirmwareSchema


Expand Down
5 changes: 4 additions & 1 deletion backend/endpoints/responses/rom.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
from handler.metadata.igdb_handler import IGDBMetadata
from handler.metadata.moby_handler import MobyMetadata
from models.rom import Rom, RomFile, RomUserStatus
from pydantic import BaseModel, computed_field
from pydantic import computed_field

from .base import BaseModel

SORT_COMPARE_REGEX = re.compile(r"^([Tt]he|[Aa]|[Aa]nd)\s")

Expand Down Expand Up @@ -121,6 +123,7 @@ class RomSchema(BaseModel):
# Metadata fields
first_release_date: int | None
youtube_video_id: str | None
average_rating: float | None
alternative_names: list[str]
genres: list[str]
franchises: list[str]
Expand Down
2 changes: 1 addition & 1 deletion backend/endpoints/responses/search.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from pydantic import BaseModel
from .base import BaseModel


class SearchRomSchema(BaseModel):
Expand Down
8 changes: 5 additions & 3 deletions backend/endpoints/rom.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import binascii
from base64 import b64encode
from datetime import datetime, timezone
from io import BytesIO
from shutil import rmtree
from typing import Annotated
Expand All @@ -19,15 +20,14 @@
from exceptions.fs_exceptions import RomAlreadyExistsException
from fastapi import HTTPException, Query, Request, UploadFile, status
from fastapi.responses import Response
from handler.auth.base_handler import Scope
from handler.auth.constants import Scope
from handler.database import db_collection_handler, db_platform_handler, db_rom_handler
from handler.filesystem import fs_resource_handler, fs_rom_handler
from handler.filesystem.base_handler import CoverSize
from handler.metadata import meta_igdb_handler, meta_moby_handler
from logger.logger import log
from models.rom import Rom, RomUser
from PIL import Image
from sqlalchemy import func
from starlette.requests import ClientDisconnect
from starlette.responses import FileResponse
from streaming_form_data import StreamingFormDataParser
Expand Down Expand Up @@ -558,6 +558,7 @@ async def delete_roms(
@protected_route(router.put, "/roms/{id}/props", [Scope.ROMS_USER_WRITE])
async def update_rom_user(request: Request, id: int) -> RomUserSchema:
data = await request.json()
data = data.get("data", {})

rom = db_rom_handler.get_rom(id)

Expand All @@ -579,11 +580,12 @@ async def update_rom_user(request: Request, id: int) -> RomUserSchema:
"difficulty",
"completion",
"status",
"last_played",
]

cleaned_data = {field: data[field] for field in fields_to_update if field in data}

if data.get("update_last_played", False):
cleaned_data.update({"last_played": func.now()})
cleaned_data.update({"last_played": datetime.now(timezone.utc)})

return db_rom_handler.update_rom_user(db_rom_user.id, cleaned_data)
2 changes: 1 addition & 1 deletion backend/endpoints/saves.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from endpoints.responses.assets import SaveSchema, UploadedSavesResponse
from exceptions.endpoint_exceptions import RomNotFoundInDatabaseException
from fastapi import File, HTTPException, Request, UploadFile, status
from handler.auth.base_handler import Scope
from handler.auth.constants import Scope
from handler.database import db_rom_handler, db_save_handler, db_screenshot_handler
from handler.filesystem import fs_asset_handler
from handler.scan_handler import scan_save
Expand Down
2 changes: 1 addition & 1 deletion backend/endpoints/screenshots.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from decorators.auth import protected_route
from endpoints.responses.assets import UploadedScreenshotsResponse
from fastapi import File, HTTPException, Request, UploadFile, status
from handler.auth.base_handler import Scope
from handler.auth.constants import Scope
from handler.database import db_rom_handler, db_screenshot_handler
from handler.filesystem import fs_asset_handler
from handler.scan_handler import scan_screenshot
Expand Down
2 changes: 1 addition & 1 deletion backend/endpoints/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from decorators.auth import protected_route
from endpoints.responses.search import SearchCoverSchema, SearchRomSchema
from fastapi import HTTPException, Request, status
from handler.auth.base_handler import Scope
from handler.auth.constants import Scope
from handler.database import db_rom_handler
from handler.metadata import meta_igdb_handler, meta_moby_handler, meta_sgdb_handler
from handler.metadata.igdb_handler import IGDB_API_ENABLED
Expand Down
2 changes: 1 addition & 1 deletion backend/endpoints/states.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from endpoints.responses.assets import StateSchema, UploadedStatesResponse
from exceptions.endpoint_exceptions import RomNotFoundInDatabaseException
from fastapi import File, HTTPException, Request, UploadFile, status
from handler.auth.base_handler import Scope
from handler.auth.constants import Scope
from handler.database import db_rom_handler, db_screenshot_handler, db_state_handler
from handler.filesystem import fs_asset_handler
from handler.scan_handler import scan_state
Expand Down
2 changes: 1 addition & 1 deletion backend/endpoints/tasks.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from decorators.auth import protected_route
from endpoints.responses import MessageResponse
from fastapi import Request
from handler.auth.base_handler import Scope
from handler.auth.constants import Scope
from tasks.update_switch_titledb import update_switch_titledb_task
from utils.router import APIRouter

Expand Down
2 changes: 1 addition & 1 deletion backend/endpoints/tests/test_oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from endpoints.auth import ACCESS_TOKEN_EXPIRE_MINUTES
from fastapi.exceptions import HTTPException
from fastapi.testclient import TestClient
from handler.auth.base_handler import WRITE_SCOPES
from handler.auth.constants import WRITE_SCOPES
from main import app


Expand Down
2 changes: 1 addition & 1 deletion backend/endpoints/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from endpoints.responses.identity import UserSchema
from fastapi import Depends, HTTPException, Request, status
from handler.auth import auth_handler
from handler.auth.base_handler import Scope
from handler.auth.constants import Scope
from handler.database import db_user_handler
from handler.filesystem import fs_asset_handler
from logger.logger import log
Expand Down
5 changes: 5 additions & 0 deletions backend/exceptions/auth_exceptions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
from fastapi import HTTPException, status

UserPassDisabledException = HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Username/password authentication disabled",
)

AuthCredentialsException = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
Expand Down
Loading

0 comments on commit 2e2707a

Please sign in to comment.