harn-stdlib 0.8.34

Embedded Harn standard library source catalog
Documentation
/**
 * std/oauth/storage — token storage backends for the OAuth client.
 *
 * Each backend factory returns a `Storage` record whose `get / set /
 * delete` closures speak the same protocol regardless of backend:
 *
 *     storage.get(key) -> TokenSet | nil
 *     storage.set(key, token_set, ttl_seconds = nil) -> nil
 *     storage.delete(key) -> nil
 *
 * The OAuth client and end users call those three closures; this
 * module is responsible for dispatching them to the right backend.
 *
 * Backends:
 *   - memory()                              ephemeral, per-session
 *   - file(path, encryption_key)            local disk, AES-256-GCM
 *   - harn_cloud_session()                  cloud-managed, per-session
 *   - harn_cloud_org()                      cloud-managed, org-scoped
 *   - custom({get, set, delete})            user-supplied (vault, KMS)
 *
 * `storage()` returns the namespace dict matching the `OAuth.Storage.*`
 * shape from the OAuth epic: `memory` / `harn_cloud_*` as ready
 * handles, `file` / `custom` as factory closures.
 */
type TokenSet = {
  access_token: string,
  refresh_token?: string,
  expires_at_unix?: int,
  token_type?: string,
  scopes?: list<string>,
  metadata?: dict,
}

/**
 * Storage is the handle dict returned by each backend factory. It
 * carries `kind` plus three closures (`get`, `set`, `delete`) and any
 * backend-specific fields. Callers should treat the rest as opaque.
 */
type Storage = dict

fn __wrap_host_handle(inner) {
  return inner
    + {
    _namespace: "oauth_storage",
    get: { key -> __oauth_storage_get(inner, key) },
    set: { key, token_set, ttl_seconds = nil -> __oauth_storage_set(inner, key, token_set, ttl_seconds) },
    delete: { key -> __oauth_storage_delete(inner, key) },
  }
}

/**
 * memory returns an ephemeral storage handle backed by an in-process
 * BTreeMap. Tokens stored here are lost when the VM exits.
 *
 * @effects: []
 * @allocation: heap
 * @errors: []
 * @api_stability: experimental
 * @example: memory()
 */
pub fn memory() -> Storage {
  return __wrap_host_handle(__oauth_storage_memory_handle())
}

/**
 * file returns a storage handle that persists tokens to `path`,
 * encrypted with AES-256-GCM. The `encryption_key` may be any
 * high-entropy bytes/string — the runtime derives a 32-byte key via
 * HKDF-SHA256. Pass a value from a key-management system or
 * `crypto.random_bytes(32)` rather than a user passphrase.
 *
 * @effects: [file_io]
 * @allocation: heap
 * @errors: [invalid_argument]
 * @api_stability: experimental
 * @example: file("/var/lib/harn/tokens.bin", env("HARN_OAUTH_KEY"))
 */
pub fn file(path, encryption_key) -> Storage {
  if type_of(path) != "string" || path == "" {
    throw "std/oauth/storage: file path must be a non-empty string"
  }
  return __wrap_host_handle(__oauth_storage_file_handle(path, encryption_key))
}

/**
 * harn_cloud_session returns a handle routing through the cloud
 * `oauth_storage.cloud_*` host capability with `scope = "session"`.
 * The embedder (e.g. harn-cloud) is responsible for tenant-scoped
 * storage and per-session isolation.
 *
 * @effects: [host_call]
 * @allocation: heap
 * @errors: []
 * @api_stability: experimental
 * @example: harn_cloud_session()
 */
pub fn harn_cloud_session() -> Storage {
  return __wrap_host_handle(__oauth_storage_cloud_handle("session"))
}

/**
 * harn_cloud_org returns a handle routing through the cloud
 * `oauth_storage.cloud_*` host capability with `scope = "org"`. The
 * embedder (e.g. harn-cloud) is responsible for org-scoped storage so
 * multiple agents in the same org share the same authenticated
 * client.
 *
 * @effects: [host_call]
 * @allocation: heap
 * @errors: []
 * @api_stability: experimental
 * @example: harn_cloud_org()
 */
pub fn harn_cloud_org() -> Storage {
  return __wrap_host_handle(__oauth_storage_cloud_handle("org"))
}

/**
 * custom returns a handle that dispatches `get / set / delete` to
 * caller-supplied closures. Use this to integrate vaults, KMS, or
 * platform-native secret stores. `handlers` is a dict containing:
 *   - get:    fn(key) -> TokenSet|nil
 *   - set:    fn(key, token_set, ttl_seconds|nil) -> nil
 *   - delete: fn(key) -> nil
 * The returned handle carries an optional `id` field (defaults to
 * `"custom"`) so multiple custom backends are distinguishable in
 * diagnostics.
 *
 * @effects: []
 * @allocation: heap
 * @errors: [invalid_argument]
 * @api_stability: experimental
 * @example: custom({get: my_get, set: my_set, delete: my_delete})
 */
pub fn custom(handlers) -> Storage {
  if type_of(handlers) != "dict" {
    throw "std/oauth/storage: custom handlers must be a dict"
  }
  let get_fn = handlers?.get
  let set_fn = handlers?.set
  let delete_fn = handlers?.delete
  if type_of(get_fn) != "closure" && type_of(get_fn) != "builtin" {
    throw "std/oauth/storage: custom.get must be a function"
  }
  if type_of(set_fn) != "closure" && type_of(set_fn) != "builtin" {
    throw "std/oauth/storage: custom.set must be a function"
  }
  if type_of(delete_fn) != "closure" && type_of(delete_fn) != "builtin" {
    throw "std/oauth/storage: custom.delete must be a function"
  }
  let id = trim(to_string(handlers?.id ?? "custom"))
  return {_namespace: "oauth_storage", kind: "custom", id: id, get: get_fn, set: set_fn, delete: delete_fn}
}

/**
 * storage returns the `OAuth.Storage.*` namespace dict matching the
 * epic API surface. `memory` / `harn_cloud_session` /
 * `harn_cloud_org` fields hold ready-to-use handles; `file` and
 * `custom` are factory closures.
 *
 * @effects: [host_call]
 * @allocation: heap
 * @errors: []
 * @api_stability: experimental
 * @example: storage().file("/var/lib/harn/tokens.bin", key)
 */
pub fn storage() {
  return {
    memory: memory(),
    file: { path, encryption_key -> file(path, encryption_key) },
    harn_cloud_session: harn_cloud_session(),
    harn_cloud_org: harn_cloud_org(),
    custom: { handlers -> custom(handlers) },
  }
}