diff --git a/api/obs/api/routes/users.py b/api/obs/api/routes/users.py index 8fd3784..715c213 100644 --- a/api/obs/api/routes/users.py +++ b/api/obs/api/routes/users.py @@ -1,6 +1,9 @@ import logging +import os +import binascii from sanic.response import json +from sanic.exceptions import InvalidUsage from obs.api.app import api, require_auth @@ -16,7 +19,7 @@ def user_to_json(user): "bio": user.bio, "image": user.image, "areTracksVisibleForAll": user.are_tracks_visible_for_all, - # "apiKey": user.api_key, + "apiKey": user.api_key, } @@ -29,13 +32,17 @@ async def get_user(req): @require_auth async def put_user(req): user = req.ctx.user + data = req.json - for key in ["username", "email", "bio", "image"]: - if key in req.json and isinstance(req.json[key], (str, type(None))): - setattr(user, key, req.json[key]) + for key in ["email", "bio", "image"]: + if key in data and isinstance(data[key], (str, type(None))): + setattr(user, key, data[key]) - if "areTracksVisibleForAll" in req.json: - user.are_tracks_visible_for_all = bool(req.json["areTracksVisibleForAll"]) + if "areTracksVisibleForAll" in data: + user.are_tracks_visible_for_all = bool(data["areTracksVisibleForAll"]) + + if data.get("updateApiKey"): + user.api_key = binascii.b2a_hex(os.urandom(16)).decode("ascii") await req.ctx.db.commit() return json(user_to_json(req.ctx.user)) diff --git a/frontend/src/pages/SettingsPage.tsx b/frontend/src/pages/SettingsPage.tsx index daf3116..bcad107 100644 --- a/frontend/src/pages/SettingsPage.tsx +++ b/frontend/src/pages/SettingsPage.tsx @@ -19,8 +19,8 @@ const SettingsPage = connect((state) => ({login: state.login}), {setLogin})(func setLoading(true) setErrors(null) try { - const response = await api.put('/user', {body: {user: changes}}) - setLogin(response.user) + const response = await api.put('/user', {body: changes}) + setLogin(response) } catch (err) { setErrors(err.errors) } finally { @@ -30,6 +30,19 @@ const SettingsPage = connect((state) => ({login: state.login}), {setLogin})(func [setLoading, setLogin, setErrors] ) + const onGenerateNewKey = React.useCallback(async () => { + setLoading(true) + setErrors(null) + try { + const response = await api.put('/user', {body: {updateApiKey: true}}) + setLogin(response) + } catch (err) { + setErrors(err.errors) + } finally { + setLoading(false) + } + }, [setLoading, setLogin, setErrors]) + return ( @@ -41,7 +54,7 @@ const SettingsPage = connect((state) => ({login: state.login}), {setLogin})(func
- + @@ -62,7 +75,7 @@ const SettingsPage = connect((state) => ({login: state.login}), {setLogin})(func - + @@ -101,7 +114,7 @@ function CopyInput({value, ...props}) { const selectField = findInput((ref) => ref?.select()) -function ApiKeyDialog({login}) { +function ApiKeyDialog({login, onGenerateNewKey}) { const config = useConfig() const [show, setShow] = React.useState(false) const onClick = React.useCallback( @@ -112,6 +125,14 @@ function ApiKeyDialog({login}) { [setShow] ) + const onGenerateNewKeyInner = React.useCallback( + (e) => { + e.preventDefault() + onGenerateNewKey() + }, + [onGenerateNewKey] + ) + return ( <>
Your API Key
@@ -120,11 +141,15 @@ function ApiKeyDialog({login}) { configuration interface to allow direct upload from the device.

Please protect your API Key carefully as it allows full control over your account.

-
+
{show ? ( - - - + login.apiKey ? ( + + + + ) : ( + + ) ) : (

The API URL should be set to:

- +
+ +
+

+ You can generate a new API Key here, which will invalidate the old one, disconnecting all devices you used it on + from your account. +

+ ) }