···6060# Use Jason for JSON parsing in Phoenix
6161config :phoenix, :json_library, Jason
62626363+config :annot_at, AnnotAt.Atproto.OAuth.Config,
6464+ scope: "atproto",
6565+ signing_jwk:
6666+ ~s({"crv":"P-256","d":"h0MvqcXLcKqWZFnqUCAuc6Bmt6gGzj5F5sFOCaUD4Jw","kty":"EC","x":"u0_K5EPDBIlGVp_rUUKucDviS-Owhiv4jnpMCeI7ojY","y":"v_XGrdUIww1wsRA7TUqMWIAJXmi2V8mnoF24Vg5OkvQ"})
6767+6368# Import environment specific config. This must remain at the bottom
6469# of this file so it overrides the configuration defined above.
6570import_config "#{config_env()}.exs"
+1
config/dev.exs
···1717# watchers to your application. For example, we can use it
1818# to bundle .js and .css sources.
1919config :annot_at, AnnotAtWeb.Endpoint,
2020+ # url: [host: "tlaloc.haddock-carp.ts.net", scheme: "https", port: 443],
2021 # Binding to loopback ipv4 address prevents access from other machines.
2122 # Change to `ip: {0, 0, 0, 0}` to allow access from other machines.
2223 http: [ip: {127, 0, 0, 1}],
···3344 alias AnnotAt.Accounts
55 alias AnnotAt.Accounts.AtprotoSession
66+ alias AnnotAt.Accounts.OAuthLoginRequest
67 alias AnnotAt.Accounts.User
7889 @did "did:plc:ewvi7nxzyoun6zhxrhs64oiz"
···92939394 refute Accounts.get_atproto_session(user.id)
9495 assert Accounts.get_user!(user.id)
9696+ end
9797+9898+ test "create_login_request/1 then take_login_request/1 round-trips a login" do
9999+ {:ok, _} = Accounts.create_login_request(login_request_attrs())
100100+101101+ request = Accounts.take_login_request("state-123")
102102+ assert @did == request.did
103103+ assert "verifier-123" == request.pkce_verifier
104104+ end
105105+106106+ test "take_login_request/1 is single-use" do
107107+ {:ok, _} = Accounts.create_login_request(login_request_attrs())
108108+109109+ assert Accounts.take_login_request("state-123")
110110+ refute Accounts.take_login_request("state-123")
111111+ end
112112+113113+ test "take_login_request/1 returns nil for an unknown state" do
114114+ refute Accounts.take_login_request("nope")
115115+ end
116116+117117+ test "delete_expired_login_requests/1 removes only old requests" do
118118+ {:ok, old} = Accounts.create_login_request(login_request_attrs(%{state: "old"}))
119119+ {:ok, _} = Accounts.create_login_request(login_request_attrs(%{state: "fresh"}))
120120+121121+ Repo.update_all(
122122+ from(r in OAuthLoginRequest, where: r.id == ^old.id),
123123+ set: [inserted_at: ~U[2020-01-01 00:00:00Z]]
124124+ )
125125+126126+ assert 1 == Accounts.delete_expired_login_requests(3600)
127127+ refute Accounts.take_login_request("old")
128128+ assert Accounts.take_login_request("fresh")
129129+ end
130130+131131+ defp login_request_attrs(overrides \\ %{}) do
132132+ Map.merge(
133133+ %{
134134+ state: "state-123",
135135+ did: @did,
136136+ handle: "alice.test",
137137+ pds_host: "https://pds.example.com",
138138+ auth_server_issuer: "https://bsky.social",
139139+ pkce_verifier: "verifier-123",
140140+ dpop_private_jwk: "{}"
141141+ },
142142+ overrides
143143+ )
95144 end
96145end
+23
test/annot_at/atproto/oauth/config_test.exs
···11+defmodule AnnotAt.Atproto.OAuth.ConfigTest do
22+ use ExUnit.Case, async: true
33+44+ alias AnnotAt.Atproto.OAuth.Config
55+66+ test "derives client_id and redirect_uri from the base URL" do
77+ assert "http://localhost:4002/oauth-client-metadata.json" == Config.client_id()
88+ assert "http://localhost:4002/auth/callback" == Config.redirect_uri()
99+ end
1010+1111+ test "scope/0 returns the configured scope" do
1212+ assert "atproto" == Config.scope()
1313+ end
1414+1515+ test "signing_key/0 parses the configured JWK" do
1616+ jwk = Config.signing_key()
1717+ {_, map} = JOSE.JWK.to_map(jwk)
1818+1919+ assert "EC" == map["kty"]
2020+ assert "P-256" == map["crv"]
2121+ assert map["d"]
2222+ end
2323+end
···3344 test "GET /", %{conn: conn} do
55 conn = get(conn, ~p"/")
66- assert html_response(conn, 200) =~ "Peace of mind from prototype to production"
66+ assert html_response(conn, 200) =~ "annot.at"
77 end
88end