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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 70f439b35a454065b4a2e45dfedc85b7316b26b0140d6f005930fc909ac78efd
4
- data.tar.gz: bbf731529fdb2c0cb979e6fecfa711e83daaa1fddfd111263d44618b38bd8fa4
3
+ metadata.gz: d55cdf40a33a4f541b1de5ecb1c19f827b92123b63bccd56781da4671944ff45
4
+ data.tar.gz: 714bc65bd0aa6a6913b51c0f59f659f41a425a5a21489103d89624149e3ff61d
5
5
  SHA512:
6
- metadata.gz: f1d1c0de85827c32f47680adad39da9457528b24376079c154aced6bd46fe40fbe72918df27a9290f54ef6df58e862b251dad7568d16d1abe5dd45c2b477bd45
7
- data.tar.gz: 8d3a5b5c14e42b1850969b6268728ff47c0b78eb5e482f94b0ad4e2b752e98addc52267588adac384d82d01a207047b1e15b25bc9640c56282d5f67c0f297bd0
6
+ metadata.gz: 0625c5efdb00b0e0d8558191c2b8ad7c9512c6ec13a5fe5896393497241f2f8313f8633b3e319358915b00ff33b23a3979b28e32c64fc20664b9a5e513625d5b
7
+ data.tar.gz: 0fb8080e7bee90799d27daa892fb9c221e5d4558b0a5c73916249988f6875793ec54ae2afdc6c93d11b526acee37f23e7350e3f2f47522c67b2e6c37c10e8626
data/README.md CHANGED
@@ -163,13 +163,35 @@ StandardId.configure do |config|
163
163
  config.social.google_client_secret = ENV["GOOGLE_CLIENT_SECRET"]
164
164
 
165
165
  # Apple Sign In
166
+ config.social.apple_mobile_client_id = ENV["APPLE_MOBILE_CLIENT_ID"]
166
167
  config.social.apple_client_id = ENV["APPLE_CLIENT_ID"]
167
168
  config.social.apple_private_key = ENV["APPLE_PRIVATE_KEY"]
168
169
  config.social.apple_key_id = ENV["APPLE_KEY_ID"]
169
170
  config.social.apple_team_id = ENV["APPLE_TEAM_ID"]
171
+ config.social.allowed_redirect_url_prefixes = ["sidekicklabs://"]
172
+
173
+ # Optional: adjust which attributes are persisted during social signup
174
+ config.social.social_account_attributes = ->(social_info:, provider:) {
175
+ {
176
+ email: social_info[:email],
177
+ name: social_info[:name] || social_info[:given_name]
178
+ }
179
+ }
180
+
181
+ # Optional: run a callback whenever a social login completes
182
+ config.social.social_callback = ->(social_info:, provider:, tokens:, account:) {
183
+ AuditLog.social_login(
184
+ provider: provider,
185
+ email: social_info[:email],
186
+ tokens: tokens,
187
+ account_id: account.id,
188
+ )
189
+ }
170
190
  end
171
191
  ```
172
192
 
193
+ `social_info` is an indifferent-access hash containing at least `email`, `name`, and `provider_id`.
194
+
173
195
  ### Passwordless Authentication
174
196
 
175
197
  ```ruby
@@ -222,7 +244,7 @@ redirect_to "/api/authorize?" + {
222
244
  response_type: "code",
223
245
  client_id: "your_client_id",
224
246
  redirect_uri: "https://your-app.com/callback",
225
- connection: "google-oauth2"
247
+ connection: "google"
226
248
  }.to_query
227
249
 
228
250
  # Apple login
@@ -0,0 +1,112 @@
1
+ module StandardId
2
+ module SocialAuthentication
3
+ extend ActiveSupport::Concern
4
+
5
+ private
6
+
7
+ def get_user_info_from_provider(connection, redirect_uri: nil, flow: :web)
8
+ case connection
9
+ when "google"
10
+ StandardId::SocialProviders::Google.get_user_info(
11
+ code: params[:code],
12
+ id_token: params[:id_token],
13
+ access_token: params[:access_token],
14
+ redirect_uri: redirect_uri
15
+ )
16
+ when "apple"
17
+ StandardId::SocialProviders::Apple.get_user_info(
18
+ code: params[:code],
19
+ id_token: params[:id_token],
20
+ redirect_uri: redirect_uri,
21
+ client_id: apple_client_id_for_flow(flow)
22
+ )
23
+ else
24
+ raise StandardId::InvalidRequestError, "Unsupported provider: #{connection}"
25
+ end
26
+ end
27
+
28
+ def apple_client_id_for_flow(flow)
29
+ flow == :web ? StandardId.config.apple_client_id : StandardId.config.apple_mobile_client_id
30
+ end
31
+
32
+ def find_or_create_account_from_social(raw_social_info, provider)
33
+ social_info = raw_social_info.to_h.with_indifferent_access
34
+ email = social_info[:email]
35
+ raise StandardId::InvalidRequestError, "No email provided by #{provider}" if email.blank?
36
+
37
+ identifier = StandardId::EmailIdentifier.find_by(value: email)
38
+
39
+ if identifier.present?
40
+ identifier.account
41
+ else
42
+ account = build_account_from_social(social_info, provider)
43
+ identifier = StandardId::EmailIdentifier.create!(
44
+ account: account,
45
+ value: email
46
+ )
47
+ identifier.verify! if identifier.respond_to?(:verify!)
48
+ account
49
+ end
50
+ end
51
+
52
+ def build_account_from_social(social_info, provider)
53
+ attrs = resolve_account_attributes(social_info, provider)
54
+ StandardId.account_class.create!(attrs)
55
+ end
56
+
57
+ def resolve_account_attributes(social_info, provider)
58
+ resolver = StandardId.config.social_account_attributes
59
+ attrs = if resolver.respond_to?(:call)
60
+ payload = {
61
+ social_info: social_info,
62
+ provider: provider
63
+ }
64
+
65
+ filtered_payload = StandardId::Utils::CallableParameterFilter.filter(resolver, payload)
66
+ resolver.call(**filtered_payload)
67
+ else
68
+ {
69
+ email: social_info[:email],
70
+ name: social_info[:name].presence || social_info[:given_name].presence || social_info[:email]
71
+ }
72
+ end
73
+
74
+ unless attrs.is_a?(Hash)
75
+ raise StandardId::InvalidRequestError, "Social account attribute resolver must return a hash"
76
+ end
77
+
78
+ attrs.symbolize_keys
79
+ end
80
+
81
+ def allow_other_host_redirect?(redirect_uri)
82
+ return false if redirect_uri.blank?
83
+
84
+ allowed = Array(StandardId.config.allowed_redirect_url_prefixes)
85
+ return false if allowed.blank?
86
+
87
+ allowed.any? do |entry|
88
+ case entry
89
+ when Regexp
90
+ entry.match?(redirect_uri)
91
+ else
92
+ redirect_uri.start_with?(entry.to_s)
93
+ end
94
+ end
95
+ end
96
+
97
+ def run_social_callback(provider:, social_info:, provider_tokens:, account:)
98
+ callback = StandardId.config.social_callback
99
+ return if callback.blank?
100
+
101
+ payload = {
102
+ provider: provider,
103
+ social_info: social_info,
104
+ tokens: provider_tokens.presence,
105
+ account: account
106
+ }
107
+
108
+ filtered_payload = StandardId::Utils::CallableParameterFilter.filter(callback, payload)
109
+ callback.call(**filtered_payload.symbolize_keys)
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,68 @@
1
+ module StandardId
2
+ module Api::Oauth
3
+ module Callback
4
+ class ProvidersController < BaseController
5
+ include StandardId::SocialAuthentication
6
+
7
+ skip_before_action :validate_content_type!
8
+
9
+ def google
10
+ expect_and_permit!([], [:id_token, :code])
11
+ handle_social_callback("google")
12
+ end
13
+
14
+ def apple
15
+ expect_and_permit!([], [:id_token, :code, :state, :flow])
16
+ handle_social_callback("apple")
17
+ end
18
+
19
+ private
20
+
21
+ def handle_social_callback(connection)
22
+ original_params = decode_state_params
23
+ flow = resolve_flow_for(connection)
24
+ provider_response = get_user_info_from_provider(connection, flow: flow)
25
+ social_info = provider_response[:user_info]
26
+ provider_tokens = provider_response[:tokens]
27
+ account = find_or_create_account_from_social(social_info, connection)
28
+
29
+ flow = StandardId::Oauth::SocialFlow.new(
30
+ params,
31
+ request,
32
+ account: account,
33
+ connection: connection,
34
+ original_params: original_params
35
+ )
36
+
37
+ token_response = flow.execute
38
+ run_social_callback(
39
+ provider: connection,
40
+ social_info: social_info,
41
+ provider_tokens: provider_tokens,
42
+ account: account,
43
+ )
44
+ render json: token_response, status: :ok
45
+ end
46
+
47
+ def decode_state_params
48
+ encoded_state = params[:state]
49
+
50
+ return {} if encoded_state.blank?
51
+
52
+ begin
53
+ JSON.parse(Base64.urlsafe_decode64(encoded_state))
54
+ rescue JSON::ParserError, ArgumentError
55
+ raise StandardId::InvalidRequestError, "Invalid state parameter"
56
+ end
57
+ end
58
+
59
+ def resolve_flow_for(connection)
60
+ return :mobile unless connection == "apple"
61
+
62
+ flow_param = params[:flow].to_s.downcase
63
+ flow_param == "web" ? :web : :mobile
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -4,107 +4,88 @@ module StandardId
4
4
  module Callback
5
5
  class ProvidersController < StandardId::Web::BaseController
6
6
  include StandardId::WebAuthentication
7
+ include StandardId::SocialAuthentication
7
8
 
8
9
  # Social callbacks must be accessible without an existing browser session
9
10
  # because they create/sign-in the session upon successful callback.
10
- skip_before_action :require_browser_session!, only: [:google, :apple]
11
+ skip_before_action :require_browser_session!, only: [:google, :apple, :apple_mobile]
12
+ skip_before_action :verify_authenticity_token, only: [:apple, :apple_mobile]
11
13
 
12
14
  def google
13
- handle_social_callback("google-oauth2")
15
+ handle_social_callback("google")
14
16
  end
15
17
 
16
18
  def apple
17
19
  handle_social_callback("apple")
18
20
  end
19
21
 
20
- private
22
+ def apple_mobile
23
+ state_data = decode_state_params
24
+ destination = state_data["redirect_uri"]
25
+
26
+ unless allow_other_host_redirect?(destination)
27
+ raise StandardId::InvalidRequestError, "Redirect URI is not allowed"
28
+ end
21
29
 
22
- def handle_social_callback(provider)
23
- # This handles the callback from social providers in web context
24
- # After successful social auth, we need to:
25
- # 1. Extract user info from the callback
26
- # 2. Create/find account
27
- # 3. Sign in the user with web session
28
- # 4. Redirect to original destination
30
+ relay_params = mobile_relay_params
31
+ @mobile_redirect_url = build_mobile_redirect(destination, relay_params)
32
+ render :apple_mobile, layout: false
33
+ rescue StandardId::InvalidRequestError => e
34
+ render plain: e.message, status: :unprocessable_entity
35
+ end
29
36
 
37
+ private
38
+
39
+ def handle_social_callback(connection)
30
40
  if params[:error].present?
31
41
  handle_callback_error
32
42
  return
33
43
  end
34
44
 
45
+ state_data = nil
46
+
35
47
  begin
36
- user_info = extract_user_info(provider)
37
- account = find_or_create_account_from_social(user_info, provider)
48
+ state_data = decode_state_params
49
+ redirect_uri = connection == "apple" ? apple_callback_url : google_callback_url
50
+ provider_response = get_user_info_from_provider(connection, redirect_uri: redirect_uri)
51
+ social_info = provider_response[:user_info]
52
+ provider_tokens = provider_response[:tokens]
53
+ account = find_or_create_account_from_social(social_info, connection)
38
54
  session_manager.sign_in_account(account)
39
55
 
40
- redirect_to decode_redirect_uri, notice: "Successfully signed in with #{provider.humanize}"
41
- rescue StandardId::OAuthError => e
42
- redirect_to login_path, alert: "Authentication failed: #{e.message}"
43
- end
44
- end
56
+ run_social_callback(
57
+ provider: connection,
58
+ social_info: social_info,
59
+ provider_tokens: provider_tokens,
60
+ account: account,
61
+ )
45
62
 
46
- def extract_user_info(provider)
47
- case provider
48
- when "google-oauth2"
49
- extract_google_user_info
50
- when "apple"
51
- extract_apple_user_info
52
- else
53
- raise StandardId::InvalidRequestError, "Unsupported connection/provider: #{provider}"
63
+ destination = state_data["redirect_uri"]
64
+ redirect_options = { notice: "Successfully signed in with #{connection.humanize}" }
65
+ redirect_options[:allow_other_host] = true if allow_other_host_redirect?(destination)
66
+ redirect_to destination, redirect_options
67
+ rescue StandardId::OAuthError => e
68
+ redirect_to StandardId::WebEngine.routes.url_helpers.login_path(redirect_uri: state_data&.dig("redirect_uri")), alert: "Authentication failed: #{e.message}"
54
69
  end
55
70
  end
56
71
 
57
- def extract_google_user_info
58
- # Exchange code for Google user info
59
- # This would integrate with Google OAuth API
60
- {
61
- email: params[:email] || "user@example.com", # Placeholder
62
- name: params[:name] || "Google User",
63
- provider: "google-oauth2",
64
- provider_id: params[:sub] || "google_123"
65
- }
72
+ def google_callback_url
73
+ auth_callback_google_url
66
74
  end
67
75
 
68
- def extract_apple_user_info
69
- # Extract user info from Apple Sign In callback
70
- # This would decode the Apple ID token
71
- {
72
- email: params[:email] || "user@privaterelay.appleid.com", # Placeholder
73
- name: params[:name] || "Apple User",
74
- provider: "apple",
75
- provider_id: params[:sub] || "apple_123"
76
- }
76
+ def apple_callback_url
77
+ auth_callback_apple_url
77
78
  end
78
79
 
79
- def find_or_create_account_from_social(user_info, provider)
80
- # Find existing account by email or create new one
81
- identifier = StandardId::EmailIdentifier.find_by(value: user_info[:email])
80
+ def decode_state_params
81
+ encoded_state = params[:state]
82
+ raise StandardId::InvalidRequestError, "Missing state parameter" if encoded_state.blank?
82
83
 
83
- if identifier
84
- identifier.account
85
- else
86
- # Create new account with social login
87
- account = ::Account.create!(
88
- email: user_info[:email],
89
- name: user_info[:name].presence || "User"
90
- )
91
- StandardId::EmailIdentifier.create!(
92
- account: account,
93
- value: user_info[:email]
94
- )
95
- account
96
- end
97
- end
98
-
99
- def decode_redirect_uri
100
- return "/" unless params[:state].present?
101
-
102
- begin
103
- state_data = JSON.parse(Base64.urlsafe_decode64(params[:state]))
104
- state_data["redirect_uri"] || "/"
105
- rescue JSON::ParserError, ArgumentError
106
- "/"
107
- end
84
+ state = JSON.parse(Base64.urlsafe_decode64(encoded_state))
85
+ state["redirect_uri"] ||= after_authentication_url
86
+ state
87
+ rescue JSON::ParserError, ArgumentError
88
+ raise StandardId::InvalidRequestError, "Invalid state parameter"
108
89
  end
109
90
 
110
91
  def handle_callback_error
@@ -117,7 +98,21 @@ module StandardId
117
98
  "Authentication failed"
118
99
  end
119
100
 
120
- redirect_to login_path, alert: error_message
101
+ redirect_to StandardId::WebEngine.routes.url_helpers.login_path, alert: error_message
102
+ end
103
+
104
+ def mobile_relay_params
105
+ params.permit(:code, :state, :user, :userIdentifier, :id_token, :identity_token, :nonce).to_h.compact
106
+ end
107
+
108
+ def build_mobile_redirect(destination, extra_params)
109
+ uri = URI.parse(destination)
110
+ existing = Rack::Utils.parse_nested_query(uri.query)
111
+ merged = existing.merge(extra_params)
112
+ uri.query = merged.to_query.presence
113
+ uri.to_s
114
+ rescue URI::InvalidURIError
115
+ destination
121
116
  end
122
117
  end
123
118
  end
@@ -33,32 +33,35 @@ module StandardId
33
33
  end
34
34
 
35
35
  def social_login_url
36
- uri = URI.parse("/api/authorize")
37
- query = {
38
- response_type: "code",
39
- client_id: StandardId.config.default_client_id,
40
- redirect_uri: callback_url,
41
- connection: params[:connection],
42
- state: encode_state
43
- }.to_query
44
- uri.query = query
45
- uri.to_s
46
- end
47
-
48
- def callback_url
49
36
  case params[:connection]
50
- when "google-oauth2"
51
- auth_callback_google_path
37
+ when "google"
38
+ google_authorization_url
52
39
  when "apple"
53
- auth_callback_apple_path
40
+ apple_authorization_url
41
+ else
42
+ raise StandardId::InvalidRequestError, "Unsupported social connection: #{connection}"
54
43
  end
55
44
  end
56
45
 
46
+ def google_authorization_url
47
+ StandardId::SocialProviders::Google.authorization_url(
48
+ state: encode_state,
49
+ redirect_uri: auth_callback_google_url
50
+ )
51
+ end
52
+
53
+ def apple_authorization_url
54
+ StandardId::SocialProviders::Apple.authorization_url(
55
+ state: encode_state,
56
+ redirect_uri: auth_callback_apple_url
57
+ )
58
+ end
59
+
57
60
  def encode_state
58
61
  Base64.urlsafe_encode64({
59
62
  redirect_uri: params[:redirect_uri] || after_authentication_url,
60
63
  timestamp: Time.current.to_i
61
- }.to_json)
64
+ }.compact.to_json)
62
65
  end
63
66
 
64
67
  def login_params
@@ -56,7 +56,7 @@ module StandardId
56
56
 
57
57
  def callback_url
58
58
  case params[:connection]
59
- when "google-oauth2"
59
+ when "google"
60
60
  auth_callback_google_url
61
61
  when "apple"
62
62
  auth_callback_apple_url
@@ -0,0 +1,50 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>Continuing Sign in with Apple…</title>
6
+ <style>
7
+ body {
8
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
9
+ margin: 0;
10
+ padding: 2rem;
11
+ text-align: center;
12
+ color: #111;
13
+ background: #f8f8f8;
14
+ }
15
+
16
+ .card {
17
+ margin: 0 auto;
18
+ max-width: 420px;
19
+ padding: 2rem;
20
+ background: #fff;
21
+ border-radius: 12px;
22
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
23
+ }
24
+
25
+ h1 {
26
+ font-size: 1.3rem;
27
+ margin-bottom: 0.5rem;
28
+ }
29
+
30
+ p {
31
+ margin: 0;
32
+ color: #555;
33
+ }
34
+ </style>
35
+ <script>
36
+ document.addEventListener("DOMContentLoaded", function () {
37
+ var target = "<%= j @mobile_redirect_url %>";
38
+ if (target) {
39
+ window.location.replace(target);
40
+ }
41
+ });
42
+ </script>
43
+ </head>
44
+ <body>
45
+ <div class="card">
46
+ <h1>Returning to your app…</h1>
47
+ <p>You can close this window if it doesn't redirect automatically.</p>
48
+ </div>
49
+ </body>
50
+ </html>
@@ -68,8 +68,8 @@
68
68
 
69
69
  <div class="mt-6 grid grid-cols-2 gap-4">
70
70
  <% if StandardId.config.google_client_id.present? %>
71
- <%= form_with url: login_path, method: :post, local: true do |form| %>
72
- <%= form.hidden_field :connection, value: "google-oauth2" %>
71
+ <%= form_with url: login_path, method: :post, local: true, data: { turbo: false } do |form| %>
72
+ <%= form.hidden_field :connection, value: "google" %>
73
73
  <%= form.hidden_field :redirect_uri, value: @redirect_uri %>
74
74
  <button type="submit" class="flex w-full items-center justify-center gap-3 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:ring-transparent dark:bg-white/10 dark:text-white dark:shadow-none dark:ring-white/5 dark:hover:bg-white/20">
75
75
  <svg viewBox="0 0 24 24" aria-hidden="true" class="h-5 w-5">
@@ -84,7 +84,7 @@
84
84
  <% end %>
85
85
 
86
86
  <% if StandardId.config.apple_client_id.present? %>
87
- <%= form_with url: login_path, method: :post, local: true do |form| %>
87
+ <%= form_with url: login_path, method: :post, local: true, data: { turbo: false } do |form| %>
88
88
  <%= form.hidden_field :connection, value: "apple" %>
89
89
  <%= form.hidden_field :redirect_uri, value: @redirect_uri %>
90
90
  <button type="submit" class="flex w-full items-center justify-center gap-3 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:ring-transparent dark:bg-white/10 dark:text-white dark:shadow-none dark:ring-white/5 dark:hover:bg-white/20">
@@ -57,7 +57,7 @@
57
57
  <div class="mt-6 grid grid-cols-2 gap-4">
58
58
  <% if StandardId.config.google_client_id.present? %>
59
59
  <%= form_with url: signup_path, method: :post, local: true do |form| %>
60
- <%= form.hidden_field :connection, value: "google-oauth2" %>
60
+ <%= form.hidden_field :connection, value: "google" %>
61
61
  <%= form.hidden_field :redirect_uri, value: @redirect_uri %>
62
62
  <button type="submit" class="flex w-full items-center justify-center gap-3 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:ring-transparent dark:bg-white/10 dark:text-white dark:shadow-none dark:ring-white/5 dark:hover:bg-white/20">
63
63
  <svg viewBox="0 0 24 24" aria-hidden="true" class="h-5 w-5">
data/config/routes/api.rb CHANGED
@@ -16,7 +16,7 @@ StandardId::ApiEngine.routes.draw do
16
16
  resource :token, only: [:create]
17
17
 
18
18
  namespace :callback do
19
- get :google, to: "providers#google"
19
+ post :google, to: "providers#google"
20
20
  post :apple, to: "providers#apple"
21
21
  end
22
22
  end
data/config/routes/web.rb CHANGED
@@ -10,6 +10,7 @@ StandardId::WebEngine.routes.draw do
10
10
  namespace :callback do
11
11
  get :google, to: "providers#google"
12
12
  post :apple, to: "providers#apple"
13
+ post :apple_mobile, to: "providers#apple_mobile"
13
14
  end
14
15
  end
15
16
 
@@ -20,6 +20,7 @@ StandardId.configure do |c|
20
20
  # c.oauth.token_lifetimes = {
21
21
  # password: 8.hours,
22
22
  # client_credentials: 24.hours
23
+ # social: 24.hours
23
24
  # }
24
25
  # c.oauth.scope_claims = {
25
26
  # profile: %i[email display_name]
@@ -34,10 +35,26 @@ StandardId.configure do |c|
34
35
  # Social login credentials (if enabled in your app)
35
36
  # c.social.google_client_id = ENV["GOOGLE_CLIENT_ID"]
36
37
  # c.social.google_client_secret = ENV["GOOGLE_CLIENT_SECRET"]
38
+ # c.social.apple_mobile_client_id = ENV["APPLE_MOBILE_CLIENT_ID"]
37
39
  # c.social.apple_client_id = ENV["APPLE_CLIENT_ID"]
38
40
  # c.social.apple_private_key = ENV["APPLE_PRIVATE_KEY"]
39
41
  # c.social.apple_key_id = ENV["APPLE_KEY_ID"]
40
42
  # c.social.apple_team_id = ENV["APPLE_TEAM_ID"]
43
+ # c.social.allowed_redirect_url_prefixes = ["sidekicklabs://"]
44
+ # c.social.social_account_attributes = ->(social_info:, provider:) {
45
+ # {
46
+ # email: social_info[:email],
47
+ # name: social_info[:name] || social_info[:given_name]
48
+ # }
49
+ # }
50
+ # c.social.social_callback = ->(social_info:, provider:, tokens:, account:) {
51
+ # Analytics.track_social_login(
52
+ # provider: provider,
53
+ # email: social_info[:email],
54
+ # tokens: tokens,
55
+ # account_id: account.id
56
+ # )
57
+ # }
41
58
 
42
59
  # OIDC Logout allow list
43
60
  # c.allowed_post_logout_redirect_uris = [
@@ -23,9 +23,10 @@ module StandardConfig
23
23
  # If set, Authorization endpoints can redirect to this path with a redirect_uri param
24
24
  attr_accessor :login_url
25
25
 
26
- # Social login provider credentials
26
+ # Social login provider credentials and hooks
27
27
  attr_accessor :google_client_id, :google_client_secret
28
28
  attr_accessor :apple_client_id, :apple_client_secret, :apple_private_key, :apple_key_id, :apple_team_id
29
+ attr_accessor :social_account_attributes, :social_callback
29
30
 
30
31
  # Passwordless authentication callbacks
31
32
  # These should be callable objects (procs/lambdas) that accept (recipient, code) parameters
@@ -54,6 +55,8 @@ module StandardConfig
54
55
  @apple_private_key = nil
55
56
  @apple_key_id = nil
56
57
  @apple_team_id = nil
58
+ @social_account_attributes = nil
59
+ @social_callback = nil
57
60
  @passwordless_email_sender = nil
58
61
  @passwordless_sms_sender = nil
59
62
  @allowed_post_logout_redirect_uris = []
@@ -47,5 +47,9 @@ StandardConfig.schema.draw do
47
47
  field :apple_private_key, type: :string, default: nil
48
48
  field :apple_key_id, type: :string, default: nil
49
49
  field :apple_team_id, type: :string, default: nil
50
+ field :apple_mobile_client_id, type: :string, default: nil
51
+ field :social_account_attributes, type: :any, default: nil
52
+ field :allowed_redirect_url_prefixes, type: :array, default: []
53
+ field :social_callback, type: :any, default: nil
50
54
  end
51
55
  end