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", to: Atex.OAuth.Plug, init_opts: [callback: {__MODULE__, :oauth_callback, []}]
19
20 def oauth_callback(conn) do
21 IO.inspect(conn, label: "callback from oauth!")
22
23 conn
24 |> put_resp_header("Location", "/whoami")
25 |> resp(307, "")
26 |> send_resp()
27 end
28
29 get "/whoami" do
30 conn = fetch_session(conn)
31
32 case XRPC.OAuthClient.from_conn(conn) do
33 {:ok, client} ->
34 send_resp(conn, 200, "hello #{client.did}")
35
36 :error ->
37 send_resp(conn, 401, "Unauthorized")
38 end
39 end
40
41 get "/create-post" do
42 conn = fetch_session(conn)
43
44 with {:ok, client} <- XRPC.OAuthClient.from_conn(conn),
45 {:ok, response, client} <-
46 XRPC.post(client, %Com.Atproto.Repo.CreateRecord{
47 input: %Com.Atproto.Repo.CreateRecord.Input{
48 repo: client.did,
49 collection: "app.bsky.feed.post",
50 rkey: Atex.TID.now() |> to_string(),
51 record: %{
52 "$type": "app.bsky.feed.post",
53 text: "Hello world from atex!",
54 createdAt: DateTime.to_iso8601(DateTime.utc_now())
55 }
56 }
57 }) do
58 IO.inspect(response, label: "output")
59
60 send_resp(conn, 200, response.body.uri)
61 else
62 :error ->
63 send_resp(conn, 401, "Unauthorized")
64
65 {:error, :reauth} ->
66 send_resp(conn, 401, "session expired but still in your cookie")
67
68 err ->
69 IO.inspect(err, label: "xrpc failed")
70 send_resp(conn, 500, "xrpc failed")
71 end
72 end
73
74 match _ do
75 send_resp(conn, 404, "oops")
76 end
77
78 def put_secret_key_base(conn, _) do
79 put_in(
80 conn.secret_key_base,
81 # Don't use this in production
82 "5ef1078e1617463a3eb3feb9b152e76587a75a6809e0485a125b6bb7ae468f086680771f700d77ff61dfdc8d8ee8a5c7848024a41cf5ad4b6eb3115f74ce6e46"
83 )
84 end
85
86 # Error handler for OAuth exceptions
87 @impl Plug.ErrorHandler
88 def handle_errors(conn, %{kind: :error, reason: %Atex.OAuth.Error{} = error, stack: _stack}) do
89 status =
90 case error.reason do
91 reason
92 when reason in [
93 :missing_handle,
94 :invalid_handle,
95 :invalid_callback_request,
96 :issuer_mismatch
97 ] ->
98 400
99
100 _ ->
101 500
102 end
103
104 conn
105 |> put_resp_content_type("text/plain")
106 |> send_resp(status, error.message)
107 end
108
109 # Fallback for other errors
110 def handle_errors(conn, %{kind: _kind, reason: _reason, stack: _stack}) do
111 send_resp(conn, conn.status, "Something went wrong")
112 end
113end