mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
max_signed_tokens_ttl setting, closes #1858
Also redesigned token format to include creation time and optional duration.
This commit is contained in:
parent
af5d5d0243
commit
382a871583
6 changed files with 99 additions and 25 deletions
|
|
@ -129,6 +129,11 @@ SETTINGS = (
|
|||
True,
|
||||
"Allow users to create and use signed API tokens",
|
||||
),
|
||||
Setting(
|
||||
"max_signed_tokens_ttl",
|
||||
0,
|
||||
"Maximum allowed expiry time for signed API tokens",
|
||||
),
|
||||
Setting("suggest_facets", True, "Calculate and display suggested facets"),
|
||||
Setting(
|
||||
"default_cache_ttl",
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ def actor_from_request(datasette, request):
|
|||
prefix = "dstok_"
|
||||
if not datasette.setting("allow_signed_tokens"):
|
||||
return None
|
||||
max_signed_tokens_ttl = datasette.setting("max_signed_tokens_ttl")
|
||||
authorization = request.headers.get("authorization")
|
||||
if not authorization:
|
||||
return None
|
||||
|
|
@ -69,11 +70,31 @@ def actor_from_request(datasette, request):
|
|||
decoded = datasette.unsign(token, namespace="token")
|
||||
except itsdangerous.BadSignature:
|
||||
return None
|
||||
expires_at = decoded.get("e")
|
||||
if expires_at is not None:
|
||||
if expires_at < time.time():
|
||||
if "t" not in decoded:
|
||||
# Missing timestamp
|
||||
return None
|
||||
created = decoded["t"]
|
||||
if not isinstance(created, int):
|
||||
# Invalid timestamp
|
||||
return None
|
||||
duration = decoded.get("d")
|
||||
if duration is not None and not isinstance(duration, int):
|
||||
# Invalid duration
|
||||
return None
|
||||
if (duration is None and max_signed_tokens_ttl) or (
|
||||
duration is not None
|
||||
and max_signed_tokens_ttl
|
||||
and duration > max_signed_tokens_ttl
|
||||
):
|
||||
duration = max_signed_tokens_ttl
|
||||
if duration:
|
||||
if time.time() - created > duration:
|
||||
# Expired
|
||||
return None
|
||||
return {"id": decoded["a"], "token": "dstok"}
|
||||
actor = {"id": decoded["a"], "token": "dstok"}
|
||||
if duration:
|
||||
actor["token_expires"] = created + duration
|
||||
return actor
|
||||
|
||||
|
||||
@hookimpl
|
||||
|
|
@ -102,9 +123,9 @@ def register_commands(cli):
|
|||
def create_token(id, secret, expires_after, debug):
|
||||
"Create a signed API token for the specified actor ID"
|
||||
ds = Datasette(secret=secret)
|
||||
bits = {"a": id, "token": "dstok"}
|
||||
bits = {"a": id, "token": "dstok", "t": int(time.time())}
|
||||
if expires_after:
|
||||
bits["e"] = int(time.time()) + expires_after
|
||||
bits["d"] = expires_after
|
||||
token = ds.sign(bits, namespace="token")
|
||||
click.echo("dstok_{}".format(token))
|
||||
if debug:
|
||||
|
|
|
|||
|
|
@ -195,20 +195,24 @@ class CreateTokenView(BaseView):
|
|||
async def post(self, request):
|
||||
self.check_permission(request)
|
||||
post = await request.post_vars()
|
||||
expires = None
|
||||
errors = []
|
||||
duration = None
|
||||
if post.get("expire_type"):
|
||||
duration = post.get("expire_duration")
|
||||
if not duration or not duration.isdigit() or not int(duration) > 0:
|
||||
duration_string = post.get("expire_duration")
|
||||
if (
|
||||
not duration_string
|
||||
or not duration_string.isdigit()
|
||||
or not int(duration_string) > 0
|
||||
):
|
||||
errors.append("Invalid expire duration")
|
||||
else:
|
||||
unit = post["expire_type"]
|
||||
if unit == "minutes":
|
||||
expires = int(duration) * 60
|
||||
duration = int(duration_string) * 60
|
||||
elif unit == "hours":
|
||||
expires = int(duration) * 60 * 60
|
||||
duration = int(duration_string) * 60 * 60
|
||||
elif unit == "days":
|
||||
expires = int(duration) * 60 * 60 * 24
|
||||
duration = int(duration_string) * 60 * 60 * 24
|
||||
else:
|
||||
errors.append("Invalid expire duration unit")
|
||||
token_bits = None
|
||||
|
|
@ -216,8 +220,10 @@ class CreateTokenView(BaseView):
|
|||
if not errors:
|
||||
token_bits = {
|
||||
"a": request.actor["id"],
|
||||
"e": (int(time.time()) + expires) if expires else None,
|
||||
"t": int(time.time()),
|
||||
}
|
||||
if duration:
|
||||
token_bits["d"] = duration
|
||||
token = "dstok_{}".format(self.ds.sign(token_bits, "token"))
|
||||
return await self.render(
|
||||
["create_token.html"],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue