standard_id 0.1.3 → 0.1.4

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.
@@ -0,0 +1,61 @@
1
+ module StandardId
2
+ module Oauth
3
+ class SocialFlow < TokenGrantFlow
4
+ attr_reader :account, :connection, :original_params
5
+
6
+ def initialize(params, request, account:, connection:, original_params: {})
7
+ super(params, request)
8
+ @account = account
9
+ @connection = connection
10
+ @original_params = original_params
11
+ end
12
+
13
+ def authenticate!
14
+ raise StandardId::InvalidGrantError, "Account is required for social flow" if @account.blank?
15
+ end
16
+
17
+ private
18
+
19
+ def subject_id
20
+ @account.id
21
+ end
22
+
23
+ def client_id
24
+ @original_params["client_id"]
25
+ end
26
+
27
+ def token_scope
28
+ @original_params["scope"]
29
+ end
30
+
31
+ def grant_type
32
+ "social"
33
+ end
34
+
35
+ def audience
36
+ @original_params["audience"]
37
+ end
38
+
39
+ def supports_refresh_token?
40
+ true
41
+ end
42
+
43
+ def token_lifetime_key
44
+ :social
45
+ end
46
+
47
+ def token_account
48
+ @account
49
+ end
50
+
51
+ def token_client
52
+ nil
53
+ end
54
+
55
+ def build_jwt_payload(expires_in)
56
+ base_payload = super(expires_in)
57
+ base_payload.merge(provider: @connection).compact
58
+ end
59
+ end
60
+ end
61
+ end
@@ -10,7 +10,7 @@ module StandardId
10
10
 
11
11
  def social_provider_url
12
12
  @social_provider_url ||= case params[:connection]
13
- when "google-oauth2"
13
+ when "google"
14
14
  build_google_oauth_url
15
15
  when "apple"
16
16
  build_apple_oauth_url
@@ -20,28 +20,18 @@ module StandardId
20
20
  end
21
21
 
22
22
  def build_google_oauth_url
23
- google_params = {
24
- client_id: StandardId.config.google_client_id,
23
+ StandardId::SocialProviders::Google.authorization_url(
24
+ state: encode_state_with_original_params,
25
25
  redirect_uri: "#{params[:base_url]}/api/oauth/callback/google",
26
- response_type: "code",
27
- scope: "openid email profile",
28
- state: encode_state_with_original_params
29
- }
30
-
31
- "https://accounts.google.com/o/oauth2/v2/auth?" + URI.encode_www_form(google_params)
26
+ scope: "openid email profile"
27
+ )
32
28
  end
33
29
 
34
30
  def build_apple_oauth_url
35
- apple_params = {
36
- client_id: StandardId.config.apple_client_id,
37
- redirect_uri: "#{params[:base_url]}/api/oauth/callback/apple",
38
- response_type: "code",
39
- scope: "name email",
40
- response_mode: "form_post",
41
- state: encode_state_with_original_params
42
- }
43
-
44
- "https://appleid.apple.com/auth/authorize?" + URI.encode_www_form(apple_params)
31
+ StandardId::SocialProviders::Apple.authorization_url(
32
+ state: encode_state_with_original_params,
33
+ redirect_uri: "#{params[:base_url]}/api/oauth/callback/apple"
34
+ )
45
35
  end
46
36
 
47
37
  def encode_state_with_original_params
@@ -155,22 +155,9 @@ module StandardId
155
155
  }
156
156
  end
157
157
 
158
- def callable_parameters(resolver)
159
- parameters = if resolver.respond_to?(:parameters)
160
- resolver.parameters
161
- elsif resolver.respond_to?(:method) && resolver.respond_to?(:call)
162
- resolver.method(:call).parameters
163
- else
164
- []
165
- end
166
-
167
- accepts_all = parameters.any? { |type, _| type == :keyrest }
168
-
169
- accepts_all ? claim_resolvers_context.keys : parameters.map { |_, name| name.to_sym }
170
- end
171
-
172
158
  def resolve_claim_value(resolver)
173
- resolver&.call(**claim_resolvers_context.slice(*callable_parameters(resolver)))
159
+ filtered_context = StandardId::Utils::CallableParameterFilter.filter(resolver, claim_resolvers_context)
160
+ resolver.call(**filtered_context)
174
161
  end
175
162
  end
176
163
  end
@@ -0,0 +1,184 @@
1
+ require "uri"
2
+ require "net/http"
3
+ require "json"
4
+ require "jwt"
5
+ require_relative "response_builder"
6
+
7
+ module StandardId
8
+ module SocialProviders
9
+ class Apple
10
+ include ResponseBuilder
11
+
12
+ ISSUER = "https://appleid.apple.com".freeze
13
+ AUTH_ENDPOINT = "#{ISSUER}/auth/authorize".freeze
14
+ TOKEN_ENDPOINT = "#{ISSUER}/auth/token".freeze
15
+ JWKS_URI = "#{ISSUER}/auth/keys".freeze
16
+ DEFAULT_SCOPE = "name email".freeze
17
+ DEFAULT_RESPONSE_MODE = "form_post".freeze
18
+
19
+ class << self
20
+ def authorization_url(state:, redirect_uri:, scope: DEFAULT_SCOPE, response_mode: DEFAULT_RESPONSE_MODE)
21
+ ensure_basic_credentials!
22
+
23
+ query = {
24
+ client_id: StandardId.config.apple_client_id,
25
+ redirect_uri: redirect_uri,
26
+ response_type: "code",
27
+ scope: scope,
28
+ response_mode: response_mode,
29
+ state: state
30
+ }
31
+
32
+ "#{AUTH_ENDPOINT}?#{URI.encode_www_form(query)}"
33
+ end
34
+
35
+ def get_user_info(code: nil, id_token: nil, redirect_uri: nil, client_id: StandardId.config.apple_client_id)
36
+ if id_token.present?
37
+ build_response(
38
+ verify_id_token(id_token: id_token, client_id: client_id),
39
+ tokens: { id_token: id_token }
40
+ )
41
+ elsif code.present?
42
+ exchange_code_for_user_info(code: code, redirect_uri: redirect_uri, client_id: client_id)
43
+ else
44
+ raise StandardId::InvalidRequestError, "Either code or id_token must be provided"
45
+ end
46
+ end
47
+
48
+ def exchange_code_for_user_info(code:, redirect_uri:, client_id: StandardId.config.apple_client_id)
49
+ ensure_full_credentials!(client_id: client_id)
50
+ raise StandardId::InvalidRequestError, "Missing authorization code" if code.blank?
51
+
52
+ token_response = HttpClient.post_form(TOKEN_ENDPOINT, {
53
+ client_id: client_id,
54
+ client_secret: generate_client_secret(client_id: client_id),
55
+ code: code,
56
+ grant_type: "authorization_code",
57
+ redirect_uri: redirect_uri
58
+ })
59
+
60
+ unless token_response.is_a?(Net::HTTPSuccess)
61
+ error_body = JSON.parse(token_response.body) rescue {}
62
+ raise StandardId::InvalidRequestError, "Failed to exchange Apple authorization code: #{error_body['error']}"
63
+ end
64
+
65
+ parsed_token = JSON.parse(token_response.body)
66
+ id_token = parsed_token["id_token"]
67
+ raise StandardId::InvalidRequestError, "Apple response missing id_token" if id_token.blank?
68
+
69
+ tokens = extract_token_payload(parsed_token)
70
+ user_info = verify_id_token(id_token: id_token, client_id: client_id)
71
+
72
+ build_response(user_info, tokens: tokens)
73
+ rescue StandardError => e
74
+ raise e if e.is_a?(StandardId::OAuthError)
75
+ raise StandardId::OAuthError, e.message, cause: e
76
+ end
77
+
78
+ def verify_id_token(id_token:, client_id: StandardId.config.apple_client_id)
79
+ raise StandardId::InvalidRequestError, "Missing id_token" if id_token.blank?
80
+ if client_id.blank?
81
+ raise StandardId::InvalidRequestError, "Apple client_id is not configured"
82
+ end
83
+
84
+ decoded_token = JWT.decode(id_token, nil, false)
85
+ header = decoded_token[1]
86
+
87
+ jwk = fetch_jwk(kid: header["kid"])
88
+
89
+ verified_payload, = JWT.decode(
90
+ id_token,
91
+ jwk.public_key,
92
+ true,
93
+ algorithm: "RS256",
94
+ iss: ISSUER,
95
+ verify_iss: true,
96
+ aud: client_id,
97
+ verify_aud: true
98
+ )
99
+
100
+ {
101
+ "sub" => verified_payload["sub"],
102
+ "email" => verified_payload["email"],
103
+ "email_verified" => verified_payload["email_verified"],
104
+ "is_private_email" => verified_payload["is_private_email"]
105
+ }.compact
106
+ rescue JWT::InvalidAudError => e
107
+ raise StandardId::InvalidRequestError, "Invalid Apple ID token audience: #{e.message}"
108
+ rescue JWT::DecodeError => e
109
+ raise StandardId::InvalidRequestError, "Invalid Apple ID token: #{e.message}"
110
+ rescue StandardError => e
111
+ raise e if e.is_a?(StandardId::OAuthError)
112
+ raise StandardId::OAuthError, e.message, cause: e
113
+ end
114
+
115
+ private
116
+
117
+ def ensure_basic_credentials!(client_id: StandardId.config.apple_client_id)
118
+ if client_id.blank?
119
+ raise StandardId::InvalidRequestError, "Apple OAuth is not configured"
120
+ end
121
+ end
122
+
123
+ def ensure_full_credentials!(client_id: nil)
124
+ ensure_basic_credentials!(client_id: client_id)
125
+
126
+ required = [
127
+ StandardId.config.apple_private_key,
128
+ StandardId.config.apple_key_id,
129
+ StandardId.config.apple_team_id
130
+ ]
131
+
132
+ if required.any?(&:blank?)
133
+ raise StandardId::InvalidRequestError, "Apple OAuth credentials are incomplete"
134
+ end
135
+ end
136
+
137
+ def generate_client_secret(client_id: StandardId.config.apple_client_id)
138
+ header = {
139
+ alg: "ES256",
140
+ kid: StandardId.config.apple_key_id
141
+ }
142
+
143
+ payload = {
144
+ iss: StandardId.config.apple_team_id,
145
+ iat: Time.current.to_i,
146
+ exp: Time.current.to_i + 3600,
147
+ aud: ISSUER,
148
+ sub: client_id
149
+ }
150
+
151
+ private_key = OpenSSL::PKey::EC.new(StandardId.config.apple_private_key)
152
+ JWT.encode(payload, private_key, "ES256", header)
153
+ end
154
+
155
+ def fetch_jwk(kid:)
156
+ uri = URI(JWKS_URI)
157
+ jwks_response = Net::HTTP.get_response(uri)
158
+
159
+ unless jwks_response.is_a?(Net::HTTPSuccess)
160
+ raise StandardId::InvalidRequestError, "Failed to fetch Apple JWKS"
161
+ end
162
+
163
+ jwks_data = JSON.parse(jwks_response.body)
164
+ jwk_data = jwks_data["keys"].find { |key| key["kid"] == kid }
165
+
166
+ raise StandardId::InvalidRequestError, "JWK with kid '#{kid}' not found in Apple's JWKS" unless jwk_data
167
+
168
+ JWT::JWK.import(jwk_data)
169
+ rescue StandardError => e
170
+ raise e if e.is_a?(StandardId::OAuthError)
171
+ raise StandardId::OAuthError, "Failed to fetch JWK: #{e.message}"
172
+ end
173
+
174
+ def extract_token_payload(parsed_token)
175
+ {
176
+ access_token: parsed_token["access_token"],
177
+ refresh_token: parsed_token["refresh_token"],
178
+ id_token: parsed_token["id_token"]
179
+ }.compact
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,168 @@
1
+ require_relative "response_builder"
2
+
3
+ module StandardId
4
+ module SocialProviders
5
+ class Google
6
+ include ResponseBuilder
7
+
8
+ AUTH_ENDPOINT = "https://accounts.google.com/o/oauth2/v2/auth".freeze
9
+ TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token".freeze
10
+ USERINFO_ENDPOINT = "https://www.googleapis.com/oauth2/v2/userinfo".freeze
11
+ TOKEN_INFO_ENDPOINT = "https://oauth2.googleapis.com/tokeninfo".freeze
12
+ DEFAULT_SCOPE = "openid email profile".freeze
13
+
14
+ class << self
15
+ def authorization_url(state:, redirect_uri:, scope: DEFAULT_SCOPE, prompt: nil)
16
+ query = {
17
+ client_id: credentials[:client_id],
18
+ redirect_uri: redirect_uri,
19
+ response_type: "code",
20
+ scope: scope,
21
+ state: state
22
+ }
23
+
24
+ query[:prompt] = prompt if prompt.present?
25
+
26
+ "#{AUTH_ENDPOINT}?#{URI.encode_www_form(query)}"
27
+ end
28
+
29
+ def get_user_info(code: nil, id_token: nil, access_token: nil, redirect_uri: nil)
30
+ if id_token.present?
31
+ build_response(
32
+ verify_id_token(id_token: id_token),
33
+ tokens: { id_token: id_token }
34
+ )
35
+ elsif access_token.present?
36
+ build_response(
37
+ fetch_user_info(access_token: access_token),
38
+ tokens: { access_token: access_token }
39
+ )
40
+ elsif code.present?
41
+ exchange_code_for_user_info(code: code, redirect_uri: redirect_uri)
42
+ else
43
+ raise StandardId::InvalidRequestError, "Either code, id_token, or access_token must be provided"
44
+ end
45
+ end
46
+
47
+ def exchange_code_for_user_info(code:, redirect_uri:)
48
+ raise StandardId::InvalidRequestError, "Missing authorization code" if code.blank?
49
+
50
+ token_response = HttpClient.post_form(TOKEN_ENDPOINT, {
51
+ client_id: credentials[:client_id],
52
+ client_secret: credentials[:client_secret],
53
+ code: code,
54
+ grant_type: "authorization_code",
55
+ redirect_uri: redirect_uri
56
+ }.compact)
57
+
58
+ unless token_response.is_a?(Net::HTTPSuccess)
59
+ raise StandardId::InvalidRequestError, "Failed to exchange Google authorization code"
60
+ end
61
+
62
+ parsed_token = JSON.parse(token_response.body)
63
+ access_token = parsed_token["access_token"]
64
+ raise StandardId::InvalidRequestError, "Google response missing access token" if access_token.blank?
65
+
66
+ tokens = extract_token_payload(parsed_token)
67
+ user_info = fetch_user_info(access_token: access_token)
68
+
69
+ build_response(user_info, tokens: tokens)
70
+ rescue StandardError => e
71
+ raise e if e.is_a?(StandardId::OAuthError)
72
+ raise StandardId::OAuthError, e.message, cause: e
73
+ end
74
+
75
+ def verify_id_token(id_token:)
76
+ raise StandardId::InvalidRequestError, "Missing id_token" if id_token.blank?
77
+
78
+ response = HttpClient.post_form(TOKEN_INFO_ENDPOINT, id_token: id_token)
79
+
80
+ unless response.is_a?(Net::HTTPSuccess)
81
+ raise StandardId::InvalidRequestError, "Invalid or expired id_token"
82
+ end
83
+
84
+ token_info = JSON.parse(response.body)
85
+
86
+ unless token_info["aud"] == credentials[:client_id]
87
+ raise StandardId::InvalidRequestError, "ID token audience mismatch. Expected: #{credentials[:client_id]}, got: #{token_info['aud']}"
88
+ end
89
+
90
+ unless ["accounts.google.com", "https://accounts.google.com"].include?(token_info["iss"])
91
+ raise StandardId::InvalidRequestError, "ID token issuer invalid. Expected Google, got: #{token_info['iss']}"
92
+ end
93
+
94
+ {
95
+ "sub" => token_info["sub"],
96
+ "email" => token_info["email"],
97
+ "email_verified" => token_info["email_verified"],
98
+ "name" => token_info["name"],
99
+ "given_name" => token_info["given_name"],
100
+ "family_name" => token_info["family_name"],
101
+ "picture" => token_info["picture"],
102
+ "locale" => token_info["locale"]
103
+ }.compact
104
+ rescue StandardError => e
105
+ raise e if e.is_a?(StandardId::OAuthError)
106
+ raise StandardId::OAuthError, e.message, cause: e
107
+ end
108
+
109
+ def fetch_user_info(access_token:)
110
+ raise StandardId::InvalidRequestError, "Missing access token" if access_token.blank?
111
+
112
+ verify_token(access_token)
113
+ user_response = HttpClient.get_with_bearer(USERINFO_ENDPOINT, access_token)
114
+
115
+ unless user_response.is_a?(Net::HTTPSuccess)
116
+ raise StandardId::InvalidRequestError, "Failed to fetch Google user info"
117
+ end
118
+
119
+ JSON.parse(user_response.body)
120
+ rescue StandardError => e
121
+ raise e if e.is_a?(StandardId::OAuthError)
122
+ raise StandardId::OAuthError, e.message, cause: e
123
+ end
124
+
125
+ private
126
+
127
+ def credentials
128
+ @credentials ||= begin
129
+ if StandardId.config.google_client_id.blank? || StandardId.config.google_client_secret.blank?
130
+ raise StandardId::InvalidRequestError, "Google provider is not configured"
131
+ end
132
+
133
+ {
134
+ client_id: StandardId.config.google_client_id,
135
+ client_secret: StandardId.config.google_client_secret
136
+ }
137
+ end
138
+ end
139
+
140
+ def verify_token(access_token)
141
+ token_info_uri = "https://www.googleapis.com/oauth2/v3/tokeninfo"
142
+
143
+ response = HttpClient.post_form(token_info_uri, access_token: access_token)
144
+
145
+ unless response.is_a?(Net::HTTPSuccess)
146
+ raise StandardId::InvalidRequestError, "Invalid or expired access token"
147
+ end
148
+
149
+ token_info = JSON.parse(response.body)
150
+
151
+ unless token_info["aud"] == credentials[:client_id]
152
+ raise StandardId::InvalidRequestError, "Access token audience mismatch. Expected: #{credentials[:client_id]}, got: #{token_info['aud']}"
153
+ end
154
+
155
+ token_info
156
+ end
157
+
158
+ def extract_token_payload(parsed_token)
159
+ {
160
+ access_token: parsed_token["access_token"],
161
+ refresh_token: parsed_token["refresh_token"],
162
+ id_token: parsed_token["id_token"]
163
+ }.compact
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,18 @@
1
+ require "active_support/concern"
2
+
3
+ module StandardId
4
+ module SocialProviders
5
+ module ResponseBuilder
6
+ extend ActiveSupport::Concern
7
+
8
+ class_methods do
9
+ def build_response(user_info, tokens: {})
10
+ {
11
+ user_info: user_info,
12
+ tokens: tokens.compact
13
+ }.with_indifferent_access
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,36 @@
1
+ module StandardId
2
+ module Utils
3
+ class CallableParameterFilter
4
+ class << self
5
+ def filter(callable, context)
6
+ return {} unless callable.respond_to?(:call) && context.present?
7
+
8
+ payload = context.to_h.symbolize_keys
9
+ accepted_keys = accepted_parameters(callable)
10
+ return payload if accepted_keys.nil?
11
+
12
+ payload.slice(*accepted_keys)
13
+ end
14
+
15
+ private
16
+
17
+ def accepted_parameters(callable)
18
+ parameters = parameter_list(callable)
19
+ return nil if parameters.any? { |type, _| type == :keyrest }
20
+
21
+ parameters.map { |_, name| name&.to_sym }.compact
22
+ end
23
+
24
+ def parameter_list(callable)
25
+ if callable.respond_to?(:parameters)
26
+ callable.parameters
27
+ elsif callable.respond_to?(:method)
28
+ callable.method(:call).parameters
29
+ else
30
+ []
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,3 +1,3 @@
1
1
  module StandardId
2
- VERSION = "0.1.3"
2
+ VERSION = "0.1.4"
3
3
  end
data/lib/standard_id.rb CHANGED
@@ -4,6 +4,7 @@ require "standard_id/web_engine"
4
4
  require "standard_id/api_engine"
5
5
  require "standard_id/config/schema"
6
6
  require "standard_id/errors"
7
+ require "standard_id/http_client"
7
8
  require "standard_id/jwt_service"
8
9
  require "standard_id/web/session_manager"
9
10
  require "standard_id/web/token_manager"
@@ -18,6 +19,7 @@ require "standard_id/oauth/client_credentials_flow"
18
19
  require "standard_id/oauth/authorization_code_flow"
19
20
  require "standard_id/oauth/password_flow"
20
21
  require "standard_id/oauth/refresh_token_flow"
22
+ require "standard_id/oauth/social_flow"
21
23
  require "standard_id/oauth/authorization_flow"
22
24
  require "standard_id/oauth/authorization_code_authorization_flow"
23
25
  require "standard_id/oauth/implicit_authorization_flow"
@@ -28,6 +30,9 @@ require "standard_id/oauth/passwordless_otp_flow"
28
30
  require "standard_id/passwordless/base_strategy"
29
31
  require "standard_id/passwordless/email_strategy"
30
32
  require "standard_id/passwordless/sms_strategy"
33
+ require "standard_id/utils/callable_parameter_filter"
34
+ require "standard_id/social_providers/google"
35
+ require "standard_id/social_providers/apple"
31
36
 
32
37
  module StandardId
33
38
  class << self
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: standard_id
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jaryl Sim
@@ -65,14 +65,15 @@ files:
65
65
  - Rakefile
66
66
  - app/assets/stylesheets/standard_id/application.css
67
67
  - app/controllers/concerns/standard_id/api_authentication.rb
68
+ - app/controllers/concerns/standard_id/social_authentication.rb
68
69
  - app/controllers/concerns/standard_id/web_authentication.rb
69
70
  - app/controllers/standard_id/api/authorization_controller.rb
70
71
  - app/controllers/standard_id/api/base_controller.rb
71
72
  - app/controllers/standard_id/api/oauth/base_controller.rb
73
+ - app/controllers/standard_id/api/oauth/callback/providers_controller.rb
72
74
  - app/controllers/standard_id/api/oauth/tokens_controller.rb
73
75
  - app/controllers/standard_id/api/oidc/logout_controller.rb
74
76
  - app/controllers/standard_id/api/passwordless_controller.rb
75
- - app/controllers/standard_id/api/providers_controller.rb
76
77
  - app/controllers/standard_id/api/userinfo_controller.rb
77
78
  - app/controllers/standard_id/web/account_controller.rb
78
79
  - app/controllers/standard_id/web/auth/callback/providers_controller.rb
@@ -114,6 +115,7 @@ files:
114
115
  - app/models/standard_id/username_identifier.rb
115
116
  - app/views/standard_id/web/account/edit.html.erb
116
117
  - app/views/standard_id/web/account/show.html.erb
118
+ - app/views/standard_id/web/auth/callback/providers/apple_mobile.html.erb
117
119
  - app/views/standard_id/web/login/show.html.erb
118
120
  - app/views/standard_id/web/reset_password/confirm/show.html.erb
119
121
  - app/views/standard_id/web/reset_password/start/show.html.erb
@@ -146,6 +148,7 @@ files:
146
148
  - lib/standard_id/config/schema.rb
147
149
  - lib/standard_id/engine.rb
148
150
  - lib/standard_id/errors.rb
151
+ - lib/standard_id/http_client.rb
149
152
  - lib/standard_id/jwt_service.rb
150
153
  - lib/standard_id/oauth/authorization_code_authorization_flow.rb
151
154
  - lib/standard_id/oauth/authorization_code_flow.rb
@@ -156,6 +159,7 @@ files:
156
159
  - lib/standard_id/oauth/password_flow.rb
157
160
  - lib/standard_id/oauth/passwordless_otp_flow.rb
158
161
  - lib/standard_id/oauth/refresh_token_flow.rb
162
+ - lib/standard_id/oauth/social_flow.rb
159
163
  - lib/standard_id/oauth/subflows/base.rb
160
164
  - lib/standard_id/oauth/subflows/social_login_grant.rb
161
165
  - lib/standard_id/oauth/subflows/traditional_code_grant.rb
@@ -164,6 +168,10 @@ files:
164
168
  - lib/standard_id/passwordless/base_strategy.rb
165
169
  - lib/standard_id/passwordless/email_strategy.rb
166
170
  - lib/standard_id/passwordless/sms_strategy.rb
171
+ - lib/standard_id/social_providers/apple.rb
172
+ - lib/standard_id/social_providers/google.rb
173
+ - lib/standard_id/social_providers/response_builder.rb
174
+ - lib/standard_id/utils/callable_parameter_filter.rb
167
175
  - lib/standard_id/version.rb
168
176
  - lib/standard_id/web/authentication_guard.rb
169
177
  - lib/standard_id/web/session_manager.rb