Initial prototype of create API token page, refs #1852

pull/1912/head
Simon Willison 2022-10-25 17:07:58 -07:00
rodzic f9ae92b377
commit 42f8b402e6
3 zmienionych plików z 142 dodań i 0 usunięć

Wyświetl plik

@ -33,6 +33,7 @@ from .views.special import (
JsonDataView,
PatternPortfolioView,
AuthTokenView,
CreateTokenView,
LogoutView,
AllowDebugView,
PermissionsDebugView,
@ -1212,6 +1213,10 @@ class Datasette:
AuthTokenView.as_view(self),
r"/-/auth-token$",
)
add_route(
CreateTokenView.as_view(self),
r"/-/create-token$",
)
add_route(
LogoutView.as_view(self),
r"/-/logout$",

Wyświetl plik

@ -0,0 +1,83 @@
{% extends "base.html" %}
{% block title %}Create an API token{% endblock %}
{% block content %}
<h1>Create an API token</h1>
<p>This token will allow API access with the same abilities as your current user.</p>
{% if errors %}
{% for error in errors %}
<p class="message-error">{{ error }}</p>
{% endfor %}
{% endif %}
<form action="{{ urls.path('-/create-token') }}" method="post">
<div>
<div class="select-wrapper" style="width: unset">
<select name="expire_type">
<option value="">Token never expires</option>
<option value="minutes">Expires after X minutes</option>
<option value="hours">Expires after X hours</option>
<option value="days">Expires after X days</option>
</select>
</div>
<input type="text" name="expire_duration" style="width: 10%">
<input type="hidden" name="csrftoken" value="{{ csrftoken() }}">
<input type="submit" value="Create token">
</div>
</form>
{% if token %}
<div>
<h2>Your API token</h2>
<form>
<input type="text" class="copyable" style="width: 40%" value="{{ token }}">
<span class="copy-link-wrapper"></span>
</form>
<!--- show token in a <details> -->
<details style="margin-top: 1em">
<summary>Token details</summary>
<pre>{{ token_bits|tojson }}</pre>
</details>
</div>
{% endif %}
<script>
var expireDuration = document.querySelector('input[name="expire_duration"]');
expireDuration.style.display = 'none';
var expireType = document.querySelector('select[name="expire_type"]');
function showHideExpireDuration() {
if (expireType.value) {
expireDuration.style.display = 'inline';
expireDuration.setAttribute("placeholder", expireType.value.replace("Expires after X ", ""));
} else {
expireDuration.style.display = 'none';
}
}
showHideExpireDuration();
expireType.addEventListener('change', showHideExpireDuration);
var copyInput = document.querySelector(".copyable");
if (copyInput) {
var wrapper = document.querySelector(".copy-link-wrapper");
var button = document.createElement("button");
button.className = "copyable-copy-button";
button.setAttribute("type", "button");
button.innerHTML = "Copy to clipboard";
button.onclick = (ev) => {
ev.preventDefault();
copyInput.select();
document.execCommand("copy");
button.innerHTML = "Copied!";
setTimeout(() => {
button.innerHTML = "Copy to clipboard";
}, 1500);
};
wrapper.appendChild(button);
wrapper.insertAdjacentElement("afterbegin", button);
}
</script>
{% endblock %}

Wyświetl plik

@ -3,6 +3,7 @@ from datasette.utils.asgi import Response, Forbidden
from datasette.utils import actor_matches_allow, add_cors_headers
from .base import BaseView
import secrets
import time
class JsonDataView(BaseView):
@ -163,3 +164,56 @@ class MessagesDebugView(BaseView):
else:
datasette.add_message(request, message, getattr(datasette, message_type))
return Response.redirect(self.ds.urls.instance())
class CreateTokenView(BaseView):
name = "create_token"
has_json_alternate = False
async def get(self, request):
if not request.actor:
raise Forbidden("You must be logged in to create a token")
return await self.render(
["create_token.html"],
request,
{"actor": request.actor},
)
async def post(self, request):
if not request.actor:
raise Forbidden("You must be logged in to create a token")
post = await request.post_vars()
expires = None
errors = []
if post.get("expire_type"):
duration = post.get("expire_duration")
if not duration or not duration.isdigit() or not int(duration) > 0:
errors.append("Invalid expire duration")
else:
unit = post["expire_type"]
if unit == "minutes":
expires = int(duration) * 60
elif unit == "hours":
expires = int(duration) * 60 * 60
elif unit == "days":
expires = int(duration) * 60 * 60 * 24
else:
errors.append("Invalid expire duration unit")
token_bits = None
token = None
if not errors:
token_bits = {
"a": request.actor,
"e": (int(time.time()) + expires) if expires else None,
}
token = self.ds.sign(token_bits, "token")
return await self.render(
["create_token.html"],
request,
{
"actor": request.actor,
"errors": errors,
"token": token,
"token_bits": token_bits,
},
)