Compare commits

..

No commits in common. "a6f1738b73b7715f21a25f03ec9a9b193559718e" and "b8b6dda131e5d85214938a8848a8210ce7503cb2" have entirely different histories.

6 changed files with 48 additions and 50 deletions

View file

@ -35,21 +35,6 @@ if typing.TYPE_CHECKING:
from .misc import Message, Response
def get_csp(request: web.Request) -> str:
data = [
"default-src 'none'",
f"script-src 'nonce-{request['hash']}'",
f"style-src 'self' 'nonce-{request['hash']}'",
"form-action 'self'",
"connect-src 'self'",
"img-src 'self'",
"object-src 'none'",
"frame-ancestors 'none'"
]
return '; '.join(data) + ';'
class Application(web.Application):
DEFAULT: Application | None = None
@ -142,6 +127,21 @@ class Application(web.Application):
return timedelta(seconds=uptime.seconds)
def get_csp(self, request: Request) -> str:
data = [
"default-src 'none'",
f"script-src 'nonce-{request['hash']}'",
f"style-src 'self' 'nonce-{request['hash']}'",
"form-action 'self'",
"connect-src 'self'",
"img-src 'self'",
"object-src 'none'",
"frame-ancestors 'none'"
]
return '; '.join(data) + ';'
def push_message(self, inbox: str, message: Message, instance: Row) -> None:
self['push_queue'].put((inbox, message, instance))
@ -240,7 +240,7 @@ class CachedStaticResource(StaticResource):
def __init__(self, prefix: str, path: Path):
StaticResource.__init__(self, prefix, path)
self.cache: dict[str, bytes] = {}
self.cache: dict[Path, bytes] = {}
for filename in path.rglob('*'):
if filename.is_dir():
@ -333,8 +333,8 @@ async def handle_response_headers(request: web.Request, handler: Callable) -> Re
resp.headers['Server'] = 'ActivityRelay'
# Still have to figure out how csp headers work
if resp.content_type == 'text/html' and not request.path.startswith("/api"):
resp.headers['Content-Security-Policy'] = get_csp(request)
if resp.content_type == 'text/html':
resp.headers['Content-Security-Policy'] = Application.DEFAULT.get_csp(request)
if not request.app['dev'] and request.path.endswith(('.css', '.js')):
# cache for 2 weeks

View file

@ -192,29 +192,26 @@ class Connection(SqlConnection):
def put_user(self, username: str, password: str | None, handle: str | None = None) -> Row:
if self.get_user(username):
data: dict[str, str] = {}
data = {
'username': username
}
if password:
data['hash'] = self.hasher.hash(password)
data['password'] = password
if handle:
data['handle'] = handle
data['handler'] = handle
stmt = Update("users", data)
stmt.set_where("username", username)
else:
if password is None:
raise ValueError('Password cannot be empty')
with self.query(stmt) as cur:
return cur.one()
if password is None:
raise ValueError('Password cannot be empty')
data = {
'username': username,
'hash': self.hasher.hash(password),
'handle': handle,
'created': datetime.now(tz = timezone.utc)
}
data = {
'username': username,
'hash': self.hasher.hash(password),
'handle': handle,
'created': datetime.now(tz = timezone.utc)
}
with self.run('put-user', data) as cur:
return cur.one() # type: ignore

View file

@ -100,8 +100,8 @@ def cli_run(dev: bool):
try:
while True:
handler.proc.stdin.write(sys.stdin.read().encode('UTF-8')) # type: ignore
handler.proc.stdin.flush() # type: ignore
handler.proc.stdin.write(sys.stdin.read().encode('UTF-8'))
handler.proc.stdin.flush()
except KeyboardInterrupt:
pass
@ -121,12 +121,12 @@ class WatchHandler(PatternMatchingEventHandler):
PatternMatchingEventHandler.__init__(self)
self.dev: bool = dev
self.proc: subprocess.Popen | None = None
self.last_restart: datetime | None = None
self.proc = None
self.last_restart = None
def kill_proc(self):
if not self.proc or self.proc.poll() is not None:
if self.proc.poll() is not None:
return
logging.info(f'Terminating process {self.proc.pid}')

View file

@ -15,7 +15,6 @@ from ..misc import Message, Response, boolean, get_app
if typing.TYPE_CHECKING:
from aiohttp.web import Request
from collections.abc import Callable, Sequence
from typing import Any
PUBLIC_API_PATHS: Sequence[tuple[str, str]] = (
@ -150,7 +149,7 @@ class Config(View):
if isinstance(data, Response):
return data
data['key'] = data['key'].replace('-', '_')
data['key'] = data['key'].replace('-', '_');
if data['key'] not in ConfigData.USER_KEYS():
return Response.new_error(400, 'Invalid key', 'json')
@ -256,7 +255,7 @@ class RequestView(View):
async def post(self, request: Request) -> Response:
data: dict[str, Any] | Response = await self.get_api_data(['domain', 'accept'], [])
data = await self.get_api_data(['domain', 'accept'], [])
data['accept'] = boolean(data['accept'])
try:
@ -431,7 +430,7 @@ class User(View):
async def patch(self, request: Request) -> Response:
data = await self.get_api_data(['username'], ['password', 'handle'])
data = await self.get_api_data(['username'], ['password', ['handle']])
if isinstance(data, Response):
return data

View file

@ -126,7 +126,7 @@ class View(AbstractView):
return Response.new_error(400, 'Invalid JSON data', 'json')
else:
post_data = convert_data(self.request.query)
post_data = convert_data(self.request.query) # type: ignore
data = {}

View file

@ -3,12 +3,14 @@ from __future__ import annotations
import typing
from aiohttp import web
from argon2.exceptions import VerifyMismatchError
from urllib.parse import urlparse
from .base import View, register_route
from ..database import THEMES
from ..database import THEMES, ConfigData
from ..logger import LogLevel
from ..misc import Response, get_app
from ..misc import ACTOR_FORMATS, Message, Response, get_app
if typing.TYPE_CHECKING:
from aiohttp.web import Request
@ -38,9 +40,9 @@ async def handle_frontend_path(request: web.Request, handler: Callable) -> Respo
return Response.new('', 302, {'Location': '/'})
if not request['user'] and request.path.startswith('/admin'):
response = Response.new('', 302, {'Location': f'/login?redir={request.path}'})
response.del_cookie('user-token')
return response
response = Response.new('', 302, {'Location': f'/login?redir={request.path}'})
response.del_cookie('user-token')
return response
response = await handler(request)