better_auth 0.8.0 → 0.11.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 +4 -4
- data/CHANGELOG.md +92 -0
- data/LICENSE.md +1 -1
- data/README.md +142 -95
- data/lib/better_auth/adapters/base.rb +64 -1
- data/lib/better_auth/adapters/internal_adapter.rb +197 -11
- data/lib/better_auth/adapters/join_support.rb +10 -0
- data/lib/better_auth/adapters/memory.rb +303 -54
- data/lib/better_auth/adapters/mssql.rb +7 -3
- data/lib/better_auth/adapters/sql.rb +390 -63
- data/lib/better_auth/api.rb +3 -24
- data/lib/better_auth/configuration.rb +48 -47
- data/lib/better_auth/context.rb +43 -67
- data/lib/better_auth/cookies.rb +119 -15
- data/lib/better_auth/core.rb +0 -2
- data/lib/better_auth/crypto.rb +4 -3
- data/lib/better_auth/doctor.rb +108 -0
- data/lib/better_auth/endpoint.rb +88 -5
- data/lib/better_auth/error.rb +3 -1
- data/lib/better_auth/http_client.rb +46 -0
- data/lib/better_auth/logger.rb +1 -1
- data/lib/better_auth/middleware/origin_check.rb +9 -1
- data/lib/better_auth/migration_plan.rb +15 -0
- data/lib/better_auth/oauth2.rb +1 -1
- data/lib/better_auth/oauth_state.rb +147 -0
- data/lib/better_auth/plugin_loader.rb +220 -0
- data/lib/better_auth/plugins/access.rb +10 -13
- data/lib/better_auth/plugins/admin.rb +85 -29
- data/lib/better_auth/plugins/anonymous.rb +2 -0
- data/lib/better_auth/plugins/api_key.rb +4 -7
- data/lib/better_auth/plugins/bearer.rb +8 -22
- data/lib/better_auth/plugins/captcha.rb +24 -6
- data/lib/better_auth/plugins/device_authorization.rb +77 -5
- data/lib/better_auth/plugins/dub.rb +10 -2
- data/lib/better_auth/plugins/email_otp.rb +30 -47
- data/lib/better_auth/plugins/generic_oauth.rb +162 -140
- data/lib/better_auth/plugins/have_i_been_pwned.rb +1 -1
- data/lib/better_auth/plugins/i18n.rb +215 -0
- data/lib/better_auth/plugins/jwt.rb +44 -19
- data/lib/better_auth/plugins/magic_link.rb +18 -21
- data/lib/better_auth/plugins/oauth_popup.rb +330 -0
- data/lib/better_auth/plugins/oauth_protocol.rb +233 -46
- data/lib/better_auth/plugins/oauth_provider.rb +4 -7
- data/lib/better_auth/plugins/oauth_proxy.rb +1 -1
- data/lib/better_auth/plugins/one_tap.rb +78 -65
- data/lib/better_auth/plugins/one_time_token.rb +1 -4
- data/lib/better_auth/plugins/open_api.rb +77 -9
- data/lib/better_auth/plugins/organization.rb +546 -102
- data/lib/better_auth/plugins/passkey.rb +4 -7
- data/lib/better_auth/plugins/phone_number.rb +25 -27
- data/lib/better_auth/plugins/scim.rb +4 -7
- data/lib/better_auth/plugins/siwe.rb +126 -16
- data/lib/better_auth/plugins/sso.rb +4 -7
- data/lib/better_auth/plugins/stripe.rb +4 -7
- data/lib/better_auth/plugins/two_factor.rb +205 -19
- data/lib/better_auth/plugins/username.rb +56 -13
- data/lib/better_auth/plugins.rb +57 -0
- data/lib/better_auth/rate_limiter.rb +300 -133
- data/lib/better_auth/request_ip.rb +123 -13
- data/lib/better_auth/router.rb +3 -3
- data/lib/better_auth/routes/account.rb +44 -8
- data/lib/better_auth/routes/email_verification.rb +93 -6
- data/lib/better_auth/routes/password.rb +6 -6
- data/lib/better_auth/routes/session.rb +7 -2
- data/lib/better_auth/routes/sign_in.rb +10 -1
- data/lib/better_auth/routes/sign_up.rb +45 -22
- data/lib/better_auth/routes/social.rb +197 -54
- data/lib/better_auth/routes/user.rb +17 -10
- data/lib/better_auth/schema/sql.rb +189 -34
- data/lib/better_auth/schema.rb +159 -5
- data/lib/better_auth/session.rb +29 -10
- data/lib/better_auth/session_store.rb +1 -1
- data/lib/better_auth/social_providers/apple.rb +6 -1
- data/lib/better_auth/social_providers/atlassian.rb +1 -0
- data/lib/better_auth/social_providers/base.rb +45 -23
- data/lib/better_auth/social_providers/cognito.rb +75 -5
- data/lib/better_auth/social_providers/facebook.rb +95 -1
- data/lib/better_auth/social_providers/figma.rb +2 -0
- data/lib/better_auth/social_providers/github.rb +2 -0
- data/lib/better_auth/social_providers/google.rb +12 -1
- data/lib/better_auth/social_providers/line.rb +35 -1
- data/lib/better_auth/social_providers/linear.rb +1 -0
- data/lib/better_auth/social_providers/microsoft_entra_id.rb +9 -0
- data/lib/better_auth/social_providers/naver.rb +24 -1
- data/lib/better_auth/social_providers/notion.rb +1 -0
- data/lib/better_auth/social_providers/paybin.rb +25 -1
- data/lib/better_auth/social_providers/paypal.rb +118 -2
- data/lib/better_auth/social_providers/reddit.rb +25 -4
- data/lib/better_auth/social_providers/salesforce.rb +1 -0
- data/lib/better_auth/social_providers/twitch.rb +24 -1
- data/lib/better_auth/social_providers/twitter.rb +36 -1
- data/lib/better_auth/social_providers/vk.rb +34 -1
- data/lib/better_auth/social_providers/wechat.rb +6 -4
- data/lib/better_auth/sql_migration.rb +566 -0
- data/lib/better_auth/version.rb +1 -1
- data/lib/better_auth.rb +14 -41
- metadata +14 -18
- data/lib/better_auth/plugins/mcp/authorization.rb +0 -111
- data/lib/better_auth/plugins/mcp/config.rb +0 -51
- data/lib/better_auth/plugins/mcp/consent.rb +0 -31
- data/lib/better_auth/plugins/mcp/legacy_aliases.rb +0 -43
- data/lib/better_auth/plugins/mcp/metadata.rb +0 -81
- data/lib/better_auth/plugins/mcp/registration.rb +0 -31
- data/lib/better_auth/plugins/mcp/resource_handler.rb +0 -37
- data/lib/better_auth/plugins/mcp/schema.rb +0 -91
- data/lib/better_auth/plugins/mcp/token.rb +0 -108
- data/lib/better_auth/plugins/mcp/userinfo.rb +0 -37
- data/lib/better_auth/plugins/mcp.rb +0 -215
- data/lib/better_auth/plugins/oidc_provider.rb +0 -710
|
@@ -32,6 +32,14 @@ module BetterAuth
|
|
|
32
32
|
operationId: "oneTapCallback",
|
|
33
33
|
summary: "One tap callback",
|
|
34
34
|
description: "Use this endpoint to authenticate with Google One Tap",
|
|
35
|
+
requestBody: OpenAPI.json_request_body(
|
|
36
|
+
OpenAPI.object_schema(
|
|
37
|
+
{
|
|
38
|
+
id_token: {type: "string", description: "Google One Tap ID token"}
|
|
39
|
+
},
|
|
40
|
+
required: ["id_token"]
|
|
41
|
+
)
|
|
42
|
+
),
|
|
35
43
|
responses: {
|
|
36
44
|
"200" => OpenAPI.json_response("Success", OpenAPI.session_response_schema_pair)
|
|
37
45
|
}
|
|
@@ -42,36 +50,32 @@ module BetterAuth
|
|
|
42
50
|
id_token = body[:id_token].to_s
|
|
43
51
|
payload = one_tap_verify_id_token(ctx, config, id_token)
|
|
44
52
|
email = fetch_value(payload, "email").to_s.downcase
|
|
53
|
+
raise APIError.new("BAD_REQUEST", message: "invalid id token") if Routes.blank_remote_id?(fetch_value(payload, "sub"))
|
|
45
54
|
|
|
46
55
|
if email.empty?
|
|
47
56
|
next ctx.json({error: "Email not available in token"})
|
|
48
57
|
end
|
|
49
58
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
)
|
|
71
|
-
raise APIError.new("INTERNAL_SERVER_ERROR", message: "Could not create user") unless created
|
|
72
|
-
|
|
73
|
-
session_data = one_tap_create_session(ctx, created[:user])
|
|
74
|
-
end
|
|
59
|
+
sub = fetch_value(payload, "sub").to_s
|
|
60
|
+
session_data = Routes.persist_social_user(
|
|
61
|
+
ctx,
|
|
62
|
+
"google",
|
|
63
|
+
{
|
|
64
|
+
id: sub,
|
|
65
|
+
email: email,
|
|
66
|
+
emailVerified: one_tap_boolean_value(fetch_value(payload, "email_verified")),
|
|
67
|
+
name: fetch_value(payload, "name").to_s,
|
|
68
|
+
image: fetch_value(payload, "picture")
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
providerId: "google",
|
|
72
|
+
accountId: sub,
|
|
73
|
+
idToken: id_token,
|
|
74
|
+
scope: "openid,profile,email"
|
|
75
|
+
},
|
|
76
|
+
disable_sign_up: config[:disable_signup]
|
|
77
|
+
)
|
|
78
|
+
one_tap_raise_persistence_error!(session_data[:error]) if session_data[:error]
|
|
75
79
|
|
|
76
80
|
Cookies.set_session_cookie(ctx, session_data)
|
|
77
81
|
ctx.json({
|
|
@@ -82,16 +86,35 @@ module BetterAuth
|
|
|
82
86
|
end
|
|
83
87
|
|
|
84
88
|
def one_tap_verify_id_token(ctx, config, id_token)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
google_provider = ctx.context.options.social_providers[:google] || {}
|
|
90
|
+
google_options = fetch_value(google_provider, "options") || {}
|
|
91
|
+
audience = config[:client_id]
|
|
92
|
+
audience = fetch_value(google_provider, "client_id") if one_tap_blank_audience?(audience)
|
|
93
|
+
if one_tap_blank_audience?(audience)
|
|
94
|
+
raise APIError.new(
|
|
95
|
+
"BAD_REQUEST",
|
|
96
|
+
message: "Google client ID is required for One Tap. Set it on the one_tap plugin (client_id) or on social_providers.google."
|
|
97
|
+
)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
begin
|
|
101
|
+
verifier = config[:verify_id_token]
|
|
102
|
+
payload = if verifier.respond_to?(:call)
|
|
103
|
+
verifier.call(id_token, ctx, audience: audience)
|
|
104
|
+
else
|
|
105
|
+
one_tap_verify_google_id_token(id_token, audience)
|
|
106
|
+
end
|
|
107
|
+
payload = one_tap_stringify_payload(payload)
|
|
108
|
+
hosted_domain = fetch_value(google_provider, "hd")
|
|
109
|
+
hosted_domain = fetch_value(google_options, "hd") if hosted_domain.nil?
|
|
110
|
+
unless SocialProviders.google_hosted_domain_allowed?(hosted_domain, payload["hd"])
|
|
111
|
+
raise "Invalid Google hosted domain"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
payload
|
|
115
|
+
rescue
|
|
116
|
+
raise APIError.new("BAD_REQUEST", message: "invalid id token")
|
|
91
117
|
end
|
|
92
|
-
one_tap_stringify_payload(payload)
|
|
93
|
-
rescue
|
|
94
|
-
raise APIError.new("BAD_REQUEST", message: "invalid id token")
|
|
95
118
|
end
|
|
96
119
|
|
|
97
120
|
def one_tap_verify_google_id_token(id_token, audience)
|
|
@@ -101,47 +124,37 @@ module BetterAuth
|
|
|
101
124
|
iss: ["https://accounts.google.com", "accounts.google.com"],
|
|
102
125
|
verify_iss: true
|
|
103
126
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
end
|
|
108
|
-
payload, = JWT.decode(id_token, nil, true, options.merge(jwks: jwks))
|
|
127
|
+
options[:aud] = audience
|
|
128
|
+
options[:verify_aud] = true
|
|
129
|
+
payload, = ::JWT.decode(id_token, nil, true, options.merge(jwks: jwks))
|
|
109
130
|
payload
|
|
110
131
|
end
|
|
111
132
|
|
|
112
133
|
def one_tap_google_jwks
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
134
|
+
cached = @one_tap_google_jwks_cache
|
|
135
|
+
return cached[:jwks] if cached && cached[:expires_at] > Time.now
|
|
136
|
+
|
|
137
|
+
payload = HTTPClient.get_json("https://www.googleapis.com/oauth2/v3/certs")
|
|
138
|
+
raise "Unable to fetch Google JWKS" unless payload
|
|
116
139
|
|
|
117
|
-
JWT::JWK::Set.new(
|
|
140
|
+
jwks = ::JWT::JWK::Set.new(payload)
|
|
141
|
+
@one_tap_google_jwks_cache = {jwks: jwks, expires_at: Time.now + 300}
|
|
142
|
+
jwks
|
|
118
143
|
end
|
|
119
144
|
|
|
120
|
-
def
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
return if account
|
|
145
|
+
def one_tap_blank_audience?(audience)
|
|
146
|
+
Array(audience).empty? || Array(audience).all? { |value| value.to_s.strip.empty? }
|
|
147
|
+
end
|
|
124
148
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
149
|
+
def one_tap_raise_persistence_error!(error)
|
|
150
|
+
case error
|
|
151
|
+
when "signup disabled"
|
|
152
|
+
raise APIError.new("BAD_GATEWAY", message: "User not found")
|
|
153
|
+
when "account not linked", "banned"
|
|
130
154
|
raise APIError.new("UNAUTHORIZED", message: "Google sub doesn't match")
|
|
155
|
+
else
|
|
156
|
+
raise APIError.new("INTERNAL_SERVER_ERROR", message: "Could not create user")
|
|
131
157
|
end
|
|
132
|
-
|
|
133
|
-
ctx.context.internal_adapter.link_account(
|
|
134
|
-
userId: user[:user]["id"],
|
|
135
|
-
providerId: "google",
|
|
136
|
-
accountId: sub,
|
|
137
|
-
scope: "openid,profile,email",
|
|
138
|
-
idToken: id_token
|
|
139
|
-
)
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
def one_tap_create_session(ctx, user)
|
|
143
|
-
session = ctx.context.internal_adapter.create_session(user["id"])
|
|
144
|
-
{session: session, user: user}
|
|
145
158
|
end
|
|
146
159
|
|
|
147
160
|
def one_tap_stringify_payload(payload)
|
|
@@ -85,12 +85,9 @@ module BetterAuth
|
|
|
85
85
|
body = normalize_hash(ctx.body)
|
|
86
86
|
token = body[:token].to_s
|
|
87
87
|
stored_token = one_time_token_stored_value(config, token)
|
|
88
|
-
verification = ctx.context.internal_adapter.
|
|
88
|
+
verification = ctx.context.internal_adapter.consume_verification_value("one-time-token:#{stored_token}")
|
|
89
89
|
raise APIError.new("BAD_REQUEST", message: "Invalid token") unless verification
|
|
90
90
|
|
|
91
|
-
ctx.context.internal_adapter.delete_verification_value(verification["id"])
|
|
92
|
-
raise APIError.new("BAD_REQUEST", message: "Token expired") if Routes.expired_time?(verification["expiresAt"])
|
|
93
|
-
|
|
94
91
|
session = ctx.context.internal_adapter.find_session(verification["value"])
|
|
95
92
|
raise APIError.new("BAD_REQUEST", message: "Session not found") unless session
|
|
96
93
|
raise APIError.new("BAD_REQUEST", message: "Session expired") if Routes.expired_time?(session[:session]["expiresAt"])
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module BetterAuth
|
|
4
4
|
module OpenAPI
|
|
5
|
+
# Better Auth action endpoints advertise upstream camelCase JSON field names in
|
|
6
|
+
# OpenAPI and generated inventories. Runtime may still accept Ruby snake_case
|
|
7
|
+
# aliases via normalize_hash and route validation.
|
|
8
|
+
|
|
5
9
|
module_function
|
|
6
10
|
|
|
7
11
|
def object_schema(properties, required: [])
|
|
@@ -128,6 +132,21 @@ module BetterAuth
|
|
|
128
132
|
}
|
|
129
133
|
end
|
|
130
134
|
|
|
135
|
+
def default_request_body
|
|
136
|
+
{
|
|
137
|
+
required: true,
|
|
138
|
+
content: {
|
|
139
|
+
"application/json" => {
|
|
140
|
+
schema: {
|
|
141
|
+
type: "object",
|
|
142
|
+
properties: {},
|
|
143
|
+
additionalProperties: true
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
end
|
|
149
|
+
|
|
131
150
|
def responses(responses = nil)
|
|
132
151
|
{"200" => success_response}.merge(default_error_responses).merge(responses || {})
|
|
133
152
|
end
|
|
@@ -142,6 +161,17 @@ module BetterAuth
|
|
|
142
161
|
)
|
|
143
162
|
end
|
|
144
163
|
|
|
164
|
+
def default_success_response
|
|
165
|
+
json_response(
|
|
166
|
+
"Success",
|
|
167
|
+
{
|
|
168
|
+
type: "object",
|
|
169
|
+
properties: {},
|
|
170
|
+
additionalProperties: true
|
|
171
|
+
}
|
|
172
|
+
)
|
|
173
|
+
end
|
|
174
|
+
|
|
145
175
|
def default_error_responses
|
|
146
176
|
{
|
|
147
177
|
"400" => error_response("Bad Request. Usually due to missing parameters, or invalid parameters.", required: true),
|
|
@@ -168,10 +198,16 @@ module BetterAuth
|
|
|
168
198
|
|
|
169
199
|
def default_metadata(path, methods)
|
|
170
200
|
method = Array(methods).reject { |value| value.to_s == "*" }.first.to_s.upcase
|
|
171
|
-
{
|
|
201
|
+
metadata = {
|
|
172
202
|
operationId: operation_id(path, method),
|
|
173
|
-
description: "#{method}
|
|
203
|
+
description: "Execute the #{operation_id(path, method)} endpoint.",
|
|
204
|
+
parameters: default_path_parameters(path),
|
|
205
|
+
responses: {
|
|
206
|
+
"200" => default_success_response
|
|
207
|
+
}
|
|
174
208
|
}
|
|
209
|
+
metadata[:requestBody] = default_request_body if %w[POST PUT PATCH].include?(method)
|
|
210
|
+
metadata
|
|
175
211
|
end
|
|
176
212
|
|
|
177
213
|
def operation_id(path, method)
|
|
@@ -183,6 +219,14 @@ module BetterAuth
|
|
|
183
219
|
|
|
184
220
|
"#{method.to_s.downcase}#{base}"
|
|
185
221
|
end
|
|
222
|
+
|
|
223
|
+
def default_path_parameters(path)
|
|
224
|
+
path.to_s.split("/").filter_map do |part|
|
|
225
|
+
next unless part.start_with?(":")
|
|
226
|
+
|
|
227
|
+
path_parameter(part.delete_prefix(":"))
|
|
228
|
+
end
|
|
229
|
+
end
|
|
186
230
|
end
|
|
187
231
|
|
|
188
232
|
module Plugins
|
|
@@ -194,7 +238,7 @@ module BetterAuth
|
|
|
194
238
|
Plugin.new(
|
|
195
239
|
id: "open-api",
|
|
196
240
|
endpoints: {
|
|
197
|
-
|
|
241
|
+
generate_openapi_schema: Endpoint.new(path: "/open-api/generate-schema", method: "GET") do |ctx|
|
|
198
242
|
ctx.json(open_api_schema(ctx.context))
|
|
199
243
|
end,
|
|
200
244
|
open_api_reference: Endpoint.new(path: config[:path], method: "GET", metadata: {hide: true}) do |ctx|
|
|
@@ -227,7 +271,7 @@ module BetterAuth
|
|
|
227
271
|
],
|
|
228
272
|
servers: [
|
|
229
273
|
{
|
|
230
|
-
url: context.
|
|
274
|
+
url: context.canonical_base_url
|
|
231
275
|
}
|
|
232
276
|
],
|
|
233
277
|
tags: [
|
|
@@ -268,6 +312,7 @@ module BetterAuth
|
|
|
268
312
|
|
|
269
313
|
def open_api_paths(endpoints, options)
|
|
270
314
|
disabled_paths = Array(options.disabled_paths).map(&:to_s)
|
|
315
|
+
used_operation_ids = Set.new
|
|
271
316
|
endpoints.each_with_object({}) do |(key, endpoint, tag), paths|
|
|
272
317
|
next unless endpoint.path
|
|
273
318
|
next if endpoint.metadata[:exclude_from_openapi] || endpoint.metadata[:SERVER_ONLY] || endpoint.metadata[:server_only]
|
|
@@ -278,7 +323,7 @@ module BetterAuth
|
|
|
278
323
|
path = open_api_path(endpoint.path)
|
|
279
324
|
paths[path] ||= {}
|
|
280
325
|
endpoint.methods.reject { |method| method == "*" }.each do |method|
|
|
281
|
-
paths[path][method.downcase.to_sym] = open_api_operation(endpoint, method, tag)
|
|
326
|
+
paths[path][method.downcase.to_sym] = open_api_operation(endpoint, method, tag, used_operation_ids)
|
|
282
327
|
end
|
|
283
328
|
end
|
|
284
329
|
end
|
|
@@ -291,12 +336,12 @@ module BetterAuth
|
|
|
291
336
|
tag == "Default" && [:ok, :error].include?(key) && endpoint.metadata[:openapi]
|
|
292
337
|
end
|
|
293
338
|
|
|
294
|
-
def open_api_operation(endpoint, method, tag)
|
|
339
|
+
def open_api_operation(endpoint, method, tag, used_operation_ids)
|
|
295
340
|
metadata = endpoint.metadata[:openapi] || {}
|
|
296
341
|
operation = {
|
|
297
342
|
tags: Array(metadata[:tags] || [tag]),
|
|
298
343
|
description: metadata[:description],
|
|
299
|
-
operationId: metadata[:operationId],
|
|
344
|
+
operationId: open_api_operation_id(metadata[:operationId], method, used_operation_ids),
|
|
300
345
|
security: [
|
|
301
346
|
{
|
|
302
347
|
bearerAuth: []
|
|
@@ -313,6 +358,26 @@ module BetterAuth
|
|
|
313
358
|
operation
|
|
314
359
|
end
|
|
315
360
|
|
|
361
|
+
def open_api_operation_id(base_id, method, used_operation_ids)
|
|
362
|
+
return if base_id.to_s.empty?
|
|
363
|
+
|
|
364
|
+
operation_id = base_id.to_s
|
|
365
|
+
unless used_operation_ids.include?(operation_id)
|
|
366
|
+
used_operation_ids << operation_id
|
|
367
|
+
return operation_id
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
suffix = method.to_s.downcase.capitalize
|
|
371
|
+
candidate = "#{operation_id}#{suffix}"
|
|
372
|
+
duplicate_index = 2
|
|
373
|
+
while used_operation_ids.include?(candidate)
|
|
374
|
+
candidate = "#{operation_id}#{suffix}#{duplicate_index}"
|
|
375
|
+
duplicate_index += 1
|
|
376
|
+
end
|
|
377
|
+
used_operation_ids << candidate
|
|
378
|
+
candidate
|
|
379
|
+
end
|
|
380
|
+
|
|
316
381
|
def open_api_components(options)
|
|
317
382
|
Schema.auth_tables(options).each_with_object({}) do |(model, table), schemas|
|
|
318
383
|
name = model.to_s.split(/[_-]/).map(&:capitalize).join
|
|
@@ -321,11 +386,14 @@ module BetterAuth
|
|
|
321
386
|
end
|
|
322
387
|
|
|
323
388
|
def schema_for_table(table)
|
|
324
|
-
required = []
|
|
389
|
+
required = ["id"]
|
|
325
390
|
properties = table[:fields].each_with_object({}) do |(field, attributes), result|
|
|
391
|
+
next if field.to_s == "id"
|
|
392
|
+
|
|
326
393
|
result[field.to_sym] = field_schema(attributes)
|
|
327
|
-
required << field if attributes[:required] && attributes[:
|
|
394
|
+
required << field if attributes[:required] && attributes[:returned] != false
|
|
328
395
|
end
|
|
396
|
+
properties = {id: {type: "string", readOnly: true}}.merge(properties)
|
|
329
397
|
{type: "object", properties: properties, required: required}
|
|
330
398
|
end
|
|
331
399
|
|