better_auth-sso 0.2.0 → 0.5.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.
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterAuth
4
+ module SSO
5
+ module Constants
6
+ AUTHN_REQUEST_KEY_PREFIX = BetterAuth::Plugins::SSO_SAML_AUTHN_REQUEST_KEY_PREFIX
7
+ USED_ASSERTION_KEY_PREFIX = BetterAuth::Plugins::SSO_SAML_USED_ASSERTION_KEY_PREFIX
8
+ SAML_SESSION_KEY_PREFIX = BetterAuth::Plugins::SSO_SAML_SESSION_KEY_PREFIX
9
+ SAML_SESSION_BY_ID_PREFIX = BetterAuth::Plugins::SSO_SAML_SESSION_BY_ID_KEY_PREFIX
10
+ LOGOUT_REQUEST_KEY_PREFIX = BetterAuth::Plugins::SSO_SAML_LOGOUT_REQUEST_KEY_PREFIX
11
+ DEFAULT_AUTHN_REQUEST_TTL_MS = BetterAuth::Plugins::SSO_DEFAULT_AUTHN_REQUEST_TTL_MS
12
+ DEFAULT_ASSERTION_TTL_MS = BetterAuth::Plugins::SSO_DEFAULT_ASSERTION_TTL_MS
13
+ DEFAULT_LOGOUT_REQUEST_TTL_MS = BetterAuth::Plugins::SSO_DEFAULT_LOGOUT_REQUEST_TTL_MS
14
+ DEFAULT_CLOCK_SKEW_MS = BetterAuth::Plugins::SSO_DEFAULT_CLOCK_SKEW_MS
15
+ DEFAULT_MAX_SAML_RESPONSE_SIZE = BetterAuth::Plugins::SSO_DEFAULT_MAX_SAML_RESPONSE_SIZE
16
+ DEFAULT_MAX_SAML_METADATA_SIZE = BetterAuth::Plugins::SSO_DEFAULT_MAX_SAML_METADATA_SIZE
17
+ SAML_STATUS_SUCCESS = BetterAuth::Plugins::SSO_SAML_STATUS_SUCCESS
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterAuth
4
+ module SSO
5
+ module DomainVerification
6
+ module_function
7
+
8
+ def identifier(config, provider_id)
9
+ BetterAuth::Plugins.sso_domain_verification_identifier(config, provider_id)
10
+ end
11
+
12
+ def hostname(domain)
13
+ BetterAuth::Plugins.sso_hostname_from_domain(domain)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterAuth
4
+ module SSO
5
+ module Linking
6
+ module OrgAssignment
7
+ module_function
8
+
9
+ def assign_organization_from_provider(ctx, provider:, user:, profile: {}, token: nil, provisioning_options: nil, config: {})
10
+ organization_id = fetch_value(provider, :organization_id)
11
+ return if organization_id.to_s.empty?
12
+
13
+ options = normalized_provisioning_options(provisioning_options, config)
14
+ return if options[:disabled]
15
+ return unless organization_plugin?(ctx)
16
+ return if member_exists?(ctx, organization_id, fetch_value(user, :id))
17
+
18
+ role = organization_role(
19
+ options,
20
+ user: user,
21
+ user_info: fetch_value(profile || {}, :raw_attributes) || {},
22
+ token: token,
23
+ provider: provider
24
+ )
25
+ create_member(ctx, organization_id, fetch_value(user, :id), role)
26
+ end
27
+
28
+ def assign_organization_by_domain(ctx, user:, provisioning_options: nil, domain_verification: nil, config: {})
29
+ options = normalized_provisioning_options(provisioning_options, config)
30
+ return if options[:disabled]
31
+ return unless organization_plugin?(ctx)
32
+
33
+ domain_config = BetterAuth::Plugins.normalize_hash(domain_verification || config[:domain_verification] || {})
34
+ domain = fetch_value(user, :email).to_s.split("@", 2)[1]
35
+ return if domain.to_s.empty?
36
+
37
+ where = [{field: "domain", value: domain}]
38
+ where << {field: "domainVerified", value: true} if domain_config[:enabled]
39
+ provider = ctx.context.adapter.find_one(model: "ssoProvider", where: where)
40
+
41
+ unless provider
42
+ fallback_where = domain_config[:enabled] ? [{field: "domainVerified", value: true}] : []
43
+ providers = ctx.context.adapter.find_many(model: "ssoProvider", where: fallback_where)
44
+ provider = providers.find do |entry|
45
+ (!domain_config[:enabled] || fetch_value(entry, :domain_verified)) &&
46
+ BetterAuth::SSO::Utils.domain_matches?(domain, fetch_value(entry, :domain))
47
+ end
48
+ end
49
+
50
+ organization_id = fetch_value(provider || {}, :organization_id)
51
+ return if organization_id.to_s.empty?
52
+ return if member_exists?(ctx, organization_id, fetch_value(user, :id))
53
+
54
+ role = organization_role(
55
+ options,
56
+ user: user,
57
+ user_info: {},
58
+ provider: provider
59
+ )
60
+ create_member(ctx, organization_id, fetch_value(user, :id), role)
61
+ end
62
+
63
+ def normalized_provisioning_options(provisioning_options, config)
64
+ BetterAuth::Plugins.normalize_hash(provisioning_options || config[:organization_provisioning] || {})
65
+ end
66
+
67
+ def organization_plugin?(ctx)
68
+ context = ctx.context
69
+ return context.hasPlugin("organization") if context.respond_to?(:hasPlugin)
70
+ return context.has_plugin?("organization") if context.respond_to?(:has_plugin?)
71
+
72
+ plugins = context.options.respond_to?(:plugins) ? context.options.plugins : []
73
+ plugins.any? { |plugin| plugin.respond_to?(:id) && plugin.id == "organization" }
74
+ end
75
+
76
+ def member_exists?(ctx, organization_id, user_id)
77
+ ctx.context.adapter.find_one(
78
+ model: "member",
79
+ where: [
80
+ {field: "organizationId", value: organization_id},
81
+ {field: "userId", value: user_id}
82
+ ]
83
+ )
84
+ end
85
+
86
+ def organization_role(options, user:, user_info:, provider:, token: nil)
87
+ get_role = options[:get_role]
88
+ if get_role.respond_to?(:call)
89
+ return get_role.call(
90
+ user: user,
91
+ userInfo: user_info,
92
+ token: token,
93
+ provider: provider
94
+ )
95
+ end
96
+
97
+ options[:default_role] || options[:role] || "member"
98
+ end
99
+
100
+ def create_member(ctx, organization_id, user_id, role)
101
+ ctx.context.adapter.create(
102
+ model: "member",
103
+ data: {
104
+ organizationId: organization_id,
105
+ userId: user_id,
106
+ role: role,
107
+ createdAt: Time.now
108
+ }
109
+ )
110
+ end
111
+
112
+ def fetch_value(data, key)
113
+ BetterAuth::Plugins.sso_fetch(data, key)
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterAuth
4
+ module SSO
5
+ module Linking
6
+ module Types
7
+ REQUIRED_PROFILE_KEYS = {
8
+ provider_type: "providerType",
9
+ provider_id: "providerId",
10
+ account_id: "accountId",
11
+ email: "email",
12
+ email_verified: "emailVerified"
13
+ }.freeze
14
+
15
+ module_function
16
+
17
+ def normalized_profile(profile)
18
+ raw_attributes = raw_value(profile, :raw_attributes, "rawAttributes", "raw_attributes")
19
+ source = BetterAuth::Plugins.normalize_hash(profile || {})
20
+ normalized = {
21
+ provider_type: source[:provider_type].to_s,
22
+ provider_id: source[:provider_id].to_s,
23
+ account_id: source[:account_id].to_s,
24
+ email: source[:email].to_s.downcase,
25
+ email_verified: !!source[:email_verified]
26
+ }
27
+ normalized[:name] = source[:name] if source.key?(:name)
28
+ normalized[:image] = source[:image] if source.key?(:image)
29
+ normalized[:raw_attributes] = raw_attributes unless raw_attributes.nil?
30
+
31
+ missing = REQUIRED_PROFILE_KEYS.filter_map do |key, upstream_name|
32
+ value = normalized[key]
33
+ upstream_name if value.nil? || (value.respond_to?(:empty?) && value.empty?)
34
+ end
35
+ raise ArgumentError, "Missing normalized SSO profile fields: #{missing.join(", ")}" unless missing.empty?
36
+ raise ArgumentError, "Invalid normalized SSO profile providerType: #{normalized[:provider_type]}" unless BetterAuth::SSO::Types.provider_type?(normalized[:provider_type])
37
+
38
+ normalized.freeze
39
+ end
40
+
41
+ def raw_value(profile, *keys)
42
+ return nil unless profile.respond_to?(:key?) && profile.respond_to?(:[])
43
+
44
+ keys.each do |key|
45
+ return profile[key] if profile.key?(key)
46
+ end
47
+ nil
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "linking/types"
4
+ require_relative "linking/org_assignment"
5
+
6
+ module BetterAuth
7
+ module SSO
8
+ module Linking
9
+ module_function
10
+
11
+ def assign_organization_from_provider(ctx, provider:, user:, config: {})
12
+ OrgAssignment.assign_organization_from_provider(ctx, provider: provider, user: user, config: config)
13
+ end
14
+
15
+ def assign_organization_by_domain(ctx, user:, config: {})
16
+ OrgAssignment.assign_organization_by_domain(ctx, user: user, config: config)
17
+ end
18
+
19
+ def normalized_profile(profile)
20
+ Types.normalized_profile(profile)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,259 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+ require "json"
5
+ require "net/http"
6
+
7
+ module BetterAuth
8
+ module SSO
9
+ module OIDC
10
+ module Discovery
11
+ module_function
12
+
13
+ REQUIRED_DISCOVERY_FIELDS = %i[issuer authorization_endpoint token_endpoint jwks_uri].freeze
14
+ DISCOVERY_URL_FIELDS = %i[
15
+ token_endpoint
16
+ authorization_endpoint
17
+ jwks_uri
18
+ userinfo_endpoint
19
+ revocation_endpoint
20
+ end_session_endpoint
21
+ introspection_endpoint
22
+ ].freeze
23
+
24
+ def compute_discovery_url(issuer)
25
+ "#{issuer.to_s.sub(%r{/+\z}, "")}/.well-known/openid-configuration"
26
+ end
27
+
28
+ def validate_discovery_url(url, trusted_origin = nil)
29
+ uri = parse_http_url!(url, "discoveryEndpoint", details: {url: url})
30
+ return true unless trusted_origin && !trusted_origin.call(uri.to_s)
31
+
32
+ raise DiscoveryError.new(
33
+ "discovery_untrusted_origin",
34
+ "The main discovery endpoint \"#{uri}\" is not trusted by your trusted origins configuration.",
35
+ details: {url: uri.to_s}
36
+ )
37
+ end
38
+
39
+ def validate_discovery_document(document, issuer)
40
+ doc = BetterAuth::Plugins.normalize_hash(document || {})
41
+ missing = REQUIRED_DISCOVERY_FIELDS.select { |field| doc[field].to_s.empty? }
42
+ unless missing.empty?
43
+ raise DiscoveryError.new(
44
+ "discovery_incomplete",
45
+ "OIDC discovery document is missing required fields: #{missing.join(", ")}",
46
+ details: {missingFields: missing.map(&:to_s)}
47
+ )
48
+ end
49
+
50
+ discovered = doc[:issuer].to_s.sub(%r{/+\z}, "")
51
+ configured = issuer.to_s.sub(%r{/+\z}, "")
52
+ return true if discovered == configured
53
+
54
+ raise DiscoveryError.new(
55
+ "issuer_mismatch",
56
+ "OIDC discovery issuer does not match configured issuer",
57
+ details: {discovered: doc[:issuer], configured: issuer}
58
+ )
59
+ end
60
+
61
+ def normalize_discovery_urls(document, issuer, trusted_origin = nil)
62
+ doc = BetterAuth::Plugins.normalize_hash(document || {}).dup
63
+ DISCOVERY_URL_FIELDS.each do |field|
64
+ next if doc[field].to_s.empty?
65
+
66
+ doc[field] = normalize_url(field.to_s, doc[field], issuer, trusted_origin)
67
+ end
68
+ doc
69
+ end
70
+
71
+ def fetch_discovery_document(url, timeout: nil, fetch: nil)
72
+ response = if fetch
73
+ fetch.call(url, timeout: timeout)
74
+ else
75
+ uri = URI(url)
76
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https", read_timeout: timeout) do |http|
77
+ http.get(uri.request_uri)
78
+ end
79
+ end
80
+ parse_discovery_fetch_response(response)
81
+ rescue DiscoveryError
82
+ raise
83
+ rescue Timeout::Error
84
+ raise DiscoveryError.new("discovery_timeout", "OIDC discovery request timed out", details: {url: url})
85
+ rescue => exception
86
+ if exception.message.match?(/aborted/i)
87
+ raise DiscoveryError.new("discovery_timeout", "OIDC discovery request timed out", details: {url: url})
88
+ end
89
+
90
+ raise DiscoveryError.new("discovery_unexpected_error", "OIDC discovery request failed", details: {url: url, error: exception.message})
91
+ end
92
+
93
+ def discover_oidc_config(issuer:, fetch: nil, existing_config: nil, discovery_endpoint: nil, trusted_origin: nil, is_trusted_origin: nil, timeout: nil)
94
+ existing = BetterAuth::Plugins.normalize_hash(existing_config || {})
95
+ origin_check = trusted_origin || is_trusted_origin
96
+ discovery_url = discovery_endpoint || existing[:discovery_endpoint] || compute_discovery_url(issuer)
97
+ validate_discovery_url(discovery_url, origin_check)
98
+
99
+ document = fetch_discovery_document(discovery_url, timeout: timeout, fetch: fetch)
100
+ validate_discovery_document(document, issuer)
101
+ normalized_document = normalize_discovery_urls(document, issuer, origin_check)
102
+
103
+ {
104
+ issuer: existing[:issuer] || normalized_document[:issuer],
105
+ discovery_endpoint: existing[:discovery_endpoint] || discovery_url,
106
+ client_id: existing[:client_id],
107
+ client_secret: existing[:client_secret],
108
+ authorization_endpoint: existing[:authorization_endpoint] || normalized_document[:authorization_endpoint],
109
+ token_endpoint: existing[:token_endpoint] || normalized_document[:token_endpoint],
110
+ jwks_endpoint: existing[:jwks_endpoint] || normalized_document[:jwks_uri],
111
+ user_info_endpoint: existing[:user_info_endpoint] || normalized_document[:userinfo_endpoint],
112
+ token_endpoint_authentication: select_token_endpoint_auth_method(normalized_document, existing[:token_endpoint_authentication]),
113
+ scopes_supported: existing[:scopes_supported] || normalized_document[:scopes_supported],
114
+ pkce: existing[:pkce],
115
+ override_user_info: existing[:override_user_info],
116
+ mapping: existing[:mapping]
117
+ }.compact
118
+ end
119
+
120
+ def normalize_url(name_or_value, value_or_issuer, issuer = nil, trusted_origin = nil)
121
+ name = issuer.nil? ? "url" : name_or_value.to_s
122
+ value = issuer.nil? ? name_or_value : value_or_issuer
123
+ issuer_value = issuer.nil? ? value_or_issuer : issuer
124
+ normalized = normalize_endpoint_url(name, value, issuer_value)
125
+
126
+ if trusted_origin && !trusted_origin.call(normalized)
127
+ raise DiscoveryError.new(
128
+ "discovery_untrusted_origin",
129
+ "The #{name} \"#{normalized}\" is not trusted by your trusted origins configuration.",
130
+ details: {endpoint: name, url: normalized}
131
+ )
132
+ end
133
+
134
+ normalized
135
+ end
136
+
137
+ def needs_runtime_discovery?(oidc_config)
138
+ config = BetterAuth::Plugins.normalize_hash(oidc_config || {})
139
+ config[:authorization_endpoint].to_s.empty? ||
140
+ config[:token_endpoint].to_s.empty? ||
141
+ config[:jwks_endpoint].to_s.empty?
142
+ end
143
+
144
+ def ensure_runtime_discovery(config, issuer, trusted_origin, fetch: nil, timeout: nil)
145
+ normalized = BetterAuth::Plugins.normalize_hash(config || {})
146
+ return config unless needs_runtime_discovery?(normalized)
147
+
148
+ discovered = discover_oidc_config(
149
+ issuer: issuer,
150
+ existing_config: normalized,
151
+ trusted_origin: trusted_origin,
152
+ fetch: fetch,
153
+ timeout: timeout
154
+ )
155
+ normalized.merge(
156
+ authorization_endpoint: discovered[:authorization_endpoint],
157
+ token_endpoint: discovered[:token_endpoint],
158
+ token_endpoint_authentication: discovered[:token_endpoint_authentication],
159
+ user_info_endpoint: discovered[:user_info_endpoint],
160
+ jwks_endpoint: discovered[:jwks_endpoint]
161
+ ).compact
162
+ end
163
+
164
+ def select_token_endpoint_auth_method(document_or_config = {}, existing_method = nil)
165
+ return existing_method if existing_method
166
+
167
+ config = BetterAuth::Plugins.normalize_hash(document_or_config || {})
168
+ return config[:token_endpoint_authentication] if config[:token_endpoint_authentication]
169
+
170
+ methods = config[:token_endpoint_auth_methods_supported] || config[:methods] || []
171
+ return "client_secret_post" if Array(methods).include?("client_secret_post") && !Array(methods).include?("client_secret_basic")
172
+
173
+ "client_secret_basic"
174
+ end
175
+
176
+ def parse_http_url!(url, name, details: {})
177
+ uri = URI.parse(url.to_s)
178
+ raise URI::InvalidURIError if uri.scheme.to_s.empty? || uri.host.to_s.empty?
179
+ unless %w[http https].include?(uri.scheme)
180
+ raise DiscoveryError.new(
181
+ "discovery_invalid_url",
182
+ "The url \"#{name}\" must use the http or https supported protocols",
183
+ details: details.merge(protocol: "#{uri.scheme}:")
184
+ )
185
+ end
186
+
187
+ uri
188
+ rescue URI::InvalidURIError
189
+ raise DiscoveryError.new(
190
+ "discovery_invalid_url",
191
+ "The url \"#{name}\" must be valid",
192
+ details: details
193
+ )
194
+ end
195
+
196
+ def normalize_endpoint_url(name, endpoint, issuer)
197
+ raw = endpoint.to_s
198
+ if raw.match?(%r{\Ahttps?://}i)
199
+ uri = parse_http_url!(raw, name, details: {endpoint: name, url: raw})
200
+ return uri.to_s
201
+ end
202
+
203
+ issuer_uri = parse_http_url!(issuer, name, details: {endpoint: name, url: raw})
204
+ issuer_base = issuer_uri.to_s.sub(%r{/+\z}, "")
205
+ endpoint_path = raw.sub(%r{\A/+}, "")
206
+ normalized = "#{issuer_base}/#{endpoint_path}"
207
+ parse_http_url!(normalized, name, details: {endpoint: name, url: normalized}).to_s
208
+ end
209
+
210
+ def parse_discovery_fetch_response(response)
211
+ if response.respond_to?(:code) && response.respond_to?(:body)
212
+ status = response.code.to_i
213
+ body = response.body
214
+ return parse_discovery_body(body) if status.between?(200, 299)
215
+
216
+ raise_discovery_http_error(status, response.message.to_s)
217
+ end
218
+
219
+ normalized = response.is_a?(Hash) ? BetterAuth::Plugins.normalize_hash(response) : {data: response}
220
+ error = normalized[:error]
221
+ if error
222
+ error_hash = BetterAuth::Plugins.normalize_hash(error)
223
+ raise_discovery_http_error(error_hash[:status].to_i, error_hash[:message].to_s)
224
+ end
225
+
226
+ data = normalized.key?(:data) ? normalized[:data] : normalized
227
+ parse_discovery_body(data)
228
+ end
229
+
230
+ def parse_discovery_body(data)
231
+ raise DiscoveryError.new("discovery_invalid_json", "OIDC discovery response was empty") if data.nil?
232
+ return BetterAuth::Plugins.normalize_hash(data) if data.is_a?(Hash)
233
+
234
+ parsed = JSON.parse(data.to_s)
235
+ raise JSON::ParserError if !parsed.is_a?(Hash)
236
+
237
+ BetterAuth::Plugins.normalize_hash(parsed)
238
+ rescue JSON::ParserError
239
+ raise DiscoveryError.new(
240
+ "discovery_invalid_json",
241
+ "OIDC discovery response was not valid JSON",
242
+ details: {bodyPreview: data.to_s[0, 200]}
243
+ )
244
+ end
245
+
246
+ def raise_discovery_http_error(status, message)
247
+ case status
248
+ when 404
249
+ raise DiscoveryError.new("discovery_not_found", "OIDC discovery endpoint was not found", details: {status: status, message: message})
250
+ when 408
251
+ raise DiscoveryError.new("discovery_timeout", "OIDC discovery request timed out", details: {status: status, message: message})
252
+ else
253
+ raise DiscoveryError.new("discovery_unexpected_error", "OIDC discovery request failed", details: {status: status, message: message})
254
+ end
255
+ end
256
+ end
257
+ end
258
+ end
259
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterAuth
4
+ module SSO
5
+ module OIDC
6
+ class DiscoveryError < StandardError
7
+ attr_reader :code, :details
8
+
9
+ def initialize(code, message, details: {})
10
+ @code = code
11
+ @details = details
12
+ super(message)
13
+ end
14
+ end
15
+
16
+ module Errors
17
+ module_function
18
+
19
+ def api_error(error)
20
+ return error if error.is_a?(APIError)
21
+
22
+ APIError.new("BAD_REQUEST", message: error.message)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterAuth
4
+ module SSO
5
+ module OIDC
6
+ module Types
7
+ DISCOVERY_ERROR_CODES = %w[
8
+ discovery_timeout
9
+ discovery_not_found
10
+ discovery_invalid_json
11
+ discovery_invalid_url
12
+ discovery_untrusted_origin
13
+ issuer_mismatch
14
+ discovery_incomplete
15
+ unsupported_token_auth_method
16
+ discovery_unexpected_error
17
+ ].freeze
18
+
19
+ REQUIRED_DISCOVERY_FIELDS = Discovery::REQUIRED_DISCOVERY_FIELDS
20
+
21
+ module_function
22
+
23
+ def discovery_error_code?(value)
24
+ DISCOVERY_ERROR_CODES.include?(value.to_s)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "oidc/discovery"
4
+ require_relative "oidc/errors"
5
+
6
+ module BetterAuth
7
+ module SSO
8
+ module OIDC
9
+ module_function
10
+
11
+ def discover_config(**kwargs)
12
+ Discovery.discover_oidc_config(**kwargs)
13
+ end
14
+
15
+ def needs_runtime_discovery?(oidc_config)
16
+ Discovery.needs_runtime_discovery?(oidc_config)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterAuth
4
+ module SSO
5
+ module Routes
6
+ module DomainVerification
7
+ module_function
8
+
9
+ def identifier(config, provider_id)
10
+ BetterAuth::Plugins.sso_domain_verification_identifier(config, provider_id)
11
+ end
12
+
13
+ def hostname(domain)
14
+ BetterAuth::Plugins.sso_hostname_from_domain(domain)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterAuth
4
+ module SSO
5
+ module Routes
6
+ module Helpers
7
+ module_function
8
+
9
+ def find_saml_provider!(ctx, provider_id)
10
+ BetterAuth::Plugins.sso_find_provider!(ctx, provider_id)
11
+ end
12
+
13
+ def create_saml_post_form(action, saml_param, saml_value, relay_state = nil)
14
+ BetterAuth::Plugins.sso_saml_post_form(action, saml_param, saml_value, relay_state)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterAuth
4
+ module SSO
5
+ module Routes
6
+ module Providers
7
+ module_function
8
+
9
+ def sanitize(provider, context)
10
+ BetterAuth::Plugins.sso_sanitize_provider(provider, context)
11
+ end
12
+
13
+ def provider_access?(provider, user_id, ctx)
14
+ BetterAuth::Plugins.sso_provider_access?(provider, user_id, ctx)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterAuth
4
+ module SSO
5
+ module Routes
6
+ module SAMLPipeline
7
+ module_function
8
+
9
+ def process_response(ctx, config = {})
10
+ BetterAuth::Plugins.sso_handle_saml_response(ctx, config)
11
+ end
12
+
13
+ def safe_redirect_url(ctx, url, provider_id)
14
+ BetterAuth::Plugins.sso_safe_slo_redirect_url(ctx, url, provider_id)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end