omniauth-atproto 0.1.0 → 0.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5e2f34cc5f324628d5f73659d4cd3ea11cbfbcdc5420cefaa78908c928008d7a
4
- data.tar.gz: 1e349da8447a8d95b29a8f0c8965d13c1cdf5525e84ecdb02a2bf69b253de82e
3
+ metadata.gz: e79046605d1235915ec27cb5f200e0744f1a645c630efcf5792b7ee3563d86f8
4
+ data.tar.gz: b36566fae3041251882c97e10c9d110fc5893edcf2da9e1f779d1c9aee525d42
5
5
  SHA512:
6
- metadata.gz: d71697e0c667218a143ad87f2bf30098e6766365cb6bef90709ffdb004bffdff3edd000c332c8124279c3dd63c4cc9a47748e3658c23124ee629a41e9eddbd4d
7
- data.tar.gz: fafbab85910a7b064e9f4ef118bc9c5f0df5d900a4ca7b8c9c01286a76ccc4c88c0bbb1ceb96d174d3db5d71de3acbdb396d3ee62dbe57a9c90d4b193d941dc4
6
+ metadata.gz: a2bffaa5781a042deb22166582c8d3d0301abee04eccd1c01ccfee320a5cd3af590a0e1a41e97e94a4e1e70b16bb3421ec5b7102334caf83f9421df76111a1e0
7
+ data.tar.gz: babee933a42f7d584dd7899aab66434d9b5c4438dc524ded0f4030b401bc1205a00d01547000787ede9835410fdaa52118b10ff6379f396e644080dd371da1e1
data/README.md CHANGED
@@ -15,7 +15,7 @@ gem 'omniauth-atproto'
15
15
 
16
16
  ## Usage
17
17
 
18
- You can cnfigure it :
18
+ You can configure it :
19
19
  ```ruby
20
20
  Rails.application.config.middleware.use OmniAuth::Builder do
21
21
  provider(:atproto,
@@ -31,7 +31,9 @@ Rails.application.config.middleware.use OmniAuth::Builder do
31
31
  client_jwk: OmniAuth::Atproto::KeyManager.current_jwk)
32
32
  end
33
33
  ```
34
- You will have to generate keys and the oauth/client-metadata.json document (a generator should come soon)
34
+ client_options are optional if you use handle resolution (see below).
35
+
36
+ You will have to generate keys and the oauth/client-metadata.json document (a generator should come soon).
35
37
 
36
38
  ```ruby
37
39
  #lib/tasks/atproto.rake
@@ -74,4 +76,19 @@ Then you can
74
76
  ```bash
75
77
  rails atproto:generate_metadata
76
78
  ```
77
- The values from the metadata endpoint should correspond to those you gave as option for the strategy (that's why a generator would be very handy)
79
+ The values from the metadata endpoint should correspond to those you gave as option for the strategy (that's why a generator would be very handy).
80
+
81
+ All subsequent request made with the token should use the same private_key (with dpop, see the atproto_client gem).
82
+
83
+ The pds is going to request your app at oauth/client-metadata.json. For developement you will have to use some kind of proxy, like ngrok (there is a "development mode" in the spec but I didnt try it)
84
+
85
+ You can either set default client_options in the initializer, or keep it empty if you want to resolve the authorization server from the user handle. In this case you can add a handle param to the original omniauth request :
86
+
87
+ ```erb
88
+ <%= form_tag('/auth/atproto', method: 'post', data: {turbo: false}) do %>
89
+ <input name="handle" value="frabr.lasercats.fr"></input>
90
+ <button type='submit'>Login with Atproto</button>
91
+ <% end %>
92
+ ```
93
+
94
+ Here is the [documentation I tried to follow](https://atproto.com/specs/oauth)
@@ -2,20 +2,15 @@ require 'omniauth-oauth2'
2
2
  require 'json'
3
3
  require 'net/http'
4
4
  require 'atproto_client'
5
+ require 'didkit'
6
+ require 'faraday'
5
7
 
6
8
  module OmniAuth
7
9
  module Strategies
8
10
  class Atproto < OmniAuth::Strategies::OAuth2
9
- def initialize(app, *args)
10
- super
11
- @dpop_handler = AtProto::DpopHandler.new(options.private_key)
12
- end
13
-
11
+ option :fields, %i[handle]
14
12
  option :scope, 'atproto'
15
13
  option :pkce, true
16
- option :token_params, {
17
- test: true
18
- }
19
14
 
20
15
  info do
21
16
  {
@@ -24,9 +19,47 @@ module OmniAuth
24
19
  }
25
20
  end
26
21
 
22
+ def request_phase
23
+ unless has_default_client_options?
24
+ @handle = request.params['handle']
25
+
26
+ unless @handle
27
+ fail!(:missing_handle,
28
+ OmniAuth::Error.new(
29
+ 'Handle parameter is required if no client options are set'
30
+ ))
31
+ end
32
+
33
+ set_client_options
34
+ end
35
+ super
36
+ end
37
+
27
38
  private
28
39
 
40
+ def has_default_client_options?
41
+ %i[site authorize_url token_url].all? { |k| options.client_options.key? k }
42
+ end
43
+
44
+ def set_client_options
45
+ options.client_options[:site] = authorization_info['issuer']
46
+ options.client_options[:authorize_url] = authorization_info['authorization_endpoint']
47
+ options.client_options[:token_url] = authorization_info['token_endpoint']
48
+ end
49
+
50
+ def authorization_info
51
+ session['omniauth.auth_info'] ||= begin
52
+ resolver = DIDKit::Resolver.new
53
+ did = resolver.resolve_handle(@handle)
54
+ endpoint = resolver.resolve_did(did).pds_endpoint
55
+ auth_server = get_authorization_server(endpoint)
56
+ auth_info = get_authorization_data(auth_server)
57
+ end
58
+ end
59
+
29
60
  def build_access_token
61
+ set_client_options unless has_default_client_options?
62
+
30
63
  new_token_params = token_params.merge(
31
64
  {
32
65
  grant_type: 'authorization_code',
@@ -37,7 +70,8 @@ module OmniAuth
37
70
  client_assertion: generate_client_assertion
38
71
  }
39
72
  )
40
- response = @dpop_handler.make_request(
73
+ dpop_handler = AtProto::DpopHandler.new(options.private_key)
74
+ response = dpop_handler.make_request(
41
75
  client.token_url,
42
76
  :post,
43
77
  headers: { 'Content-Type' => 'application/json', 'Accept' => 'application/json' },
@@ -60,7 +94,6 @@ module OmniAuth
60
94
  else
61
95
  raise 'Invalid private_key format'
62
96
  end
63
-
64
97
  jwt_payload = {
65
98
  iss: options.client_id,
66
99
  sub: options.client_id,
@@ -81,6 +114,46 @@ module OmniAuth
81
114
  }
82
115
  )
83
116
  end
117
+
118
+ def get_authorization_server(pds_endpoint)
119
+ response = Faraday.get("#{pds_endpoint}/.well-known/oauth-protected-resource")
120
+
121
+ unless response.success?
122
+ fail!(:invalid_auth_server,
123
+ OmniAuth::Error.new(
124
+ "Failed to get PDS authorization server: #{response.status}"
125
+ ))
126
+ end
127
+
128
+ result = JSON.parse(response.body)
129
+
130
+ auth_server = result.dig('authorization_servers', 0)
131
+ unless auth_server
132
+ fail!(:invalid_auth_server,
133
+ OmniAuth::Error.new('No authorization server found in response'))
134
+ end
135
+ auth_server
136
+ end
137
+
138
+ def get_authorization_data(issuer)
139
+ response = Faraday.get("#{issuer}/.well-known/oauth-authorization-server")
140
+
141
+ unless response.success?
142
+ fail!(:invalid_metadata,
143
+ OmniAuth::Error.new(
144
+ "Failed to get authorization server metadata: #{response.status}"
145
+ ))
146
+ end
147
+ result = JSON.parse(response.body)
148
+
149
+ unless result['issuer'] == issuer
150
+ fail!(:invalid_metadata,
151
+ OmniAuth::Error.new('Invalid metadata - issuer mismatch'))
152
+ end
153
+ # we cannot keep everything in session (cookie overflow error)
154
+ fields = %w[issuer authorization_endpoint token_endpoint]
155
+ result.select { |k, _v| fields.include?(k) }
156
+ end
84
157
  end
85
158
  end
86
159
  end
@@ -48,11 +48,11 @@ module OmniAuth
48
48
  def rotate_keys
49
49
  # Backup current keys if they exist
50
50
  if File.exist?(KEY_PATH)
51
- File.write(KEY_PATH, 'config/old_atproto_private_key.pem')
51
+ # File.write(KEY_PATH, 'config/old_atproto_private_key.pem')
52
52
  FileUtils.rm(KEY_PATH)
53
53
  end
54
54
  if File.exist?(JWK_PATH)
55
- File.write(JWK_PATH, 'config/old_atproto_jwk.json')
55
+ # File.write(JWK_PATH, 'config/old_atproto_jwk.json')
56
56
  FileUtils.rm(JWK_PATH)
57
57
  end
58
58
  load_or_generate_keys
@@ -1,5 +1,5 @@
1
1
  module OmniAuth
2
2
  module Atproto
3
- VERSION = "0.1.0"
3
+ VERSION = '0.1.1'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omniauth-atproto
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - frabr
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-11-26 00:00:00.000000000 Z
11
+ date: 2024-11-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: atproto_client
@@ -25,47 +25,61 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: jwt
28
+ name: didkit
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '2.7'
33
+ version: '0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '2.7'
40
+ version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: omniauth-oauth2
42
+ name: faraday
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: jwt
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
59
  - - "~>"
46
60
  - !ruby/object:Gem::Version
47
- version: '1.8'
61
+ version: '2.7'
48
62
  type: :runtime
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
66
  - - "~>"
53
67
  - !ruby/object:Gem::Version
54
- version: '1.8'
68
+ version: '2.7'
55
69
  - !ruby/object:Gem::Dependency
56
- name: omniauth-rails_csrf_protection
70
+ name: omniauth-oauth2
57
71
  requirement: !ruby/object:Gem::Requirement
58
72
  requirements:
59
- - - ">="
73
+ - - "~>"
60
74
  - !ruby/object:Gem::Version
61
- version: '0'
75
+ version: '1.8'
62
76
  type: :runtime
63
77
  prerelease: false
64
78
  version_requirements: !ruby/object:Gem::Requirement
65
79
  requirements:
66
- - - ">="
80
+ - - "~>"
67
81
  - !ruby/object:Gem::Version
68
- version: '0'
82
+ version: '1.8'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: bundler
71
85
  requirement: !ruby/object:Gem::Requirement