omniauth-apple2 1.0.0
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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +129 -0
- data/lib/omniauth/apple2/version.rb +7 -0
- data/lib/omniauth/apple2.rb +4 -0
- data/lib/omniauth/strategies/apple2.rb +257 -0
- data/lib/omniauth-apple2.rb +3 -0
- data/omniauth-apple2.gemspec +35 -0
- metadata +100 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 2ad52fcdf800abdbd15cdcac8562772a46789e397a76676a024a6d4191d4663a
|
|
4
|
+
data.tar.gz: 06f8523bf5fe15369050d1d3a3945df365c49ea51def9e80922dfd7b3f1fefb6
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: c9c95ee2731850f2bc2361250dfdbe3c7205bd34381bd42c632e9f1fd63ce72ab3f6698a5c8cd5a2f13c90bebaabb481ad302b8d129e47a4b5a438fce226423f
|
|
7
|
+
data.tar.gz: f1077a68f8797ec68b77957878cdf4717243fe3eb63ba69a222313277759c404f6c38bfed5ebaa885ec8bfdc1d2db90c10e2f45b2574c97e07ceeffe93080b5c
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 icoretech
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# OmniAuth Apple2 Strategy
|
|
2
|
+
|
|
3
|
+
[](https://github.com/icoretech/omniauth-apple2/actions/workflows/test.yml?query=branch%3Amain)
|
|
4
|
+
[](https://badge.fury.io/rb/omniauth-apple2)
|
|
5
|
+
|
|
6
|
+
`omniauth-apple2` provides a Sign in with Apple OAuth2 strategy for OmniAuth.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
Add this line to your application's Gemfile:
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
gem 'omniauth-apple2'
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Then run:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
bundle install
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
Configure OmniAuth in your Rack/Rails app:
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
use OmniAuth::Builder do
|
|
28
|
+
# Second positional arg is intentionally nil:
|
|
29
|
+
# client secret is generated internally from team_id/key_id/pem.
|
|
30
|
+
provider :apple,
|
|
31
|
+
ENV.fetch('APPLE_CLIENT_ID'),
|
|
32
|
+
nil,
|
|
33
|
+
team_id: ENV.fetch('APPLE_TEAM_ID'),
|
|
34
|
+
key_id: ENV.fetch('APPLE_KEY_ID'),
|
|
35
|
+
pem: ENV.fetch('APPLE_PRIVATE_KEY_PEM').gsub('\\n', "\n")
|
|
36
|
+
end
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`provider :apple2` is also supported. `provider :apple` exists for drop-in compatibility.
|
|
40
|
+
|
|
41
|
+
## Apple Key PEM Handling
|
|
42
|
+
|
|
43
|
+
Apple private keys are often stored in env vars with escaped newlines (`\\n`), which Ruby/OpenSSL cannot parse as a valid PEM until you normalize them.
|
|
44
|
+
|
|
45
|
+
Use this pattern:
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
pem: ENV.fetch('APPLE_PRIVATE_KEY_PEM').gsub('\\n', "\n")
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
If your secret manager supports multiline values, store the key as real multiline text and pass it directly without `gsub`.
|
|
52
|
+
|
|
53
|
+
Common parsing failures caused by unnormalized keys:
|
|
54
|
+
|
|
55
|
+
- `OpenSSL::PKey::ECError`
|
|
56
|
+
- `Neither PUB key nor PRIV key`
|
|
57
|
+
- `invalid curve name`
|
|
58
|
+
|
|
59
|
+
## Provider App Setup
|
|
60
|
+
|
|
61
|
+
- Apple Developer docs (Sign in with Apple REST API): <https://developer.apple.com/documentation/signinwithapplerestapi>
|
|
62
|
+
- Register callback URL (example): `https://your-app.example.com/auth/apple/callback`
|
|
63
|
+
|
|
64
|
+
## Options
|
|
65
|
+
|
|
66
|
+
- `scope`: default `email name`
|
|
67
|
+
- `response_mode`: default `form_post`
|
|
68
|
+
- `response_type`: default `code`
|
|
69
|
+
- `authorized_client_ids`: additional accepted `aud` values for `id_token` verification
|
|
70
|
+
- `callback_url` / `redirect_uri`: force exact redirect URI for token exchange
|
|
71
|
+
|
|
72
|
+
## Auth Hash
|
|
73
|
+
|
|
74
|
+
Example payload from `request.env['omniauth.auth']` (real flow shape, anonymized):
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"uid": "apple-user-id",
|
|
79
|
+
"info": {
|
|
80
|
+
"name": "Sample User",
|
|
81
|
+
"email": "sample@example.test",
|
|
82
|
+
"first_name": "Sample",
|
|
83
|
+
"last_name": "User",
|
|
84
|
+
"email_verified": true,
|
|
85
|
+
"is_private_email": false
|
|
86
|
+
},
|
|
87
|
+
"credentials": {
|
|
88
|
+
"token": "sample-access-token",
|
|
89
|
+
"refresh_token": "sample-refresh-token",
|
|
90
|
+
"expires": true,
|
|
91
|
+
"expires_at": 1773000000,
|
|
92
|
+
"scope": "email name"
|
|
93
|
+
},
|
|
94
|
+
"extra": {
|
|
95
|
+
"raw_info": {
|
|
96
|
+
"id_info": {
|
|
97
|
+
"sub": "apple-user-id",
|
|
98
|
+
"aud": "com.example.web",
|
|
99
|
+
"iss": "https://appleid.apple.com",
|
|
100
|
+
"email": "sample@example.test"
|
|
101
|
+
},
|
|
102
|
+
"user_info": {
|
|
103
|
+
"name": {
|
|
104
|
+
"firstName": "Sample",
|
|
105
|
+
"lastName": "User"
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
"id_token": "sample-id-token"
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Ruby and Rails Compatibility
|
|
115
|
+
|
|
116
|
+
- Ruby: `>= 3.2`
|
|
117
|
+
- Rails integration lanes in CI: `7.1`, `7.2`, `8.0`, `8.1`
|
|
118
|
+
|
|
119
|
+
## Development
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
bundle install
|
|
123
|
+
bundle exec rake lint test_unit
|
|
124
|
+
RAILS_VERSION='~> 8.1.0' bundle exec rake test_rails_integration
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
MIT
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'base64'
|
|
4
|
+
require 'jwt'
|
|
5
|
+
require 'net/http'
|
|
6
|
+
require 'omniauth-oauth2'
|
|
7
|
+
require 'openssl'
|
|
8
|
+
require 'securerandom'
|
|
9
|
+
require 'uri'
|
|
10
|
+
|
|
11
|
+
module OmniAuth
|
|
12
|
+
module Strategies
|
|
13
|
+
# OmniAuth strategy for Sign in with Apple.
|
|
14
|
+
class Apple2 < OmniAuth::Strategies::OAuth2
|
|
15
|
+
ISSUER = 'https://appleid.apple.com'
|
|
16
|
+
JWKS_URL = 'https://appleid.apple.com/auth/keys'
|
|
17
|
+
|
|
18
|
+
option :name, 'apple2'
|
|
19
|
+
option :authorize_options, %i[scope state response_mode response_type nonce]
|
|
20
|
+
option :scope, 'email name'
|
|
21
|
+
option :response_mode, 'form_post'
|
|
22
|
+
option :response_type, 'code'
|
|
23
|
+
option :authorized_client_ids, []
|
|
24
|
+
|
|
25
|
+
option :client_options,
|
|
26
|
+
site: ISSUER,
|
|
27
|
+
authorize_url: '/auth/authorize',
|
|
28
|
+
token_url: '/auth/token',
|
|
29
|
+
auth_scheme: :request_body,
|
|
30
|
+
connection_opts: {
|
|
31
|
+
headers: {
|
|
32
|
+
user_agent: 'icoretech-omniauth-apple2 gem',
|
|
33
|
+
accept: 'application/json'
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
uid { id_info['sub'] }
|
|
38
|
+
|
|
39
|
+
info do
|
|
40
|
+
{
|
|
41
|
+
name: full_name,
|
|
42
|
+
email: id_info['email'],
|
|
43
|
+
first_name: user_info.dig('name', 'firstName'),
|
|
44
|
+
last_name: user_info.dig('name', 'lastName'),
|
|
45
|
+
email_verified: true_claim?(id_info['email_verified']),
|
|
46
|
+
is_private_email: true_claim?(id_info['is_private_email'])
|
|
47
|
+
}.compact
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
credentials do
|
|
51
|
+
{
|
|
52
|
+
'token' => access_token.token,
|
|
53
|
+
'refresh_token' => access_token.refresh_token,
|
|
54
|
+
'expires_at' => access_token.expires_at,
|
|
55
|
+
'expires' => access_token.expires?,
|
|
56
|
+
'scope' => token_scope
|
|
57
|
+
}.compact
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
extra do
|
|
61
|
+
{
|
|
62
|
+
'raw_info' => {
|
|
63
|
+
'id_info' => id_info,
|
|
64
|
+
'user_info' => user_info,
|
|
65
|
+
'id_token' => raw_id_token
|
|
66
|
+
}.compact
|
|
67
|
+
}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def authorize_params
|
|
71
|
+
super.tap do |params|
|
|
72
|
+
params[:response_mode] ||= options[:response_mode]
|
|
73
|
+
params[:response_type] ||= options[:response_type]
|
|
74
|
+
params[:scope] ||= options[:scope]
|
|
75
|
+
params[:nonce] ||= new_nonce
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def callback_url
|
|
80
|
+
options[:callback_url] || options[:redirect_uri] || super
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def query_string
|
|
84
|
+
return '' if request.params['code']
|
|
85
|
+
|
|
86
|
+
super
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def client
|
|
90
|
+
::OAuth2::Client.new(client_id, client_secret, deep_symbolize(options.client_options))
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def id_info
|
|
96
|
+
@id_info ||= begin
|
|
97
|
+
token = raw_id_token
|
|
98
|
+
raise CallbackError.new(:id_token_missing, 'id_token is missing') if blank?(token)
|
|
99
|
+
|
|
100
|
+
decode_and_verify_id_token(token)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def user_info
|
|
105
|
+
raw_user = request.params['user']
|
|
106
|
+
return {} if blank?(raw_user)
|
|
107
|
+
|
|
108
|
+
@user_info ||= JSON.parse(raw_user)
|
|
109
|
+
rescue JSON::ParserError
|
|
110
|
+
{}
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def raw_id_token
|
|
114
|
+
request.params['id_token'] || access_token&.params&.dig('id_token')
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def full_name
|
|
118
|
+
parts = [user_info.dig('name', 'firstName'), user_info.dig('name', 'lastName')].compact
|
|
119
|
+
return parts.join(' ') unless parts.empty?
|
|
120
|
+
|
|
121
|
+
id_info['email']
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def token_scope
|
|
125
|
+
token_params = access_token.respond_to?(:params) ? access_token.params : {}
|
|
126
|
+
token_params['scope'] || (access_token['scope'] if access_token.respond_to?(:[]))
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def decode_and_verify_id_token(token)
|
|
130
|
+
jwk = fetch_jwk(extract_kid(token))
|
|
131
|
+
payload = decode_payload(token, jwk)
|
|
132
|
+
|
|
133
|
+
verify_nonce!(payload)
|
|
134
|
+
|
|
135
|
+
payload
|
|
136
|
+
rescue JSON::ParserError, ArgumentError, JWT::DecodeError => e
|
|
137
|
+
raise CallbackError.new(:id_token_invalid, e.message)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def verify_nonce!(payload)
|
|
141
|
+
return unless payload.key?('nonce')
|
|
142
|
+
|
|
143
|
+
expected_nonce = stored_nonce
|
|
144
|
+
return if payload['nonce'] == expected_nonce
|
|
145
|
+
|
|
146
|
+
raise CallbackError.new(:id_token_nonce_invalid, 'nonce does not match')
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def fetch_jwk(expected_kid)
|
|
150
|
+
jwks = fetch_jwks_keys
|
|
151
|
+
matching_key = jwks.find { |key| key['kid'] == expected_kid }
|
|
152
|
+
raise CallbackError.new(:jwks_key_not_found, expected_kid) unless matching_key
|
|
153
|
+
|
|
154
|
+
JWT::JWK.import(matching_key)
|
|
155
|
+
rescue JSON::ParserError, SocketError, SystemCallError => e
|
|
156
|
+
raise CallbackError.new(:jwks_fetch_failed, e.message)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def fetch_jwks_keys
|
|
160
|
+
uri = URI(JWKS_URL)
|
|
161
|
+
response = Net::HTTP.get_response(uri)
|
|
162
|
+
raise CallbackError.new(:jwks_fetch_failed, response.code) unless response.is_a?(Net::HTTPSuccess)
|
|
163
|
+
|
|
164
|
+
JSON.parse(response.body).fetch('keys', [])
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def valid_audiences
|
|
168
|
+
[options.client_id, *Array(options.authorized_client_ids)].compact
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def extract_kid(token)
|
|
172
|
+
header_segment = token.split('.').first
|
|
173
|
+
decoded_header = Base64.urlsafe_decode64(pad_base64(header_segment))
|
|
174
|
+
JSON.parse(decoded_header)['kid']
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def decode_payload(token, jwk)
|
|
178
|
+
payload, = JWT.decode(
|
|
179
|
+
token,
|
|
180
|
+
jwk.public_key,
|
|
181
|
+
true,
|
|
182
|
+
decode_options
|
|
183
|
+
)
|
|
184
|
+
payload
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def decode_options
|
|
188
|
+
{
|
|
189
|
+
algorithms: ['RS256'],
|
|
190
|
+
iss: ISSUER,
|
|
191
|
+
verify_iss: true,
|
|
192
|
+
aud: valid_audiences,
|
|
193
|
+
verify_aud: true,
|
|
194
|
+
verify_iat: true,
|
|
195
|
+
verify_expiration: true
|
|
196
|
+
}
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def client_id
|
|
200
|
+
options.client_id
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def client_secret
|
|
204
|
+
JWT.encode(client_secret_claims, private_key, 'ES256', client_secret_headers)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def private_key
|
|
208
|
+
OpenSSL::PKey::EC.new(options.pem)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def client_secret_claims
|
|
212
|
+
now = Time.now.to_i
|
|
213
|
+
{
|
|
214
|
+
iss: options.team_id,
|
|
215
|
+
iat: now,
|
|
216
|
+
exp: now + 60,
|
|
217
|
+
aud: ISSUER,
|
|
218
|
+
sub: client_id
|
|
219
|
+
}
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def client_secret_headers
|
|
223
|
+
{
|
|
224
|
+
kid: options.key_id
|
|
225
|
+
}
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def new_nonce
|
|
229
|
+
session['omniauth.nonce'] = SecureRandom.urlsafe_base64(16)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def stored_nonce
|
|
233
|
+
session.delete('omniauth.nonce')
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def true_claim?(value)
|
|
237
|
+
[true, 'true'].include?(value)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def blank?(value)
|
|
241
|
+
value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def pad_base64(value)
|
|
245
|
+
value + ('=' * ((4 - (value.length % 4)) % 4))
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Backward-compatible strategy name for existing callback paths.
|
|
250
|
+
class Apple < Apple2
|
|
251
|
+
option :name, 'apple'
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
OmniAuth.config.add_camelization 'apple2', 'Apple2'
|
|
257
|
+
OmniAuth.config.add_camelization 'apple', 'Apple'
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
|
+
require 'omniauth/apple2/version'
|
|
6
|
+
|
|
7
|
+
Gem::Specification.new do |spec|
|
|
8
|
+
spec.name = 'omniauth-apple2'
|
|
9
|
+
spec.version = OmniAuth::Apple2::VERSION
|
|
10
|
+
spec.authors = ['Claudio Poli']
|
|
11
|
+
spec.email = ['masterkain@gmail.com']
|
|
12
|
+
|
|
13
|
+
spec.summary = 'OmniAuth strategy for Sign in with Apple authentication.'
|
|
14
|
+
spec.description = 'OAuth2 strategy for OmniAuth that authenticates users with Sign in with Apple.'
|
|
15
|
+
spec.homepage = 'https://github.com/icoretech/omniauth-apple2'
|
|
16
|
+
spec.license = 'MIT'
|
|
17
|
+
spec.required_ruby_version = '>= 3.2'
|
|
18
|
+
|
|
19
|
+
spec.metadata['source_code_uri'] = 'https://github.com/icoretech/omniauth-apple2'
|
|
20
|
+
spec.metadata['bug_tracker_uri'] = 'https://github.com/icoretech/omniauth-apple2/issues'
|
|
21
|
+
spec.metadata['changelog_uri'] = 'https://github.com/icoretech/omniauth-apple2/releases'
|
|
22
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
23
|
+
|
|
24
|
+
spec.files = Dir[
|
|
25
|
+
'lib/**/*.rb',
|
|
26
|
+
'README*',
|
|
27
|
+
'LICENSE*',
|
|
28
|
+
'*.gemspec'
|
|
29
|
+
]
|
|
30
|
+
spec.require_paths = ['lib']
|
|
31
|
+
|
|
32
|
+
spec.add_dependency 'cgi', '>= 0.3.6'
|
|
33
|
+
spec.add_dependency 'jwt', '>= 2.8'
|
|
34
|
+
spec.add_dependency 'omniauth-oauth2', '>= 1.8', '< 2.0'
|
|
35
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: omniauth-apple2
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Claudio Poli
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: cgi
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 0.3.6
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 0.3.6
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: jwt
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.8'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '2.8'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: omniauth-oauth2
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.8'
|
|
47
|
+
- - "<"
|
|
48
|
+
- !ruby/object:Gem::Version
|
|
49
|
+
version: '2.0'
|
|
50
|
+
type: :runtime
|
|
51
|
+
prerelease: false
|
|
52
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
53
|
+
requirements:
|
|
54
|
+
- - ">="
|
|
55
|
+
- !ruby/object:Gem::Version
|
|
56
|
+
version: '1.8'
|
|
57
|
+
- - "<"
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '2.0'
|
|
60
|
+
description: OAuth2 strategy for OmniAuth that authenticates users with Sign in with
|
|
61
|
+
Apple.
|
|
62
|
+
email:
|
|
63
|
+
- masterkain@gmail.com
|
|
64
|
+
executables: []
|
|
65
|
+
extensions: []
|
|
66
|
+
extra_rdoc_files: []
|
|
67
|
+
files:
|
|
68
|
+
- LICENSE.txt
|
|
69
|
+
- README.md
|
|
70
|
+
- lib/omniauth-apple2.rb
|
|
71
|
+
- lib/omniauth/apple2.rb
|
|
72
|
+
- lib/omniauth/apple2/version.rb
|
|
73
|
+
- lib/omniauth/strategies/apple2.rb
|
|
74
|
+
- omniauth-apple2.gemspec
|
|
75
|
+
homepage: https://github.com/icoretech/omniauth-apple2
|
|
76
|
+
licenses:
|
|
77
|
+
- MIT
|
|
78
|
+
metadata:
|
|
79
|
+
source_code_uri: https://github.com/icoretech/omniauth-apple2
|
|
80
|
+
bug_tracker_uri: https://github.com/icoretech/omniauth-apple2/issues
|
|
81
|
+
changelog_uri: https://github.com/icoretech/omniauth-apple2/releases
|
|
82
|
+
rubygems_mfa_required: 'true'
|
|
83
|
+
rdoc_options: []
|
|
84
|
+
require_paths:
|
|
85
|
+
- lib
|
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
87
|
+
requirements:
|
|
88
|
+
- - ">="
|
|
89
|
+
- !ruby/object:Gem::Version
|
|
90
|
+
version: '3.2'
|
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0'
|
|
96
|
+
requirements: []
|
|
97
|
+
rubygems_version: 3.6.9
|
|
98
|
+
specification_version: 4
|
|
99
|
+
summary: OmniAuth strategy for Sign in with Apple authentication.
|
|
100
|
+
test_files: []
|