Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alternative, better mechanism for authenticating user than --root #37

Closed
simonw opened this issue Sep 2, 2021 · 7 comments
Closed

Alternative, better mechanism for authenticating user than --root #37

simonw opened this issue Sep 2, 2021 · 7 comments
Labels
custom-datasette Need to customize Datasette itself (custom templates, CSS, JS etc)

Comments

@simonw
Copy link
Owner

simonw commented Sep 2, 2021

This is partly an alternative to #36 where I wanted to share cookies between multiple BrowserWindow instances.

But also... I don't like using --root because it turns on a bunch of confusing tools that are usually intended for debugging. Instead I think datasette-app-support should provide a mechanism for signing the user in as "id": "datasette-app" - then I can use custom permissions to enable things like datasette-upload-csvs without turning on debugging menus for the root user.

@simonw simonw added the custom-datasette Need to customize Datasette itself (custom templates, CSS, JS etc) label Sep 2, 2021
@simonw
Copy link
Owner Author

simonw commented Sep 2, 2021

The plugin could set its own random secret (equivalent to how --root works) and then support a /-/auth-app-user?token=xxx&redirect_to=/-/plugins URL which the shell could then use when opening new windows - e.g. for the "list installed plugins" menu option.

@simonw simonw added this to the First public installer release milestone Sep 2, 2021
@simonw
Copy link
Owner Author

simonw commented Sep 3, 2021

For the moment I'm going to lock this down so that it's only visible to the current, authenticated use from localhost - but in the future it might be good to have an option for sharing your data by running the server attached to 0.0.0.0 so you can share links with other people on your network.

@simonw
Copy link
Owner Author

simonw commented Sep 4, 2021

#52 (option to expose server to other people on your network) makes this even more relevant.

I'm going to have a user account called "admin" (because "root" already does some things in the Datasette world, and as language it's less user-friendly than "admin") - the plugin will set a ds_actor cookie for it when a new browser window is opened.

@simonw
Copy link
Owner Author

simonw commented Sep 4, 2021

Could even do this by calling a special API endpoint, /-/generate-admin-actor-cookie, which is authenticated using the mechanism introduced by #53 - and then setting the return value as that in a cookie when first instantiating the BrowserWindow.

@simonw
Copy link
Owner Author

simonw commented Sep 4, 2021

Setting cookies from Electron code looks too complicated - I'll go with the simpler mechanism where every new BrowserWindow hits /-/auth-app-user?redirect=/-/plugins which accepts the Authorization: Bearer xxx token from #53 and redirects with the newly set cookie.

simonw added a commit to simonw/datasette-app-support that referenced this issue Sep 4, 2021
@simonw
Copy link
Owner Author

simonw commented Sep 4, 2021

I'm going to need to refactor all of the places that create a BrowserWindow and load a URL in it to a new method on the DatasetteServer class which uses the new /-/auth-app-user endpoint - otherwise users could accidentally create windows that don't have the signed cookie.

One exception: this code, because it displays the loading.html screen before the server has started (so it can continue to display while pip install datasette etc is happening on first run).

datasette-app/main.js

Lines 185 to 208 in 9a8bf4e

mainWindow = new BrowserWindow({
width: 800,
height: 600,
show: false,
});
mainWindow.loadFile("loading.html");
mainWindow.once("ready-to-show", () => {
mainWindow.show();
});
postConfigure(mainWindow);
portfinder.getPort(
{
port: 8001,
},
async (err, freePort) => {
if (err) {
console.error("Failed to obtain a port", err);
app.quit();
}
// Start Python Datasette process
datasette = new DatasetteServer(app, freePort);
const url = await datasette.startOrRestart();
mainWindow.loadURL(url);

@simonw
Copy link
Owner Author

simonw commented Sep 4, 2021

Here's how to pass the authorization: bearer xx header in a POST request to that API endpoint:

      newWindow.loadURL(`http://localhost:${this.port}/-/auth-app-user`, {
        extraHeaders: `authorization: Bearer ${this.apiToken}`,
        postData: [
          {
            type: "rawData",
            bytes: Buffer.from(JSON.stringify({ redirect: path })),
          },
        ],
      });

This wasn't obvious: the extraHeaders takes a string (headers can be separated by newlines) rather than a dictionary, and the postData thing was a very odd shape too.

simonw added a commit that referenced this issue Sep 4, 2021
simonw added a commit that referenced this issue Sep 7, 2021
Fixed bug where closing all windows and then clicking the icon
wauld throw an error.

Refs #37
@simonw simonw closed this as completed Sep 7, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
custom-datasette Need to customize Datasette itself (custom templates, CSS, JS etc)
Projects
None yet
Development

No branches or pull requests

1 participant