omniauth-facebook2 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3c051fcad79b58c9fd1988af188abaa207b500e3cf6380f005ff2541709419d3
4
+ data.tar.gz: f3a352c67dec65e0332fbafd805f3e508675ee5a5fc71cde0e6e8c055f114b71
5
+ SHA512:
6
+ metadata.gz: 5433ff4b75e0bb8335e975b5f973d111cdc645c2de8cff6cc82527deae5eee643eaf5f0b57ed49a24941375533f37c44791fbdf9ded21e3d8556e041c08bae41
7
+ data.tar.gz: 331e183495646573db070811ac4ea5ff4c53dec6db57956d75fa5daf17b1d9f01416986fbec5287de861e1be14027b2f4b7673741cb30feff23de875581548e8
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Claudio Poli
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,132 @@
1
+ # OmniAuth Facebook2 Strategy
2
+
3
+ [![Test](https://github.com/icoretech/omniauth-facebook2/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/icoretech/omniauth-facebook2/actions/workflows/test.yml?query=branch%3Amain)
4
+ [![Gem Version](https://img.shields.io/gem/v/omniauth-facebook2.svg)](https://rubygems.org/gems/omniauth-facebook2)
5
+
6
+ `omniauth-facebook2` provides a Facebook OAuth2 strategy for OmniAuth.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'omniauth-facebook2'
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
+ Rails.application.config.middleware.use OmniAuth::Builder do
28
+ provider :facebook2,
29
+ ENV.fetch('FACEBOOK_APP_ID'),
30
+ ENV.fetch('FACEBOOK_APP_SECRET')
31
+ end
32
+ ```
33
+
34
+ Compatibility alias is available, so existing callback paths can keep using `facebook`:
35
+
36
+ ```ruby
37
+ Rails.application.config.middleware.use OmniAuth::Builder do
38
+ provider :facebook,
39
+ ENV.fetch('FACEBOOK_APP_ID'),
40
+ ENV.fetch('FACEBOOK_APP_SECRET')
41
+ end
42
+ ```
43
+
44
+ ## Provider App Setup
45
+
46
+ - Meta for Developers: <https://developers.facebook.com/apps/>
47
+ - Facebook Login docs: <https://developers.facebook.com/docs/facebook-login/>
48
+ - Register callback URL (example): `https://your-app.example.com/auth/facebook/callback`
49
+
50
+ ## Options
51
+
52
+ Supported request/provider options include:
53
+
54
+ - `scope` (default: `email`)
55
+ - `display`
56
+ - `auth_type` (example: `rerequest`)
57
+ - `config_id` (Facebook Login for Business)
58
+ - `redirect_uri`
59
+ - `info_fields` (default: `name,email`)
60
+ - `locale`
61
+ - `image_size` (symbol/string like `:normal`, or hash `{ width:, height: }`)
62
+ - `secure_image_url` (default: `true`)
63
+ - `callback_url` / `callback_path`
64
+
65
+ ## Auth Hash
66
+
67
+ Example payload from `request.env['omniauth.auth']` (captured from a real smoke run):
68
+
69
+ ```json
70
+ {
71
+ "uid": "10230653256947200",
72
+ "info": {
73
+ "email": "claudio@icorete.ch",
74
+ "name": "Claudio Poli",
75
+ "image": "https://graph.facebook.com/v25.0/10230653256947200/picture"
76
+ },
77
+ "credentials": {
78
+ "token": "[REDACTED]",
79
+ "expires_at": 1777885934,
80
+ "expires": true
81
+ },
82
+ "extra": {
83
+ "raw_info": {
84
+ "name": "Claudio Poli",
85
+ "email": "claudio@icorete.ch",
86
+ "id": "10230653256947200"
87
+ }
88
+ }
89
+ }
90
+ ```
91
+
92
+ ## Development
93
+
94
+ ```bash
95
+ bundle install
96
+ bundle exec rake
97
+ ```
98
+
99
+ Run Rails integration tests with an explicit Rails version:
100
+
101
+ ```bash
102
+ RAILS_VERSION='~> 8.1.0' bundle install
103
+ RAILS_VERSION='~> 8.1.0' bundle exec rake test_rails_integration
104
+ ```
105
+
106
+ ## Compatibility
107
+
108
+ - Ruby: `>= 3.2` (tested on `3.2`, `3.3`, `3.4`, `4.0`)
109
+ - `omniauth-oauth2`: `>= 1.8`, `< 1.9`
110
+ - Rails integration lanes: `~> 7.1.0`, `~> 7.2.0`, `~> 8.0.0`, `~> 8.1.0`
111
+
112
+ ## Endpoints
113
+
114
+ Default endpoints target Facebook Graph API `v25.0`:
115
+
116
+ - Authorize: `https://www.facebook.com/v25.0/dialog/oauth`
117
+ - Token: `https://graph.facebook.com/v25.0/oauth/access_token`
118
+ - User info: `https://graph.facebook.com/v25.0/me`
119
+
120
+ ## Test Structure
121
+
122
+ - `test/omniauth_facebook2_test.rb`: strategy/unit behavior
123
+ - `test/rails_integration_test.rb`: full Rack/Rails request+callback flow
124
+ - `test/test_helper.rb`: shared test bootstrap
125
+
126
+ ## Release
127
+
128
+ Tag releases as `vX.Y.Z`; GitHub Actions publishes the gem to RubyGems.
129
+
130
+ ## License
131
+
132
+ MIT
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+ require 'json'
5
+ require 'openssl'
6
+
7
+ module OmniAuth
8
+ module Facebook2
9
+ # Parser for Facebook signed request cookie payloads used by client-side login flow.
10
+ class SignedRequest
11
+ class UnknownSignatureAlgorithmError < NotImplementedError; end
12
+
13
+ SUPPORTED_ALGORITHM = 'HMAC-SHA256'
14
+
15
+ attr_reader :value, :secret
16
+
17
+ def self.parse(value, secret)
18
+ new(value, secret).payload
19
+ end
20
+
21
+ def initialize(value, secret)
22
+ @value = value
23
+ @secret = secret
24
+ end
25
+
26
+ def payload
27
+ @payload ||= parse_signed_request
28
+ end
29
+
30
+ private
31
+
32
+ def parse_signed_request
33
+ signature, encoded_payload = value.to_s.split('.', 2)
34
+ return if blank?(signature) || blank?(encoded_payload)
35
+
36
+ decoded_signature = base64_decode_url(signature)
37
+ decoded_payload = JSON.parse(base64_decode_url(encoded_payload))
38
+
39
+ unless decoded_payload['algorithm'] == SUPPORTED_ALGORITHM
40
+ raise UnknownSignatureAlgorithmError, "unknown algorithm: #{decoded_payload['algorithm']}"
41
+ end
42
+
43
+ decoded_payload if valid_signature?(decoded_signature, encoded_payload)
44
+ end
45
+
46
+ def valid_signature?(signature, payload, algorithm = OpenSSL::Digest.new('SHA256'))
47
+ OpenSSL::HMAC.digest(algorithm, secret, payload) == signature
48
+ end
49
+
50
+ def base64_decode_url(value)
51
+ value += '=' * ((4 - value.size.modulo(4)) % 4)
52
+ Base64.decode64(value.tr('-_', '+/'))
53
+ end
54
+
55
+ def blank?(value)
56
+ value.nil? || (value.respond_to?(:empty?) && value.empty?)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OmniAuth
4
+ module Facebook2
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'omniauth/facebook2/version'
4
+ require 'omniauth/facebook2/signed_request'
5
+ require 'omniauth/strategies/facebook2'
@@ -0,0 +1,217 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'omniauth-oauth2'
4
+ require 'openssl'
5
+ require 'rack/utils'
6
+ require 'uri'
7
+
8
+ module OmniAuth
9
+ module Strategies
10
+ # OmniAuth strategy for Facebook OAuth2.
11
+ class Facebook2 < OmniAuth::Strategies::OAuth2
12
+ class NoAuthorizationCodeError < StandardError; end
13
+
14
+ DEFAULT_SCOPE = 'email'
15
+ DEFAULT_FACEBOOK_API_VERSION = 'v25.0'
16
+ DEFAULT_INFO_FIELDS = 'name,email'
17
+
18
+ option :name, 'facebook2'
19
+ option :scope, DEFAULT_SCOPE
20
+ option :authorize_options, %i[scope display auth_type config_id redirect_uri]
21
+ option :secure_image_url, true
22
+ option :appsecret_proof, true
23
+ option :authorization_code_from_signed_request_in_cookie, nil
24
+
25
+ option :client_options,
26
+ site: "https://graph.facebook.com/#{DEFAULT_FACEBOOK_API_VERSION}",
27
+ authorize_url: "https://www.facebook.com/#{DEFAULT_FACEBOOK_API_VERSION}/dialog/oauth",
28
+ token_url: 'oauth/access_token',
29
+ connection_opts: {
30
+ headers: {
31
+ user_agent: 'icoretech-omniauth-facebook2 gem',
32
+ accept: 'application/json',
33
+ content_type: 'application/json'
34
+ }
35
+ }
36
+
37
+ option :access_token_options,
38
+ header_format: 'OAuth %s',
39
+ param_name: 'access_token'
40
+
41
+ uid { raw_info['id'] }
42
+
43
+ info do
44
+ prune(
45
+ {
46
+ 'nickname' => raw_info['username'],
47
+ 'email' => raw_info['email'],
48
+ 'name' => raw_info['name'],
49
+ 'first_name' => raw_info['first_name'],
50
+ 'last_name' => raw_info['last_name'],
51
+ 'image' => image_url(uid),
52
+ 'description' => raw_info['bio'],
53
+ 'urls' => {
54
+ 'Facebook' => raw_info['link'],
55
+ 'Website' => raw_info['website']
56
+ },
57
+ 'location' => raw_info.dig('location', 'name'),
58
+ 'verified' => raw_info['verified']
59
+ }
60
+ )
61
+ end
62
+
63
+ credentials do
64
+ {
65
+ 'token' => access_token.token,
66
+ 'refresh_token' => access_token.refresh_token,
67
+ 'expires_at' => access_token.expires_at,
68
+ 'expires' => access_token.expires?,
69
+ 'scope' => token_scope
70
+ }.compact
71
+ end
72
+
73
+ extra do
74
+ data = {}
75
+ data['raw_info'] = raw_info unless skip_info?
76
+ prune(data)
77
+ end
78
+
79
+ def raw_info
80
+ @raw_info ||= access_token.get('me', info_options).parsed || {}
81
+ end
82
+
83
+ def info_options
84
+ params = {
85
+ fields: options[:info_fields] || DEFAULT_INFO_FIELDS
86
+ }
87
+ params[:appsecret_proof] = appsecret_proof if options[:appsecret_proof]
88
+ params[:locale] = options[:locale] if options[:locale]
89
+
90
+ { params: params }
91
+ end
92
+
93
+ def callback_phase
94
+ with_authorization_code! { super }
95
+ rescue NoAuthorizationCodeError => e
96
+ fail!(:no_authorization_code, e)
97
+ rescue OmniAuth::Facebook2::SignedRequest::UnknownSignatureAlgorithmError => e
98
+ fail!(:unknown_signature_algorithm, e)
99
+ end
100
+
101
+ def callback_url
102
+ return '' if options.authorization_code_from_signed_request_in_cookie
103
+
104
+ options[:callback_url] || super
105
+ end
106
+
107
+ def query_string
108
+ return '' if request.params['code']
109
+
110
+ super
111
+ end
112
+
113
+ def access_token_options
114
+ options.access_token_options.to_h.transform_keys(&:to_sym)
115
+ end
116
+
117
+ def authorize_params
118
+ super.tap do |params|
119
+ options.authorize_options.each do |key|
120
+ request_value = request.params[key.to_s]
121
+ params[key] = request_value unless blank?(request_value)
122
+ end
123
+
124
+ params[:scope] ||= options[:scope] || DEFAULT_SCOPE
125
+ end
126
+ end
127
+
128
+ protected
129
+
130
+ def build_access_token
131
+ super.tap do |token|
132
+ token.options.merge!(access_token_options)
133
+ end
134
+ end
135
+
136
+ private
137
+
138
+ def signed_request_from_cookie
139
+ @signed_request_from_cookie ||= begin
140
+ signed_request = raw_signed_request_from_cookie
141
+ signed_request && OmniAuth::Facebook2::SignedRequest.parse(signed_request, client.secret)
142
+ end
143
+ end
144
+
145
+ def raw_signed_request_from_cookie
146
+ request.cookies["fbsr_#{client.id}"]
147
+ end
148
+
149
+ def with_authorization_code!
150
+ if request.params.key?('code') && !blank?(request.params['code'])
151
+ yield
152
+ elsif (code_from_signed_request = signed_request_from_cookie && signed_request_from_cookie['code'])
153
+ request.params['code'] = code_from_signed_request
154
+ options.authorization_code_from_signed_request_in_cookie = true
155
+ original_provider_ignores_state = options.provider_ignores_state
156
+ options.provider_ignores_state = true
157
+
158
+ begin
159
+ yield
160
+ ensure
161
+ request.params.delete('code')
162
+ options.authorization_code_from_signed_request_in_cookie = false
163
+ options.provider_ignores_state = original_provider_ignores_state
164
+ end
165
+ else
166
+ raise NoAuthorizationCodeError,
167
+ 'must pass either a `code` (query param) or an `fbsr_<app_id>` signed request cookie'
168
+ end
169
+ end
170
+
171
+ def prune(hash)
172
+ return hash unless hash.is_a?(Hash)
173
+
174
+ hash.delete_if do |_key, value|
175
+ prune(value) if value.is_a?(Hash)
176
+ value.nil? || (value.respond_to?(:empty?) && value.empty?)
177
+ end
178
+ end
179
+
180
+ def image_url(user_id)
181
+ uri_class = options[:secure_image_url] ? URI::HTTPS : URI::HTTP
182
+ site_uri = URI.parse(client.site)
183
+ url = uri_class.build(host: site_uri.host, path: "#{site_uri.path}/#{user_id}/picture")
184
+
185
+ query = if options[:image_size].is_a?(String) || options[:image_size].is_a?(Symbol)
186
+ { type: options[:image_size] }
187
+ elsif options[:image_size].is_a?(Hash)
188
+ options[:image_size]
189
+ end
190
+ url.query = Rack::Utils.build_query(query) if query
191
+
192
+ url.to_s
193
+ end
194
+
195
+ def appsecret_proof
196
+ @appsecret_proof ||= OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('SHA256'), client.secret, access_token.token)
197
+ end
198
+
199
+ def token_scope
200
+ token_params = access_token.respond_to?(:params) ? access_token.params : {}
201
+ token_params['scope'] || (access_token['scope'] if access_token.respond_to?(:[]))
202
+ end
203
+
204
+ def blank?(value)
205
+ value.nil? || (value.respond_to?(:empty?) && value.empty?)
206
+ end
207
+ end
208
+
209
+ # Backward-compatible strategy name for existing `facebook` callback paths.
210
+ class Facebook < Facebook2
211
+ option :name, 'facebook'
212
+ end
213
+ end
214
+ end
215
+
216
+ OmniAuth.config.add_camelization 'facebook2', 'Facebook2'
217
+ OmniAuth.config.add_camelization 'facebook', 'Facebook'
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'omniauth/facebook2'
@@ -0,0 +1,36 @@
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/facebook2/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'omniauth-facebook2'
9
+ spec.version = OmniAuth::Facebook2::VERSION
10
+ spec.authors = ['Claudio Poli']
11
+ spec.email = ['masterkain@gmail.com']
12
+
13
+ spec.summary = 'OmniAuth strategy for Facebook OAuth2 authentication.'
14
+ spec.description =
15
+ 'OAuth2 strategy for OmniAuth that authenticates users with Facebook ' \
16
+ 'and exposes profile metadata from the Graph API.'
17
+ spec.homepage = 'https://github.com/icoretech/omniauth-facebook2'
18
+ spec.license = 'MIT'
19
+ spec.required_ruby_version = '>= 3.2'
20
+
21
+ spec.metadata['source_code_uri'] = 'https://github.com/icoretech/omniauth-facebook2'
22
+ spec.metadata['bug_tracker_uri'] = 'https://github.com/icoretech/omniauth-facebook2/issues'
23
+ spec.metadata['changelog_uri'] = 'https://github.com/icoretech/omniauth-facebook2/releases'
24
+ spec.metadata['rubygems_mfa_required'] = 'true'
25
+
26
+ spec.files = Dir[
27
+ 'lib/**/*.rb',
28
+ 'README*',
29
+ 'LICENSE*',
30
+ '*.gemspec'
31
+ ]
32
+ spec.require_paths = ['lib']
33
+
34
+ spec.add_dependency 'cgi', '>= 0.3.6'
35
+ spec.add_dependency 'omniauth-oauth2', '>= 1.8', '< 1.9'
36
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: omniauth-facebook2
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.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: omniauth-oauth2
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '1.8'
33
+ - - "<"
34
+ - !ruby/object:Gem::Version
35
+ version: '1.9'
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '1.8'
43
+ - - "<"
44
+ - !ruby/object:Gem::Version
45
+ version: '1.9'
46
+ description: OAuth2 strategy for OmniAuth that authenticates users with Facebook and
47
+ exposes profile metadata from the Graph API.
48
+ email:
49
+ - masterkain@gmail.com
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - LICENSE.txt
55
+ - README.md
56
+ - lib/omniauth-facebook2.rb
57
+ - lib/omniauth/facebook2.rb
58
+ - lib/omniauth/facebook2/signed_request.rb
59
+ - lib/omniauth/facebook2/version.rb
60
+ - lib/omniauth/strategies/facebook2.rb
61
+ - omniauth-facebook2.gemspec
62
+ homepage: https://github.com/icoretech/omniauth-facebook2
63
+ licenses:
64
+ - MIT
65
+ metadata:
66
+ source_code_uri: https://github.com/icoretech/omniauth-facebook2
67
+ bug_tracker_uri: https://github.com/icoretech/omniauth-facebook2/issues
68
+ changelog_uri: https://github.com/icoretech/omniauth-facebook2/releases
69
+ rubygems_mfa_required: 'true'
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '3.2'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubygems_version: 3.6.9
85
+ specification_version: 4
86
+ summary: OmniAuth strategy for Facebook OAuth2 authentication.
87
+ test_files: []