Add FCM device registration and Gitea bug reporter
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
"""Device registration only. This module deliberately contains no push sender."""
|
||||
|
||||
import hashlib
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import Request
|
||||
from fastapi.responses import JSONResponse
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class DeviceRegistration(BaseModel):
|
||||
device_id: UUID
|
||||
token: str = Field(min_length=20, max_length=4096, pattern=r'^[A-Za-z0-9_:.-]+$')
|
||||
platform: str = Field(pattern=r'^android$')
|
||||
app_version: str = Field(default='', max_length=32, pattern=r'^[0-9A-Za-z.+_-]*$')
|
||||
session_tag: str = Field(pattern=r'^[a-f0-9]{64}$')
|
||||
|
||||
|
||||
def session_hash(request, cookie_name):
|
||||
return hashlib.sha256(request.cookies.get(cookie_name, '').encode()).hexdigest()
|
||||
|
||||
|
||||
def register_routes(app, get_db, get_user, cookie_name):
|
||||
@app.get('/api/push/session')
|
||||
def push_session(request: Request):
|
||||
user = get_user(request)
|
||||
payload = {'authenticated': bool(user)}
|
||||
if user:
|
||||
payload.update(user_id=user['id'], session_tag=session_hash(request, cookie_name))
|
||||
return JSONResponse(payload, headers={'Cache-Control': 'no-store'})
|
||||
|
||||
@app.post('/api/push/devices')
|
||||
def register_device(request: Request, data: DeviceRegistration):
|
||||
user = get_user(request)
|
||||
if not user:
|
||||
return JSONResponse({'error': 'authentication_required'}, status_code=401)
|
||||
token_hash = session_hash(request, cookie_name)
|
||||
if data.session_tag != token_hash:
|
||||
return JSONResponse({'error': 'session_changed'}, status_code=409)
|
||||
with get_db() as connection:
|
||||
with connection.cursor() as cursor:
|
||||
# The row lock serializes registration against logout/session deletion.
|
||||
cursor.execute('SELECT id FROM sessions WHERE token_hash=%s AND user_id=%s '
|
||||
'AND expires_at>CURRENT_TIMESTAMP FOR UPDATE', (token_hash, user['id']))
|
||||
session = cursor.fetchone()
|
||||
if not session:
|
||||
return JSONResponse({'error': 'session_expired'}, status_code=401)
|
||||
# Serialize token/device transfers across users as well as token rotations.
|
||||
cursor.execute('SELECT pg_advisory_xact_lock(71001)')
|
||||
cursor.execute('DELETE FROM push_devices WHERE token=%s AND device_id<>%s',
|
||||
(data.token, data.device_id))
|
||||
cursor.execute('''
|
||||
INSERT INTO push_devices(user_id, session_id, device_id, token, platform, app_version)
|
||||
VALUES (%s,%s,%s,%s,%s,%s)
|
||||
ON CONFLICT(device_id) DO UPDATE SET
|
||||
user_id=EXCLUDED.user_id, session_id=EXCLUDED.session_id,
|
||||
token=EXCLUDED.token, platform=EXCLUDED.platform, app_version=EXCLUDED.app_version,
|
||||
updated_at=CURRENT_TIMESTAMP, last_seen_at=CURRENT_TIMESTAMP
|
||||
RETURNING id
|
||||
''', (user['id'], session[0], data.device_id, data.token, data.platform, data.app_version))
|
||||
device_id = cursor.fetchone()[0]
|
||||
cursor.execute('DELETE FROM push_devices WHERE session_id IN '
|
||||
'(SELECT id FROM sessions WHERE expires_at<=CURRENT_TIMESTAMP) '
|
||||
'OR last_seen_at<CURRENT_TIMESTAMP - INTERVAL \'90 days\'')
|
||||
connection.commit()
|
||||
return JSONResponse({'registered': True, 'id': device_id}, headers={'Cache-Control': 'no-store'})
|
||||
|
||||
@app.delete('/api/push/devices/{device_id}')
|
||||
def unregister_device(request: Request, device_id: UUID):
|
||||
user = get_user(request)
|
||||
if not user:
|
||||
return JSONResponse({'error': 'authentication_required'}, status_code=401)
|
||||
with get_db() as connection:
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute('DELETE FROM push_devices WHERE device_id=%s AND user_id=%s '
|
||||
'AND session_id IN (SELECT id FROM sessions WHERE token_hash=%s)',
|
||||
(device_id, user['id'], session_hash(request, cookie_name)))
|
||||
connection.commit()
|
||||
return JSONResponse({'registered': False}, headers={'Cache-Control': 'no-store'})
|
||||
Reference in New Issue
Block a user