omniauth-atproto 0.1.0 → 0.1.2

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: 2dacca65da2377668999f5815835a51e76d4d04e5e0cdcf6a00098a52595fef1
4
+ data.tar.gz: a8118eaa5ddc0783e1ffdef5dc247249dff770448b815f4d41f22084ebf00159
5
5
  SHA512:
6
- metadata.gz: d71697e0c667218a143ad87f2bf30098e6766365cb6bef90709ffdb004bffdff3edd000c332c8124279c3dd63c4cc9a47748e3658c23124ee629a41e9eddbd4d
7
- data.tar.gz: fafbab85910a7b064e9f4ef118bc9c5f0df5d900a4ca7b8c9c01286a76ccc4c88c0bbb1ceb96d174d3db5d71de3acbdb396d3ee62dbe57a9c90d4b193d941dc4
6
+ metadata.gz: 0b9bdf0247dc29d947be68633642a9da43ef6332b39e126c05766dea3dc694e087bf9c95aa935d3a7681335138fbb6393a6a5da0316955fefad2267d0f265c23
7
+ data.tar.gz: c8687b847a82eee5a57023940184033dc01294bfcc5b4cc0b9da05737b8c2120fafe5ecca5503f4ab63f66d6a1630db5f324a3a98b4d14b5dcf8b2b8da1b6b64
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,6 +19,36 @@ module OmniAuth
24
19
  }
25
20
  end
26
21
 
22
+ def self.setup
23
+ lambda do |env|
24
+ session = env["rack.session"]
25
+
26
+ if env["rack.request.form_hash"] && handle = env["rack.request.form_hash"]["handle"]
27
+ resolver = DIDKit::Resolver.new
28
+ did = resolver.resolve_handle(handle)
29
+
30
+ unless did
31
+ env['omniauth.strategy'].fail!(:unknown_handle,
32
+ OmniAuth::Error.new(
33
+ 'Handle parameter did not resolve to a did'
34
+ ))
35
+ end
36
+
37
+ endpoint = resolver.resolve_did(did).pds_endpoint
38
+ auth_server = get_authorization_server(endpoint)
39
+ session["authorization_info"] = authorization_info = get_authorization_data(auth_server)
40
+ end
41
+
42
+ if authorization_info ||= session.delete("authorization_info")
43
+ env['omniauth.strategy'].options["client_options"]["site"] = authorization_info["issuer"]
44
+ env['omniauth.strategy'].options["client_options"]["authorize_url"] = authorization_info['authorization_endpoint']
45
+ env['omniauth.strategy'].options["client_options"]["token_url"] = authorization_info['token_endpoint']
46
+ end
47
+ end
48
+ end
49
+
50
+ option :setup, setup
51
+
27
52
  private
28
53
 
29
54
  def build_access_token
@@ -34,10 +59,11 @@ module OmniAuth
34
59
  code: request.params['code'],
35
60
  client_id: options.client_id,
36
61
  client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
37
- client_assertion: generate_client_assertion
62
+ client_assertion: generate_client_assertion,
38
63
  }
39
64
  )
40
- response = @dpop_handler.make_request(
65
+ dpop_handler = AtProto::DpopHandler.new(options.private_key)
66
+ response = dpop_handler.make_request(
41
67
  client.token_url,
42
68
  :post,
43
69
  headers: { 'Content-Type' => 'application/json', 'Accept' => 'application/json' },
@@ -60,7 +86,6 @@ module OmniAuth
60
86
  else
61
87
  raise 'Invalid private_key format'
62
88
  end
63
-
64
89
  jwt_payload = {
65
90
  iss: options.client_id,
66
91
  sub: options.client_id,
@@ -81,6 +106,46 @@ module OmniAuth
81
106
  }
82
107
  )
83
108
  end
109
+
110
+ def self.get_authorization_server(pds_endpoint)
111
+ response = Faraday.get("#{pds_endpoint}/.well-known/oauth-protected-resource")
112
+
113
+ unless response.success?
114
+ fail!(:invalid_auth_server,
115
+ OmniAuth::Error.new(
116
+ "Failed to get PDS authorization server: #{response.status}"
117
+ ))
118
+ end
119
+
120
+ result = JSON.parse(response.body)
121
+
122
+ auth_server = result.dig('authorization_servers', 0)
123
+ unless auth_server
124
+ fail!(:invalid_auth_server,
125
+ OmniAuth::Error.new('No authorization server found in response'))
126
+ end
127
+ auth_server
128
+ end
129
+
130
+ def self.get_authorization_data(issuer)
131
+ response = Faraday.get("#{issuer}/.well-known/oauth-authorization-server")
132
+
133
+ unless response.success?
134
+ fail!(:invalid_metadata,
135
+ OmniAuth::Error.new(
136
+ "Failed to get authorization server metadata: #{response.status}"
137
+ ))
138
+ end
139
+ result = JSON.parse(response.body)
140
+
141
+ unless result['issuer'] == issuer
142
+ fail!(:invalid_metadata,
143
+ OmniAuth::Error.new('Invalid metadata - issuer mismatch'))
144
+ end
145
+ # we cannot keep everything in session (cookie overflow error)
146
+ fields = %w[issuer authorization_endpoint token_endpoint]
147
+ result.select { |k, _v| fields.include?(k) }
148
+ end
84
149
  end
85
150
  end
86
151
  end
@@ -1,5 +1,5 @@
1
1
  module OmniAuth
2
2
  module Atproto
3
- VERSION = "0.1.0"
3
+ VERSION = '0.1.2'
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.2
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-12-06 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