authproto 0.3.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +1 -0
  3. data/.rubocop.yml +31 -0
  4. data/.tm_properties +1 -0
  5. data/.yardopts +4 -0
  6. data/CHANGELOG.md +52 -0
  7. data/MIT-LICENSE.txt +21 -0
  8. data/README.md +304 -0
  9. data/Rakefile +8 -0
  10. data/SECURITY.md +16 -0
  11. data/examples/confidential_client/.gitignore +5 -0
  12. data/examples/confidential_client/Gemfile +14 -0
  13. data/examples/confidential_client/Gemfile.lock +88 -0
  14. data/examples/confidential_client/README.md +226 -0
  15. data/examples/confidential_client/app.rb +290 -0
  16. data/examples/confidential_client/config/client-metadata.example.json +25 -0
  17. data/examples/confidential_client/config.ru +4 -0
  18. data/examples/confidential_client/public/styles.css +70 -0
  19. data/examples/confidential_client/screenshots/screenshot-1-sign-in.png +0 -0
  20. data/examples/confidential_client/screenshots/screenshot-2-success.png +0 -0
  21. data/examples/confidential_client/scripts/generate_keys.rb +15 -0
  22. data/examples/confidential_client/views/authorized.erb +46 -0
  23. data/examples/confidential_client/views/index.erb +44 -0
  24. data/examples/confidential_client/views/layout.erb +11 -0
  25. data/examples/minisky_console/.gitignore +2 -0
  26. data/examples/minisky_console/Gemfile +12 -0
  27. data/examples/minisky_console/Gemfile.lock +131 -0
  28. data/examples/minisky_console/README.md +112 -0
  29. data/examples/minisky_console/app.rb +50 -0
  30. data/examples/minisky_console/bin/console +6 -0
  31. data/examples/minisky_console/config.ru +5 -0
  32. data/examples/minisky_console/localhost.rb +53 -0
  33. data/examples/minisky_console/production.rb +32 -0
  34. data/lib/authproto/client.rb +583 -0
  35. data/lib/authproto/client_metadata.rb +478 -0
  36. data/lib/authproto/configuration.rb +139 -0
  37. data/lib/authproto/dpop/client.rb +161 -0
  38. data/lib/authproto/dpop/key_manager.rb +223 -0
  39. data/lib/authproto/dpop/nonce_manager.rb +145 -0
  40. data/lib/authproto/dpop/proof_generator.rb +124 -0
  41. data/lib/authproto/dpop/stored_nonce.rb +37 -0
  42. data/lib/authproto/encryption.rb +250 -0
  43. data/lib/authproto/errors.rb +75 -0
  44. data/lib/authproto/http_client.rb +320 -0
  45. data/lib/authproto/insecure_http_client.rb +24 -0
  46. data/lib/authproto/localhost_server.rb +274 -0
  47. data/lib/authproto/minisky.rb +273 -0
  48. data/lib/authproto/par/client.rb +240 -0
  49. data/lib/authproto/par/client_assertion.rb +69 -0
  50. data/lib/authproto/par/request.rb +243 -0
  51. data/lib/authproto/par/response.rb +43 -0
  52. data/lib/authproto/par.rb +48 -0
  53. data/lib/authproto/pkce.rb +125 -0
  54. data/lib/authproto/serialization/base.rb +258 -0
  55. data/lib/authproto/serialization/dpop_key.rb +38 -0
  56. data/lib/authproto/serialization/session.rb +88 -0
  57. data/lib/authproto/serialization/stored_nonce.rb +46 -0
  58. data/lib/authproto/serialization/token_set.rb +54 -0
  59. data/lib/authproto/server_metadata/authorization_server.rb +267 -0
  60. data/lib/authproto/server_metadata/origin_url.rb +71 -0
  61. data/lib/authproto/server_metadata/resource_server.rb +98 -0
  62. data/lib/authproto/server_metadata.rb +28 -0
  63. data/lib/authproto/state/session.rb +168 -0
  64. data/lib/authproto/state/session_manager.rb +162 -0
  65. data/lib/authproto/state/token_set.rb +101 -0
  66. data/lib/authproto/state.rb +61 -0
  67. data/lib/authproto/storage/interface.rb +121 -0
  68. data/lib/authproto/storage/key_builder.rb +76 -0
  69. data/lib/authproto/storage/memory.rb +222 -0
  70. data/lib/authproto/storage/redis.rb +156 -0
  71. data/lib/authproto/storage/sqlite.rb +281 -0
  72. data/lib/authproto/token/refresh.rb +414 -0
  73. data/lib/authproto/version.rb +8 -0
  74. data/lib/authproto.rb +18 -0
  75. data/sig/authproto/client.rbs +43 -0
  76. data/sig/authproto/client_metadata.rbs +113 -0
  77. data/sig/authproto/dependencies.rbs +19 -0
  78. data/sig/authproto/dpop/client.rbs +23 -0
  79. data/sig/authproto/dpop/key_manager.rbs +26 -0
  80. data/sig/authproto/dpop/nonce_manager.rbs +23 -0
  81. data/sig/authproto/dpop/proof_generator.rbs +40 -0
  82. data/sig/authproto/dpop/stored_nonce.rbs +10 -0
  83. data/sig/authproto/encryption.rbs +42 -0
  84. data/sig/authproto/http_client.rbs +45 -0
  85. data/sig/authproto/insecure_http_client.rbs +8 -0
  86. data/sig/authproto/localhost_server.rbs +30 -0
  87. data/sig/authproto/minisky_oauth.rbs +22 -0
  88. data/sig/authproto/par/client.rbs +25 -0
  89. data/sig/authproto/par/client_assertion.rbs +10 -0
  90. data/sig/authproto/par/request.rbs +73 -0
  91. data/sig/authproto/par/response.rbs +17 -0
  92. data/sig/authproto/par.rbs +7 -0
  93. data/sig/authproto/pkce.rbs +24 -0
  94. data/sig/authproto/serialization/base.rbs +36 -0
  95. data/sig/authproto/serialization/dpop_key.rbs +14 -0
  96. data/sig/authproto/serialization/session.rbs +19 -0
  97. data/sig/authproto/serialization/stored_nonce.rbs +14 -0
  98. data/sig/authproto/serialization/token_set.rbs +14 -0
  99. data/sig/authproto/server_metadata/authorization_server.rbs +71 -0
  100. data/sig/authproto/server_metadata/origin_url.rbs +21 -0
  101. data/sig/authproto/server_metadata/resource_server.rbs +27 -0
  102. data/sig/authproto/state/session.rbs +28 -0
  103. data/sig/authproto/state/session_manager.rbs +15 -0
  104. data/sig/authproto/state/token_set.rbs +40 -0
  105. data/sig/authproto/state.rbs +6 -0
  106. data/sig/authproto/storage/interface.rbs +20 -0
  107. data/sig/authproto/storage/key_builder.rbs +16 -0
  108. data/sig/authproto/storage/memory.rbs +22 -0
  109. data/sig/authproto/storage/redis.rbs +19 -0
  110. data/sig/authproto/storage/redis_client.rbs +11 -0
  111. data/sig/authproto/storage/sqlite.rbs +26 -0
  112. data/sig/authproto/token/refresh.rbs +47 -0
  113. data/sig/authproto/version.rbs +3 -0
  114. data/sig/authproto.rbs +54 -0
  115. metadata +200 -0
@@ -0,0 +1,44 @@
1
+ <div class="flex flex-col items-center md:flex md:flex-row md:justify-stretch md:items-center min-h-screen min-w-screen bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
2
+ <div class="px-6 pt-4 md:max-w-lg md:grid md:content-center md:justify-items-end md:self-stretch md:w-1/2 md:max-w-fix md:p-4 md:text-right md:dark:border-r md:dark:border-gray-700 md:bg-gray-100 md:dark:bg-gray-800">
3
+ <h1 class="text-xl md:text-2xl lg:text-5xl md:mt-4 mb-4 font-semibold text-brand">Sign in</h1>
4
+ <p class="hidden md:block max-w-xs text-gray-500 dark:text-gray-500">Enter your AT Protocol handle</p>
5
+ </div>
6
+ <div class="w-full px-6 md:max-w-3xl md:px-12">
7
+ <form method="post" action="/auth" class="flex flex-col py-4 space-y-4">
8
+ <div class="space-y-4">
9
+ <% if session[:error] %>
10
+ <div class="mb-8 bg-red-50 border-red-700 text-red-700 p-4 rounded-md">
11
+ <%= session[:error] %>
12
+ <% session[:error] = nil %>
13
+ </div>
14
+ <% end %>
15
+ <% if session[:notice] %>
16
+ <div class="mb-8 bg-yellow-50 border-yellow-700 text-yellow-700 p-4 rounded-md">
17
+ <%= session[:notice] %>
18
+ <% session[:notice] = nil %>
19
+ </div>
20
+ <% end %>
21
+ <fieldset>
22
+ <p class="mb-1 text-gray-600 dark:text-gray-400 text-sm font-medium">Handle</p>
23
+ <div class="flex flex-col space-y-4">
24
+ <div class="pl-1 pr-2 min-h-12 flex items-center justify-stretch rounded-lg text-gray-700 dark:text-gray-100 bg-gray-100 has-[:focus]:bg-gray-200 dark:bg-gray-800 dark:has-[:focus]:bg-gray-700 outline-none border-solid border-2 border-transparent hover:border-gray-400 has-[:focus]:border-brand hover:has-[:focus]:border-brand dark:hover:border-gray-500">
25
+ <div class="self-start shrink-0 grow-0 w-8 h-12 flex items-center justify-center text-gray-500">
26
+ <div>
27
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-5">
28
+ <path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M12 4a8 8 0 1 0 4.21 14.804 1 1 0 0 1 1.054 1.7A9.958 9.958 0 0 1 12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10c0 1.104-.27 2.31-.949 3.243-.716.984-1.849 1.6-3.331 1.465a4.207 4.207 0 0 1-2.93-1.585c-.94 1.21-2.388 1.94-3.985 1.715-2.53-.356-4.04-2.91-3.682-5.458.358-2.547 2.514-4.586 5.044-4.23.905.127 1.68.536 2.286 1.126a1 1 0 0 1 1.964.368l-.515 3.545v.002a2.222 2.222 0 0 0 1.999 2.526c.75.068 1.212-.21 1.533-.65.358-.493.566-1.245.566-2.067a8 8 0 0 0-8-8Zm-.112 5.13c-1.195-.168-2.544.819-2.784 2.529-.24 1.71.784 3.03 1.98 3.198 1.195.168 2.543-.819 2.784-2.529.24-1.71-.784-3.03-1.98-3.198Z"></path>
29
+ </svg>
30
+ </div>
31
+ </div>
32
+ <div class="relative">
33
+ <input class="w-full bg-transparent bg-clip-padding text-base text-inherit outline-none dark:placeholder-gray-500" name="handle" type="text" placeholder="handle.bsky.social" aria-label="Handle" autocapitalize="none" autocorrect="off" autocomplete="username" spellcheck="false" dir="auto" enterkeyhint="next" required title="valid handle">
34
+ </div>
35
+ </div>
36
+ </div>
37
+ </fieldset>
38
+ </div>
39
+ <div class="pt-4">
40
+ <button role="Button" type="submit" aria-label="Next" class="py-2 px-6 rounded-lg truncate cursor-pointer touch-manipulation tracking-wide overflow-hidden bg-blue-400 text-white">Next</button>
41
+ </div>
42
+ </form>
43
+ </div>
44
+ </div>
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>AT Protocol OAuth Example</title>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <script src="https://cdn.tailwindcss.com"></script>
7
+ </head>
8
+ <body>
9
+ <%= yield %>
10
+ </body>
11
+ </html>
@@ -0,0 +1,2 @@
1
+ .env
2
+ db
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gem 'authproto', path: '../..'
6
+ gem 'dotenv'
7
+ gem 'irb'
8
+ gem 'minisky'
9
+ gem 'puma'
10
+ gem 'rackup'
11
+ gem 'sinatra'
12
+ gem 'sqlite3'
@@ -0,0 +1,131 @@
1
+ PATH
2
+ remote: ../..
3
+ specs:
4
+ authproto (0.2.5)
5
+ didkit (~> 0.5.0)
6
+ jose (~> 1.2)
7
+ jwt (~> 3.3)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ base58 (0.2.3)
13
+ base64 (0.3.0)
14
+ concurrent-ruby (1.3.8)
15
+ didkit (0.5.0)
16
+ base58 (~> 0.2)
17
+ dotenv (3.2.0)
18
+ erb (6.0.7)
19
+ immutable-ruby (0.2.0)
20
+ concurrent-ruby (~> 1.1)
21
+ sorted_set (~> 1.0)
22
+ io-console (0.9.4)
23
+ irb (1.18.0)
24
+ pp (>= 0.6.0)
25
+ prism (>= 1.3.0)
26
+ rdoc (>= 4.0.0)
27
+ reline (>= 0.4.2)
28
+ jose (1.2.0)
29
+ base64
30
+ immutable-ruby
31
+ jwt (3.3.0)
32
+ base64
33
+ logger (1.7.0)
34
+ minisky (0.5.1)
35
+ base64 (~> 0.1)
36
+ mustermann (3.1.1)
37
+ nio4r (2.7.5)
38
+ pp (0.6.4)
39
+ prettyprint
40
+ prettyprint (0.2.1)
41
+ prism (2.0.0)
42
+ prism (2.0.0-arm64-darwin)
43
+ puma (8.0.2)
44
+ nio4r (~> 2.0)
45
+ rack (3.2.7)
46
+ rack-protection (4.2.1)
47
+ base64 (>= 0.1.0)
48
+ logger (>= 1.6.0)
49
+ rack (>= 3.0.0, < 4)
50
+ rack-session (2.1.2)
51
+ base64 (>= 0.1.0)
52
+ rack (>= 3.0.0)
53
+ rackup (2.3.1)
54
+ rack (>= 3)
55
+ rbs (4.2.0)
56
+ logger
57
+ prism (>= 1.6.0)
58
+ tsort
59
+ rbtree (0.4.7)
60
+ rdoc (8.1.0)
61
+ erb
62
+ prism (>= 1.6.0)
63
+ rbs (>= 4.0.0)
64
+ tsort
65
+ reline (0.7.0)
66
+ io-console (~> 0.5)
67
+ sinatra (4.2.1)
68
+ logger (>= 1.6.0)
69
+ mustermann (~> 3.0)
70
+ rack (>= 3.0.0, < 4)
71
+ rack-protection (= 4.2.1)
72
+ rack-session (>= 2.0.0, < 3)
73
+ tilt (~> 2.0)
74
+ sorted_set (1.1.0)
75
+ rbtree
76
+ sqlite3 (2.10.2-arm64-darwin)
77
+ tilt (2.9.0)
78
+ tsort (0.2.0)
79
+
80
+ PLATFORMS
81
+ arm64-darwin-24
82
+
83
+ DEPENDENCIES
84
+ authproto!
85
+ dotenv
86
+ irb
87
+ minisky
88
+ puma
89
+ rackup
90
+ sinatra
91
+ sqlite3
92
+
93
+ CHECKSUMS
94
+ authproto (0.2.5)
95
+ base58 (0.2.3) sha256=fda5c5d3bda8b429ad0d1a5309c2d1e488ba46f0a2ab99a639890f26c61bf3be
96
+ base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
97
+ bundler (4.0.23) sha256=407ba8a1d928f412847f819aa5f8b6aef7c59fb51feb517112af8a165487287b
98
+ concurrent-ruby (1.3.8) sha256=b2f1be836e968ccc78ccfce277ea79c72a88633f22306782c16ff23fb415d1e1
99
+ didkit (0.5.0) sha256=8b24320fbf137cd4095b7fc7cc8a1dbee9b1aafa49fa80b9aac2902f4c282463
100
+ dotenv (3.2.0) sha256=e375b83121ea7ca4ce20f214740076129ab8514cd81378161f11c03853fe619d
101
+ erb (6.0.7) sha256=c5ca6dc25b0ef974a44dc8f59fe847577122483b1968a38dec305c60bf91ee92
102
+ immutable-ruby (0.2.0) sha256=1ab896e0d9c8c3fe21ddc3dd29be87511da2f6b0970c5a1c36838a9a4a4605e7
103
+ io-console (0.9.4) sha256=a403304f32928ee85e56d457b4612ca43f2703b561a3258844992bd24085fbb1
104
+ irb (1.18.0) sha256=de9454a0703a54704b9811a5ef31a60c86949fbf4013fcf244fabc7c775248e3
105
+ jose (1.2.0) sha256=c6e7061b71d2efc5aa70875c203f10595c67a0a48a58012527844c045ac2cbee
106
+ jwt (3.3.0) sha256=44cc34fbc341c148233cf66641522b4bce0dfb1ce55e8073f05c56f44f4ee546
107
+ logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
108
+ minisky (0.5.1) sha256=47a7d63a1ca16c786e3bd3d4ddb15c5d585165efd1dfbe2be9bf47335fe19d4f
109
+ mustermann (3.1.1) sha256=4c6170c7234d5499c345562ba7c7dfe73e1754286dcc1abb053064d66a127198
110
+ nio4r (2.7.5) sha256=6c90168e48fb5f8e768419c93abb94ba2b892a1d0602cb06eef16d8b7df1dca1
111
+ pp (0.6.4) sha256=dfcb0fce700c41456265922884f9fe195d7fbb0674a3578e6c0f69588e82b570
112
+ prettyprint (0.2.1) sha256=d555d57ea6d7cf001d4774b693546a6bd88bec1e778ddffb5c60b68ceec4b7d4
113
+ prism (2.0.0) sha256=fd551446f0c6ca71455ef8d1d40bd2ca1538044834fa22df8837e5095f3a5a39
114
+ prism (2.0.0-arm64-darwin) sha256=edebbfcc16f6a879f47dac9501b20a7cd95abe96397bb485106d6670541abeb1
115
+ puma (8.0.2) sha256=c8ed871dfbbe66448ea9ffd46692342d9804d4071522b52b5331b7b6e7b686fb
116
+ rack (3.2.7) sha256=93e13e1c24f93556671d85d2d79fa228c3485815c50d7e2f265b5330c6528fb7
117
+ rack-protection (4.2.1) sha256=cf6e2842df8c55f5e4d1a4be015e603e19e9bc3a7178bae58949ccbb58558bac
118
+ rack-session (2.1.2) sha256=595434f8c0c3473ae7d7ac56ecda6cc6dfd9d37c0b2b5255330aa1576967ffe8
119
+ rackup (2.3.1) sha256=6c79c26753778e90983761d677a48937ee3192b3ffef6bc963c0950f94688868
120
+ rbs (4.2.0) sha256=51f7b886dcc05bc09e10b901daa6a81829f6adc03101d6ca9ea4aac6103e0674
121
+ rbtree (0.4.7) sha256=1efabbcb3fd5f12249c9c8a610a765074868164eed0c50c9db2531c00ed161cb
122
+ rdoc (8.1.0) sha256=befc37880a5cfd36dcb176c748b5fd5ee39dd3536a75e81824e4972ce230f60a
123
+ reline (0.7.0) sha256=5b012d8e55dbf9d450f12bde2cf7d15ff546ae80b3f8f3b30e570d431815583d
124
+ sinatra (4.2.1) sha256=b7aeb9b11d046b552972ade834f1f9be98b185fa8444480688e3627625377080
125
+ sorted_set (1.1.0) sha256=bac01eb16b6bcfcdb7d732a28e87422be8b5c9fdcfeadbe9072682407524ee83
126
+ sqlite3 (2.10.2-arm64-darwin) sha256=5b0d235ea037e6dcd1b8387a19d73be571917db9f93044d35741cf933a21a37d
127
+ tilt (2.9.0) sha256=da5735d0280bba96e9a91041bb14aee435ccad5c17b0fa519249ae543d9aa3a5
128
+ tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f
129
+
130
+ BUNDLED WITH
131
+ 4.0.23
@@ -0,0 +1,112 @@
1
+ # Minisky OAuth Console Example
2
+
3
+ This is an example showing how to do an (experimental) OAuth client based on Minisky from the `irb` level, both locally and against the client metadata hosted on a production server. Both configurations are *public* clients.
4
+
5
+ Note: both variants receive and save the access tokens locally on your machine, and in both you perform requests using the received token locally in `irb`. The production server code only serves the JSON file and prints the callback params, it does not use the `AuthProto` library itself.
6
+
7
+
8
+ ## Requirements
9
+
10
+ - Ruby 3.3+
11
+ - Bundler
12
+ - A domain name for your application that matches your client metadata (required for production)
13
+ - SSL certificate for your domain (required for production)
14
+
15
+ ## Setup
16
+
17
+ 1. Clone the repository and navigate to the example directory:
18
+
19
+ ```bash
20
+ cd examples/minisky_console
21
+ ```
22
+
23
+ 2. Install dependencies:
24
+
25
+ ```bash
26
+ bundle install
27
+ ```
28
+
29
+ 3. Generate an encryption key:
30
+
31
+ ```bash
32
+ openssl rand -base64 32
33
+
34
+ cat > .env
35
+ ATPROTO_MASTER_KEY=<value from above>
36
+ ```
37
+
38
+
39
+ ## Localhost use
40
+
41
+ Run `bin/console`, then:
42
+
43
+ ```rb
44
+ require_relative 'localhost'
45
+
46
+ client = create_client('your.pds.hostname.here')
47
+ authorize(client)
48
+ ```
49
+
50
+ This will print:
51
+
52
+ ```
53
+ Open this URL in the browser and complete authorization:
54
+ https://....
55
+
56
+ Waiting for OAuth callback at http://127.0.0.1:9292/callback (Ctrl-C to cancel)
57
+ ```
58
+
59
+ Open this URL and authorize the app. The example starts a temporary inline HTTP server on the specified port on localhost, which only receives the callback and calls the provided block, which completes the authorization.
60
+
61
+ Once the browser redirects to the callback URL after your approval, the console should print:
62
+
63
+ ```
64
+ Authentication complete ✓
65
+ ```
66
+
67
+ Then, you can run `client.get_request(...)` and `client.post_request(...)` to perform authorized actions on the given account. By default, the example asks for permissions `repo:app.bsky.feed.post` and `rpc:app.bsky.feed.getTimeline?aud=*`, which allow calling `getTimeline` and creating/modifying posts.
68
+
69
+ (Note, the session is currently lost when you close the console.)
70
+
71
+
72
+ ## Production use
73
+
74
+ Deploy the code in `examples/minisky_console` to a production server using a method of your choice. The relevant part is in the `app.rb` file.
75
+
76
+ Create an `.env` file in the app folder with:
77
+
78
+ ```
79
+ SERVER_HOST=your.server.hostname
80
+ ```
81
+
82
+ Locally, run `bin/console`, then:
83
+
84
+ ```rb
85
+ require_relative 'production'
86
+
87
+ client = create_client('your.server.hostname', 'your.pds.hostname')
88
+ authorize(client)
89
+ ```
90
+
91
+ This will print:
92
+
93
+ ```
94
+ Open this URL in the browser and complete authorization:
95
+ https://....
96
+ ```
97
+
98
+ Open this URL and authorize the app. Once the browser redirects to the callback URL after your approval, the page you get redirected to will print the received params.
99
+
100
+ Copy the printed command back to the console:
101
+
102
+ ```rb
103
+ handle_callback(client, { ... })
104
+ ```
105
+
106
+ This will print:
107
+
108
+ ```
109
+ Authentication complete ✓
110
+ ```
111
+
112
+ Then, you can run `client.get_request(...)` and `client.post_request(...)` to perform authorized actions on the given account.
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dotenv/load'
4
+ require 'json'
5
+ require 'sinatra'
6
+
7
+ $server_host = ENV.fetch('SERVER_HOST')
8
+
9
+ $metadata = {
10
+ 'application_type' => 'native',
11
+ 'client_id' => "https://#{$server_host}/oauth-client-metadata.json",
12
+ 'client_name' => 'Minisky OAuth Example App',
13
+ 'redirect_uris' => ["https://#{$server_host}/callback"],
14
+ 'grant_types' => ['authorization_code', 'refresh_token'],
15
+ 'response_types' => ['code'],
16
+ 'scope' => 'atproto repo:app.bsky.feed.post rpc:app.bsky.feed.getTimeline?aud=*',
17
+ 'token_endpoint_auth_method' => 'none',
18
+ 'dpop_bound_access_tokens' => true,
19
+ }
20
+
21
+ get "/" do
22
+ content_type 'text/plain'
23
+
24
+ "To start authentication, run this in bin/console:\n\n" +
25
+ "require_relative 'production'\n" +
26
+ "client = create_client('#{$server_host}', 'target.pds.hostname')\n" +
27
+ "authorize(client)\n\n" +
28
+ "Then open the printed URL and authorize the app. You will receive a hash with data, which you need to paste to:\n\n" +
29
+ "handle_callback(client, { ... })\n"
30
+ end
31
+
32
+ get "/oauth-client-metadata.json" do
33
+ content_type :json
34
+
35
+ JSON.generate($metadata) + "\n"
36
+ end
37
+
38
+ get "/callback" do
39
+ content_type 'text/plain'
40
+
41
+ if params[:error]
42
+ params.slice(:error, :error_description).to_json
43
+ elsif params[:code] && params[:state] && params[:iss]
44
+ "To complete authentication, run this in the console where you started the authentication flow " +
45
+ "(replace \"client\" with the local variable name you've used):\n\n" +
46
+ "handle_callback(client, #{params.slice(:code, :state, :iss).map { |k, v| [k.to_sym, v] }.to_h.inspect})\n"
47
+ else
48
+ "Missing params"
49
+ end
50
+ end
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'irb'
5
+
6
+ IRB.start(__FILE__)
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'app'
4
+
5
+ run Sinatra::Application
@@ -0,0 +1,53 @@
1
+ require 'authproto/minisky'
2
+ require 'dotenv/load'
3
+ require 'uri'
4
+
5
+ AuthProto.configure do |config|
6
+ config.http_client = AuthProto::InsecureHTTPClient.new(timeout: 10)
7
+
8
+ config.storage = AuthProto::Storage::SQLite.new(
9
+ path: File.join(__dir__, "db", "localhost_auth.sqlite3")
10
+ )
11
+ end
12
+
13
+ $metadata = {
14
+ 'application_type' => 'native',
15
+ 'client_id' => 'http://localhost/',
16
+ 'client_name' => 'AT Protocol OAuth Ruby Example',
17
+ 'redirect_uris' => ['http://127.0.0.1:9292/callback'],
18
+ 'grant_types' => ['authorization_code', 'refresh_token'],
19
+ 'response_types' => ['code'],
20
+ 'scope' => 'atproto repo:app.bsky.feed.post rpc:app.bsky.feed.getTimeline?aud=*',
21
+ 'token_endpoint_auth_method' => 'none',
22
+ 'dpop_bound_access_tokens' => true,
23
+ }
24
+
25
+ $client_id = $metadata['client_id'] + '?' + URI.encode_www_form({
26
+ redirect_uri: $metadata['redirect_uris'][0],
27
+ scope: $metadata['scope']
28
+ })
29
+
30
+ $metadata['client_id'] = $client_id
31
+
32
+ def create_client(pds)
33
+ Minisky::OAuthClient.new(pds,
34
+ client_id: $client_id,
35
+ redirect_uri: $metadata['redirect_uris'][0],
36
+ metadata: $metadata
37
+ )
38
+ end
39
+
40
+ def authorize(client)
41
+ auth = client.authorize(pds_url: "https://#{client.host}", scope: $metadata['scope'])
42
+
43
+ puts "Open this URL in the browser and complete authorization:"
44
+ puts auth[:url]
45
+ puts
46
+
47
+ AuthProto::LocalhostServer.start($metadata) do |json|
48
+ client.handle_callback(**json)
49
+ end
50
+
51
+ puts "Authentication complete ✓"
52
+ puts "Your session id: #{auth[:session_id]}"
53
+ end
@@ -0,0 +1,32 @@
1
+ require 'authproto/minisky'
2
+ require 'dotenv/load'
3
+ require 'uri'
4
+
5
+ AuthProto.configure do |config|
6
+ config.http_client = AuthProto::HTTPClient.new(timeout: 10)
7
+
8
+ config.storage = AuthProto::Storage::SQLite.new(
9
+ path: File.join(__dir__, "db", "production_auth.sqlite3")
10
+ )
11
+ end
12
+
13
+ def create_client(server_hostname, pds)
14
+ Minisky::OAuthClient.new(pds,
15
+ client_id: "https://#{server_hostname}/oauth-client-metadata.json",
16
+ redirect_uri: "https://#{server_hostname}/callback"
17
+ )
18
+ end
19
+
20
+ def authorize(client)
21
+ auth = client.authorize(pds_url: "https://#{client.host}", scope: client.oauth_client.client_metadata.scope)
22
+
23
+ puts "Open this URL in the browser and complete authorization:"
24
+ puts auth[:url]
25
+ end
26
+
27
+ def handle_callback(client, json)
28
+ result = client.handle_callback(**json)
29
+
30
+ puts "Authentication complete ✓"
31
+ puts "Your session id: #{result[:session_id]}"
32
+ end