🚀 Feature: Bootstrap Admin API Key #195

Closed
opened 2025-10-07 23:57:12 +03:00 by OVERLORD · 4 comments
Owner

Originally created by @nicholascioli on GitHub.

Feature description

Externally managing Pocket ID (either through terraform, ansible, and the like) can be difficult without manually bootstrapping an admin user, a passkey for the admin user, and then an API key for accessing the management API. This can be overkill, especially in cases where Pocket ID will be entirely managed externally. It would be nice if there existed a configuration option for a static and always valid API token that an external system could use to manage all aspects of Pocket ID.

Pitch

External management of core infrastructure is typically handled through infrastructure-as-code resources using tooling like ansible or kubernetes operators. These external managers usually replace the need for a dedicated admin user and allow for tighter control over configuration and access control. Needing to bootstrap an admin user complicates this process and introduces more avenues for error. Other software that implements something similar to this are listed below:

I saw that there exists a way to generate one time passcodes for accounts, which would definitely help in the bootstrap process. The only issue is that the user needs to exist first, so it doesn't work with bootstrapping an admin API token since the admin user isn't created until later.

Originally created by @nicholascioli on GitHub. ### Feature description Externally managing Pocket ID (either through terraform, ansible, and the like) can be difficult without manually bootstrapping an admin user, a passkey for the admin user, and then an API key for accessing the management API. This can be overkill, especially in cases where Pocket ID will be entirely managed externally. It would be nice if there existed a configuration option for a static and always valid API token that an external system could use to manage all aspects of Pocket ID. ### Pitch External management of core infrastructure is typically handled through infrastructure-as-code resources using tooling like ansible or kubernetes operators. These external managers usually replace the need for a dedicated admin user and allow for tighter control over configuration and access control. Needing to bootstrap an admin user complicates this process and introduces more avenues for error. Other software that implements something similar to this are listed below: - Consul supports a [bootstrap token for its ACL system](https://developer.hashicorp.com/consul/docs/reference/agent/configuration-file/acl#acl_tokens_initial_management) - Garage allows [specifying the admin token](https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#admin_token) - Rauthy allows setting the [bootstrap admin user / pass](https://sebadob.github.io/rauthy/config/config.html) (there are no direct hyperlinks, so look for `BOOTSTRAP_ADMIN_EMAIL` and `BOOTSTRAP_ADMIN_PASSWORD_ARGON2ID`) I saw that there exists a way to generate one time passcodes for accounts, which would definitely help in the bootstrap process. The only issue is that the user needs to exist first, so it doesn't work with bootstrapping an admin API token since the admin user isn't created until later.
OVERLORD added the feature label 2025-10-07 23:57:12 +03:00
Author
Owner

@kmendell commented on GitHub:

@stonith404 I'll defer to you on this one if you feel its worth implementing.

@kmendell commented on GitHub: @stonith404 I'll defer to you on this one if you feel its worth implementing.
Author
Owner

@stonith404 commented on GitHub:

Thank you for your suggestion. While I understand your use case, I don't think it's worth implementing because it's quite easy to do with a simple script. Here is an example in JS:

const APP_URL = "http://localhost:1411";
const API_KEY_CONFIG = {
  name: "New Key",
  description: "",
  expiresAt: "2025-06-29T22:00:00.000Z",
};

const response = await fetch(APP_URL + "/api/one-time-access-token/setup", {
  method: "POST",
});

const accessToken = response.headers
  .getSetCookie()[0]
  .split(";")[0]
  .split("=")[1];

const apiKey = await fetch(APP_URL + "/api/api-keys", {
  headers: {
    Authorization: `Bearer ${accessToken}`,
  },
  body: JSON.stringify({
    name: "New Key",
    description: "",
    expiresAt: "2025-06-29T22:00:00.000Z",
  }),
  method: "POST",
  mode: "cors",
  credentials: "include",
}).then((res) => res.json());

console.log("API Key:", apiKey.token);

You basically have to call the /api/one-time-access-token/setup endpoint, which only works if there is only one user in the database and this user hasn't added a passkey yet. This endpoint returns a temporary access token which you then can use to create an actual API key.

I hope this fits your needs.

@stonith404 commented on GitHub: Thank you for your suggestion. While I understand your use case, I don't think it's worth implementing because it's quite easy to do with a simple script. Here is an example in JS: ```js const APP_URL = "http://localhost:1411"; const API_KEY_CONFIG = { name: "New Key", description: "", expiresAt: "2025-06-29T22:00:00.000Z", }; const response = await fetch(APP_URL + "/api/one-time-access-token/setup", { method: "POST", }); const accessToken = response.headers .getSetCookie()[0] .split(";")[0] .split("=")[1]; const apiKey = await fetch(APP_URL + "/api/api-keys", { headers: { Authorization: `Bearer ${accessToken}`, }, body: JSON.stringify({ name: "New Key", description: "", expiresAt: "2025-06-29T22:00:00.000Z", }), method: "POST", mode: "cors", credentials: "include", }).then((res) => res.json()); console.log("API Key:", apiKey.token); ``` You basically have to call the `/api/one-time-access-token/setup` endpoint, which only works if there is only one user in the database and this user hasn't added a passkey yet. This endpoint returns a temporary access token which you then can use to create an actual API key. I hope this fits your needs.
Author
Owner

@nicholascioli commented on GitHub:

For context, this came up since I'm trying to write a (simple) kubernetes operator for Pocket ID and got sad thinking about how to correctly bootstrap it without manual intervention.

@nicholascioli commented on GitHub: For context, this came up since I'm trying to write a (simple) kubernetes operator for Pocket ID and got sad thinking about how to correctly bootstrap it without manual intervention.
Author
Owner

@elonen commented on GitHub:

The /api/one-time-access-token/setup endpoint didn't actually work in v1.8.
Instead, I had to create a temporary user and then create the API key with something like:

    def create_bootstrap_api_key(self) -> str:

      # Create initial user
      setup_response = self._make_request('POST', '/api/signup/setup', json={
          "username": "setup-admin",
          "email": "setup-admin@localhost.local",  # Has to be formally valid, API error otherwise
          "firstName": "Setup",
          "lastName": ""
      })
      user_id = setup_response.json().get("id")

      # Extract session token from Set-Cookie header
      set_cookie_header = setup_response.headers.get('Set-Cookie', '')
      access_token = None
      for cookie in set_cookie_header.split(','):
          cookie = cookie.strip()
          if 'access_token' in cookie:
              access_token = cookie.split(';')[0].split('=')[1]
              break

      # Create the API key as setup-admin
      api_response = self._make_request('POST', '/api/api-keys', headers={
              "Content-Type": "application/json",
              "Authorization": f"Bearer {access_token}"
          }, json={
            "name": "Automated Setup Key",
            "description": "API key created during automated setup",
            "expiresAt": (datetime.now() + timedelta(hours=1)).isoformat() + "Z"
        })

      return api_response.json()["token"]

Also, it seems deleting the temporary user will invalidate the API key, too.

@elonen commented on GitHub: The `/api/one-time-access-token/setup` endpoint didn't actually work in v1.8. Instead, I had to create a temporary user and then create the API key with something like: ```python def create_bootstrap_api_key(self) -> str: # Create initial user setup_response = self._make_request('POST', '/api/signup/setup', json={ "username": "setup-admin", "email": "setup-admin@localhost.local", # Has to be formally valid, API error otherwise "firstName": "Setup", "lastName": "" }) user_id = setup_response.json().get("id") # Extract session token from Set-Cookie header set_cookie_header = setup_response.headers.get('Set-Cookie', '') access_token = None for cookie in set_cookie_header.split(','): cookie = cookie.strip() if 'access_token' in cookie: access_token = cookie.split(';')[0].split('=')[1] break # Create the API key as setup-admin api_response = self._make_request('POST', '/api/api-keys', headers={ "Content-Type": "application/json", "Authorization": f"Bearer {access_token}" }, json={ "name": "Automated Setup Key", "description": "API key created during automated setup", "expiresAt": (datetime.now() + timedelta(hours=1)).isoformat() + "Z" }) return api_response.json()["token"] ``` Also, it seems deleting the temporary user will invalidate the API key, too.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/pocket-id-pocket-id-1#195