An Elixir toolkit for the AT Protocol.
hexdocs.pm/atex
elixir
bluesky
atproto
decentralization
1defmodule ExampleOAuthPlug do
2 require Logger
3 use Plug.Router
4 use Plug.ErrorHandler
5 alias Atex.OAuth
6 alias Atex.XRPC
7
8 plug :put_secret_key_base
9
10 plug Plug.Session,
11 store: :cookie,
12 key: "atex-oauth",
13 signing_salt: "signing-salt"
14
15 plug :match
16 plug :dispatch
17
18 forward "/oauth",
19 to: Atex.OAuth.Plug,
20 init_opts: [
21 callback: {__MODULE__, :oauth_callback, []},
22 logout_callback: {__MODULE__, :logout_callback, []}
23 ]
24
25 def oauth_callback(conn) do
26 IO.inspect(conn, label: "callback from oauth!")
27
28 conn
29 |> put_resp_header("Location", "/whoami")
30 |> resp(307, "")
31 |> send_resp()
32 end
33
34 def logout_callback(conn) do
35 conn
36 |> put_resp_header("Location", "/")
37 |> resp(302, "")
38 |> send_resp()
39 end
40
41 get "/whoami" do
42 conn = fetch_session(conn)
43
44 case XRPC.OAuthClient.from_conn(conn) do
45 {:ok, client} ->
46 did = XRPC.OAuthClient.did(client)
47 send_resp(conn, 200, "hello #{did}")
48
49 :error ->
50 send_resp(conn, 401, "Unauthorized")
51 end
52 end
53
54 get "/create-post" do
55 conn = fetch_session(conn)
56
57 with {:ok, client} <- XRPC.OAuthClient.from_conn(conn),
58 {:ok, response, client} <-
59 XRPC.post(client, "com.atproto.repo.createRecord",
60 json: %{
61 repo: client.did,
62 collection: "app.bsky.feed.post",
63 rkey: Atex.TID.now() |> to_string(),
64 record: %{
65 "$type": "app.bsky.feed.post",
66 text: "Hello world from atex!",
67 createdAt: DateTime.to_iso8601(DateTime.utc_now())
68 }
69 }
70 ) do
71 IO.inspect(response, label: "output")
72
73 send_resp(conn, 200, response.body.uri)
74 else
75 :error ->
76 send_resp(conn, 401, "Unauthorized")
77
78 {:error, :reauth} ->
79 send_resp(conn, 401, "session expired but still in your cookie")
80
81 err ->
82 IO.inspect(err, label: "xrpc failed")
83 send_resp(conn, 500, "xrpc failed")
84 end
85 end
86
87 match _ do
88 send_resp(conn, 404, "oops")
89 end
90
91 def put_secret_key_base(conn, _) do
92 put_in(
93 conn.secret_key_base,
94 # Don't use this in production
95 "5ef1078e1617463a3eb3feb9b152e76587a75a6809e0485a125b6bb7ae468f086680771f700d77ff61dfdc8d8ee8a5c7848024a41cf5ad4b6eb3115f74ce6e46"
96 )
97 end
98
99 # Error handler for OAuth exceptions
100 @impl Plug.ErrorHandler
101 def handle_errors(conn, %{kind: :error, reason: %Atex.OAuth.Error{} = error, stack: _stack}) do
102 status =
103 case error.reason do
104 reason
105 when reason in [
106 :missing_handle,
107 :invalid_handle,
108 :invalid_callback_request,
109 :issuer_mismatch
110 ] ->
111 400
112
113 _ ->
114 500
115 end
116
117 conn
118 |> put_resp_content_type("text/plain")
119 |> send_resp(status, error.message)
120 end
121
122 # Fallback for other errors
123 def handle_errors(conn, %{kind: _kind, reason: _reason, stack: _stack}) do
124 send_resp(conn, conn.status, "Something went wrong")
125 end
126end