mirror of
https://git.pleroma.social/pleroma/relay.git
synced 2024-11-08 09:47:58 +00:00
remove tokens table and fix auth handling
This commit is contained in:
parent
f98ca54ab7
commit
773922e263
|
@ -310,15 +310,21 @@ async def handle_response_headers(
|
|||
|
||||
if request.path == "/" or request.path.startswith(TOKEN_PATHS):
|
||||
with app.database.session() as conn:
|
||||
if (token := request.headers.get('Authorization')) is not None:
|
||||
token = token.replace('Bearer', '').strip()
|
||||
tokens = (
|
||||
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['user'] = conn.get_user_by_app_token(token)
|
||||
|
||||
elif (token := request.cookies.get('user-token')) is not None:
|
||||
request['token'] = conn.get_token(token)
|
||||
request['user'] = conn.get_user_by_token(token)
|
||||
if request['token'] is not None:
|
||||
request['user'] = conn.get_user(request['token'].user)
|
||||
|
||||
break
|
||||
|
||||
try:
|
||||
resp = await handler(request)
|
||||
|
|
|
@ -50,17 +50,9 @@ WHERE username = :value or handle = :value;
|
|||
|
||||
-- name: get-user-by-token
|
||||
SELECT * FROM users
|
||||
WHERE username = (
|
||||
SELECT user FROM tokens
|
||||
WHERE code = :code
|
||||
);
|
||||
|
||||
|
||||
-- name: get-user-by-app-token
|
||||
SELECT * FROM users
|
||||
WHERE username = (
|
||||
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;
|
||||
|
||||
|
||||
-- name: get-app-token
|
||||
-- name: get-app-with-token
|
||||
SELECT * FROM app
|
||||
WHERE client_id = :id and client_secret = :secret and token = :token;
|
||||
|
||||
|
||||
-- name: get-app-by-token
|
||||
SELECT * FROM app
|
||||
SELECT * FROM apps
|
||||
WHERE token = :token;
|
||||
|
||||
-- name: del-app
|
||||
DELETE FROM users
|
||||
DELETE FROM apps
|
||||
WHERE client_id = :id and client_secret = :secret;
|
||||
|
||||
|
||||
-- name: del-app-token
|
||||
DELETE FROM users
|
||||
-- name: del-app-with-token
|
||||
DELETE FROM apps
|
||||
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
|
||||
SELECT * FROM software_bans WHERE name = :name;
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ from collections.abc import Iterator
|
|||
from datetime import datetime, timezone
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from urllib.parse import urlparse
|
||||
from uuid import uuid4
|
||||
|
||||
from . import schema
|
||||
from .config import (
|
||||
|
@ -73,10 +72,6 @@ class Connection(SqlConnection):
|
|||
data = {'created': sban.created.timestamp()}
|
||||
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):
|
||||
data = {'created': user.created.timestamp()}
|
||||
self.update('users', data, username = user.username)
|
||||
|
@ -230,13 +225,8 @@ class Connection(SqlConnection):
|
|||
return cur.one(schema.User)
|
||||
|
||||
|
||||
def get_user_by_token(self, code: str) -> schema.User | None:
|
||||
with self.run('get-user-by-token', {'code': code}) 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:
|
||||
def get_user_by_token(self, token: str) -> schema.User | None:
|
||||
with self.run('get-user-by-token', {'token': token}) as cur:
|
||||
return cur.one(schema.User)
|
||||
|
||||
|
||||
|
@ -328,13 +318,34 @@ class Connection(SqlConnection):
|
|||
'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:
|
||||
raise RuntimeError(f'Failed to insert app: {name}')
|
||||
|
||||
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:
|
||||
data: dict[str, str | None] = {}
|
||||
|
||||
|
@ -367,7 +378,7 @@ class Connection(SqlConnection):
|
|||
}
|
||||
|
||||
if token is not None:
|
||||
command = 'del-app-token'
|
||||
command = 'del-app-with-token'
|
||||
params['token'] = token
|
||||
|
||||
else:
|
||||
|
@ -380,37 +391,6 @@ class Connection(SqlConnection):
|
|||
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:
|
||||
if domain.startswith('http'):
|
||||
domain = urlparse(domain).netloc
|
||||
|
|
|
@ -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
|
||||
class App(Row):
|
||||
table_name: str = 'apps'
|
||||
|
@ -148,9 +131,8 @@ class App(Row):
|
|||
|
||||
def get_api_data(self, include_token: bool = False) -> dict[str, Any]:
|
||||
data = deepcopy(self)
|
||||
data.pop('user')
|
||||
data.pop('auth_code')
|
||||
data.pop('created')
|
||||
data.pop('accessed')
|
||||
|
||||
if not include_token:
|
||||
data.pop('token')
|
||||
|
@ -176,15 +158,11 @@ def migrate_20240206(conn: Connection) -> None:
|
|||
|
||||
@migration
|
||||
def migrate_20240310(conn: Connection) -> None:
|
||||
conn.execute('ALTER TABLE "inboxes" ADD COLUMN "accepted" BOOLEAN')
|
||||
conn.execute('UPDATE "inboxes" SET accepted = 1')
|
||||
conn.execute('ALTER TABLE "inboxes" ADD COLUMN "accepted" BOOLEAN').close()
|
||||
conn.execute('UPDATE "inboxes" SET accepted = 1').close()
|
||||
|
||||
|
||||
@migration
|
||||
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.execute('DROP TABLE tokens').close()
|
||||
|
|
|
@ -76,12 +76,11 @@ JSON_PATHS: tuple[str, ...] = (
|
|||
)
|
||||
|
||||
TOKEN_PATHS: tuple[str, ...] = (
|
||||
'/api',
|
||||
'/login',
|
||||
'/logout',
|
||||
'/admin',
|
||||
'/api',
|
||||
'/oauth/authorize',
|
||||
'/oauth/revoke',
|
||||
'/admin'
|
||||
'/oauth/revoke'
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import secrets
|
||||
import traceback
|
||||
|
||||
from aiohttp.web import Request, middleware
|
||||
|
@ -209,12 +208,12 @@ class Login(View):
|
|||
except VerifyMismatchError:
|
||||
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(
|
||||
'user-token',
|
||||
token.code,
|
||||
app.token, # type: ignore[arg-type]
|
||||
max_age = 60 * 60 * 24 * 365,
|
||||
domain = self.config.domain,
|
||||
path = '/',
|
||||
|
@ -226,38 +225,6 @@ class Login(View):
|
|||
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')
|
||||
class RelayInfo(View):
|
||||
async def get(self, request: Request) -> Response:
|
||||
|
|
|
@ -18,7 +18,7 @@ async def handle_frontend_path(
|
|||
if request['user'] is not None and request.path == '/login':
|
||||
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':
|
||||
return Response.new_redir('/')
|
||||
|
||||
|
@ -62,7 +62,7 @@ class Login(View):
|
|||
class Logout(View):
|
||||
async def get(self, request: web.Request) -> Response:
|
||||
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.del_cookie('user-token', domain = self.config.domain, path = '/')
|
||||
|
|
Loading…
Reference in a new issue