Skip to content

Commit 245bbcc

Browse files
authored
Merge pull request #74 from nebulabroadcast/general-cleanup-607
General cleanup for version 6.0.7
2 parents ddb696a + bb2e0f2 commit 245bbcc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+687
-590
lines changed

backend/api/auth/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
__all__ = ["LoginRequest", "LogoutRequest", "SetPasswordRequest"]
2+
3+
from .login_request import LoginRequest
4+
from .logout_request import LogoutRequest
5+
from .set_password_request import SetPasswordRequest

backend/api/auth.py renamed to backend/api/auth/login_request.py

Lines changed: 1 addition & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,13 @@
11
import time
22

3-
from fastapi import Header, Request, Response
3+
from fastapi import Request
44
from pydantic import Field
55

66
import nebula
77
from server.clientinfo import get_real_ip
8-
from server.dependencies import CurrentUser
98
from server.models import RequestModel, ResponseModel
109
from server.request import APIRequest
1110
from server.session import Session
12-
from server.utils import parse_access_token
13-
14-
#
15-
# Models
16-
#
1711

1812

1913
class LoginRequestModel(RequestModel):
@@ -40,16 +34,6 @@ class LoginResponseModel(ResponseModel):
4034
)
4135

4236

43-
class PasswordRequestModel(RequestModel):
44-
login: str | None = Field(None, title="Login", examples=["admin"])
45-
password: str = Field(..., title="Password", examples=["Password.123"])
46-
47-
48-
#
49-
# Request
50-
#
51-
52-
5337
async def check_failed_login(ip_address: str) -> None:
5438
banned_until = await nebula.redis.get("banned-ip-until", ip_address)
5539
if banned_until is None:
@@ -121,65 +105,3 @@ async def handle(
121105

122106
session = await Session.create(user, request)
123107
return LoginResponseModel(access_token=session.token)
124-
125-
126-
class LogoutRequest(APIRequest):
127-
"""Log out the current user.
128-
129-
This request will invalidate the access token used in the Authorization header.
130-
"""
131-
132-
name: str = "logout"
133-
title: str = "Logout"
134-
135-
async def handle(self, authorization: str | None = Header(None)) -> None:
136-
if not authorization:
137-
raise nebula.UnauthorizedException("No authorization header provided")
138-
139-
access_token = parse_access_token(authorization)
140-
if not access_token:
141-
raise nebula.UnauthorizedException("Invalid authorization header provided")
142-
143-
await Session.delete(access_token)
144-
145-
raise nebula.UnauthorizedException("Logged out")
146-
147-
148-
class SetPassword(APIRequest):
149-
"""Set a new password for the current (or a given) user.
150-
151-
Normal users can only change their own password.
152-
153-
In order to set a password for another user,
154-
the current user must be an admin, otherwise a 403 error is returned.
155-
"""
156-
157-
name: str = "password"
158-
title: str = "Set password"
159-
160-
async def handle(
161-
self,
162-
request: PasswordRequestModel,
163-
user: CurrentUser,
164-
) -> Response:
165-
if request.login:
166-
if not user.is_admin:
167-
raise nebula.ForbiddenException(
168-
"Only admin can change other user's password"
169-
)
170-
query = "SELECT meta FROM users WHERE login = $1"
171-
async for row in nebula.db.iterate(query, request.login):
172-
target_user = nebula.User.from_row(row)
173-
break
174-
else:
175-
raise nebula.NotFoundException(f"User {request.login} not found")
176-
else:
177-
target_user = user
178-
179-
if len(request.password) < 8:
180-
raise nebula.BadRequestException("Password is too short")
181-
182-
target_user.set_password(request.password)
183-
await target_user.save()
184-
185-
return Response(status_code=204)

backend/api/auth/logout_request.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from fastapi import Header
2+
3+
import nebula
4+
from server.request import APIRequest
5+
from server.session import Session
6+
from server.utils import parse_access_token
7+
8+
9+
class LogoutRequest(APIRequest):
10+
"""Log out the current user.
11+
12+
This request will invalidate the access token used in the Authorization header.
13+
"""
14+
15+
name = "logout"
16+
title = "Logout"
17+
18+
async def handle(self, authorization: str | None = Header(None)) -> None:
19+
if not authorization:
20+
raise nebula.UnauthorizedException("No authorization header provided")
21+
22+
access_token = parse_access_token(authorization)
23+
if not access_token:
24+
raise nebula.UnauthorizedException("Invalid authorization header provided")
25+
26+
await Session.delete(access_token)
27+
28+
raise nebula.UnauthorizedException("Logged out")
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from fastapi import Response
2+
from pydantic import Field
3+
4+
import nebula
5+
from server.dependencies import CurrentUser
6+
from server.models import RequestModel
7+
from server.request import APIRequest
8+
9+
10+
class PasswordRequestModel(RequestModel):
11+
login: str | None = Field(None, title="Login", examples=["admin"])
12+
password: str = Field(..., title="Password", examples=["Password.123"])
13+
14+
15+
class SetPasswordRequest(APIRequest):
16+
"""Set a new password for the current (or a given) user.
17+
18+
Normal users can only change their own password.
19+
20+
In order to set a password for another user,
21+
the current user must be an admin, otherwise a 403 error is returned.
22+
"""
23+
24+
name = "password"
25+
title = "Set password"
26+
27+
async def handle(
28+
self,
29+
request: PasswordRequestModel,
30+
user: CurrentUser,
31+
) -> Response:
32+
if request.login:
33+
if not user.is_admin:
34+
raise nebula.ForbiddenException(
35+
"Only admin can change other user's password"
36+
)
37+
query = "SELECT meta FROM users WHERE login = $1"
38+
async for row in nebula.db.iterate(query, request.login):
39+
target_user = nebula.User.from_row(row)
40+
break
41+
else:
42+
raise nebula.NotFoundException(f"User {request.login} not found")
43+
else:
44+
target_user = user
45+
46+
if len(request.password) < 8:
47+
raise nebula.BadRequestException("Password is too short")
48+
49+
target_user.set_password(request.password)
50+
await target_user.save()
51+
52+
return Response(status_code=204)

backend/api/browse.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,8 @@ def build_query(
256256
class Request(APIRequest):
257257
"""Browse the assets database."""
258258

259-
name: str = "browse"
259+
name = "browse"
260+
title = "Browse assets"
260261
response_model = BrowseResponseModel
261262

262263
async def handle(

backend/api/delete.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ class DeleteRequestModel(RequestModel):
2121

2222

2323
class Request(APIRequest):
24-
"""Delete object(s)"""
24+
"""Delete one or multiple objects from the database"""
2525

26-
name: str = "delete"
27-
title: str = "Delete objects"
26+
name = "delete"
27+
title = "Delete objects"
2828
responses: list[int] = [204, 401, 403]
2929

3030
async def handle(

backend/api/get.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,7 @@ def can_access_object(user: nebula.User, meta: dict[str, Any]) -> bool:
4343
if user.is_admin or (user.id in meta.get("assignees", [])):
4444
return True
4545
elif user.is_limited:
46-
if meta.get("created_by") != user.id:
47-
return False
48-
return True
46+
return meta.get("created_by") == user.id
4947
if id_folder := meta.get("id_folder"):
5048
# Users can view assets in folders they have access to
5149
return user.can("asset_view", id_folder)
@@ -62,8 +60,8 @@ def can_access_object(user: nebula.User, meta: dict[str, Any]) -> bool:
6260
class Request(APIRequest):
6361
"""Get a list of objects"""
6462

65-
name: str = "get"
66-
title: str = "Get objects"
63+
name = "get"
64+
title = "Get objects"
6765
response_model = GetResponseModel
6866

6967
async def handle(

backend/api/init/__init__.py

Lines changed: 2 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,104 +1,3 @@
1-
from typing import Any, Literal
1+
__all__ = ["InitRequest"]
22

3-
import fastapi
4-
from pydantic import Field
5-
6-
import nebula
7-
from nebula.plugins.frontend import PluginItemModel, get_frontend_plugins
8-
from nebula.settings import load_settings
9-
from nebula.settings.common import LanguageCode
10-
from server.context import ScopedEndpoint, server_context
11-
from server.dependencies import CurrentUserOptional
12-
from server.models import ResponseModel
13-
from server.request import APIRequest
14-
15-
from .settings import ClientSettingsModel, get_client_settings
16-
17-
18-
class InitResponseModel(ResponseModel):
19-
installed: Literal[True] | None = Field(
20-
True,
21-
title="Installed",
22-
description="Is Nebula installed?",
23-
)
24-
25-
motd: str | None = Field(
26-
None,
27-
title="Message of the day",
28-
description="Server welcome string (displayed on login page)",
29-
)
30-
31-
user: dict[str, Any] | None = Field(
32-
None,
33-
title="User data",
34-
description="User data if user is logged in",
35-
)
36-
37-
settings: ClientSettingsModel | None = Field(
38-
None,
39-
title="Client settings",
40-
)
41-
42-
frontend_plugins: list[PluginItemModel] = Field(
43-
default_factory=list,
44-
title="Frontend plugins",
45-
description="List of plugins available for the web frontend",
46-
)
47-
48-
scoped_endpoints: list[ScopedEndpoint] = Field(default_factory=list)
49-
50-
oauth2_options: list[dict[str, Any]] = Field(
51-
default_factory=list,
52-
title="OAuth2 options",
53-
)
54-
55-
56-
class Request(APIRequest):
57-
"""Initial client request to ensure user is logged in.
58-
59-
If a valid access token is provided, user data is returned,
60-
in the result.
61-
62-
Additionally (regadless the authorization), a message of the day
63-
(motd) and OAuth2 options are returned.
64-
"""
65-
66-
name: str = "init"
67-
title: str = "Login"
68-
response_model = InitResponseModel
69-
70-
async def handle(
71-
self,
72-
request: fastapi.Request,
73-
user: CurrentUserOptional,
74-
) -> InitResponseModel:
75-
default_motd = f"Nebula {nebula.__version__} @ {nebula.config.site_name}"
76-
motd = nebula.config.motd or default_motd
77-
78-
# Nebula is not installed. Frontend should display
79-
# an error message or redirect to the installation page.
80-
if not nebula.settings.installed:
81-
await load_settings()
82-
if not nebula.settings.installed:
83-
return InitResponseModel(installed=False)
84-
85-
# Not logged in. Only return motd and oauth2 options.
86-
if user is None:
87-
return InitResponseModel(motd=motd)
88-
89-
# TODO: get preferred user language
90-
lang: LanguageCode = user.language
91-
92-
# Construct client settings
93-
client_settings = await get_client_settings(lang)
94-
client_settings.server_url = f"{request.url.scheme}://{request.url.netloc}"
95-
plugins = get_frontend_plugins()
96-
97-
return InitResponseModel(
98-
installed=True,
99-
motd=motd,
100-
user=user.meta,
101-
settings=client_settings,
102-
frontend_plugins=plugins,
103-
scoped_endpoints=server_context.scoped_endpoints,
104-
)
3+
from .init_request import InitRequest
File renamed without changes.

0 commit comments

Comments
 (0)