better_auth-oauth-provider 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7297666c2085bbacc7b4fbdd7d79c7abf8f34d53a1a4e5956a7ba47fc1572692
4
+ data.tar.gz: 35108fb4c887ba57d68a782b29abbac8fe6c192c229eebc8958029b37811d0b4
5
+ SHA512:
6
+ metadata.gz: e46b5e993d4df92e9722b9c69f11d9010f4ddb0ba4c9b5a4c5920aef38ebf89906c841669ba690b24ec7c2fb1bb444407d7047ebd47f8f5a08bdafab4a7af365
7
+ data.tar.gz: 92627c999ec60244099a2042e1259ddf44896f302db499fd40ebf32c1d317112e04d8f0543d83a336934b7032bbba42249af1931647dbea7832a78eb8f6ca034
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - Initial package skeleton for Better Auth OAuth provider.
data/README.md ADDED
@@ -0,0 +1,18 @@
1
+ # Better Auth OAuth Provider
2
+
3
+ External OAuth provider plugin package for `better_auth`.
4
+
5
+ Upstream ships OAuth provider as `@better-auth/oauth-provider`, separate from core plugin exports. This gem mirrors that boundary for Ruby.
6
+
7
+ ```ruby
8
+ require "better_auth"
9
+ require "better_auth/oauth_provider"
10
+
11
+ BetterAuth.auth(
12
+ plugins: [
13
+ BetterAuth::Plugins.oauth_provider
14
+ ]
15
+ )
16
+ ```
17
+
18
+ OIDC provider remains a core `better_auth` plugin because upstream still exposes it from `better-auth/plugins`. OAuth provider is the newer standalone provider package.
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterAuth
4
+ module OAuthProvider
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "better_auth"
4
+ require_relative "oauth_provider/version"
5
+ require_relative "plugins/oauth_provider"
6
+
7
+ module BetterAuth
8
+ module OAuthProvider
9
+ end
10
+ end
@@ -0,0 +1,433 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterAuth
4
+ module Plugins
5
+ module OAuthProvider
6
+ module_function
7
+
8
+ def validate_issuer_url(value)
9
+ uri = URI.parse(value.to_s)
10
+ uri.query = nil
11
+ uri.fragment = nil
12
+ if uri.scheme == "http" && !["localhost", "127.0.0.1"].include?(uri.host)
13
+ uri.scheme = "https"
14
+ end
15
+ uri.to_s.sub(%r{/+\z}, "")
16
+ rescue URI::InvalidURIError
17
+ value.to_s.split(/[?#]/).first.sub(%r{/+\z}, "")
18
+ end
19
+ end
20
+
21
+ module_function
22
+
23
+ remove_method :oauth_provider if method_defined?(:oauth_provider) || private_method_defined?(:oauth_provider)
24
+ singleton_class.remove_method(:oauth_provider) if singleton_class.method_defined?(:oauth_provider) || singleton_class.private_method_defined?(:oauth_provider)
25
+
26
+ def oauth_provider(options = {})
27
+ config = {
28
+ login_page: "/login",
29
+ consent_page: "/oauth2/consent",
30
+ scopes: [],
31
+ grant_types: [OAuthProtocol::AUTH_CODE_GRANT, OAuthProtocol::CLIENT_CREDENTIALS_GRANT, OAuthProtocol::REFRESH_GRANT],
32
+ store: OAuthProtocol.stores
33
+ }.merge(normalize_hash(options))
34
+
35
+ Plugin.new(
36
+ id: "oauth-provider",
37
+ endpoints: oauth_provider_endpoints(config),
38
+ schema: oauth_provider_schema,
39
+ options: config
40
+ )
41
+ end
42
+
43
+ def oauth_provider_endpoints(config)
44
+ {
45
+ get_o_auth_server_config: oauth_server_metadata_endpoint(config),
46
+ get_open_id_config: oauth_openid_metadata_endpoint(config),
47
+ register_o_auth_client: oauth_register_client_endpoint(config),
48
+ get_o_auth_client: oauth_get_client_endpoint(config),
49
+ get_o_auth_client_public: oauth_get_client_public_endpoint(config),
50
+ list_o_auth_clients: oauth_list_clients_endpoint,
51
+ delete_o_auth_client: oauth_delete_client_endpoint,
52
+ o_auth2_authorize: oauth_authorize_endpoint(config),
53
+ o_auth2_consent: oauth_consent_endpoint(config),
54
+ o_auth2_token: oauth_token_endpoint(config),
55
+ o_auth2_introspect: oauth_introspect_endpoint(config),
56
+ o_auth2_revoke: oauth_revoke_endpoint(config),
57
+ o_auth2_user_info: oauth_userinfo_endpoint(config)
58
+ }
59
+ end
60
+
61
+ def oauth_server_metadata_endpoint(config)
62
+ Endpoint.new(path: "/.well-known/oauth-authorization-server", method: "GET", metadata: {hide: true}) do |ctx|
63
+ base = OAuthProtocol.endpoint_base(ctx)
64
+ ctx.json({
65
+ issuer: OAuthProvider.validate_issuer_url(OAuthProtocol.issuer(ctx)),
66
+ authorization_endpoint: "#{base}/oauth2/authorize",
67
+ token_endpoint: "#{base}/oauth2/token",
68
+ jwks_uri: "#{base}/jwks",
69
+ registration_endpoint: "#{base}/oauth2/register",
70
+ introspection_endpoint: "#{base}/oauth2/introspect",
71
+ revocation_endpoint: "#{base}/oauth2/revoke",
72
+ response_types_supported: ["code"],
73
+ response_modes_supported: ["query"],
74
+ grant_types_supported: config[:grant_types],
75
+ token_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"],
76
+ introspection_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"],
77
+ revocation_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"],
78
+ code_challenge_methods_supported: ["S256"],
79
+ authorization_response_iss_parameter_supported: true,
80
+ scopes_supported: config[:scopes]
81
+ })
82
+ end
83
+ end
84
+
85
+ def oauth_openid_metadata_endpoint(config)
86
+ Endpoint.new(path: "/.well-known/openid-configuration", method: "GET", metadata: {hide: true}) do |ctx|
87
+ base = OAuthProtocol.endpoint_base(ctx)
88
+ ctx.json({
89
+ issuer: OAuthProvider.validate_issuer_url(OAuthProtocol.issuer(ctx)),
90
+ authorization_endpoint: "#{base}/oauth2/authorize",
91
+ token_endpoint: "#{base}/oauth2/token",
92
+ jwks_uri: "#{base}/jwks",
93
+ registration_endpoint: "#{base}/oauth2/register",
94
+ introspection_endpoint: "#{base}/oauth2/introspect",
95
+ revocation_endpoint: "#{base}/oauth2/revoke",
96
+ response_types_supported: ["code"],
97
+ response_modes_supported: ["query"],
98
+ grant_types_supported: config[:grant_types],
99
+ token_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"],
100
+ introspection_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"],
101
+ revocation_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"],
102
+ code_challenge_methods_supported: ["S256"],
103
+ authorization_response_iss_parameter_supported: true,
104
+ scopes_supported: config[:scopes],
105
+ userinfo_endpoint: "#{base}/oauth2/userinfo",
106
+ subject_types_supported: ["public"],
107
+ id_token_signing_alg_values_supported: ["HS256"],
108
+ end_session_endpoint: "#{base}/oauth2/end-session",
109
+ acr_values_supported: ["urn:mace:incommon:iap:bronze"],
110
+ prompt_values_supported: ["login", "consent", "create", "select_account"],
111
+ claims_supported: config[:claims] || []
112
+ })
113
+ end
114
+ end
115
+
116
+ def oauth_register_client_endpoint(_config)
117
+ Endpoint.new(path: "/oauth2/register", method: "POST") do |ctx|
118
+ session = Routes.current_session(ctx, allow_nil: true)
119
+ body = OAuthProtocol.stringify_keys(ctx.body)
120
+ public_request = body["token_endpoint_auth_method"] == "none"
121
+ raise APIError.new("UNAUTHORIZED") unless session || public_request
122
+
123
+ ctx.json(OAuthProtocol.create_client(ctx, model: "oauthClient", body: body, owner_session: session))
124
+ end
125
+ end
126
+
127
+ def oauth_get_client_endpoint(_config)
128
+ Endpoint.new(path: "/oauth2/client/:id", method: "GET") do |ctx|
129
+ Routes.current_session(ctx)
130
+ client = OAuthProtocol.find_client(ctx, "oauthClient", ctx.params["id"] || ctx.params[:id])
131
+ raise APIError.new("NOT_FOUND", message: "client not found") unless client
132
+
133
+ ctx.json(OAuthProtocol.client_response(client, include_secret: false))
134
+ end
135
+ end
136
+
137
+ def oauth_get_client_public_endpoint(_config)
138
+ Endpoint.new(path: "/oauth2/client", method: "GET") do |ctx|
139
+ query = OAuthProtocol.stringify_keys(ctx.query)
140
+ client = OAuthProtocol.find_client(ctx, "oauthClient", query["client_id"])
141
+ raise APIError.new("NOT_FOUND", message: "client not found") unless client
142
+
143
+ ctx.json(OAuthProtocol.client_response(client, include_secret: false))
144
+ end
145
+ end
146
+
147
+ def oauth_list_clients_endpoint
148
+ Endpoint.new(path: "/oauth2/clients", method: "GET") do |ctx|
149
+ session = Routes.current_session(ctx)
150
+ clients = ctx.context.adapter.find_many(model: "oauthClient", where: [{field: "userId", value: session[:user]["id"]}])
151
+ ctx.json(clients.map { |client| OAuthProtocol.client_response(client, include_secret: false) })
152
+ end
153
+ end
154
+
155
+ def oauth_delete_client_endpoint
156
+ Endpoint.new(path: "/oauth2/client", method: "DELETE") do |ctx|
157
+ Routes.current_session(ctx)
158
+ body = OAuthProtocol.stringify_keys(ctx.body)
159
+ ctx.context.adapter.delete(model: "oauthClient", where: [{field: "clientId", value: body["client_id"]}])
160
+ ctx.json({status: true})
161
+ end
162
+ end
163
+
164
+ def oauth_authorize_endpoint(config)
165
+ Endpoint.new(path: "/oauth2/authorize", method: "GET") do |ctx|
166
+ query = OAuthProtocol.stringify_keys(ctx.query)
167
+ session = Routes.current_session(ctx, allow_nil: true)
168
+ unless session
169
+ if OAuthProtocol.parse_scopes(query["prompt"]).include?("none")
170
+ raise ctx.redirect(OAuthProtocol.redirect_uri_with_params(query["redirect_uri"], error: "login_required", state: query["state"], iss: OAuthProvider.validate_issuer_url(OAuthProtocol.issuer(ctx))))
171
+ end
172
+
173
+ raise ctx.redirect(OAuthProtocol.redirect_uri_with_params(config[:login_page], query))
174
+ end
175
+
176
+ client = OAuthProtocol.find_client(ctx, "oauthClient", query["client_id"])
177
+ raise APIError.new("BAD_REQUEST", message: "invalid_client") unless client
178
+ OAuthProtocol.validate_redirect_uri!(client, query["redirect_uri"])
179
+
180
+ scopes = OAuthProtocol.parse_scopes(query["scope"])
181
+ scopes = OAuthProtocol.parse_scopes(OAuthProtocol.stringify_keys(client)["scopes"] || config[:scopes]) if scopes.empty?
182
+ prompts = OAuthProtocol.parse_scopes(query["prompt"])
183
+ client_data = OAuthProtocol.stringify_keys(client)
184
+ requires_consent = !client_data["skipConsent"] && (prompts.include?("consent") || !oauth_consent_granted?(ctx, client_data["clientId"], session[:user]["id"], scopes))
185
+
186
+ if requires_consent
187
+ if prompts.include?("none")
188
+ raise ctx.redirect(OAuthProtocol.redirect_uri_with_params(query["redirect_uri"], error: "consent_required", state: query["state"], iss: OAuthProvider.validate_issuer_url(OAuthProtocol.issuer(ctx))))
189
+ end
190
+
191
+ consent_code = Crypto.random_string(32)
192
+ config[:store][:consents][consent_code] = {
193
+ query: query,
194
+ session: session,
195
+ client: client,
196
+ scopes: scopes,
197
+ expires_at: Time.now + 600
198
+ }
199
+ raise ctx.redirect(OAuthProtocol.redirect_uri_with_params(config[:consent_page], consent_code: consent_code, client_id: client_data["clientId"], scope: OAuthProtocol.scope_string(scopes)))
200
+ end
201
+
202
+ oauth_redirect_with_code(ctx, config, query, session, client, scopes)
203
+ end
204
+ end
205
+
206
+ def oauth_consent_endpoint(config)
207
+ Endpoint.new(path: "/oauth2/consent", method: "POST") do |ctx|
208
+ Routes.current_session(ctx)
209
+ body = OAuthProtocol.stringify_keys(ctx.body)
210
+ consent = config[:store][:consents].delete(body["consent_code"].to_s)
211
+ raise APIError.new("BAD_REQUEST", message: "invalid consent_code") unless consent
212
+ raise APIError.new("BAD_REQUEST", message: "expired consent_code") if consent[:expires_at] <= Time.now
213
+
214
+ query = consent[:query]
215
+ if body["accept"] == false || body["accept"].to_s == "false"
216
+ redirect = OAuthProtocol.redirect_uri_with_params(query["redirect_uri"], error: "access_denied", state: query["state"], iss: OAuthProvider.validate_issuer_url(OAuthProtocol.issuer(ctx)))
217
+ next ctx.json({redirectURI: redirect})
218
+ end
219
+
220
+ oauth_store_consent(ctx, consent[:client], consent[:session], consent[:scopes])
221
+ redirect = oauth_authorization_redirect(ctx, config, query, consent[:session], consent[:client], consent[:scopes])
222
+ ctx.json({redirectURI: redirect})
223
+ end
224
+ end
225
+
226
+ def oauth_token_endpoint(config)
227
+ Endpoint.new(path: "/oauth2/token", method: "POST", metadata: {allowed_media_types: ["application/x-www-form-urlencoded", "application/json"]}) do |ctx|
228
+ body = OAuthProtocol.stringify_keys(ctx.body)
229
+ client = OAuthProtocol.authenticate_client!(ctx, "oauthClient")
230
+ response = case body["grant_type"]
231
+ when OAuthProtocol::AUTH_CODE_GRANT
232
+ code = OAuthProtocol.consume_code!(
233
+ config[:store],
234
+ body["code"],
235
+ client_id: body["client_id"],
236
+ redirect_uri: body["redirect_uri"],
237
+ code_verifier: body["code_verifier"]
238
+ )
239
+ OAuthProtocol.issue_tokens(
240
+ ctx,
241
+ config[:store],
242
+ model: "oauthAccessToken",
243
+ client: client,
244
+ session: code[:session],
245
+ scopes: code[:scopes],
246
+ include_refresh: code[:scopes].include?("offline_access") || OAuthProtocol.parse_scopes(OAuthProtocol.stringify_keys(client)["grantTypes"]).include?(OAuthProtocol::REFRESH_GRANT),
247
+ issuer: OAuthProvider.validate_issuer_url(OAuthProtocol.issuer(ctx))
248
+ )
249
+ when OAuthProtocol::CLIENT_CREDENTIALS_GRANT
250
+ requested = OAuthProtocol.parse_scopes(body["scope"])
251
+ allowed = OAuthProtocol.parse_scopes(OAuthProtocol.stringify_keys(client)["scopes"] || config[:scopes])
252
+ unless requested.all? { |scope| allowed.include?(scope) }
253
+ raise APIError.new("BAD_REQUEST", message: "invalid_scope")
254
+ end
255
+
256
+ OAuthProtocol.issue_tokens(ctx, config[:store], model: "oauthAccessToken", client: client, session: {"user" => {}, "session" => {}}, scopes: requested, include_refresh: false, issuer: OAuthProtocol.issuer(ctx))
257
+ when OAuthProtocol::REFRESH_GRANT
258
+ OAuthProtocol.refresh_tokens(ctx, config[:store], model: "oauthAccessToken", client: client, refresh_token: body["refresh_token"], scopes: body["scope"], issuer: OAuthProtocol.issuer(ctx))
259
+ else
260
+ raise APIError.new("BAD_REQUEST", message: "unsupported_grant_type")
261
+ end
262
+ ctx.json(response)
263
+ end
264
+ end
265
+
266
+ def oauth_authorization_redirect(ctx, config, query, session, client, scopes)
267
+ code = Crypto.random_string(32)
268
+ OAuthProtocol.store_code(
269
+ config[:store],
270
+ code: code,
271
+ client_id: query["client_id"],
272
+ redirect_uri: query["redirect_uri"],
273
+ session: session,
274
+ scopes: scopes,
275
+ code_challenge: query["code_challenge"],
276
+ code_challenge_method: query["code_challenge_method"]
277
+ )
278
+ OAuthProtocol.redirect_uri_with_params(query["redirect_uri"], code: code, state: query["state"], iss: OAuthProvider.validate_issuer_url(OAuthProtocol.issuer(ctx)))
279
+ end
280
+
281
+ def oauth_redirect_with_code(ctx, config, query, session, client, scopes)
282
+ raise ctx.redirect(oauth_authorization_redirect(ctx, config, query, session, client, scopes))
283
+ end
284
+
285
+ def oauth_consent_granted?(ctx, client_id, user_id, scopes)
286
+ consent = ctx.context.adapter.find_one(
287
+ model: "oauthConsent",
288
+ where: [
289
+ {field: "clientId", value: client_id},
290
+ {field: "userId", value: user_id}
291
+ ]
292
+ )
293
+ return false unless consent && consent["consentGiven"]
294
+
295
+ granted = OAuthProtocol.parse_scopes(consent["scopes"])
296
+ scopes.all? { |scope| granted.include?(scope) }
297
+ end
298
+
299
+ def oauth_store_consent(ctx, client, session, scopes)
300
+ client_id = OAuthProtocol.stringify_keys(client)["clientId"]
301
+ user_id = session[:user]["id"]
302
+ existing = ctx.context.adapter.find_one(
303
+ model: "oauthConsent",
304
+ where: [
305
+ {field: "clientId", value: client_id},
306
+ {field: "userId", value: user_id}
307
+ ]
308
+ )
309
+ data = {clientId: client_id, userId: user_id, scopes: scopes, consentGiven: true}
310
+ if existing
311
+ ctx.context.adapter.update(model: "oauthConsent", where: [{field: "id", value: existing.fetch("id")}], update: data)
312
+ else
313
+ ctx.context.adapter.create(model: "oauthConsent", data: data)
314
+ end
315
+ end
316
+
317
+ def oauth_introspect_endpoint(config)
318
+ Endpoint.new(path: "/oauth2/introspect", method: "POST", metadata: {allowed_media_types: ["application/x-www-form-urlencoded", "application/json"]}) do |ctx|
319
+ OAuthProtocol.authenticate_client!(ctx, "oauthClient")
320
+ body = OAuthProtocol.stringify_keys(ctx.body)
321
+ token = config[:store][:tokens][body["token"].to_s] || config[:store][:refresh_tokens][body["token"].to_s]
322
+ active = token && !token["revoked"] && (!token["expiresAt"] || token["expiresAt"] > Time.now)
323
+ ctx.json(active ? {
324
+ active: true,
325
+ client_id: token["clientId"],
326
+ scope: OAuthProtocol.scope_string(token["scope"] || token["scopes"]),
327
+ sub: token.dig("user", "id"),
328
+ exp: token["expiresAt"]&.to_i
329
+ } : {active: false})
330
+ end
331
+ end
332
+
333
+ def oauth_revoke_endpoint(config)
334
+ Endpoint.new(path: "/oauth2/revoke", method: "POST", metadata: {allowed_media_types: ["application/x-www-form-urlencoded", "application/json"]}) do |ctx|
335
+ OAuthProtocol.authenticate_client!(ctx, "oauthClient")
336
+ body = OAuthProtocol.stringify_keys(ctx.body)
337
+ if (token = config[:store][:tokens][body["token"].to_s] || config[:store][:refresh_tokens][body["token"].to_s])
338
+ token["revoked"] = Time.now
339
+ end
340
+ ctx.json({revoked: true})
341
+ end
342
+ end
343
+
344
+ def oauth_userinfo_endpoint(config)
345
+ Endpoint.new(path: "/oauth2/userinfo", method: "GET") do |ctx|
346
+ ctx.json(OAuthProtocol.userinfo(config[:store], ctx.headers["authorization"]))
347
+ end
348
+ end
349
+
350
+ def oauth_provider_schema
351
+ {
352
+ oauthClient: {
353
+ modelName: "oauthClient",
354
+ fields: {
355
+ clientId: {type: "string", unique: true, required: true},
356
+ clientSecret: {type: "string", required: false},
357
+ disabled: {type: "boolean", default_value: false, required: false},
358
+ skipConsent: {type: "boolean", required: false},
359
+ enableEndSession: {type: "boolean", required: false},
360
+ scopes: {type: "string[]", required: false},
361
+ userId: {type: "string", required: false},
362
+ createdAt: {type: "date", required: true, default_value: -> { Time.now }},
363
+ updatedAt: {type: "date", required: true, default_value: -> { Time.now }, on_update: -> { Time.now }},
364
+ name: {type: "string", required: false},
365
+ uri: {type: "string", required: false},
366
+ icon: {type: "string", required: false},
367
+ contacts: {type: "string[]", required: false},
368
+ tos: {type: "string", required: false},
369
+ policy: {type: "string", required: false},
370
+ softwareId: {type: "string", required: false},
371
+ softwareVersion: {type: "string", required: false},
372
+ softwareStatement: {type: "string", required: false},
373
+ redirectUris: {type: "string[]", required: true},
374
+ postLogoutRedirectUris: {type: "string[]", required: false},
375
+ tokenEndpointAuthMethod: {type: "string", required: false},
376
+ grantTypes: {type: "string[]", required: false},
377
+ responseTypes: {type: "string[]", required: false},
378
+ public: {type: "boolean", required: false},
379
+ type: {type: "string", required: false},
380
+ requirePKCE: {type: "boolean", required: false},
381
+ referenceId: {type: "string", required: false},
382
+ metadata: {type: "json", required: false}
383
+ }
384
+ },
385
+ oauthRefreshToken: {
386
+ fields: {
387
+ token: {type: "string", required: true},
388
+ clientId: {type: "string", required: true},
389
+ sessionId: {type: "string", required: false},
390
+ userId: {type: "string", required: false},
391
+ referenceId: {type: "string", required: false},
392
+ expiresAt: {type: "date", required: false},
393
+ createdAt: {type: "date", required: true, default_value: -> { Time.now }},
394
+ revoked: {type: "date", required: false},
395
+ scopes: {type: "string[]", required: true}
396
+ }
397
+ },
398
+ oauthAccessToken: {
399
+ modelName: "oauthAccessToken",
400
+ fields: {
401
+ accessToken: {type: "string", unique: true, required: false},
402
+ token: {type: "string", unique: true, required: false},
403
+ refreshToken: {type: "string", unique: true, required: false},
404
+ accessTokenExpiresAt: {type: "date", required: false},
405
+ expiresAt: {type: "date", required: false},
406
+ clientId: {type: "string", required: true},
407
+ userId: {type: "string", required: false},
408
+ sessionId: {type: "string", required: false},
409
+ scope: {type: "string", required: false},
410
+ scopes: {type: "string[]", required: false},
411
+ revoked: {type: "date", required: false},
412
+ referenceId: {type: "string", required: false},
413
+ refreshId: {type: "string", required: false},
414
+ createdAt: {type: "date", required: true, default_value: -> { Time.now }},
415
+ updatedAt: {type: "date", required: true, default_value: -> { Time.now }, on_update: -> { Time.now }}
416
+ }
417
+ },
418
+ oauthConsent: {
419
+ modelName: "oauthConsent",
420
+ fields: {
421
+ clientId: {type: "string", required: true},
422
+ userId: {type: "string", required: false},
423
+ referenceId: {type: "string", required: false},
424
+ scopes: {type: "string[]", required: true},
425
+ consentGiven: {type: "boolean", required: false},
426
+ createdAt: {type: "date", required: true, default_value: -> { Time.now }},
427
+ updatedAt: {type: "date", required: true, default_value: -> { Time.now }, on_update: -> { Time.now }}
428
+ }
429
+ }
430
+ }
431
+ end
432
+ end
433
+ end
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: better_auth-oauth-provider
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sebastian Sala
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: better_auth
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '0.1'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '0.1'
26
+ - !ruby/object:Gem::Dependency
27
+ name: bundler
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.5'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2.5'
40
+ - !ruby/object:Gem::Dependency
41
+ name: minitest
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '5.25'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '5.25'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rake
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '13.2'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '13.2'
68
+ - !ruby/object:Gem::Dependency
69
+ name: standardrb
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '1.0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '1.0'
82
+ description: Adds OAuth 2.0 provider metadata, client registration, authorization,
83
+ token, introspection, revocation, and userinfo routes for Better Auth Ruby.
84
+ email:
85
+ - sebastian.sala.tech@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - CHANGELOG.md
91
+ - README.md
92
+ - lib/better_auth/oauth_provider.rb
93
+ - lib/better_auth/oauth_provider/version.rb
94
+ - lib/better_auth/plugins/oauth_provider.rb
95
+ homepage: https://github.com/sebasxsala/better-auth
96
+ licenses:
97
+ - MIT
98
+ metadata:
99
+ homepage_uri: https://github.com/sebasxsala/better-auth
100
+ source_code_uri: https://github.com/sebasxsala/better-auth
101
+ changelog_uri: https://github.com/sebasxsala/better-auth/blob/main/packages/better_auth-oauth-provider/CHANGELOG.md
102
+ bug_tracker_uri: https://github.com/sebasxsala/better-auth/issues
103
+ rdoc_options: []
104
+ require_paths:
105
+ - lib
106
+ required_ruby_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: 3.2.0
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ requirements: []
117
+ rubygems_version: 3.6.9
118
+ specification_version: 4
119
+ summary: OAuth provider plugin package for Better Auth Ruby
120
+ test_files: []