datasette-secrets 0.1a0__py3-none-any.whl → 0.1a2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of datasette-secrets might be problematic. Click here for more details.
- datasette_secrets/__init__.py +39 -18
- datasette_secrets/templates/secrets_index.html +5 -2
- datasette_secrets/templates/secrets_update.html +4 -2
- {datasette_secrets-0.1a0.dist-info → datasette_secrets-0.1a2.dist-info}/METADATA +21 -4
- datasette_secrets-0.1a2.dist-info/RECORD +10 -0
- datasette_secrets-0.1a0.dist-info/RECORD +0 -10
- {datasette_secrets-0.1a0.dist-info → datasette_secrets-0.1a2.dist-info}/LICENSE +0 -0
- {datasette_secrets-0.1a0.dist-info → datasette_secrets-0.1a2.dist-info}/WHEEL +0 -0
- {datasette_secrets-0.1a0.dist-info → datasette_secrets-0.1a2.dist-info}/entry_points.txt +0 -0
- {datasette_secrets-0.1a0.dist-info → datasette_secrets-0.1a2.dist-info}/top_level.txt +0 -0
datasette_secrets/__init__.py
CHANGED
|
@@ -13,7 +13,7 @@ MAX_NOTE_LENGTH = 100
|
|
|
13
13
|
pm.add_hookspecs(hookspecs)
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
async def get_secret(datasette, secret_name):
|
|
16
|
+
async def get_secret(datasette, secret_name, actor_id=None):
|
|
17
17
|
secrets_by_name = {secret.name: secret for secret in await get_secrets(datasette)}
|
|
18
18
|
if secret_name not in secrets_by_name:
|
|
19
19
|
return None
|
|
@@ -24,23 +24,40 @@ async def get_secret(datasette, secret_name):
|
|
|
24
24
|
# Now look it up in the database
|
|
25
25
|
config = get_config(datasette)
|
|
26
26
|
db = get_database(datasette)
|
|
27
|
-
|
|
27
|
+
db_secret = (
|
|
28
28
|
await db.execute(
|
|
29
|
-
"select encrypted from datasette_secrets where name = ? order by version desc limit 1",
|
|
29
|
+
"select id, encrypted from datasette_secrets where name = ? order by version desc limit 1",
|
|
30
30
|
(secret_name,),
|
|
31
31
|
)
|
|
32
32
|
).first()
|
|
33
|
-
if not
|
|
33
|
+
if not db_secret:
|
|
34
34
|
return None
|
|
35
35
|
key = Fernet(config["encryption_key"].encode("utf-8"))
|
|
36
|
-
decrypted = key.decrypt(
|
|
36
|
+
decrypted = key.decrypt(db_secret["encrypted"])
|
|
37
|
+
# Update the last used timestamp and actor_id
|
|
38
|
+
params = (actor_id, db_secret["id"])
|
|
39
|
+
if not actor_id:
|
|
40
|
+
params = (db_secret["id"],)
|
|
41
|
+
await db.execute_write(
|
|
42
|
+
"""
|
|
43
|
+
update datasette_secrets
|
|
44
|
+
set last_used_at = datetime('now'),
|
|
45
|
+
last_used_by = {}
|
|
46
|
+
where id = ?
|
|
47
|
+
""".format(
|
|
48
|
+
"?" if actor_id else "null"
|
|
49
|
+
),
|
|
50
|
+
params,
|
|
51
|
+
)
|
|
37
52
|
return decrypted.decode("utf-8")
|
|
38
53
|
|
|
39
54
|
|
|
40
55
|
@dataclasses.dataclass
|
|
41
56
|
class Secret:
|
|
42
57
|
name: str
|
|
43
|
-
|
|
58
|
+
description: Optional[str] = None
|
|
59
|
+
obtain_url: Optional[str] = None
|
|
60
|
+
obtain_label: Optional[str] = None
|
|
44
61
|
|
|
45
62
|
|
|
46
63
|
SCHEMA = """
|
|
@@ -51,7 +68,6 @@ create table if not exists datasette_secrets (
|
|
|
51
68
|
version integer not null default 1,
|
|
52
69
|
encrypted blob,
|
|
53
70
|
encryption_key_name text not null,
|
|
54
|
-
redacted text,
|
|
55
71
|
created_at text,
|
|
56
72
|
created_by text,
|
|
57
73
|
updated_at text,
|
|
@@ -100,25 +116,20 @@ def register_permissions(datasette):
|
|
|
100
116
|
|
|
101
117
|
async def get_secrets(datasette):
|
|
102
118
|
secrets = []
|
|
119
|
+
seen = set()
|
|
103
120
|
for result in pm.hook.register_secrets(datasette=datasette):
|
|
104
121
|
result = await await_me_maybe(result)
|
|
105
|
-
|
|
122
|
+
for secret in result:
|
|
123
|
+
if secret.name in seen:
|
|
124
|
+
continue # Skip duplicates
|
|
125
|
+
seen.add(secret.name)
|
|
126
|
+
secrets.append(secret)
|
|
106
127
|
# if not secrets:
|
|
107
128
|
secrets.append(Secret("EXAMPLE_SECRET", "An example secret"))
|
|
108
129
|
|
|
109
130
|
return secrets
|
|
110
131
|
|
|
111
132
|
|
|
112
|
-
@hookimpl
|
|
113
|
-
def register_secrets():
|
|
114
|
-
return [
|
|
115
|
-
Secret(
|
|
116
|
-
"OPENAI_API_KEY",
|
|
117
|
-
'An OpenAI API key. Get them from <a href="https://platform.openai.com/api-keys">here</a>.',
|
|
118
|
-
),
|
|
119
|
-
]
|
|
120
|
-
|
|
121
|
-
|
|
122
133
|
@hookimpl
|
|
123
134
|
def register_commands(cli):
|
|
124
135
|
@cli.group()
|
|
@@ -169,6 +180,16 @@ async def secrets_index(datasette, request):
|
|
|
169
180
|
list(environment_secrets_names),
|
|
170
181
|
)
|
|
171
182
|
existing_secrets = {row["name"]: dict(row) for row in existing_secrets_result.rows}
|
|
183
|
+
# Try to turn updated_by into actors
|
|
184
|
+
actors = await datasette.actors_from_ids(
|
|
185
|
+
{row["updated_by"] for row in existing_secrets.values() if row["updated_by"]}
|
|
186
|
+
)
|
|
187
|
+
for secret in existing_secrets.values():
|
|
188
|
+
if secret["updated_by"]:
|
|
189
|
+
actor = actors.get(secret["updated_by"])
|
|
190
|
+
if actor:
|
|
191
|
+
display = actor.get("username") or actor.get("name") or actor.get("id")
|
|
192
|
+
secret["updated_by"] = display
|
|
172
193
|
unset_secrets = [
|
|
173
194
|
secret
|
|
174
195
|
for secret in all_secrets
|
|
@@ -25,7 +25,10 @@
|
|
|
25
25
|
<ul>
|
|
26
26
|
{% for secret in unset_secrets %}
|
|
27
27
|
<li><strong><a href="{{ urls.path("/-/secrets/") }}{{ secret.name }}">{{ secret.name }}</a></strong>
|
|
28
|
-
{% if secret.
|
|
28
|
+
{% if secret.description or secret.obtain_label %}
|
|
29
|
+
- {{ secret.description or "" }}{% if secret.description and secret.obtain_label %}, {% endif %}
|
|
30
|
+
{% if secret.obtain_label %}<a href="{{ secret.obtain_url }}">{{ secret.obtain_label }}</a>{% endif %}
|
|
31
|
+
{% endif %}</li>
|
|
29
32
|
{% endfor %}
|
|
30
33
|
</ul>
|
|
31
34
|
{% endif %}
|
|
@@ -34,7 +37,7 @@
|
|
|
34
37
|
<p style="margin-top: 2em">The following secret{% if environment_secrets|length == 1 %} is{% else %}s are{% endif %} set using environment variables:</p>
|
|
35
38
|
<ul>
|
|
36
39
|
{% for secret in environment_secrets %}
|
|
37
|
-
<li><strong>{{ secret.name }}</a></strong
|
|
40
|
+
<li><strong>{{ secret.name }}</a></strong>{% if secret.description %} - {{ secret.description }}{% endif %}<br>
|
|
38
41
|
<span style="font-size: 0.8 em">Set by <code>DATASETTE_SECRETS_{{ secret.name }}</code></span></li>
|
|
39
42
|
{% endfor %}
|
|
40
43
|
</ul>
|
|
@@ -9,8 +9,10 @@
|
|
|
9
9
|
{% block content %}
|
|
10
10
|
<h1>{% if current_secret %}Update{% else %}Add{% endif %} secret: {{ secret_name }}</h1>
|
|
11
11
|
|
|
12
|
-
{% if secret_details
|
|
13
|
-
<p>{{ secret_details.
|
|
12
|
+
{% if secret_details.description or secret_details.obtain_label %}
|
|
13
|
+
<p>{{ secret_details.description or "" }}{% if secret_details.description and secret_details.obtain_label %}. {% endif %}
|
|
14
|
+
{% if secret_details.obtain_label %}<a href="{{ secret_details.obtain_url }}">{{ secret_details.obtain_label }}</a>{% endif %}
|
|
15
|
+
</p>
|
|
14
16
|
{% endif %}
|
|
15
17
|
|
|
16
18
|
{% if error %}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: datasette-secrets
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.1a2
|
|
4
4
|
Summary: Manage secrets such as API keys for use with other Datasette plugins
|
|
5
5
|
Author: Datasette
|
|
6
6
|
License: Apache-2.0
|
|
@@ -105,6 +105,8 @@ datasette data.db --internal internal.db \
|
|
|
105
105
|
|
|
106
106
|
users with the `manage-secrets` permission will see a new "Manage secrets" link in the Datasette navigation menu. This interface can also be accessed at `/-/secrets`.
|
|
107
107
|
|
|
108
|
+
The page with the list of secrets will show the user who last updated each secret. This will use the [actors_from_ids()](https://docs.datasette.io/en/latest/plugin_hooks.html#actors-from-ids-datasette-actor-ids) mechanism, displaying the actor's `username` if available, otherwise the `name`, otherwise the `id`.
|
|
109
|
+
|
|
108
110
|
## For plugin authors
|
|
109
111
|
|
|
110
112
|
Plugins can depend on this plugin if they want to implement secrets.
|
|
@@ -121,11 +123,24 @@ from datasette_secrets import Secret
|
|
|
121
123
|
def register_secrets():
|
|
122
124
|
return [
|
|
123
125
|
Secret(
|
|
124
|
-
"OPENAI_API_KEY",
|
|
125
|
-
|
|
126
|
+
name="OPENAI_API_KEY",
|
|
127
|
+
description="An OpenAI API key"
|
|
128
|
+
),
|
|
129
|
+
]
|
|
130
|
+
```
|
|
131
|
+
You can also provide optional `obtain_url` and `obtain_label` fields to link to a page where a user can obtain an API key:
|
|
132
|
+
```python
|
|
133
|
+
@hookimpl
|
|
134
|
+
def register_secrets():
|
|
135
|
+
return [
|
|
136
|
+
Secret(
|
|
137
|
+
name="OPENAI_API_KEY",
|
|
138
|
+
obtain_url="https://platform.openai.com/api-keys",
|
|
139
|
+
obtain_label="Get an OpenAI API key"
|
|
126
140
|
),
|
|
127
141
|
]
|
|
128
142
|
```
|
|
143
|
+
|
|
129
144
|
The hook can take an optional `datasette` argument. It can return a list or an `async def` function that, when awaited, returns a list.
|
|
130
145
|
|
|
131
146
|
The list should consist of `Secret()` instances, each with a name and an optional description. The description can contain HTML.
|
|
@@ -135,12 +150,14 @@ To obtain the current value of the secret, use the `await get_secret()` method:
|
|
|
135
150
|
```python
|
|
136
151
|
from datasette_secrets import get_secret
|
|
137
152
|
|
|
138
|
-
|
|
153
|
+
# Third argument is the actor_id, optional
|
|
154
|
+
secret = await get_secret(datasette, "OPENAI_API_KEY", "root")
|
|
139
155
|
```
|
|
140
156
|
If the Datasette administrator set a `DATASETTE_SECRETS_OPENAI_API_KEY` environment variable, that will be returned.
|
|
141
157
|
|
|
142
158
|
Otherwise the encrypted value in the database table will be decrypted and returned - or `None` if there is no configured secret.
|
|
143
159
|
|
|
160
|
+
The `last_used_at` column is updated every time a secret is accessed. The `last_used_by` column will be set to the actor ID passed to `get_secret()`, or `null` if no actor ID was passed.
|
|
144
161
|
|
|
145
162
|
## Development
|
|
146
163
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
datasette_secrets/__init__.py,sha256=QFIh9c--SqVDhv-5WXfmI40-UqyyZrngdzYJ9t_hVE4,10409
|
|
2
|
+
datasette_secrets/hookspecs.py,sha256=57v14e2Y4o5eZyAgCLpuzp1KZn7CjwLXeKwfq6Zvux8,205
|
|
3
|
+
datasette_secrets/templates/secrets_index.html,sha256=ZgIy_huFZQfnI6GjO0qauWEkTWhkBCM5WrT73Nqq4BY,1737
|
|
4
|
+
datasette_secrets/templates/secrets_update.html,sha256=qMPLVCuKKslqw_In0aSCVobB3maPw9oaYZDQQImdRIU,1678
|
|
5
|
+
datasette_secrets-0.1a2.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
6
|
+
datasette_secrets-0.1a2.dist-info/METADATA,sha256=h-UZ9xZYyvTXjwJBNntAqhPp90OY5Y-INqedW2iqLuU,7040
|
|
7
|
+
datasette_secrets-0.1a2.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
8
|
+
datasette_secrets-0.1a2.dist-info/entry_points.txt,sha256=2083uWbPpGntxRulh8_hVaelQO-xdtjedG6rGzwPUH0,40
|
|
9
|
+
datasette_secrets-0.1a2.dist-info/top_level.txt,sha256=ZBJKQk-DdDU9Vnwu4x79X9aaEulwGJMoLx62IZJPDaQ,18
|
|
10
|
+
datasette_secrets-0.1a2.dist-info/RECORD,,
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
datasette_secrets/__init__.py,sha256=0hepqbZLfwETYcTA6fU2raP3F1igb_Z5OaqwdjByCYY,9514
|
|
2
|
-
datasette_secrets/hookspecs.py,sha256=57v14e2Y4o5eZyAgCLpuzp1KZn7CjwLXeKwfq6Zvux8,205
|
|
3
|
-
datasette_secrets/templates/secrets_index.html,sha256=Zr8wmVngxzABB69hM052W8IOHgGIlJLM9KKnDxAYmQI,1510
|
|
4
|
-
datasette_secrets/templates/secrets_update.html,sha256=SQs_TmrCw-eanePGiEnRiqH6OKy_G5iJJcEfg_z6RFg,1463
|
|
5
|
-
datasette_secrets-0.1a0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
6
|
-
datasette_secrets-0.1a0.dist-info/METADATA,sha256=s_AUhTHJOv70M7vKmu-FzEy37FA5DwNpFW76Y7vs0rA,6179
|
|
7
|
-
datasette_secrets-0.1a0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
8
|
-
datasette_secrets-0.1a0.dist-info/entry_points.txt,sha256=2083uWbPpGntxRulh8_hVaelQO-xdtjedG6rGzwPUH0,40
|
|
9
|
-
datasette_secrets-0.1a0.dist-info/top_level.txt,sha256=ZBJKQk-DdDU9Vnwu4x79X9aaEulwGJMoLx62IZJPDaQ,18
|
|
10
|
-
datasette_secrets-0.1a0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|