remove tokens table and fix auth handling

This commit is contained in:
Izalia Mae 2024-07-04 22:00:54 -04:00
parent f98ca54ab7
commit 773922e263
7 changed files with 55 additions and 154 deletions

View file

@ -310,15 +310,21 @@ async def handle_response_headers(
if request.path == "/" or request.path.startswith(TOKEN_PATHS): if request.path == "/" or request.path.startswith(TOKEN_PATHS):
with app.database.session() as conn: with app.database.session() as conn:
if (token := request.headers.get('Authorization')) is not None: tokens = (
token = token.replace('Bearer', '').strip() request.headers.get('Authorization', '').replace('Bearer', '').strip(),
request.cookies.get('user-token')
)
for token in tokens:
if not token:
continue
request['token'] = conn.get_app_by_token(token) request['token'] = conn.get_app_by_token(token)
request['user'] = conn.get_user_by_app_token(token)
elif (token := request.cookies.get('user-token')) is not None: if request['token'] is not None:
request['token'] = conn.get_token(token) request['user'] = conn.get_user(request['token'].user)
request['user'] = conn.get_user_by_token(token)
break
try: try:
resp = await handler(request) resp = await handler(request)

View file

@ -50,17 +50,9 @@ WHERE username = :value or handle = :value;
-- name: get-user-by-token -- name: get-user-by-token
SELECT * FROM users SELECT * FROM users
WHERE username = (
SELECT user FROM tokens
WHERE code = :code
);
-- name: get-user-by-app-token
SELECT * FROM users
WHERE username = ( WHERE username = (
SELECT user FROM app SELECT user FROM app
WHERE code = :code WHERE token = :token
); );
@ -80,46 +72,25 @@ SELECT * FROM app
WHERE client_id = :id and client_secret = :secret; WHERE client_id = :id and client_secret = :secret;
-- name: get-app-token -- name: get-app-with-token
SELECT * FROM app SELECT * FROM app
WHERE client_id = :id and client_secret = :secret and token = :token; WHERE client_id = :id and client_secret = :secret and token = :token;
-- name: get-app-by-token -- name: get-app-by-token
SELECT * FROM app SELECT * FROM apps
WHERE token = :token; WHERE token = :token;
-- name: del-app -- name: del-app
DELETE FROM users DELETE FROM apps
WHERE client_id = :id and client_secret = :secret; WHERE client_id = :id and client_secret = :secret;
-- name: del-app-token -- name: del-app-with-token
DELETE FROM users DELETE FROM apps
WHERE client_id = :id and client_secret = :secret and token = :token; WHERE client_id = :id and client_secret = :secret and token = :token;
-- name: get-token
SELECT * FROM tokens
WHERE code = :code;
-- name: put-token
INSERT INTO tokens (code, user, created)
VALUES (:code, :user, :created)
RETURNING *;
-- name: del-token
DELETE FROM tokens
WHERE code = :code;
-- name: del-token-user
DELETE FROM tokens
WHERE user = :username;
-- name: get-software-ban -- name: get-software-ban
SELECT * FROM software_bans WHERE name = :name; SELECT * FROM software_bans WHERE name = :name;

View file

@ -9,7 +9,6 @@ from collections.abc import Iterator
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING, Any
from urllib.parse import urlparse from urllib.parse import urlparse
from uuid import uuid4
from . import schema from . import schema
from .config import ( from .config import (
@ -73,10 +72,6 @@ class Connection(SqlConnection):
data = {'created': sban.created.timestamp()} data = {'created': sban.created.timestamp()}
self.update('software_bans', data, name = sban.name) self.update('software_bans', data, name = sban.name)
for token in self.select('tokens').all(schema.Token):
data = {'created': token.created.timestamp(), 'accessed': token.accessed.timestamp()}
self.update('tokens', data, code = token.code)
for user in self.select('users').all(schema.User): for user in self.select('users').all(schema.User):
data = {'created': user.created.timestamp()} data = {'created': user.created.timestamp()}
self.update('users', data, username = user.username) self.update('users', data, username = user.username)
@ -230,13 +225,8 @@ class Connection(SqlConnection):
return cur.one(schema.User) return cur.one(schema.User)
def get_user_by_token(self, code: str) -> schema.User | None: def get_user_by_token(self, token: str) -> schema.User | None:
with self.run('get-user-by-token', {'code': code}) as cur: with self.run('get-user-by-token', {'token': token}) as cur:
return cur.one(schema.User)
def get_user_by_app_token(self, code: str) -> schema.User | None:
with self.run('get-user-by-app-token', {'code': code}) as cur:
return cur.one(schema.User) return cur.one(schema.User)
@ -328,13 +318,34 @@ class Connection(SqlConnection):
'accessed': Date.new_utc().timestamp() 'accessed': Date.new_utc().timestamp()
} }
with self.insert('app', params) as cur: with self.insert('apps', params) as cur:
if (row := cur.one(schema.App)) is None: if (row := cur.one(schema.App)) is None:
raise RuntimeError(f'Failed to insert app: {name}') raise RuntimeError(f'Failed to insert app: {name}')
return row return row
def put_app_login(self, user: schema.User) -> schema.App:
params = {
'name': 'Web',
'redirect_uri': 'urn:ietf:wg:oauth:2.0:oob',
'website': None,
'user': user.username,
'client_id': secrets.token_hex(20),
'client_secret': secrets.token_hex(20),
'auth_code': None,
'token': secrets.token_hex(20),
'created': Date.new_utc().timestamp(),
'accessed': Date.new_utc().timestamp()
}
with self.insert('apps', params) as cur:
if (row := cur.one(schema.App)) is None:
raise RuntimeError(f'Failed to create app for "{user.username}"')
return row
def update_app(self, app: schema.App, user: schema.User | None, set_auth: bool) -> schema.App: def update_app(self, app: schema.App, user: schema.User | None, set_auth: bool) -> schema.App:
data: dict[str, str | None] = {} data: dict[str, str | None] = {}
@ -367,7 +378,7 @@ class Connection(SqlConnection):
} }
if token is not None: if token is not None:
command = 'del-app-token' command = 'del-app-with-token'
params['token'] = token params['token'] = token
else: else:
@ -380,37 +391,6 @@ class Connection(SqlConnection):
return cur.row_count == 0 return cur.row_count == 0
def get_token(self, code: str) -> schema.Token | None:
with self.run('get-token', {'code': code}) as cur:
return cur.one(schema.Token)
def get_tokens(self, username: str | None = None) -> Iterator[schema.Token]:
if username is None:
return self.select('tokens').all(schema.Token)
return self.select('tokens', username = username).all(schema.Token)
def put_token(self, username: str) -> schema.Token:
data = {
'code': uuid4().hex,
'user': username,
'created': datetime.now(tz = timezone.utc)
}
with self.run('put-token', data) as cur:
if (row := cur.one(schema.Token)) is None:
raise RuntimeError(f"Failed to insert token for user: {username}")
return row
def del_token(self, code: str) -> None:
with self.run('del-token', {'code': code}):
pass
def get_domain_ban(self, domain: str) -> schema.DomainBan | None: def get_domain_ban(self, domain: str) -> schema.DomainBan | None:
if domain.startswith('http'): if domain.startswith('http'):
domain = urlparse(domain).netloc domain = urlparse(domain).netloc

View file

@ -105,23 +105,6 @@ class User(Row):
) )
@TABLES.add_row
class Token(Row):
table_name: str = 'tokens'
code: Column[str] = Column('code', 'text', primary_key = True, unique = True, nullable = False)
user: Column[str] = Column('user', 'text', nullable = False)
created: Column[Date] = Column(
'created', 'timestamp', nullable = False,
deserializer = deserialize_timestamp, serializer = Date.timestamp
)
accessed: Column[Date] = Column(
'accessed', 'timestamp', nullable = False,
deserializer = deserialize_timestamp, serializer = Date.timestamp
)
@TABLES.add_row @TABLES.add_row
class App(Row): class App(Row):
table_name: str = 'apps' table_name: str = 'apps'
@ -148,9 +131,8 @@ class App(Row):
def get_api_data(self, include_token: bool = False) -> dict[str, Any]: def get_api_data(self, include_token: bool = False) -> dict[str, Any]:
data = deepcopy(self) data = deepcopy(self)
data.pop('user')
data.pop('auth_code') data.pop('auth_code')
data.pop('created')
data.pop('accessed')
if not include_token: if not include_token:
data.pop('token') data.pop('token')
@ -176,15 +158,11 @@ def migrate_20240206(conn: Connection) -> None:
@migration @migration
def migrate_20240310(conn: Connection) -> None: def migrate_20240310(conn: Connection) -> None:
conn.execute('ALTER TABLE "inboxes" ADD COLUMN "accepted" BOOLEAN') conn.execute('ALTER TABLE "inboxes" ADD COLUMN "accepted" BOOLEAN').close()
conn.execute('UPDATE "inboxes" SET accepted = 1') conn.execute('UPDATE "inboxes" SET accepted = 1').close()
@migration @migration
def migrate_20240625(conn: Connection) -> None: def migrate_20240625(conn: Connection) -> None:
conn.execute('ALTER TABLE "tokens" ADD "accessed" timestamp')
for token in conn.get_tokens():
conn.update('tokens', {'accessed': token.created}, code = token.code).one()
conn.create_tables() conn.create_tables()
conn.execute('DROP TABLE tokens').close()

View file

@ -76,12 +76,11 @@ JSON_PATHS: tuple[str, ...] = (
) )
TOKEN_PATHS: tuple[str, ...] = ( TOKEN_PATHS: tuple[str, ...] = (
'/api',
'/login',
'/logout', '/logout',
'/admin',
'/api',
'/oauth/authorize', '/oauth/authorize',
'/oauth/revoke', '/oauth/revoke'
'/admin'
) )

View file

@ -1,4 +1,3 @@
import secrets
import traceback import traceback
from aiohttp.web import Request, middleware from aiohttp.web import Request, middleware
@ -209,12 +208,12 @@ class Login(View):
except VerifyMismatchError: except VerifyMismatchError:
raise HttpError(401, 'Invalid password') raise HttpError(401, 'Invalid password')
token = conn.put_token(data['username']) app = conn.put_app_login(user)
resp = Response.new({'token': token.code}, ctype = 'json') resp = Response.new({'token': app.token}, ctype = 'json')
resp.set_cookie( resp.set_cookie(
'user-token', 'user-token',
token.code, app.token, # type: ignore[arg-type]
max_age = 60 * 60 * 24 * 365, max_age = 60 * 60 * 24 * 365,
domain = self.config.domain, domain = self.config.domain,
path = '/', path = '/',
@ -226,38 +225,6 @@ class Login(View):
return resp return resp
async def post2(self, request: Request) -> Response:
data = await self.get_api_data(['username', 'password'], [])
with self.database.session(True) as conn:
if not (user := conn.get_user(data['username'])):
raise HttpError(401, 'User not found')
try:
conn.hasher.verify(user['hash'], data['password'])
except VerifyMismatchError:
raise HttpError(401, 'Invalid password')
app = conn.put_app(
data['app_name'],
DEFAULT_REDIRECT,
data.get('website')
)
params = {
'code': secrets.token_hex(20),
'user': user.username
}
with conn.update('app', params, client_id = app.client_id) as cur:
if (row := cur.one(schema.App)) is None:
raise HttpError(500, 'Failed to create app')
return Response.new(row.get_api_data(True), ctype = 'json')
@register_route('/api/v1/relay') @register_route('/api/v1/relay')
class RelayInfo(View): class RelayInfo(View):
async def get(self, request: Request) -> Response: async def get(self, request: Request) -> Response:

View file

@ -18,7 +18,7 @@ async def handle_frontend_path(
if request['user'] is not None and request.path == '/login': if request['user'] is not None and request.path == '/login':
return Response.new_redir('/') return Response.new_redir('/')
if request.path.startswith(TOKEN_PATHS) and request['user'] is None: if request.path.startswith(TOKEN_PATHS[:2]) and request['user'] is None:
if request.path == '/logout': if request.path == '/logout':
return Response.new_redir('/') return Response.new_redir('/')
@ -62,7 +62,7 @@ class Login(View):
class Logout(View): class Logout(View):
async def get(self, request: web.Request) -> Response: async def get(self, request: web.Request) -> Response:
with self.database.session(True) as conn: with self.database.session(True) as conn:
conn.del_token(request['token'].code) conn.del_app(request['token'].client_id, request['token'].client_secret)
resp = Response.new_redir('/') resp = Response.new_redir('/')
resp.del_cookie('user-token', domain = self.config.domain, path = '/') resp.del_cookie('user-token', domain = self.config.domain, path = '/')