authproto 0.3.0.beta1 → 0.3.0.beta2
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.
- checksums.yaml +4 -4
- data/.yardopts +1 -0
- data/AGENTS.md +80 -0
- data/CHANGELOG.md +48 -7
- data/README.md +20 -0
- data/examples/confidential_client/.gitignore +2 -1
- data/examples/confidential_client/Gemfile.lock +5 -1
- data/examples/confidential_client/README.md +2 -2
- data/examples/confidential_client/app.rb +17 -17
- data/examples/confidential_client/config/{client-metadata.example.json → oauth-client-metadata.example.json} +0 -1
- data/examples/confidential_client/scripts/generate_keys.rb +15 -9
- data/examples/minisky_console/Gemfile.lock +2 -2
- data/examples/minisky_console/localhost.rb +8 -25
- data/examples/minisky_console/production.rb +1 -1
- data/lib/authproto/client.rb +150 -201
- data/lib/authproto/dpop/client.rb +12 -60
- data/lib/authproto/dpop/key_manager.rb +17 -17
- data/lib/authproto/dpop/nonce_manager.rb +5 -19
- data/lib/authproto/dpop/proof_generator.rb +27 -23
- data/lib/authproto/errors.rb +1 -1
- data/lib/authproto/http_client.rb +17 -31
- data/lib/authproto/localhost_server.rb +15 -6
- data/lib/authproto/metadata/authorization_server.rb +358 -0
- data/lib/authproto/metadata/client_metadata.rb +574 -0
- data/lib/authproto/metadata/resource_server.rb +137 -0
- data/lib/authproto/{server_metadata.rb → metadata.rb} +7 -7
- data/lib/authproto/minisky.rb +8 -10
- data/lib/authproto/par/client.rb +51 -55
- data/lib/authproto/par/client_assertion.rb +58 -15
- data/lib/authproto/par/request.rb +58 -140
- data/lib/authproto/par/response.rb +9 -10
- data/lib/authproto/par.rb +3 -5
- data/lib/authproto/pkce.rb +29 -106
- data/lib/authproto/serialization/session.rb +11 -11
- data/lib/authproto/serialization/token_set.rb +6 -6
- data/lib/authproto/state/session.rb +27 -31
- data/lib/authproto/state/session_manager.rb +2 -2
- data/lib/authproto/state/token_set.rb +4 -4
- data/lib/authproto/state.rb +3 -3
- data/lib/authproto/token/refresh.rb +35 -43
- data/lib/authproto/validated_uri.rb +57 -0
- data/lib/authproto/version.rb +1 -1
- data/lib/authproto.rb +1 -0
- data/misc/yard_attributes.rb +34 -0
- data/sig/authproto/client.rbs +9 -10
- data/sig/authproto/dpop/client.rbs +1 -8
- data/sig/authproto/dpop/nonce_manager.rbs +0 -1
- data/sig/authproto/dpop/proof_generator.rbs +5 -5
- data/sig/authproto/http_client.rbs +2 -2
- data/sig/authproto/{server_metadata → metadata}/authorization_server.rbs +18 -12
- data/sig/authproto/metadata/client_metadata.rbs +118 -0
- data/sig/authproto/metadata/resource_server.rbs +27 -0
- data/sig/authproto/minisky_oauth.rbs +5 -3
- data/sig/authproto/par/client.rbs +4 -6
- data/sig/authproto/par/client_assertion.rbs +2 -1
- data/sig/authproto/par/request.rbs +5 -19
- data/sig/authproto/par/response.rbs +1 -5
- data/sig/authproto/pkce.rbs +6 -16
- data/sig/authproto/serialization/session.rbs +2 -2
- data/sig/authproto/state/session.rbs +4 -7
- data/sig/authproto/state/session_manager.rbs +2 -2
- data/sig/authproto/token/refresh.rbs +4 -4
- data/sig/authproto/validated_uri.rbs +36 -0
- data/sig/authproto.rbs +5 -3
- metadata +13 -11
- data/lib/authproto/client_metadata.rb +0 -478
- data/lib/authproto/server_metadata/authorization_server.rb +0 -267
- data/lib/authproto/server_metadata/origin_url.rb +0 -71
- data/lib/authproto/server_metadata/resource_server.rb +0 -98
- data/sig/authproto/client_metadata.rbs +0 -113
- data/sig/authproto/server_metadata/origin_url.rbs +0 -21
- data/sig/authproto/server_metadata/resource_server.rbs +0 -27
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a24a4c3d46d68006268cf34364953c33cd23225f2203f2054e7167f8f1470970
|
|
4
|
+
data.tar.gz: 80287bbe86fc764f9c8b272d090b8736a17ab76470b5ac0fa32903b94dc64cce
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cb4ddce58f52416d16f92dff7f6caf0212c5770addb626610c9459d90894e1c9da4cc664122d359f3a4cdfe572000841cdd3b9daf1ac3defbf472cb3b7fd2474
|
|
7
|
+
data.tar.gz: 3dd080bf47dc27d1b318784aad020263ab362d2e49f4fbfd4e2716029841de99f0eb7199ac5ba1483208fe1993967b574793ad01fdd41240fd2e5b37d56ddc7f
|
data/.yardopts
CHANGED
data/AGENTS.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
### Ruby code:
|
|
2
|
+
|
|
3
|
+
- always add `# frozen_string_literal: true` at the beginning of Ruby files (both library and tests)
|
|
4
|
+
- keep `require`s and `require_relative`s organized in separate blocks, ordered alphabetically in each:
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
require 'ipaddr'
|
|
8
|
+
require 'socket'
|
|
9
|
+
require 'timeout'
|
|
10
|
+
require 'uri'
|
|
11
|
+
|
|
12
|
+
require_relative 'client_metadata'
|
|
13
|
+
require_relative 'validated_uri'
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
- wrap lines longer than 120 characters
|
|
17
|
+
- for multi-line `if ... end` / `unless ... end` blocks, keep one empty line above/below separating them from the rest of the code in the method, unless it's at the beginning/end of the method, e.g.:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
def validate_grant_types(grant_types)
|
|
21
|
+
unless grant_types.include?('authorization_code')
|
|
22
|
+
raise InvalidClientMetadata, "grant_types must include authorization_code"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
valid_types = %w[authorization_code refresh_token]
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- prefer single-quote strings for strings containing identifiers without spaces, like `'code'`, `'access_token'`, and for `require`s
|
|
29
|
+
- prefer double-quote strings for strings containing messages with spaces meant to be shown to the user, e.g. `puts "grant_types must include authorization_code"`
|
|
30
|
+
|
|
31
|
+
### Unit tests:
|
|
32
|
+
|
|
33
|
+
- use the classic RSpec `foo.should be_...` matcher style instead of `expect()`
|
|
34
|
+
- except for calling matchers on blocks as in `expect { ... }.to(not) raise_error...`
|
|
35
|
+
- use the classic `it "should do this"` naming for test cases instead of `it "does this"`
|
|
36
|
+
- use raw `describe` instead of `RSpec.describe` at the top level
|
|
37
|
+
|
|
38
|
+
### Documentation:
|
|
39
|
+
|
|
40
|
+
- use YARD with Markdown formatting
|
|
41
|
+
- for class comments, keep one empty # line at the beginning and end, and one empty line above and below that, like:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
#
|
|
46
|
+
# OAuth client implementation class.
|
|
47
|
+
#
|
|
48
|
+
|
|
49
|
+
class ...
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- for method comments, leave one empty line above, no empty # starting line, and one empty # trailing line that is immediately followed by the method definition, like:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
# Reads tokens for a session with a currently valid access token.
|
|
57
|
+
#
|
|
58
|
+
# @param session_id [String] session identifier
|
|
59
|
+
# @return [Hash{Symbol => String, Integer, nil}, nil] token details, or `nil` for missing/unauthorized sessions
|
|
60
|
+
#
|
|
61
|
+
def get_tokens(session_id)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
- for properties, and also for one-liner classes like error types, use a compact style like:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
# Identifier of the session associated with the failure, if known.
|
|
69
|
+
# @return [String, nil]
|
|
70
|
+
attr_reader :session_id
|
|
71
|
+
|
|
72
|
+
# Error raised when token operations fail
|
|
73
|
+
class TokenError < Error; end
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
### Running code:
|
|
79
|
+
|
|
80
|
+
- make sure you don't use the ancient system Ruby 2.6, but a version supported by the library (my installation uses `asdf`)
|
data/CHANGELOG.md
CHANGED
|
@@ -1,34 +1,75 @@
|
|
|
1
|
-
## [0.
|
|
1
|
+
## [0.3.0.beta2] – 2026-10-31 👻
|
|
2
|
+
|
|
3
|
+
- private keys for signing are now passed separately as `signing_key:` instead of being taken from the same `jwks` in the metadata that holds public keys
|
|
4
|
+
- generate localhost metadata from `client_id` params
|
|
5
|
+
- `confidential_client`: validate `SESSION_SECRET` before start and throw an error early
|
|
6
|
+
- additional validations for URLs, resource server and authorization server metadata, and PAR request and response
|
|
7
|
+
- set `code_challenge_method` in the PAR request automatically to `S256`
|
|
8
|
+
- various renames, refactors, cleanups
|
|
9
|
+
- encode '*' in the authorization URL – unencoded asterisk might break click-to-open-URL in some terminals
|
|
10
|
+
- strip query part from URLs in `htu` in the DPoP proof generator
|
|
11
|
+
- keep existing query part in the PAR authorization endpoint when adding params to it
|
|
12
|
+
- verify that all scopes passed during authorization are declared in client metadata
|
|
13
|
+
- don't reuse client assertion JWT on retried requests
|
|
14
|
+
- POST redirects are not allowed, GET redirects are disabled by default
|
|
15
|
+
|
|
16
|
+
## [0.3.0.beta1], as AuthProto – 2026-10-28
|
|
17
|
+
|
|
18
|
+
- modernized code
|
|
19
|
+
- various renames, refactors, cleanups
|
|
20
|
+
- removed some unused code
|
|
21
|
+
- removed strict dependency on the Redis gem
|
|
22
|
+
- added alternative SQLite storage
|
|
23
|
+
- added HTTP client version that allows `http://` connections
|
|
24
|
+
- added experimental Minisky adapter and `minisky_console` example
|
|
25
|
+
- added temporary in-process `localhost` HTTP server for receiving callbacks inside `irb`
|
|
26
|
+
- additional validations for request IPs and URLs, responses, and client metadata
|
|
27
|
+
- fixed `confidential_example` running on localhost
|
|
28
|
+
- updated `confidential_example` with a UI for making a test post to Bluesky
|
|
29
|
+
- updated examples to use `oauth-client-metadata.json` instead of `client-metadata.json`
|
|
30
|
+
- converted test suite to RSpec
|
|
31
|
+
- replaced `Identity` module with DIDKit
|
|
32
|
+
- fixed randomly generated encryption key not being shared between different parts of the app
|
|
33
|
+
- fixed some token refresh code failing due to wrong exception names
|
|
34
|
+
- don't try to add client assertions for public clients
|
|
35
|
+
- switched to JWT gem version 3.x
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
# Earlier releases as `atproto_auth`:
|
|
41
|
+
|
|
42
|
+
## [0.2.5] – 2024-12-13
|
|
2
43
|
|
|
3
44
|
Fixed:
|
|
4
45
|
|
|
5
46
|
- Additional token refresh edge case handling
|
|
6
47
|
|
|
7
|
-
## [0.2.4]
|
|
48
|
+
## [0.2.4] – 2024-12-13
|
|
8
49
|
|
|
9
50
|
Fixed:
|
|
10
51
|
|
|
11
52
|
- Token refresh now correctly handles expired tokens
|
|
12
53
|
|
|
13
|
-
## [0.2.2]
|
|
54
|
+
## [0.2.2] – 2024-12-12
|
|
14
55
|
|
|
15
56
|
Added:
|
|
16
57
|
|
|
17
58
|
- Added support for did:web users
|
|
18
59
|
|
|
19
|
-
## [0.2.1]
|
|
60
|
+
## [0.2.1] – 2024-12-11
|
|
20
61
|
|
|
21
62
|
Fixed:
|
|
22
63
|
|
|
23
64
|
- Fixed a bug with access token hash generation
|
|
24
65
|
|
|
25
|
-
## [0.2.0]
|
|
66
|
+
## [0.2.0] – 2024-12-10
|
|
26
67
|
|
|
27
68
|
Added:
|
|
28
69
|
|
|
29
70
|
- Client#handle_callback now returns session did as part of the response
|
|
30
71
|
|
|
31
|
-
## [0.1.0]
|
|
72
|
+
## [0.1.0] – 2024-12-07
|
|
32
73
|
|
|
33
74
|
Added:
|
|
34
75
|
|
|
@@ -47,6 +88,6 @@ Changed:
|
|
|
47
88
|
- Session and token management now use configured storage backend
|
|
48
89
|
- Improved thread safety for all storage operations
|
|
49
90
|
|
|
50
|
-
## [0.0.1]
|
|
91
|
+
## [0.0.1] – 2024-12-05
|
|
51
92
|
|
|
52
93
|
- Initial release
|
data/README.md
CHANGED
|
@@ -103,6 +103,13 @@ Client metadata must explicitly set `token_endpoint_auth_method` to `none` for
|
|
|
103
103
|
public clients or `private_key_jwt` for confidential clients. Omitting it defaults
|
|
104
104
|
to `client_secret_basic` in OAuth metadata, which this library does not support.
|
|
105
105
|
|
|
106
|
+
For localhost development, `Metadata::ClientMetadata.from_url` generates virtual metadata
|
|
107
|
+
without making an HTTP request. It defaults to native public authentication,
|
|
108
|
+
the `atproto` scope, and redirects `http://127.0.0.1/` and `http://[::1]/`.
|
|
109
|
+
The client ID query can specify one `scope` parameter and multiple `redirect_uri`
|
|
110
|
+
parameters, encoded using `URI.encode_www_form`. These values are decoded and
|
|
111
|
+
validated; the original client ID is preserved exactly.
|
|
112
|
+
|
|
106
113
|
### Storage Backends
|
|
107
114
|
|
|
108
115
|
The library supports multiple storage backends for managing OAuth state:
|
|
@@ -190,11 +197,24 @@ end
|
|
|
190
197
|
|
|
191
198
|
Here's a basic example of using the library in a confidential client application:
|
|
192
199
|
|
|
200
|
+
Supply the private EC P-256 client-authentication JWK through `signing_key:`,
|
|
201
|
+
separately from metadata. It must have a nonempty `kid` and permit ES256 signing.
|
|
202
|
+
Metadata's `jwks` contains only public keys; the client checks that the matching
|
|
203
|
+
public key and `kid` are advertised and active. A public key may declare
|
|
204
|
+
`use: "sig"` or `key_ops: ["verify"]`.
|
|
205
|
+
Other advertised public keys may serve other purposes, such as encryption;
|
|
206
|
+
the matching authentication key must permit signature verification.
|
|
207
|
+
|
|
208
|
+
With `jwks_uri`, the client still uses your separate private signing key. It does
|
|
209
|
+
not fetch that URL or check the advertised key locally; the authorization server
|
|
210
|
+
fetches it to verify assertions.
|
|
211
|
+
|
|
193
212
|
```ruby
|
|
194
213
|
# Initialize client with metadata
|
|
195
214
|
client = AuthProto::Client.new(
|
|
196
215
|
client_id: "https://app.example.com/oauth-client-metadata.json",
|
|
197
216
|
redirect_uri: "https://app.example.com/callback",
|
|
217
|
+
signing_key: JSON.parse(File.read("config/keys.json")).fetch("keys").first,
|
|
198
218
|
metadata: {
|
|
199
219
|
# Your client metadata...
|
|
200
220
|
}
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: ../..
|
|
3
3
|
specs:
|
|
4
|
-
authproto (0.
|
|
4
|
+
authproto (0.3.0.beta1)
|
|
5
|
+
didkit (~> 0.5.0)
|
|
5
6
|
jose (~> 1.2)
|
|
6
7
|
jwt (~> 3.3)
|
|
7
8
|
|
|
8
9
|
GEM
|
|
9
10
|
remote: https://rubygems.org/
|
|
10
11
|
specs:
|
|
12
|
+
base58 (0.2.3)
|
|
11
13
|
base64 (0.3.0)
|
|
12
14
|
concurrent-ruby (1.3.8)
|
|
13
15
|
connection_pool (3.0.2)
|
|
16
|
+
didkit (0.5.0)
|
|
17
|
+
base58 (~> 0.2)
|
|
14
18
|
dotenv (3.2.0)
|
|
15
19
|
faraday (2.14.3)
|
|
16
20
|
faraday-net_http (>= 2.0, < 3.5)
|
|
@@ -43,7 +43,7 @@ bundle install
|
|
|
43
43
|
3. Generate EC keys for client authentication:
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
|
-
bundle exec ruby scripts/generate_keys.rb
|
|
46
|
+
bundle exec ruby scripts/generate_keys.rb
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
4. Generate session & encryption keys:
|
|
@@ -64,7 +64,7 @@ SESSION_SECRET=<hex value from second command>
|
|
|
64
64
|
```
|
|
65
65
|
- Set the correct `client_id` URL where your metadata will be hosted
|
|
66
66
|
- Configure valid `redirect_uris` for your application
|
|
67
|
-
-
|
|
67
|
+
- Copy the public JWKS from `config/public-keys.json` (not `config/keys.json`, which is the private keys) into the `jwks` field in oauth-client-metadata.json.
|
|
68
68
|
- For localhost testing, set `client_id` to `http://localhost/` and `redirect_uris` to `["http://127.0.0.1:9292/callback"]`
|
|
69
69
|
|
|
70
70
|
6. Set up environment variables:
|
|
@@ -12,6 +12,9 @@ require 'faraday'
|
|
|
12
12
|
require 'sinatra/base'
|
|
13
13
|
require 'sinatra/reloader'
|
|
14
14
|
|
|
15
|
+
session_secret = ENV.fetch("SESSION_SECRET") { SecureRandom.hex(32) }
|
|
16
|
+
raise "SESSION_SECRET must be at least 64 characters long (got: #{session_secret})" unless session_secret.length >= 64
|
|
17
|
+
|
|
15
18
|
# Main app entry point
|
|
16
19
|
class ExampleApp < Sinatra::Base
|
|
17
20
|
def check_stored_session(session_id)
|
|
@@ -54,20 +57,24 @@ class ExampleApp < Sinatra::Base
|
|
|
54
57
|
metadata_path = File.join(__dir__, "config", "oauth-client-metadata.json")
|
|
55
58
|
metadata = JSON.parse(File.read(metadata_path))
|
|
56
59
|
|
|
57
|
-
client_id = metadata[
|
|
60
|
+
client_id = metadata['client_id']
|
|
58
61
|
|
|
59
62
|
if client_id.chomp("/") == "http://localhost"
|
|
60
|
-
scope = CGI.escape(metadata[
|
|
61
|
-
redirect_uri = CGI.escape(metadata[
|
|
63
|
+
scope = CGI.escape(metadata['scope'])
|
|
64
|
+
redirect_uri = CGI.escape(metadata['redirect_uris'][0])
|
|
62
65
|
client_id += "?scope=#{scope}&redirect_uri=#{redirect_uri}"
|
|
63
66
|
end
|
|
64
67
|
|
|
68
|
+
# Private client-authentication keys are kept outside public metadata.
|
|
69
|
+
signing_key = JSON.parse(File.read(File.join(__dir__, "config", "keys.json")))["keys"].first
|
|
70
|
+
|
|
65
71
|
# Create OAuth client
|
|
66
72
|
set :oauth_client, AuthProto::Client.new(
|
|
67
73
|
client_id: client_id,
|
|
68
|
-
redirect_uri: metadata[
|
|
74
|
+
redirect_uri: metadata['redirect_uris'][0],
|
|
69
75
|
metadata: metadata,
|
|
70
|
-
|
|
76
|
+
signing_key: signing_key,
|
|
77
|
+
dpop_key: signing_key
|
|
71
78
|
)
|
|
72
79
|
end
|
|
73
80
|
|
|
@@ -78,7 +85,7 @@ class ExampleApp < Sinatra::Base
|
|
|
78
85
|
use Rack::Session::Cookie,
|
|
79
86
|
key: "atproto.session",
|
|
80
87
|
expire_after: 86_400, # 1 day in seconds
|
|
81
|
-
secret:
|
|
88
|
+
secret: session_secret,
|
|
82
89
|
secure: ENV.fetch("RACK_ENV", "development") == "production", # Only send over HTTPS
|
|
83
90
|
httponly: true, # Not accessible via JavaScript
|
|
84
91
|
same_site: :lax # CSRF protection
|
|
@@ -115,14 +122,7 @@ class ExampleApp < Sinatra::Base
|
|
|
115
122
|
metadata_path = File.join(__dir__, "config", "oauth-client-metadata.json")
|
|
116
123
|
metadata = JSON.parse(File.read(metadata_path))
|
|
117
124
|
|
|
118
|
-
#
|
|
119
|
-
if metadata["jwks"] && metadata["jwks"]["keys"]
|
|
120
|
-
metadata["jwks"]["keys"] = metadata["jwks"]["keys"].map do |key|
|
|
121
|
-
key.except("d")
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
# Return sanitized metadata
|
|
125
|
+
# This file contains only the public counterparts of config/keys.json.
|
|
126
126
|
JSON.generate(metadata)
|
|
127
127
|
end
|
|
128
128
|
|
|
@@ -190,7 +190,7 @@ class ExampleApp < Sinatra::Base
|
|
|
190
190
|
# Get auth headers for request
|
|
191
191
|
headers = settings.oauth_client.auth_headers(
|
|
192
192
|
session_id: session_id,
|
|
193
|
-
method:
|
|
193
|
+
method: 'GET',
|
|
194
194
|
url: "https://api.bsky.app/xrpc/com.atproto.identity.resolveHandle"
|
|
195
195
|
)
|
|
196
196
|
|
|
@@ -236,7 +236,7 @@ class ExampleApp < Sinatra::Base
|
|
|
236
236
|
|
|
237
237
|
headers = settings.oauth_client.auth_headers(
|
|
238
238
|
session_id: session_id,
|
|
239
|
-
method:
|
|
239
|
+
method: 'POST',
|
|
240
240
|
url: endpoint
|
|
241
241
|
)
|
|
242
242
|
|
|
@@ -273,7 +273,7 @@ class ExampleApp < Sinatra::Base
|
|
|
273
273
|
get "/signout" do
|
|
274
274
|
if session[:oauth_session_id]
|
|
275
275
|
# Clean up stored session
|
|
276
|
-
settings.oauth_client.
|
|
276
|
+
settings.oauth_client.delete_session(session[:oauth_session_id])
|
|
277
277
|
end
|
|
278
278
|
|
|
279
279
|
session.clear
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'jose'
|
|
4
|
+
require 'json'
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
keypair = JOSE::JWK.generate_key([:ec, "P-256"])
|
|
6
|
+
key = JOSE::JWK.generate_key([:ec, "P-256"])
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
private_jwk = key.to_map.to_h.merge("kid" => "key-1", "use" => "sig")
|
|
9
|
+
public_jwk = key.to_public.to_map.to_h.merge("kid" => "key-1", "use" => "sig")
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
config_dir = File.expand_path('../config', __dir__)
|
|
12
|
+
private_path = File.join(config_dir, 'keys.json')
|
|
13
|
+
|
|
14
|
+
File.open(private_path, File::WRONLY | File::CREAT | File::TRUNC, 0o600) do |file|
|
|
15
|
+
file.chmod(0o600)
|
|
16
|
+
file.write(JSON.pretty_generate('keys' => [private_jwk]) + "\n")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
File.write(File.join(config_dir, 'public-keys.json'), JSON.pretty_generate('keys' => [public_jwk]) + "\n")
|
|
20
|
+
|
|
21
|
+
puts "Private keys saved to config/keys.json; publish only config/public-keys.json in metadata."
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: ../..
|
|
3
3
|
specs:
|
|
4
|
-
authproto (0.
|
|
4
|
+
authproto (0.3.0.beta1)
|
|
5
5
|
didkit (~> 0.5.0)
|
|
6
6
|
jose (~> 1.2)
|
|
7
7
|
jwt (~> 3.3)
|
|
@@ -91,7 +91,7 @@ DEPENDENCIES
|
|
|
91
91
|
sqlite3
|
|
92
92
|
|
|
93
93
|
CHECKSUMS
|
|
94
|
-
authproto (0.
|
|
94
|
+
authproto (0.3.0.beta1)
|
|
95
95
|
base58 (0.2.3) sha256=fda5c5d3bda8b429ad0d1a5309c2d1e488ba46f0a2ab99a639890f26c61bf3be
|
|
96
96
|
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
|
|
97
97
|
bundler (4.0.23) sha256=407ba8a1d928f412847f819aa5f8b6aef7c59fb51feb517112af8a165487287b
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'authproto/insecure_http_client'
|
|
2
|
+
require 'authproto/localhost_server'
|
|
1
3
|
require 'authproto/minisky'
|
|
2
4
|
require 'dotenv/load'
|
|
3
5
|
require 'uri'
|
|
@@ -10,41 +12,22 @@ AuthProto.configure do |config|
|
|
|
10
12
|
)
|
|
11
13
|
end
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
|
15
|
+
REDIRECT_URI = 'http://127.0.0.1:9292/callback'
|
|
16
|
+
SCOPE = 'atproto repo:app.bsky.feed.post rpc:app.bsky.feed.getTimeline?aud=*'
|
|
17
|
+
CLIENT_ID = 'http://localhost/?' + URI.encode_www_form({ redirect_uri: REDIRECT_URI, scope: SCOPE })
|
|
31
18
|
|
|
32
19
|
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
|
-
)
|
|
20
|
+
Minisky::OAuthClient.new(pds, client_id: CLIENT_ID, redirect_uri: REDIRECT_URI)
|
|
38
21
|
end
|
|
39
22
|
|
|
40
23
|
def authorize(client)
|
|
41
|
-
auth = client.authorize(
|
|
24
|
+
auth = client.authorize(pds: "https://#{client.host}", scope: SCOPE)
|
|
42
25
|
|
|
43
26
|
puts "Open this URL in the browser and complete authorization:"
|
|
44
27
|
puts auth[:url]
|
|
45
28
|
puts
|
|
46
29
|
|
|
47
|
-
AuthProto::LocalhostServer.start(
|
|
30
|
+
AuthProto::LocalhostServer.start(client.oauth_client.client_metadata) do |json|
|
|
48
31
|
client.handle_callback(**json)
|
|
49
32
|
end
|
|
50
33
|
|
|
@@ -18,7 +18,7 @@ def create_client(server_hostname, pds)
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def authorize(client)
|
|
21
|
-
auth = client.authorize(
|
|
21
|
+
auth = client.authorize(pds: "https://#{client.host}", scope: client.oauth_client.client_metadata.scope)
|
|
22
22
|
|
|
23
23
|
puts "Open this URL in the browser and complete authorization:"
|
|
24
24
|
puts auth[:url]
|