atproto_auth 0.0.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.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +16 -0
  3. data/CHANGELOG.md +5 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +179 -0
  6. data/Rakefile +16 -0
  7. data/examples/confidential_client/Gemfile +12 -0
  8. data/examples/confidential_client/Gemfile.lock +84 -0
  9. data/examples/confidential_client/README.md +110 -0
  10. data/examples/confidential_client/app.rb +136 -0
  11. data/examples/confidential_client/config/client-metadata.json +25 -0
  12. data/examples/confidential_client/config.ru +4 -0
  13. data/examples/confidential_client/public/client-metadata.json +24 -0
  14. data/examples/confidential_client/public/styles.css +70 -0
  15. data/examples/confidential_client/scripts/generate_keys.rb +15 -0
  16. data/examples/confidential_client/views/authorized.erb +29 -0
  17. data/examples/confidential_client/views/index.erb +44 -0
  18. data/examples/confidential_client/views/layout.erb +11 -0
  19. data/lib/atproto_auth/client.rb +410 -0
  20. data/lib/atproto_auth/client_metadata.rb +264 -0
  21. data/lib/atproto_auth/configuration.rb +17 -0
  22. data/lib/atproto_auth/dpop/client.rb +122 -0
  23. data/lib/atproto_auth/dpop/key_manager.rb +235 -0
  24. data/lib/atproto_auth/dpop/nonce_manager.rb +138 -0
  25. data/lib/atproto_auth/dpop/proof_generator.rb +112 -0
  26. data/lib/atproto_auth/errors.rb +47 -0
  27. data/lib/atproto_auth/http_client.rb +227 -0
  28. data/lib/atproto_auth/identity/document.rb +104 -0
  29. data/lib/atproto_auth/identity/resolver.rb +221 -0
  30. data/lib/atproto_auth/identity.rb +24 -0
  31. data/lib/atproto_auth/par/client.rb +203 -0
  32. data/lib/atproto_auth/par/client_assertion.rb +50 -0
  33. data/lib/atproto_auth/par/request.rb +140 -0
  34. data/lib/atproto_auth/par/response.rb +23 -0
  35. data/lib/atproto_auth/par.rb +40 -0
  36. data/lib/atproto_auth/pkce.rb +105 -0
  37. data/lib/atproto_auth/server_metadata/authorization_server.rb +175 -0
  38. data/lib/atproto_auth/server_metadata/origin_url.rb +51 -0
  39. data/lib/atproto_auth/server_metadata/resource_server.rb +71 -0
  40. data/lib/atproto_auth/server_metadata.rb +24 -0
  41. data/lib/atproto_auth/state/session.rb +117 -0
  42. data/lib/atproto_auth/state/session_manager.rb +75 -0
  43. data/lib/atproto_auth/state/token_set.rb +68 -0
  44. data/lib/atproto_auth/state.rb +54 -0
  45. data/lib/atproto_auth/version.rb +5 -0
  46. data/lib/atproto_auth.rb +56 -0
  47. data/sig/atproto_auth/client_metadata.rbs +95 -0
  48. data/sig/atproto_auth/dpop/client.rbs +38 -0
  49. data/sig/atproto_auth/dpop/key_manager.rbs +33 -0
  50. data/sig/atproto_auth/dpop/nonce_manager.rbs +48 -0
  51. data/sig/atproto_auth/dpop/proof_generator.rbs +42 -0
  52. data/sig/atproto_auth/http_client.rbs +58 -0
  53. data/sig/atproto_auth/identity/document.rbs +31 -0
  54. data/sig/atproto_auth/identity/resolver.rbs +41 -0
  55. data/sig/atproto_auth/par/client.rbs +31 -0
  56. data/sig/atproto_auth/par/request.rbs +73 -0
  57. data/sig/atproto_auth/par/response.rbs +17 -0
  58. data/sig/atproto_auth/pkce.rbs +24 -0
  59. data/sig/atproto_auth/server_metadata/authorization_server.rbs +69 -0
  60. data/sig/atproto_auth/server_metadata/origin_url.rbs +21 -0
  61. data/sig/atproto_auth/server_metadata/resource_server.rbs +27 -0
  62. data/sig/atproto_auth/state/session.rbs +50 -0
  63. data/sig/atproto_auth/state/session_manager.rbs +26 -0
  64. data/sig/atproto_auth/state/token_set.rbs +40 -0
  65. data/sig/atproto_auth/version.rbs +3 -0
  66. data/sig/atproto_auth.rbs +39 -0
  67. metadata +142 -0
@@ -0,0 +1,69 @@
1
+ module AtprotoAuth
2
+ module ServerMetadata
3
+ class AuthorizationServer
4
+ @issuer: String
5
+ @authorization_endpoint: String
6
+ @token_endpoint: String
7
+ @pushed_authorization_request_endpoint: String
8
+ @response_types_supported: Array[String]
9
+ @grant_types_supported: Array[String]
10
+ @code_challenge_methods_supported: Array[String]
11
+ @token_endpoint_auth_methods_supported: Array[String]
12
+ @token_endpoint_auth_signing_alg_values_supported: Array[String]
13
+ @scopes_supported: Array[String]
14
+ @dpop_signing_alg_values_supported: Array[String]
15
+
16
+ REQUIRED_FIELDS: ::Array["issuer" | "authorization_endpoint" | "token_endpoint" | "response_types_supported" | "grant_types_supported" | "code_challenge_methods_supported" | "token_endpoint_auth_methods_supported" | "token_endpoint_auth_signing_alg_values_supported" | "scopes_supported" | "dpop_signing_alg_values_supported" | "pushed_authorization_request_endpoint"]
17
+
18
+ attr_reader issuer: String
19
+ attr_reader authorization_endpoint: String
20
+ attr_reader token_endpoint: String
21
+ attr_reader pushed_authorization_request_endpoint: String
22
+ attr_reader response_types_supported: Array[String]
23
+ attr_reader grant_types_supported: Array[String]
24
+ attr_reader code_challenge_methods_supported: Array[String]
25
+ attr_reader token_endpoint_auth_methods_supported: Array[String]
26
+ attr_reader token_endpoint_auth_signing_alg_values_supported: Array[String]
27
+ attr_reader scopes_supported: Array[String]
28
+ attr_reader dpop_signing_alg_values_supported: Array[String]
29
+
30
+ def initialize: (Hash[String, untyped] metadata) -> void
31
+
32
+ # Fetches and validates Authorization Server metadata from an issuer URL
33
+ # @param issuer [String] Authorization Server issuer URL
34
+ # @return [AuthorizationServer] new instance with fetched metadata
35
+ # @raise [InvalidAuthorizationServer] if metadata is invalid
36
+ def self.from_issuer: (String issuer) -> AuthorizationServer
37
+
38
+ private
39
+
40
+ def validate_and_set_metadata!: (Hash[String, untyped] metadata) -> void
41
+
42
+ def validate_issuer!: (String issuer) -> String
43
+
44
+ def validate_https_url!: (String url) -> String
45
+
46
+ def validate_response_types!: (Array[String] types) -> void
47
+
48
+ def validate_grant_types!: (Array[String] types) -> void
49
+
50
+ def validate_code_challenge_methods!: (Array[String] methods) -> void
51
+
52
+ def validate_token_endpoint_auth_methods!: (Array[String] methods) -> void
53
+
54
+ def validate_token_endpoint_auth_signing_algs!: (Array[String] algs) -> void
55
+
56
+ def validate_dpop_signing_algs!: (Array[String] algs) -> void
57
+
58
+ def validate_scopes!: (Array[String] scopes) -> void
59
+
60
+ def validate_boolean_field!: (Hash[String, untyped] metadata, String field, bool required_value) -> void
61
+
62
+ def self.fetch_metadata: (String issuer) -> Hash[Symbol, String]
63
+
64
+ def self.parse_metadata: (String body) -> Hash[String, untyped]
65
+
66
+ def self.validate_issuer!: (String metadata_issuer, String request_issuer) -> void
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,21 @@
1
+ module AtprotoAuth
2
+ module ServerMetadata
3
+ class OriginUrl
4
+ @url: String
5
+ @uri: URI
6
+
7
+ attr_reader url: String
8
+ attr_reader uri: URI
9
+
10
+ def initialize: (String url) -> void
11
+
12
+ def valid?: () -> bool
13
+
14
+ private
15
+
16
+ def uses_https_scheme?: () -> bool
17
+ def has_root_path?: () -> bool
18
+ def has_explicit_port?: () -> bool
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,27 @@
1
+ module AtprotoAuth
2
+ module ServerMetadata
3
+ class ResourceServer
4
+ @authorization_servers: Array[String]
5
+
6
+ attr_reader authorization_servers: Array[String]
7
+
8
+ def initialize: (Hash[String, untyped] metadata) -> void
9
+
10
+ def self.from_url: (String url) -> ResourceServer
11
+
12
+ private
13
+
14
+ def validate_authorization_servers!: (Array[String] servers) -> Array[String]
15
+
16
+ def ensure_servers_exist: (Array[String] | nil) -> void
17
+
18
+ def ensure_exactly_one_server: (Array[String]) -> void
19
+
20
+ def validate_server_url_format: (String server_url) -> void
21
+
22
+ def self.fetch_metadata: (String url) -> Hash[Symbol, String]
23
+
24
+ def self.parse_metadata: (String body) -> Hash[String, untyped]
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,50 @@
1
+ module AtprotoAuth
2
+ module State
3
+ class Session
4
+ @session_id: String
5
+ @state_token: String
6
+ @client_id: String
7
+ @scope: String
8
+ @auth_server: AuthorizationServer?
9
+ @did: String?
10
+ @pkce_verifier: String
11
+ @pkce_challenge: String
12
+ @tokens: TokenSet?
13
+
14
+ include MonitorMixin
15
+
16
+ attr_reader session_id: String
17
+ attr_reader state_token: String
18
+ attr_reader client_id: String
19
+ attr_reader scope: String
20
+ attr_reader pkce_verifier: String
21
+ attr_reader pkce_challenge: String
22
+ attr_reader auth_server: AuthorizationServer?
23
+ attr_reader did: String?
24
+ attr_reader tokens: TokenSet?
25
+
26
+ def initialize: (
27
+ client_id: String,
28
+ scope: String,
29
+ ?auth_server: AuthorizationServer?,
30
+ ?did: String?
31
+ ) -> void
32
+
33
+ def authorization_server=: (AuthorizationServer server) -> void
34
+
35
+ def did=: (String did) -> void
36
+
37
+ def tokens=: (TokenSet tokens) -> void
38
+
39
+ def authorized?: () -> bool
40
+
41
+ def renewable?: () -> bool
42
+
43
+ def validate_state: (String state) -> bool
44
+
45
+ private
46
+
47
+ def secure_compare: (String str1, String str2) -> bool
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,26 @@
1
+ module AtprotoAuth
2
+ module State
3
+ class SessionManager
4
+ @sessions: Hash[String, Session]
5
+
6
+ include MonitorMixin
7
+
8
+ def initialize: () -> void
9
+
10
+ def create_session: (
11
+ client_id: String,
12
+ scope: String,
13
+ ?auth_server: ServerMetadata::AuthorizationServer?,
14
+ ?did: String?
15
+ ) -> Session
16
+
17
+ def get_session: (String session_id) -> Session?
18
+
19
+ def get_session_by_state: (String state) -> Session?
20
+
21
+ def remove_session: (String session_id) -> void
22
+
23
+ def cleanup_expired: () -> void
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,40 @@
1
+ module AtprotoAuth
2
+ module State
3
+ class TokenSet
4
+ @access_token: String
5
+ @refresh_token: String?
6
+ @token_type: String
7
+ @scope: String
8
+ @sub: String
9
+ @expires_at: Time
10
+
11
+ attr_reader access_token: String
12
+ attr_reader refresh_token: String?
13
+ attr_reader token_type: String
14
+ attr_reader scope: String
15
+ attr_reader expires_at: Time
16
+ attr_reader sub: String
17
+
18
+ def initialize: (
19
+ access_token: String,
20
+ token_type: String,
21
+ expires_in: Integer,
22
+ scope: String,
23
+ sub: String,
24
+ ?refresh_token: String?
25
+ ) -> void
26
+
27
+ def renewable?: () -> bool
28
+
29
+ def expired?: (?Integer buffer) -> bool
30
+
31
+ private
32
+
33
+ def validate_token_type!: (String type) -> void
34
+
35
+ def validate_required!: (String name, String? value) -> void
36
+
37
+ def validate_expires_in!: (Integer expires_in) -> void
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,3 @@
1
+ module AtprotoAuth
2
+ VERSION: String
3
+ end
@@ -0,0 +1,39 @@
1
+ interface _HTTPClient
2
+ def get: (String, ?Hash[Symbol, untyped]) -> { status: Integer, body: String, headers: Hash[String, String] }
3
+ def post: (String, ?Hash[Symbol, untyped]) -> { status: Integer, body: String, headers: Hash[String, String] }
4
+ def put: (String, ?Hash[Symbol, untyped]) -> { status: Integer, body: String, headers: Hash[String, String] }
5
+ def delete: (String, ?Hash[Symbol, untyped]) -> { status: Integer, body: String, headers: Hash[String, String] }
6
+ end
7
+
8
+ module AtprotoAuth
9
+ class Error < StandardError
10
+ end
11
+
12
+ class OAuthError
13
+ attr_reader error_code: String
14
+
15
+ def initialize: (String message, String error_code) -> void
16
+ end
17
+
18
+ class InvalidClientMetadata < OAuthError
19
+ def initialize: (String message) -> void
20
+ end
21
+
22
+ class InvalidAuthorizationServer < OAuthError
23
+ def initialize: (String message) -> void
24
+ end
25
+
26
+ class Configuration
27
+ attr_accessor default_token_lifetime: Integer
28
+ attr_accessor dpop_nonce_lifetime: Integer
29
+ attr_accessor http_client: _HTTPClient?
30
+
31
+ def initialize: () -> void
32
+ end
33
+
34
+ attr_writer self.configuration: Configuration
35
+
36
+ def self.configuration: () -> Configuration
37
+
38
+ def self.configure: () { (Configuration) -> untyped } -> Configuration
39
+ end
metadata ADDED
@@ -0,0 +1,142 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: atproto_auth
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Josh Huckabee
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-12-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: jose
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: jwt
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.9'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.9'
41
+ description: A Ruby library for implementing AT Protocol OAuth flows, including DPoP,
42
+ PAR, and dynamic client registration. Supports both client and server-side implementations
43
+ with comprehensive security features.
44
+ email:
45
+ - mail@joshhuckabee.com
46
+ executables: []
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - ".rubocop.yml"
51
+ - CHANGELOG.md
52
+ - LICENSE.txt
53
+ - README.md
54
+ - Rakefile
55
+ - examples/confidential_client/Gemfile
56
+ - examples/confidential_client/Gemfile.lock
57
+ - examples/confidential_client/README.md
58
+ - examples/confidential_client/app.rb
59
+ - examples/confidential_client/config.ru
60
+ - examples/confidential_client/config/client-metadata.json
61
+ - examples/confidential_client/public/client-metadata.json
62
+ - examples/confidential_client/public/styles.css
63
+ - examples/confidential_client/scripts/generate_keys.rb
64
+ - examples/confidential_client/views/authorized.erb
65
+ - examples/confidential_client/views/index.erb
66
+ - examples/confidential_client/views/layout.erb
67
+ - lib/atproto_auth.rb
68
+ - lib/atproto_auth/client.rb
69
+ - lib/atproto_auth/client_metadata.rb
70
+ - lib/atproto_auth/configuration.rb
71
+ - lib/atproto_auth/dpop/client.rb
72
+ - lib/atproto_auth/dpop/key_manager.rb
73
+ - lib/atproto_auth/dpop/nonce_manager.rb
74
+ - lib/atproto_auth/dpop/proof_generator.rb
75
+ - lib/atproto_auth/errors.rb
76
+ - lib/atproto_auth/http_client.rb
77
+ - lib/atproto_auth/identity.rb
78
+ - lib/atproto_auth/identity/document.rb
79
+ - lib/atproto_auth/identity/resolver.rb
80
+ - lib/atproto_auth/par.rb
81
+ - lib/atproto_auth/par/client.rb
82
+ - lib/atproto_auth/par/client_assertion.rb
83
+ - lib/atproto_auth/par/request.rb
84
+ - lib/atproto_auth/par/response.rb
85
+ - lib/atproto_auth/pkce.rb
86
+ - lib/atproto_auth/server_metadata.rb
87
+ - lib/atproto_auth/server_metadata/authorization_server.rb
88
+ - lib/atproto_auth/server_metadata/origin_url.rb
89
+ - lib/atproto_auth/server_metadata/resource_server.rb
90
+ - lib/atproto_auth/state.rb
91
+ - lib/atproto_auth/state/session.rb
92
+ - lib/atproto_auth/state/session_manager.rb
93
+ - lib/atproto_auth/state/token_set.rb
94
+ - lib/atproto_auth/version.rb
95
+ - sig/atproto_auth.rbs
96
+ - sig/atproto_auth/client_metadata.rbs
97
+ - sig/atproto_auth/dpop/client.rbs
98
+ - sig/atproto_auth/dpop/key_manager.rbs
99
+ - sig/atproto_auth/dpop/nonce_manager.rbs
100
+ - sig/atproto_auth/dpop/proof_generator.rbs
101
+ - sig/atproto_auth/http_client.rbs
102
+ - sig/atproto_auth/identity/document.rbs
103
+ - sig/atproto_auth/identity/resolver.rbs
104
+ - sig/atproto_auth/par/client.rbs
105
+ - sig/atproto_auth/par/request.rbs
106
+ - sig/atproto_auth/par/response.rbs
107
+ - sig/atproto_auth/pkce.rbs
108
+ - sig/atproto_auth/server_metadata/authorization_server.rbs
109
+ - sig/atproto_auth/server_metadata/origin_url.rbs
110
+ - sig/atproto_auth/server_metadata/resource_server.rbs
111
+ - sig/atproto_auth/state/session.rbs
112
+ - sig/atproto_auth/state/session_manager.rbs
113
+ - sig/atproto_auth/state/token_set.rbs
114
+ - sig/atproto_auth/version.rbs
115
+ homepage: https://github.com/jhuckabee/atproto_auth
116
+ licenses:
117
+ - MIT
118
+ metadata:
119
+ homepage_uri: https://github.com/jhuckabee/atproto_auth
120
+ source_code_uri: https://github.com/jhuckabee/atproto_auth
121
+ changelog_uri: https://github.com/jhuckabee/atproto_auth/blob/main/CHANGELOG.md
122
+ rubygems_mfa_required: 'true'
123
+ post_install_message:
124
+ rdoc_options: []
125
+ require_paths:
126
+ - lib
127
+ required_ruby_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: 3.0.0
132
+ required_rubygems_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ requirements: []
138
+ rubygems_version: 3.5.23
139
+ signing_key:
140
+ specification_version: 4
141
+ summary: Ruby implementation of the AT Protocol OAuth specification
142
+ test_files: []