apple_id 0.0.1 → 0.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 57f3b090e123b3239c7ee81b0d54fb532d1cded860820d69f66985a5c764c789
4
- data.tar.gz: 6f89d4c1685fd2b59220aab71e3a11d852ac75aaf6422c3b959626482467bb0f
3
+ metadata.gz: e000f6003a2baeecbb6683a9636b311cac3eec9ec20df988f79f0bbd50231135
4
+ data.tar.gz: e4890bdb603981bcd9d4489915e607029490c221210816315f96dd3d6fccfd9d
5
5
  SHA512:
6
- metadata.gz: bbe0117b9c67246b03b6814c5ba75f1fd7f893129cfdcac09d5ba9aa731f7f3f462cb233634a75acd6edd38ffeb34982a37b4591278271654a11a91d79d11170
7
- data.tar.gz: 7541063a289eec294bcaadd363f6bbb23f937c42fd1334e6ff7be8a1e4a24e945cb62268cb91da896e256eca3060ea1bd402a9cee96b4566397a05b286453032
6
+ metadata.gz: 1bb4b14f28e1906cf6517e2382422a5dd44c864479ea550bf2ad54a2758b166ab0464e0e04539f0e2f26c592daae932eba81d4f82f508b0aae2d45f956d1bcce
7
+ data.tar.gz: 406ae5c6dca6868bfac746b83b3422b1a92876e6f6d75ced236f2b653b88e8d6469ca5544b33afae387d8bca78aa2237608e1557022daff8d3f334667c8e1663
data/README.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # AppleID
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/apple_id`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ "Sign-in with Apple" is an implementation of OpenID Connect with small custom features.
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
5
+ This gem handles such custom features.
6
+
7
+ Basically, this gem is based on my [OpenID Connect gem](https://github.com/nov/openid_connect) and [OAuth2 gem](https://github.com/nov/rack-oauth2), so the usage is almost same with them.
6
8
 
7
9
  ## Installation
8
10
 
@@ -22,7 +24,54 @@ Or install it yourself as:
22
24
 
23
25
  ## Usage
24
26
 
25
- TODO: Write usage instructions here
27
+ There is [a sample rails app](https://github.com/nov/signin-with-apple) running at [signin-with-apple.herokuapp.com](https://signin-with-apple.herokuapp.com).
28
+
29
+ If you run script in your terminal only, do like below.
30
+
31
+ ```ruby
32
+ require 'apple_id'
33
+
34
+ # NOTE: in debugging mode, you can see all HTTPS request & response in the log.
35
+ # AppleID.debug!
36
+
37
+ pem = <<-PEM
38
+ -----BEGIN PRIVATE KEY-----
39
+ :
40
+ :
41
+ -----END PRIVATE KEY-----
42
+ PEM
43
+ private_key = OpenSSL::PKey::EC.new pem
44
+
45
+ client = AppleID::Client.new(
46
+ identifier: '<YOUR-CLIENT-ID>',
47
+ team_id: '<YOUR-TEAM-ID>',
48
+ key_id: '<YOUR-KEY-ID>',
49
+ private_key: private_key,
50
+ redirect_uri: '<YOUR-REDIRECT-URI>'
51
+ )
52
+
53
+ authorization_uri = client.authorization_uri(scope: [:email, :name])
54
+ puts authorization_uri
55
+ `open "#{authorization_uri}"`
56
+
57
+ print 'code: ' and STDOUT.flush
58
+ code = gets.chop
59
+
60
+ client.authorization_code = code
61
+ response = client.access_token!
62
+
63
+ response.id_token.verify!(
64
+ client,
65
+ access_token: response.access_token,
66
+
67
+ # NOTE:
68
+ # When verifying signature, one http request to Apple's JWKs are required.
69
+ # You can skip ID Token signature verification when you got the token directly from the token endpoint in TLS channel.
70
+ verify_signature: false
71
+ )
72
+ puts response.id_token.sub # => OpenID Connect Subject Identifier (= Apple User ID)
73
+ puts response.id_token.original_jwt.pretty_generate
74
+ ```
26
75
 
27
76
  ## Development
28
77
 
data/Rakefile CHANGED
@@ -1,6 +1,19 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
3
 
4
+ require 'rspec/core/rake_task'
4
5
  RSpec::Core::RakeTask.new(:spec)
5
6
 
7
+ namespace :coverage do
8
+ desc 'Open coverage report'
9
+ task :report do
10
+ require 'simplecov'
11
+ `open "#{File.join SimpleCov.coverage_path, 'index.html'}"`
12
+ end
13
+ end
14
+
15
+ task :spec do
16
+ Rake::Task[:'coverage:report'].invoke unless ENV['TRAVIS_RUBY_VERSION']
17
+ end
18
+
6
19
  task :default => :spec
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.1
1
+ 0.1.0
@@ -20,7 +20,10 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_runtime_dependency 'rack-oauth2', '~> 1.9.3'
22
22
  spec.add_runtime_dependency 'openid_connect', '~> 1.0'
23
- spec.add_development_dependency 'bundler', '~> 1.17'
24
- spec.add_development_dependency 'rake', '~> 10.0'
25
- spec.add_development_dependency 'rspec', '~> 3.0'
23
+ spec.add_development_dependency 'bundler'
24
+ spec.add_development_dependency 'rake'
25
+ spec.add_development_dependency 'rspec'
26
+ spec.add_development_dependency 'rspec-its'
27
+ spec.add_development_dependency 'webmock'
28
+ spec.add_development_dependency 'simplecov'
26
29
  end
@@ -2,6 +2,7 @@ require 'openid_connect'
2
2
 
3
3
  module AppleID
4
4
  ISSUER = 'https://appleid.apple.com'
5
+ JWKS_URI = 'https://appleid.apple.com/auth/keys'
5
6
 
6
7
  VERSION = ::File.read(
7
8
  ::File.join(::File.dirname(__FILE__), '../VERSION')
@@ -50,4 +51,6 @@ module AppleID
50
51
  self.debugging = false
51
52
  end
52
53
 
53
- require 'apple_id/client'
54
+ require 'apple_id/client'
55
+ require 'apple_id/access_token'
56
+ require 'apple_id/id_token'
@@ -0,0 +1,10 @@
1
+ module AppleID
2
+ class AccessToken < OpenIDConnect::AccessToken
3
+ undef_required_attributes :client
4
+
5
+ def initialize(access_token, attributes = {})
6
+ super attributes.merge(access_token: access_token)
7
+ self.id_token = IdToken.decode(id_token) if id_token.present?
8
+ end
9
+ end
10
+ end
@@ -1,12 +1,13 @@
1
1
  module AppleID
2
2
  class Client < OpenIDConnect::Client
3
+ class Error < Rack::OAuth2::Client::Error; end
4
+
3
5
  attr_required :team_id, :key_id, :private_key
4
6
 
5
7
  def initialize(attributes)
6
8
  attributes_with_default = {
7
- host: 'appleid.apple.com',
8
- authorization_endpoint: '/auth/authorize',
9
- token_endpoint: '/auth/token'
9
+ authorization_endpoint: File.join(ISSUER, '/auth/authorize'),
10
+ token_endpoint: File.join(ISSUER, '/auth/token')
10
11
  }.merge(attributes)
11
12
  super attributes_with_default
12
13
  end
@@ -21,7 +22,7 @@ module AppleID
21
22
  def client_secret_jwt
22
23
  jwt = JSON::JWT.new(
23
24
  iss: team_id,
24
- aud: AppleID::ISSUER,
25
+ aud: ISSUER,
25
26
  sub: identifier,
26
27
  iat: Time.now,
27
28
  exp: 1.minutes.from_now
@@ -29,5 +30,22 @@ module AppleID
29
30
  jwt.kid = key_id
30
31
  jwt.sign(private_key)
31
32
  end
33
+
34
+ def setup_required_scope(scopes)
35
+ # NOTE:
36
+ # openid_connect gem add `openid` scope automatically.
37
+ # However, it's not required for Sign-in with Apple.
38
+ scopes
39
+ end
40
+
41
+ def handle_success_response(response)
42
+ token_hash = JSON.parse(response.body).with_indifferent_access
43
+ AccessToken.new token_hash.delete(:access_token), token_hash
44
+ end
45
+
46
+ def handle_error_response(response)
47
+ error = JSON.parse(response.body).with_indifferent_access
48
+ raise Error.new(response.status, error)
49
+ end
32
50
  end
33
- end
51
+ end
@@ -0,0 +1,45 @@
1
+ module AppleID
2
+ class IdToken < OpenIDConnect::ResponseObject::IdToken
3
+ class VerificationFailed < StandardError; end
4
+
5
+ alias_method :original_jwt, :raw_attributes
6
+
7
+ def verify!(expected_client, access_token: nil, code: nil, verify_signature: true)
8
+ verify_signature! if verify_signature
9
+ verify_claims! expected_client, access_token, code
10
+ self
11
+ end
12
+
13
+ class << self
14
+ def decode(jwt_string)
15
+ super jwt_string, :skip_verification
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def jwks
22
+ @jwks ||= JSON.parse(
23
+ OpenIDConnect.http_client.get_content(JWKS_URI)
24
+ ).with_indifferent_access
25
+ JSON::JWK::Set.new @jwks[:keys]
26
+ end
27
+
28
+ def verify_signature!
29
+ original_jwt.verify! jwks
30
+ rescue
31
+ raise VerificationFailed, 'Signature Verification Failed'
32
+ end
33
+
34
+ def verify_claims!(expected_client, access_token, code)
35
+ # TODO: verify at_hash & c_hash
36
+ unless (
37
+ iss == ISSUER &&
38
+ aud == expected_client.identifier &&
39
+ Time.now.to_i.between?(iat, exp)
40
+ )
41
+ raise VerificationFailed, 'Claims Verification Failed'
42
+ end
43
+ end
44
+ end
45
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: apple_id
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - nov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-06-04 00:00:00.000000000 Z
11
+ date: 2019-06-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack-oauth2
@@ -42,44 +42,86 @@ dependencies:
42
42
  name: bundler
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '1.17'
47
+ version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '1.17'
54
+ version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rake
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: '10.0'
61
+ version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - "~>"
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: '10.0'
68
+ version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rspec
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - "~>"
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
- version: '3.0'
75
+ version: '0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - "~>"
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec-its
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: webmock
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: simplecov
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
81
123
  - !ruby/object:Gem::Version
82
- version: '3.0'
124
+ version: '0'
83
125
  description: Sign-in with Apple backend library in Ruby.
84
126
  email:
85
127
  - nov@matake.jp
@@ -100,7 +142,9 @@ files:
100
142
  - bin/console
101
143
  - bin/setup
102
144
  - lib/apple_id.rb
145
+ - lib/apple_id/access_token.rb
103
146
  - lib/apple_id/client.rb
147
+ - lib/apple_id/id_token.rb
104
148
  homepage: https://github.com/nov/apple_id
105
149
  licenses:
106
150
  - MIT