From a46e18b8887bcd26a6654cc333c0821b5c25630e Mon Sep 17 00:00:00 2001 From: Johanna Larsson Date: Tue, 21 Jul 2026 07:20:44 +0100 Subject: [PATCH] Accept signing_key as a JSON string This way the implementor can store it as a string in env and pass it straight through without having to worry about what it is. --- lib/latch.ex | 4 ++-- lib/latch/client_assertion.ex | 2 +- lib/latch/config.ex | 8 ++++---- lib/latch/dpop.ex | 4 ++-- lib/latch/session.ex | 4 ++-- test/latch/client_test.exs | 2 +- test/latch/config_test.exs | 4 ++-- test/latch/flow_test.exs | 6 +++--- test/latch/nonce_cache_test.exs | 2 +- test/latch_test.exs | 10 +++++----- 10 files changed, 23 insertions(+), 23 deletions(-) diff --git a/lib/latch.ex b/lib/latch.ex index 04c6654..7d3e09c 100644 --- a/lib/latch.ex +++ b/lib/latch.ex @@ -25,7 +25,7 @@ defmodule Latch do client_id: "https://myapp.example/oauth-client-metadata.json", redirect_uri: "https://myapp.example/auth/callback", scope: "atproto", - signing_key: MyApp.Credentials.signing_key()} + signing_key: "key"} ] Optional keys: `:client_name`, `:client_uri` and `request_ttl`. @@ -161,7 +161,7 @@ defmodule Latch do ## Examples - iex> {:ok, _pid} = Latch.start_link(name: LatchAuthorizeExample, store: Latch.TestStore, client_id: "https://myapp.example/metadata.json", redirect_uri: "https://myapp.example/callback", scope: "atproto", signing_key: Latch.DPoP.generate_key()) + iex> {:ok, _pid} = Latch.start_link(name: LatchAuthorizeExample, store: Latch.TestStore, client_id: "https://myapp.example/metadata.json", redirect_uri: "https://myapp.example/callback", scope: "atproto", signing_key: Jason.encode!(Latch.DPoP.generate_key())) iex> Latch.authorize(LatchAuthorizeExample, "not a handle") {:error, %Latch.Error.HandleNotFound{handle: "not a handle", reason: :invalid_handle}} """ diff --git a/lib/latch/client_assertion.ex b/lib/latch/client_assertion.ex index 0689f57..5b2f00a 100644 --- a/lib/latch/client_assertion.ex +++ b/lib/latch/client_assertion.ex @@ -22,7 +22,7 @@ defmodule Latch.ClientAssertion do Signs a client assertion JWT. ## Arguments - - `jwk` - the client's private JOSE JWK + - `jwk` - the client's private JOSE JWK map - `client_id` - used as both `iss` and `sub` - `audience` - the authorization server's `issuer` URL diff --git a/lib/latch/config.ex b/lib/latch/config.ex index c447633..5427ee5 100644 --- a/lib/latch/config.ex +++ b/lib/latch/config.ex @@ -7,7 +7,7 @@ defmodule Latch.Config do * `:client_id` - the URL of the published client metadata document * `:redirect_uri` - the OAuth callback URL * `:scope` - the requsted scopes - * `:signing_key` - the ES256 `JOSE.JWK` private key for `private_key_jwt` + * `:signing_key` - the ES256 `JOSE.JWK` private key for `private_key_jwt` as string * `:client_name` - shown on the authorization consent screen * `:client_uri` - client home page * `:name` - the name of the Latch instance @@ -23,7 +23,7 @@ defmodule Latch.Config do client_id: String.t(), redirect_uri: String.t(), scope: String.t(), - signing_key: map(), + signing_key: String.t(), name: atom() | pid(), client_name: String.t() | nil, client_uri: String.t() | nil, @@ -35,7 +35,7 @@ defmodule Latch.Config do client_id: [type: :string, required: true], redirect_uri: [type: :string, required: true], scope: [type: :string, required: true], - signing_key: [type: :any, required: true], + signing_key: [type: :string, required: true], name: [type: {:or, [:atom, :pid]}, required: true], client_name: [type: :string, required: false], client_uri: [type: :string, required: false], @@ -52,7 +52,7 @@ defmodule Latch.Config do client_id: validated[:client_id], redirect_uri: validated[:redirect_uri], scope: validated[:scope], - signing_key: validated[:signing_key], + signing_key: Jason.decode!(validated[:signing_key]), name: validated[:name], client_name: validated[:client_name], client_uri: validated[:client_uri], diff --git a/lib/latch/dpop.ex b/lib/latch/dpop.ex index 60666c8..8c03625 100644 --- a/lib/latch/dpop.ex +++ b/lib/latch/dpop.ex @@ -10,7 +10,7 @@ defmodule Latch.DPoP do @jwt_type "dpop+jwt" @doc """ - Generates a new ES256 (P-256) key pair as a JOSE JWK. + Generates a new ES256 (P-256) key pair as a JOSE JWK map. """ @spec generate_key() :: map() def generate_key do @@ -22,7 +22,7 @@ defmodule Latch.DPoP do @doc """ Signs a DPoP proof JWT for an HTTP request. ## Arguments - - `jwk` — private JOSE JWK for this OAuth session + - `jwk` — private JOSE JWK map for this OAuth session - `method` — HTTP method (e.g. `"POST"`) - `url` — request URL; query string is stripped for `htu` per atproto ## Options diff --git a/lib/latch/session.ex b/lib/latch/session.ex index 79f36f2..9c88028 100644 --- a/lib/latch/session.ex +++ b/lib/latch/session.ex @@ -4,9 +4,9 @@ defmodule Latch.Session do make authenticated PDS requests and to refresh the access token. The `dpop_key` is the per-session private key every request is signed with. - The access and refresh tokens are bound to it. `issue` identifies the + The access and refresh tokens are bound to it. `issuer` identifies the authorization server (for refresh and re-discovery), `pds_endpoint` is where - authenticated XPRC calls go. + authenticated XRPC calls go. """ @derive {Inspect, except: [:access_token, :refresh_token, :dpop_key]} diff --git a/test/latch/client_test.exs b/test/latch/client_test.exs index 469c60d..902aa8b 100644 --- a/test/latch/client_test.exs +++ b/test/latch/client_test.exs @@ -137,7 +137,7 @@ defmodule Latch.ClientTest do client_id: @client_id, redirect_uri: @redirect_uri, scope: "atproto", - signing_key: nil, + signing_key: ~s({"kty":"EC"}), name: :name } end diff --git a/test/latch/config_test.exs b/test/latch/config_test.exs index f4ead4f..01c22db 100644 --- a/test/latch/config_test.exs +++ b/test/latch/config_test.exs @@ -7,7 +7,7 @@ defmodule Latch.ConfigTest do @client_id "client_id" @redirect_uri "redirect_uri" @scope "atproto something" - @signing_key "signing_key" + @signing_key ~s({"kty":"EC"}) @name :name @client_name "client_name" @client_uri "client_uri" @@ -20,7 +20,7 @@ defmodule Latch.ConfigTest do assert config.client_id == @client_id assert config.redirect_uri == @redirect_uri assert config.scope == @scope - assert config.signing_key == @signing_key + assert config.signing_key == Jason.decode!(@signing_key) assert config.name == @name assert config.client_name == @client_name assert config.client_uri == @client_uri diff --git a/test/latch/flow_test.exs b/test/latch/flow_test.exs index 503f55c..dcc2062 100644 --- a/test/latch/flow_test.exs +++ b/test/latch/flow_test.exs @@ -20,7 +20,7 @@ defmodule Latch.FlowTest do client_id: "https://client.example.com/oauth-client-metadata.json", redirect_uri: "https://client.example.com/oauth/callback", scope: "atproto", - signing_key: nil, + signing_key: ~s({"kty":"EC"}), name: :"flow_test_#{inspect(self())}" } @@ -89,7 +89,7 @@ defmodule Latch.FlowTest do client_id: "https://client.example.com/oauth-client-metadata.json", redirect_uri: "https://client.example.com/oauth/callback", scope: "atproto", - signing_key: nil, + signing_key: ~s({"kty":"EC"}), name: :"flow_test_#{inspect(self())}" } @@ -144,7 +144,7 @@ defmodule Latch.FlowTest do client_id: "https://client.example.com/oauth-client-metadata.json", redirect_uri: "https://client.example.com/oauth/callback", scope: "atproto", - signing_key: nil, + signing_key: ~s({"kty":"EC"}), name: :"flow_test_#{inspect(self())}" } diff --git a/test/latch/nonce_cache_test.exs b/test/latch/nonce_cache_test.exs index 34aae0d..749cd0b 100644 --- a/test/latch/nonce_cache_test.exs +++ b/test/latch/nonce_cache_test.exs @@ -80,7 +80,7 @@ defmodule Latch.NonceCacheTest do client_id: "client-id", redirect_uri: "redirect-uri", scope: "atproto", - signing_key: nil, + signing_key: ~s({"kty":"EC"}), name: name } end diff --git a/test/latch_test.exs b/test/latch_test.exs index 3d8670a..e2167ca 100644 --- a/test/latch_test.exs +++ b/test/latch_test.exs @@ -29,7 +29,7 @@ defmodule LatchTest do client_id: @client_id, redirect_uri: @redirect_uri, scope: "atproto", - signing_key: nil + signing_key: ~s({"kty":"EC"}) ) identity = %Identity{did: @did, handle: @handle, pds_endpoint: @pds} @@ -89,7 +89,7 @@ defmodule LatchTest do client_id: @client_id, redirect_uri: @redirect_uri, scope: "atproto", - signing_key: nil + signing_key: ~s({"kty":"EC"}) ) request = %Request{ @@ -150,7 +150,7 @@ defmodule LatchTest do client_id: @client_id, redirect_uri: @redirect_uri, scope: "atproto", - signing_key: nil + signing_key: ~s({"kty":"EC"}) ) expect(Client, :query, fn _config, @did, "app.bsky.actor.getProfile", actor: @did -> @@ -170,7 +170,7 @@ defmodule LatchTest do client_id: @client_id, redirect_uri: @redirect_uri, scope: "atproto", - signing_key: nil + signing_key: ~s({"kty":"EC"}) ) body = %{ @@ -196,7 +196,7 @@ defmodule LatchTest do client_id: @client_id, redirect_uri: @redirect_uri, scope: "atproto", - signing_key: nil + signing_key: ~s({"kty":"EC"}) ) expect(Client, :upload_blob, fn _config, @did, <<1, 2, 3>>, "image/png" -> -- 2.51.2