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 +4 -4
- data/README.md +20 -3
- data/lib/omniauth/strategies/atproto.rb +76 -11
- data/lib/omniauth-atproto/version.rb +1 -1
- metadata +29 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2dacca65da2377668999f5815835a51e76d4d04e5e0cdcf6a00098a52595fef1
|
4
|
+
data.tar.gz: a8118eaa5ddc0783e1ffdef5dc247249dff770448b815f4d41f22084ebf00159
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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.
|
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
|
+
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:
|
28
|
+
name: didkit
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
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: '
|
40
|
+
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
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: '
|
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: '
|
68
|
+
version: '2.7'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
|
-
name: omniauth-
|
70
|
+
name: omniauth-oauth2
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
58
72
|
requirements:
|
59
|
-
- - "
|
73
|
+
- - "~>"
|
60
74
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
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: '
|
82
|
+
version: '1.8'
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: bundler
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|