ProtoRune.Security (proto_rune v0.3.0)

Copy Markdown

Safe defaults for persisting AT Protocol session tokens.

Bot and app authors often need to keep a session across restarts, but an access/refresh JWT pair written to disk in plaintext is a liability. This module combines the two halves of a safer default:

The encryption key must be kept outside the token storage itself, typically in an environment variable or a secret manager:

# once, to provision the key:
key = ProtoRune.Security.generate_key()
System.put_env("PROTO_RUNE_TOKEN_KEY", ProtoRune.Security.encode_key(key))

# on boot:
{:ok, key} = ProtoRune.Security.decode_key(System.fetch_env!("PROTO_RUNE_TOKEN_KEY"))

Then persist and restore sessions with:

:ok = ProtoRune.Security.save_session(session, key)
{:ok, session} = ProtoRune.Security.load_session("did:plc:alice", key)
:ok = ProtoRune.Security.delete_session("did:plc:alice")

Sessions are keyed by their DID. To use another storage backend, pass a {module, opts} tuple implementing ProtoRune.Security.TokenStore as the last argument.

Summary

Functions

Decodes a Base64-encoded key. See Crypto.decode_key/1.

Deletes the session stored under did. Deleting a missing session returns :ok.

Encodes a key as Base64 for storage in env vars or config. See Crypto.encode_key/1.

Generates a random 32-byte encryption key. See Crypto.generate_key/0.

Loads and decrypts the session stored under did.

Encrypts session and stores it under its DID in store.

Functions

decode_key(encoded)

@spec decode_key(String.t()) ::
  {:ok, ProtoRune.Security.Crypto.key()}
  | {:error, :invalid_key_size | :invalid_key_encoding}

Decodes a Base64-encoded key. See Crypto.decode_key/1.

delete_session(did, store \\ {ProtoRune.Security.TokenStore.Dets, []})

@spec delete_session(
  ProtoRune.Security.TokenStore.id(),
  ProtoRune.Security.TokenStore.backend()
) ::
  :ok | {:error, term()}

Deletes the session stored under did. Deleting a missing session returns :ok.

encode_key(key)

@spec encode_key(ProtoRune.Security.Crypto.key()) :: String.t()

Encodes a key as Base64 for storage in env vars or config. See Crypto.encode_key/1.

generate_key()

@spec generate_key() :: ProtoRune.Security.Crypto.key()

Generates a random 32-byte encryption key. See Crypto.generate_key/0.

load_session(did, key, store \\ {ProtoRune.Security.TokenStore.Dets, []})

Loads and decrypts the session stored under did.

Returns {:error, :not_found} when no session is stored for did, {:error, :decrypt_failed} when the key is wrong or the stored blob was tampered with, and {:error, :invalid_session} when the decrypted payload is not a ProtoRune.Atproto.Session.

Examples

{:ok, session} = ProtoRune.Security.load_session("did:plc:alice", key)

save_session(session, key, store \\ {ProtoRune.Security.TokenStore.Dets, []})

Encrypts session and stores it under its DID in store.

Returns :ok on success. store defaults to {ProtoRune.Security.TokenStore.Dets, []}.

Examples

:ok = ProtoRune.Security.save_session(session, key)
:ok = ProtoRune.Security.save_session(session, key, {MyApp.TokenStore, []})