add secret to User & add user REST routes
This commit is contained in:
parent
9bb433eb0c
commit
f7913c30c8
4 changed files with 118 additions and 3 deletions
|
|
@ -256,6 +256,12 @@ async def update(item):
|
|||
await shared_connection().execute(query=query, values=values)
|
||||
|
||||
|
||||
async def remove(item):
|
||||
values = asplain(item, fields_={"id"})
|
||||
query = f"DELETE FROM {item._table} WHERE id=:id"
|
||||
await shared_connection().execute(query=query, values=values)
|
||||
|
||||
|
||||
async def add_or_update_user(user: User):
|
||||
db_user = await get(User, imdb_id=user.imdb_id)
|
||||
if not db_user:
|
||||
|
|
|
|||
|
|
@ -69,12 +69,15 @@ def optional_fields(o):
|
|||
yield f
|
||||
|
||||
|
||||
def asplain(o) -> dict[str, Any]:
|
||||
def asplain(o, *, fields_: set = None) -> dict[str, Any]:
|
||||
validate(o)
|
||||
|
||||
d = {}
|
||||
for f in fields(o):
|
||||
|
||||
if fields_ is not None and f.name not in fields_:
|
||||
continue
|
||||
|
||||
target = f.type
|
||||
# XXX this doesn't properly support any kind of nested types
|
||||
if (otype := optional_type(f.type)) is not None:
|
||||
|
|
@ -278,6 +281,7 @@ class User:
|
|||
id: ULID = field(default_factory=ULID)
|
||||
imdb_id: str = None
|
||||
name: str = None # canonical user name
|
||||
secret: str = None
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
|
|||
22
unwind/sql/20210801-201151--add-user-secret.sql
Normal file
22
unwind/sql/20210801-201151--add-user-secret.sql
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
-- add secret to users
|
||||
|
||||
CREATE TABLE _migrate_users (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
imdb_id TEXT NOT NULL UNIQUE,
|
||||
name TEXT NOT NULL,
|
||||
secret TEXT NOT NULL
|
||||
);;
|
||||
|
||||
INSERT INTO _migrate_users
|
||||
SELECT
|
||||
id,
|
||||
imdb_id,
|
||||
name,
|
||||
'' AS secret
|
||||
FROM users
|
||||
WHERE true;;
|
||||
|
||||
DROP TABLE users;;
|
||||
|
||||
ALTER TABLE _migrate_users
|
||||
RENAME TO users;;
|
||||
|
|
@ -27,7 +27,7 @@ from .db import close_connection_pool, find_ratings, open_connection_pool
|
|||
from .middleware.responsetime import ResponseTimeMiddleware
|
||||
from .models import Group, Movie, User, asplain
|
||||
from .types import ULID
|
||||
from .utils import b64encode, phc_compare, phc_scrypt
|
||||
from .utils import b64decode, b64encode, phc_compare, phc_scrypt
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -308,7 +308,90 @@ async def list_users(request):
|
|||
@requires(["authenticated", "admin"])
|
||||
async def add_user(request):
|
||||
|
||||
not_implemented()
|
||||
name, imdb_id = await json_from_body(request, ["name", "imdb_id"])
|
||||
|
||||
# XXX restrict name
|
||||
# XXX check if imdb_id is well-formed
|
||||
|
||||
secret = secrets.token_bytes()
|
||||
|
||||
user = User(name=name, imdb_id=imdb_id, secret=phc_scrypt(secret))
|
||||
await db.add(user)
|
||||
|
||||
return JSONResponse(
|
||||
{
|
||||
"secret": b64encode(secret),
|
||||
"user": asplain(user),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@route("/users/{user_id}")
|
||||
@requires(["authenticated", "admin"])
|
||||
async def show_user(request):
|
||||
|
||||
user_id = as_ulid(request.path_params["user_id"])
|
||||
|
||||
user = await db.get(User, id=str(user_id))
|
||||
if not user:
|
||||
return not_found()
|
||||
|
||||
return JSONResponse(asplain(user))
|
||||
|
||||
|
||||
@route("/users/{user_id}", methods=["DELETE"])
|
||||
@requires(["authenticated", "admin"])
|
||||
async def remove_user(request):
|
||||
|
||||
user_id = as_ulid(request.path_params["user_id"])
|
||||
|
||||
user = await db.get(User, id=str(user_id))
|
||||
if not user:
|
||||
return not_found()
|
||||
|
||||
async with db.shared_connection().transaction():
|
||||
# XXX remove user refs from groups and ratings
|
||||
|
||||
await db.remove(user)
|
||||
|
||||
return JSONResponse(asplain(user))
|
||||
|
||||
|
||||
@route("/users/{user_id}", methods=["PATCH"])
|
||||
@requires(["authenticated"])
|
||||
async def modify_user(request):
|
||||
|
||||
user_id = as_ulid(request.path_params["user_id"])
|
||||
|
||||
user = await db.get(User, id=str(user_id))
|
||||
if not user:
|
||||
return not_found()
|
||||
|
||||
is_admin, is_owner = auth(request, user.secret)
|
||||
if not (is_admin or is_owner):
|
||||
return forbidden()
|
||||
|
||||
data = await json_from_body(request)
|
||||
|
||||
if is_admin and "name" in data:
|
||||
# XXX restrict name
|
||||
user.name = data["name"]
|
||||
|
||||
if is_admin and "imdb_id" in data:
|
||||
# XXX check if imdb_id is well-formed
|
||||
user.imdb_id = data["imdb_id"]
|
||||
|
||||
if "secret" in data:
|
||||
try:
|
||||
secret = b64decode(data["secret"])
|
||||
except:
|
||||
raise HTTPException(422, f"Invalid secret.")
|
||||
|
||||
user.secret = phc_scrypt(secret)
|
||||
|
||||
await db.update(user)
|
||||
|
||||
return JSONResponse(asplain(user))
|
||||
|
||||
|
||||
@route("/users/{user_id}/ratings")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue