Beamlet.Tokens (Beamlet v0.1.1)

Copy Markdown View Source

Creates, updates, lists and deletes tokens from code.

These are what beamlet tokens.* runs. Creating a token is the only time its secret is visible:

{:ok, token} = Beamlet.Tokens.create(name: "laptop", policy: "explorer")
token.secret
#=> "wJalrXUtnFEMI_K7MDENG_bPxRfiCYEXAMPLEKEY_q0"

A client sends the secret as a bearer token.

update/2 renames a cli token or changes its policy. It refuses oauth tokens. delete/1 works on either kind, and the token's next request fails.

The authenticate and rotate functions serve the MCP endpoint and the OAuth token endpoint. Most callers will not need them.

Summary

Functions

Turns a presented secret into its token.

Turns a presented refresh secret into its oauth token, for the token endpoint.

Creates a token.

Deletes a token.

Finds a token by id.

Every token on the beamlet, oldest first.

Rotates an oauth token in place, with new secrets and expiries.

Updates a cli token's name or policy.

Functions

authenticate(secret)

@spec authenticate(term()) ::
  {:ok, Beamlet.Token.t()} | {:error, :unknown_token | :expired_token}

Turns a presented secret into its token.

Anything that is not the secret of a stored token, including a deleted token's secret or a value that never was one, is {:error, :unknown_token}. The secret of an oauth token past its expiry is {:error, :expired_token}.

authenticate_refresh(secret)

@spec authenticate_refresh(term()) ::
  {:ok, Beamlet.Token.t()} | {:error, :unknown_token | :expired_token}

Turns a presented refresh secret into its oauth token, for the token endpoint.

Anything that is not the refresh secret of a stored token, a cli token's secret among them, is {:error, :unknown_token}. A refresh secret past refresh_expires_at is {:error, :expired_token}.

create(attrs)

@spec create(map() | keyword()) ::
  {:ok, Beamlet.Token.t()} | {:error, Ecto.Changeset.t()}

Creates a token.

kind is :cli unless given. A cli token takes name and an optional policy. An oauth token takes client, policy, expires_at and refresh_expires_at. The policy must be one the beamlet declares.

The returned token carries its secret, and an oauth token its refresh_secret too. Nothing else ever will.

delete(token)

@spec delete(Beamlet.Token.t()) ::
  {:ok, Beamlet.Token.t()} | {:error, Ecto.Changeset.t()}

Deletes a token.

Requests presenting its secret fail from then on.

find(id)

@spec find(pos_integer()) :: {:ok, Beamlet.Token.t()} | {:error, :not_found}

Finds a token by id.

list()

@spec list() :: [Beamlet.Token.t()]

Every token on the beamlet, oldest first.

rotate(token, attrs)

@spec rotate(Beamlet.Token.t(), map() | keyword()) ::
  {:ok, Beamlet.Token.t()}
  | {:error, Ecto.Changeset.t() | :cli_token | :unknown_token}

Rotates an oauth token in place, with new secrets and expiries.

attrs carries expires_at and refresh_expires_at. The row and its id stay, so provenance keeps pointing at the same token. The old secrets stop working at once, and the returned token carries the new secret and refresh_secret. A cli token answers {:error, :cli_token}.

A refresh secret redeems once. The rotation only lands while the row still holds the refresh secret token was loaded with, so of two rotations from one secret the second answers {:error, :unknown_token}, as a spent secret does.

update(token, attrs)

@spec update(Beamlet.Token.t(), map() | keyword()) ::
  {:ok, Beamlet.Token.t()} | {:error, Ecto.Changeset.t() | :oauth_token}

Updates a cli token's name or policy.

The secret cannot change, so create a new token instead. An oauth token cannot be updated and answers {:error, :oauth_token}.