{"html_url": "https://github.com/simonw/datasette/issues/699#issuecomment-636494374", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/699", "id": 636494374, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjQ5NDM3NA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-31T16:29:48Z", "updated_at": "2020-05-31T16:29:48Z", "author_association": "OWNER", "body": "If Datasette were to support authentication out-of-the-box, without plugins (which makes more sense with writable canned queries, #698) what would that look like?\r\n\r\nSome options:\r\n\r\n- Jupyter notebook style: output a magic URL on the console with a one-time token to authenticate the user as an \"admin\"\r\n- Really simple password authentication - via an environment variable perhaps?\r\n- SQL based authentication: I was going to do this as a plugin, but maybe it should be default? A way of configuring a SQL query which can be used to authenticate a user based on their username and password.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582526961, "label": "Authentication (and permissions) as a core concept"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/699#issuecomment-636495005", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/699", "id": 636495005, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjQ5NTAwNQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-31T16:35:10Z", "updated_at": "2020-05-31T16:35:26Z", "author_association": "OWNER", "body": "I think I want to keep full username/password authentication against a database table as a plugin. I'll experiment with Jupyter-style URLs as a starting point.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582526961, "label": "Authentication (and permissions) as a core concept"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/699#issuecomment-636495124", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/699", "id": 636495124, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjQ5NTEyNA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-31T16:36:08Z", "updated_at": "2020-05-31T16:36:08Z", "author_association": "OWNER", "body": "HTTP Basic auth would be a good default option. No need to build a custom login UI for it.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582526961, "label": "Authentication (and permissions) as a core concept"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/699#issuecomment-636498770", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/699", "id": 636498770, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjQ5ODc3MA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-31T17:03:38Z", "updated_at": "2020-05-31T17:03:38Z", "author_association": "OWNER", "body": "I'm going to draw the line here: default Datasette supports authentication but only for a single user account (\"admin\"). Plugins can then add support for multiple user accounts, social auth, SSO etc.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582526961, "label": "Authentication (and permissions) as a core concept"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/699#issuecomment-636498913", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/699", "id": 636498913, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjQ5ODkxMw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-31T17:04:50Z", "updated_at": "2020-05-31T17:06:40Z", "author_association": "OWNER", "body": "This also means some writable canned queries can allow writes from unauthenticated users (for stuff like feedback forms), while others can require an authenticated user - all with core Datasette without any plugins needed.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582526961, "label": "Authentication (and permissions) as a core concept"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/699#issuecomment-636499075", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/699", "id": 636499075, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjQ5OTA3NQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-31T17:06:09Z", "updated_at": "2020-05-31T17:06:09Z", "author_association": "OWNER", "body": "I believe that this plugin hook design is flexible enough that role-based permissions could be built on top of it as a separate plugin.\r\n\r\nWould be good to check that with a proof of concept though.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582526961, "label": "Authentication (and permissions) as a core concept"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/699#issuecomment-636510303", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/699", "id": 636510303, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjUxMDMwMw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-31T18:35:17Z", "updated_at": "2020-05-31T18:35:17Z", "author_association": "OWNER", "body": "Keeping the structure of the actor dictionary completely undefined doesn't make sense if Datasette is going to ship with a default authentication mechanism for admin users.\r\n\r\nI'm going to define a small set of required keys for the actor dictionary, and enforce them in code.\r\n\r\nBut which keys? I feel I need a unique key representing the identity of the actor, plus a key that can be displayed in the \"You are logged in as X\" navigation. Maybe these are the same key?\r\n\r\nSo the single required key could be `id`. Problem is: is that a string or an integer? Some use-cases may call for an integer, which matches to how SQLite auto incrementing primary keys work. `admin` is a string.\r\n\r\nMaybe `id` is required, `name` is optional - but if `name` is present then the \"You are logged in as...\" uses that in preference to `id`. `id` has to be a string, and if you want to store integer IDs in your database you need to remember to convert them to a string in your `actor_from_request` implementation.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582526961, "label": "Authentication (and permissions) as a core concept"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/699#issuecomment-636510398", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/699", "id": 636510398, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjUxMDM5OA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-31T18:35:57Z", "updated_at": "2020-05-31T18:36:05Z", "author_association": "OWNER", "body": "Again I will use exploratory prototyping to inform a decision on the minimum subset design for the `actor` dictionary.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582526961, "label": "Authentication (and permissions) as a core concept"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/699#issuecomment-636510647", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/699", "id": 636510647, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjUxMDY0Nw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-31T18:37:39Z", "updated_at": "2020-05-31T18:37:39Z", "author_association": "OWNER", "body": "Maybe the default single account should be called something other than `admin`? The problem with `admin` is that it sounds like more of a role - in larger installations one can expect multiple admins. `root` may be better since there's clearly only one root account. Bit of a technical term though.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582526961, "label": "Authentication (and permissions) as a core concept"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/699#issuecomment-636510761", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/699", "id": 636510761, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjUxMDc2MQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-31T18:38:30Z", "updated_at": "2020-05-31T18:38:30Z", "author_association": "OWNER", "body": "I quite like `root` - it supports the idea that best practice is to NOT do things as the root account, but to use a plugin to set up separate accounts for different purposes.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582526961, "label": "Authentication (and permissions) as a core concept"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/784#issuecomment-636510838", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/784", "id": 636510838, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjUxMDgzOA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-31T18:39:08Z", "updated_at": "2020-05-31T18:39:08Z", "author_association": "OWNER", "body": "I'm calling this the `root` account now, for reasons discussed in these two comments: https://github.com/simonw/datasette/issues/699#issuecomment-636510647", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628003707, "label": "Ability to sign in to Datasette as a root account"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/784#issuecomment-636514974", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/784", "id": 636514974, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjUxNDk3NA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-31T19:12:48Z", "updated_at": "2020-05-31T19:12:48Z", "author_association": "OWNER", "body": "For the first version of this I'm not going to use passwords at all. I'll implement this:\r\n\r\n $ datasette fixtures.db --root\r\n\r\nThe `--root` option will cause Datasette to output a URL with a one-time-use token in it which, when clicked, will authenticate the user as the root account (by setting a signed cookie).\r\n\r\nSigned cookie means Datasette needs a secrets recipe. I'll open a new issue for that.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628003707, "label": "Ability to sign in to Datasette as a root account"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/785#issuecomment-636515599", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/785", "id": 636515599, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjUxNTU5OQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-31T19:17:43Z", "updated_at": "2020-05-31T19:17:43Z", "author_association": "OWNER", "body": "I previously solved this for the `datasette-auth-existing-cookies` plugin as described in this issue: https://github.com/simonw/datasette-auth-existing-cookies/issues/1\r\n\r\n> Concrete plan: you have to pass a secret to the class constructor. The Datasette plugin (the code in `__init__.py`) uses the following in order of preference (first things are most preferred):\r\n> \r\n> - A plugin configuration option called `cookie_secret` - which can be protected by this mechanism: https://datasette.readthedocs.io/en/stable/plugins.html#secret-configuration-values\r\n> - A JSON configuration file in the `user_state_dir` file, if it exists\r\n> - If that does not exist, a secret is generated and written to that JSON file\r\n> \r\n> I originally planned to have separate support for an environment variable, but the existence of the [secret configuration values](https://datasette.readthedocs.io/en/stable/plugins.html#secret-configuration-values) mechanism means this is already handled.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628025100, "label": "Datasette secret mechanism - initially for signed cookies"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/785#issuecomment-636515671", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/785", "id": 636515671, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjUxNTY3MQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-31T19:18:18Z", "updated_at": "2020-05-31T19:18:18Z", "author_association": "OWNER", "body": "That `user_state_dir` solution may have been more trouble than it was worth though - I seem to remember it causing issues on some hosting providers.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628025100, "label": "Datasette secret mechanism - initially for signed cookies"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/785#issuecomment-636515763", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/785", "id": 636515763, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjUxNTc2Mw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-31T19:19:03Z", "updated_at": "2020-05-31T19:19:13Z", "author_association": "OWNER", "body": "Maybe Datasette should have a `--secrets=path/to/secrets.json` command-line option for storing these?", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628025100, "label": "Datasette secret mechanism - initially for signed cookies"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/785#issuecomment-636537679", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/785", "id": 636537679, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjUzNzY3OQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-31T22:09:23Z", "updated_at": "2020-05-31T22:09:23Z", "author_association": "OWNER", "body": "I'm going to use https://github.com/pallets/itsdangerous for this.\r\n\r\nAnnoyingly they're very close to release v2.0 which adds support for key rotation... but it's not quite out of pre-release yet. I'll go with 1.1.0 for the moment and upgrade to 2.0 as soon as that is out.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628025100, "label": "Datasette secret mechanism - initially for signed cookies"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/785#issuecomment-636537921", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/785", "id": 636537921, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjUzNzkyMQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-31T22:11:29Z", "updated_at": "2020-05-31T22:11:29Z", "author_association": "OWNER", "body": "First version of cookie signing will use a secret that is either pulled from `DATASETTE_SECRET` environment variable or generated every time the server starts. I'll add a non-environment-variable based secret later.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628025100, "label": "Datasette secret mechanism - initially for signed cookies"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/785#issuecomment-636538298", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/785", "id": 636538298, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjUzODI5OA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-31T22:14:43Z", "updated_at": "2020-05-31T22:15:01Z", "author_association": "OWNER", "body": "... actually no I'll do it using a CLI option that can also be in an environment variable:\r\n\r\nhttps://click.palletsprojects.com/en/7.x/options/#values-from-environment-variables\r\n\r\n```python\r\n@click.command()\r\n@click.option('--secret', envvar='DATASETTE_SECRET')\r\ndef greet(secret):\r\n ...\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628025100, "label": "Datasette secret mechanism - initially for signed cookies"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/785#issuecomment-636539295", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/785", "id": 636539295, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjUzOTI5NQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-31T22:24:14Z", "updated_at": "2020-05-31T22:28:27Z", "author_association": "OWNER", "body": "I'll add two utility methods to the Datasette class:\r\n\r\n- `datasette.sign(value, \"namespace\")` - returns signed string\r\n- `datasette.unsign(signed, \"namespace\")` - returns value OR raises `BadSignature`", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628025100, "label": "Datasette secret mechanism - initially for signed cookies"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/785#issuecomment-636541630", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/785", "id": 636541630, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjU0MTYzMA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-31T22:45:07Z", "updated_at": "2020-05-31T22:45:07Z", "author_association": "OWNER", "body": "Documentation for those new methods: https://github.com/simonw/datasette/blob/e28207e76ec3b26b2c396370fd3fb325a60bfd49/docs/internals.rst#signvalue-namespacedefault", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628025100, "label": "Datasette secret mechanism - initially for signed cookies"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/785#issuecomment-636541827", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/785", "id": 636541827, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjU0MTgyNw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-31T22:46:34Z", "updated_at": "2020-06-01T00:17:35Z", "author_association": "OWNER", "body": "This is nearly ready to close. I'm going to add documentation for `--secret` and the `DATASETTE_SECRET` environment variable.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628025100, "label": "Datasette secret mechanism - initially for signed cookies"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/785#issuecomment-636541929", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/785", "id": 636541929, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjU0MTkyOQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-31T22:47:17Z", "updated_at": "2020-05-31T22:47:17Z", "author_association": "OWNER", "body": "I'll add a section about secrets to this page: https://datasette.readthedocs.io/en/latest/config.html", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628025100, "label": "Datasette secret mechanism - initially for signed cookies"}, "performed_via_github_app": null}