standard_id 0.1.3 → 0.1.5
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 +4 -4
- data/README.md +23 -1
- data/app/controllers/concerns/standard_id/social_authentication.rb +112 -0
- data/app/controllers/standard_id/api/oauth/callback/providers_controller.rb +68 -0
- data/app/controllers/standard_id/web/auth/callback/providers_controller.rb +67 -72
- data/app/controllers/standard_id/web/login_controller.rb +20 -17
- data/app/controllers/standard_id/web/signup_controller.rb +1 -1
- data/app/views/standard_id/web/auth/callback/providers/apple_mobile.html.erb +50 -0
- data/app/views/standard_id/web/login/show.html.erb +3 -3
- data/app/views/standard_id/web/signup/show.html.erb +1 -1
- data/config/routes/api.rb +1 -1
- data/config/routes/web.rb +1 -0
- data/lib/generators/standard_id/install/templates/standard_id.rb +17 -0
- data/lib/standard_config/config.rb +4 -1
- data/lib/standard_id/config/schema.rb +4 -0
- data/lib/standard_id/http_client.rb +22 -0
- data/lib/standard_id/oauth/social_flow.rb +61 -0
- data/lib/standard_id/oauth/subflows/social_login_grant.rb +9 -19
- data/lib/standard_id/oauth/token_grant_flow.rb +2 -15
- data/lib/standard_id/social_providers/apple.rb +184 -0
- data/lib/standard_id/social_providers/google.rb +168 -0
- data/lib/standard_id/social_providers/response_builder.rb +18 -0
- data/lib/standard_id/utils/callable_parameter_filter.rb +36 -0
- data/lib/standard_id/version.rb +1 -1
- data/lib/standard_id.rb +5 -0
- metadata +10 -2
- data/app/controllers/standard_id/api/providers_controller.rb +0 -174
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
module StandardId
|
|
2
|
-
module Api
|
|
3
|
-
class ProvidersController < BaseController
|
|
4
|
-
skip_before_action :validate_content_type!
|
|
5
|
-
|
|
6
|
-
def google
|
|
7
|
-
expect_and_permit!([:state, :code], [:state, :code])
|
|
8
|
-
handle_social_callback("google-oauth2")
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def apple
|
|
12
|
-
expect_and_permit!([:state, :code], [:state, :code])
|
|
13
|
-
handle_social_callback("apple")
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
private
|
|
17
|
-
|
|
18
|
-
def handle_social_callback(provider)
|
|
19
|
-
original_params = decode_state_params
|
|
20
|
-
user_info = exchange_social_code_for_user_info(provider, params[:code])
|
|
21
|
-
account = find_or_create_account_from_social(user_info, provider)
|
|
22
|
-
|
|
23
|
-
authorization_code = generate_authorization_code
|
|
24
|
-
store_authorization_code(authorization_code, original_params, account, provider)
|
|
25
|
-
|
|
26
|
-
redirect_params = {
|
|
27
|
-
code: authorization_code,
|
|
28
|
-
state: original_params["state"]
|
|
29
|
-
}.compact
|
|
30
|
-
|
|
31
|
-
redirect_url = build_redirect_uri(original_params["redirect_uri"], redirect_params)
|
|
32
|
-
redirect_to redirect_url, allow_other_host: true, status: :found
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def decode_state_params
|
|
36
|
-
encoded_state = params[:state]
|
|
37
|
-
raise StandardId::InvalidRequestError, "Missing state parameter" if encoded_state.blank?
|
|
38
|
-
|
|
39
|
-
begin
|
|
40
|
-
JSON.parse(Base64.urlsafe_decode64(encoded_state))
|
|
41
|
-
rescue JSON::ParserError, ArgumentError
|
|
42
|
-
raise StandardId::InvalidRequestError, "Invalid state parameter"
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def exchange_social_code_for_user_info(provider, code)
|
|
47
|
-
case provider
|
|
48
|
-
when "google-oauth2"
|
|
49
|
-
exchange_google_code(code)
|
|
50
|
-
when "apple"
|
|
51
|
-
exchange_apple_code(code)
|
|
52
|
-
else
|
|
53
|
-
raise StandardId::InvalidRequestError, "Unsupported provider: #{provider}"
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def exchange_google_code(code)
|
|
58
|
-
token_response = HTTParty.post("https://oauth2.googleapis.com/token", {
|
|
59
|
-
body: {
|
|
60
|
-
client_id: StandardId.config.google_client_id,
|
|
61
|
-
client_secret: StandardId.config.google_client_secret,
|
|
62
|
-
code: code,
|
|
63
|
-
grant_type: "authorization_code",
|
|
64
|
-
redirect_uri: "#{request.base_url}/api/oauth/callback/google"
|
|
65
|
-
},
|
|
66
|
-
headers: { "Content-Type" => "application/x-www-form-urlencoded" }
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
raise StandardId::InvalidRequestError, "Failed to exchange Google code" unless token_response.success?
|
|
70
|
-
|
|
71
|
-
access_token = token_response.parsed_response["access_token"]
|
|
72
|
-
|
|
73
|
-
user_response = HTTParty.get("https://www.googleapis.com/oauth2/v2/userinfo", {
|
|
74
|
-
headers: { "Authorization" => "Bearer #{access_token}" }
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
raise StandardId::InvalidRequestError, "Failed to get Google user info" unless user_response.success?
|
|
78
|
-
|
|
79
|
-
user_response.parsed_response
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def exchange_apple_code(code)
|
|
83
|
-
client_secret = generate_apple_client_secret
|
|
84
|
-
|
|
85
|
-
token_response = HTTParty.post("https://appleid.apple.com/auth/token", {
|
|
86
|
-
body: {
|
|
87
|
-
client_id: StandardId.config.apple_client_id,
|
|
88
|
-
client_secret: client_secret,
|
|
89
|
-
code: code,
|
|
90
|
-
grant_type: "authorization_code",
|
|
91
|
-
redirect_uri: "#{request.base_url}/api/oauth/callback/apple"
|
|
92
|
-
},
|
|
93
|
-
headers: { "Content-Type" => "application/x-www-form-urlencoded" }
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
raise StandardId::InvalidRequestError, "Failed to exchange Apple code" unless token_response.success?
|
|
97
|
-
|
|
98
|
-
id_token = token_response.parsed_response["id_token"]
|
|
99
|
-
JWT.decode(id_token, nil, false)[0]
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def generate_apple_client_secret
|
|
103
|
-
header = {
|
|
104
|
-
alg: "ES256",
|
|
105
|
-
kid: StandardId.config.apple_key_id
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
payload = {
|
|
109
|
-
iss: StandardId.config.apple_team_id,
|
|
110
|
-
iat: Time.current.to_i,
|
|
111
|
-
exp: Time.current.to_i + 3600,
|
|
112
|
-
aud: "https://appleid.apple.com",
|
|
113
|
-
sub: StandardId.config.apple_client_id
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
private_key = OpenSSL::PKey::EC.new(StandardId.config.apple_private_key)
|
|
117
|
-
JWT.encode(payload, private_key, "ES256", header)
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
def find_or_create_account_from_social(user_info, provider)
|
|
121
|
-
email = user_info["email"]
|
|
122
|
-
raise StandardId::InvalidRequestError, "No email provided by #{provider}" if email.blank?
|
|
123
|
-
|
|
124
|
-
identifier = StandardId::EmailIdentifier.find_by(value: email)
|
|
125
|
-
|
|
126
|
-
return identifier.account if identifier.present?
|
|
127
|
-
|
|
128
|
-
account = Account.create!(
|
|
129
|
-
name: (user_info["name"] || user_info["given_name"] || email),
|
|
130
|
-
email:
|
|
131
|
-
)
|
|
132
|
-
|
|
133
|
-
identifier = StandardId::EmailIdentifier.create!(
|
|
134
|
-
account:,
|
|
135
|
-
value: email
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
identifier.verify!
|
|
139
|
-
|
|
140
|
-
account
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
def generate_authorization_code
|
|
144
|
-
SecureRandom.urlsafe_base64(32)
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
def store_authorization_code(code, original_params, account, provider)
|
|
148
|
-
StandardId::AuthorizationCode.issue!(
|
|
149
|
-
plaintext_code: code,
|
|
150
|
-
client_id: original_params["client_id"],
|
|
151
|
-
redirect_uri: original_params["redirect_uri"],
|
|
152
|
-
scope: original_params["scope"],
|
|
153
|
-
audience: original_params["audience"],
|
|
154
|
-
account: account,
|
|
155
|
-
code_challenge: original_params["code_challenge"],
|
|
156
|
-
code_challenge_method: original_params["code_challenge_method"],
|
|
157
|
-
metadata: { state: original_params["state"], provider: provider }.compact
|
|
158
|
-
)
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
def build_redirect_uri(base_uri, params_hash)
|
|
162
|
-
uri = URI.parse(base_uri)
|
|
163
|
-
query_params = URI.decode_www_form(uri.query || "")
|
|
164
|
-
|
|
165
|
-
params_hash.each do |key, value|
|
|
166
|
-
query_params << [key.to_s, value.to_s] if value.present?
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
uri.query = URI.encode_www_form(query_params)
|
|
170
|
-
uri.to_s
|
|
171
|
-
end
|
|
172
|
-
end
|
|
173
|
-
end
|
|
174
|
-
end
|