Packages
fnord
0.8.55
0.9.40
0.9.39
0.9.38
0.9.37
0.9.36
0.9.35
0.9.34
0.9.33
0.9.32
0.9.31
0.9.30
0.9.29
0.9.28
0.9.27
0.9.26
0.9.25
0.9.24
0.9.23
0.9.22
0.9.21
0.9.20
0.9.19
0.9.18
0.9.17
0.9.16
0.9.15
0.9.14
0.9.13
0.9.12
0.9.11
0.9.10
0.9.9
0.9.8
0.9.7
0.9.6
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.99
0.8.98
0.8.97
0.8.96
0.8.95
0.8.94
0.8.93
0.8.92
0.8.91
0.8.90
0.8.89
0.8.88
0.8.87
0.8.86
0.8.85
0.8.84
0.8.83
0.8.82
0.8.81
0.8.80
0.8.79
0.8.78
0.8.77
0.8.76
0.8.75
0.8.74
0.8.73
0.8.72
0.8.71
0.8.70
0.8.69
0.8.68
0.8.67
0.8.66
0.8.65
0.8.64
0.8.63
0.8.62
0.8.61
0.8.60
0.8.59
0.8.58
0.8.57
0.8.56
0.8.55
0.8.54
0.8.53
0.8.52
0.8.51
0.8.50
0.8.49
0.8.48
0.8.47
0.8.46
0.8.45
0.8.44
0.8.43
0.8.42
0.8.41
0.8.40
0.8.39
0.8.38
0.8.37
0.8.36
0.8.35
0.8.34
0.8.33
0.8.32
0.8.31
0.8.30
0.8.29
0.8.27
0.8.26
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.1
0.8.0
0.7.24
0.7.23
0.7.22
0.7.21
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.3
0.7.2
0.7.1
0.7.0
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.1
0.6.0
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.44
0.4.43
0.4.42
0.4.41
0.4.40
0.4.39
0.4.38
0.4.37
0.4.36
0.4.35
0.4.34
0.4.33
0.4.32
0.4.30
0.4.29
0.4.28
0.4.27
0.4.26
0.4.25
0.4.24
0.4.23
0.4.22
0.4.21
0.4.20
0.4.19
0.4.18
0.4.17
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.0
0.1.0
AI code archaeology
Current section
Files
Jump to
Current section
Files
lib/mcp/oauth2/credentials_store.ex
defmodule MCP.OAuth2.CredentialsStore do
@moduledoc """
Minimal credentials store for OAuth2 tokens.
- JSON file at `~/.fnord/credentials.json`
- Atomic writes (`.tmp` + rename), final perms set to 0600
- Per-server entries under the `"servers"` key
MVP: no encryption; keep tokens out of logs; caller is responsible for locking if needed.
"""
@default_rel ".fnord/credentials.json"
@spec path() :: String.t()
def path do
Path.join(Settings.get_user_home(), @default_rel)
end
@spec read(String.t()) :: {:ok, map()} | {:error, :not_found | term()}
def read(server) do
with {:ok, %{"servers" => servers}} <- read_all(),
entry when is_map(entry) <- Map.get(servers, server) do
{:ok, entry}
else
_ -> {:error, :not_found}
end
end
@spec write(String.t(), map()) :: :ok | {:error, term()}
def write(server, token_map) when is_map(token_map) do
base =
case read_all() do
{:ok, m} -> m
_ -> %{}
end
servers = base |> Map.get("servers", %{}) |> Map.put(server, token_map)
updated = %{"servers" => servers, "last_updated" => System.os_time(:second)}
atomic_write(updated)
end
@spec delete(String.t()) :: :ok | {:error, term()}
def delete(server) do
base =
case read_all() do
{:ok, m} -> m
_ -> %{}
end
servers = base |> Map.get("servers", %{}) |> Map.delete(server)
updated = %{"servers" => servers, "last_updated" => System.os_time(:second)}
atomic_write(updated)
end
@spec read_all() :: {:ok, map()} | {:error, term()}
def read_all do
p = path()
case File.read(p) do
{:ok, body} -> Jason.decode(body)
{:error, :enoent} -> {:ok, %{}}
{:error, e} -> {:error, e}
end
end
defp atomic_write(map) do
p = path()
dir = Path.dirname(p)
tmp = p <> ".tmp"
File.mkdir_p!(dir)
body = Jason.encode!(map, pretty: true)
with :ok <- File.write(tmp, body),
:ok <- File.rename(tmp, p),
:ok <- File.chmod(p, 0o600) do
:ok
else
err ->
_ = File.rm(tmp)
err
end
end
end