better_auth-oauth-provider 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 24a78dcb3a3ee2da7f9f62938f167887fb30b6b1f7585cf35a003be2bcf0bf3b
4
- data.tar.gz: 0cfb1cd9eacabfd74a979b335f3fc482ddc79110781cb2473cc19c007af0ab34
3
+ metadata.gz: 064ea232e262c58fa331b01d271cacace56d0787690411c0c10509573e97575c
4
+ data.tar.gz: fb803328e302b653dcdad4efbf0f40067a798964e0e0a6c3b830933eb297a942
5
5
  SHA512:
6
- metadata.gz: 2e6642f06f181d19b533af40736184e5e129786ae7461e73d8206ff2c5b2958f66bd4fa69c15da22bb617da0d4fec3295f54d3c6475e892b0b7262f35adfac31
7
- data.tar.gz: 07a111e2a9d439de5fb060818e23a6bc49ca67b8f270321fd3c400be8be65e7a7e77384831accbfef180d3044fb28b9b2b7995c379ee44f72044f7351dba4056
6
+ metadata.gz: 531242dbca74547d088b8de6b7181a962e650725b6739743d1ccde257f28357f547b9f6d1c0fce32618dfaa74c94909a5fbeb8b86cb5bcc6e9a32bf585882b14
7
+ data.tar.gz: c3d2ad67c5e0ba91433f410accd11575a8e64f4057962c4217dfa591c14f73112fa87a382f5a2009b4427d45a67b964c2339350bcf8fb7d34613062c03e0c6c1
data/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.3.0 - 2026-04-30
6
+
7
+ - Added upstream-parity support for provider init validation, request URI resolution, prompt handling, consent reference IDs, client references, custom token/id-token claims, scope-specific access-token expiry, M2M token defaults, userinfo JWT verification, and expanded introspection fields.
8
+ - Aligned dynamic registration, admin client creation, authorization, consent, token, refresh, revoke, and userinfo behavior with upstream edge cases.
9
+ - Expanded OAuth provider upstream parity tests across authorization, metadata, client privileges, pairwise endpoints, organization integration, prompts, rate limits, PKCE/token handling, and userinfo.
10
+
5
11
  ## 0.2.0 - 2026-04-29
6
12
 
7
13
  - Aligned OAuth provider server behavior with upstream `@better-auth/oauth-provider` v1.6.9: upstream-shaped client and consent CRUD routes, server-only admin client routes, discovery metadata auth-method and signing-alg semantics, canonical access-token and consent schema, dynamic-registration PKCE defaults, refresh replay cascade revocation, rotate-secret response shape, and pairwise sector identifiers.
@@ -2,6 +2,6 @@
2
2
 
3
3
  module BetterAuth
4
4
  module OAuthProvider
5
- VERSION = "0.2.0"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
@@ -12,7 +12,7 @@ module BetterAuth
12
12
  uri = URI.parse(value.to_s)
13
13
  uri.query = nil
14
14
  uri.fragment = nil
15
- if uri.scheme == "http" && !["localhost", "127.0.0.1"].include?(uri.host)
15
+ if uri.scheme == "http" && !["localhost", "127.0.0.1", "::1"].include?(uri.hostname || uri.host)
16
16
  uri.scheme = "https"
17
17
  end
18
18
  uri.to_s.sub(%r{/+\z}, "")
@@ -42,12 +42,16 @@ module BetterAuth
42
42
  store_client_secret: "plain",
43
43
  prefix: {},
44
44
  refresh_token_expires_in: 2_592_000,
45
+ access_token_expires_in: 3600,
46
+ m2m_access_token_expires_in: 3600,
47
+ scope_expirations: {},
45
48
  store: OAuthProtocol.stores
46
49
  }.merge(normalize_hash(options))
47
50
 
48
51
  Plugin.new(
49
52
  id: "oauth-provider",
50
53
  version: BetterAuth::OAuthProvider::VERSION,
54
+ init: oauth_provider_init(config),
51
55
  endpoints: oauth_provider_endpoints(config),
52
56
  schema: oauth_provider_schema,
53
57
  rate_limit: oauth_provider_rate_limits(config),
@@ -55,6 +59,24 @@ module BetterAuth
55
59
  )
56
60
  end
57
61
 
62
+ def oauth_provider_init(config)
63
+ lambda do |context|
64
+ advertised_scopes = Array(config.dig(:advertised_metadata, :scopes_supported)).map(&:to_s)
65
+ provider_scopes = OAuthProtocol.parse_scopes(config[:scopes])
66
+ missing_scopes = advertised_scopes - provider_scopes
67
+ unless missing_scopes.empty?
68
+ raise APIError.new("BAD_REQUEST", message: "advertised_metadata.scopes_supported #{missing_scopes.first} not found in scopes")
69
+ end
70
+ if config[:pairwise_secret] && config[:pairwise_secret].to_s.length < 32
71
+ raise APIError.new("BAD_REQUEST", message: "pairwise_secret must be at least 32 characters")
72
+ end
73
+ if context.options.secondary_storage && !context.options.session[:store_session_in_database]
74
+ raise APIError.new("BAD_REQUEST", message: "OAuth Provider requires session.store_session_in_database when using secondary storage")
75
+ end
76
+ nil
77
+ end
78
+ end
79
+
58
80
  def oauth_provider_endpoints(config)
59
81
  {
60
82
  get_o_auth_server_config: oauth_server_metadata_endpoint(config),
@@ -182,7 +204,10 @@ module BetterAuth
182
204
  allowed_scopes: config[:client_registration_allowed_scopes] || config[:scopes],
183
205
  store_client_secret: config[:store_client_secret],
184
206
  prefix: config[:prefix],
185
- dynamic_registration: true
207
+ dynamic_registration: true,
208
+ pairwise_secret: config[:pairwise_secret],
209
+ strip_client_metadata: true,
210
+ reference_id: oauth_client_reference(config, session)
186
211
  )
187
212
  ctx.json(client, status: 201, headers: {"Cache-Control" => "no-store", "Pragma" => "no-cache"})
188
213
  end
@@ -203,7 +228,10 @@ module BetterAuth
203
228
  store_client_secret: config[:store_client_secret],
204
229
  prefix: config[:prefix],
205
230
  dynamic_registration: false,
206
- admin: false
231
+ admin: false,
232
+ pairwise_secret: config[:pairwise_secret],
233
+ strip_client_metadata: true,
234
+ reference_id: oauth_client_reference(config, session)
207
235
  )
208
236
  ctx.json(client, status: 201, headers: {"Cache-Control" => "no-store", "Pragma" => "no-cache"})
209
237
  end
@@ -290,6 +318,13 @@ module BetterAuth
290
318
 
291
319
  def oauth_admin_create_client_endpoint(config)
292
320
  Endpoint.new(path: "/admin/oauth2/create-client", method: "POST", metadata: {server_only: true}) do |ctx|
321
+ session = nil
322
+ if config[:client_privileges].respond_to?(:call)
323
+ session = Routes.current_session(ctx)
324
+ oauth_assert_client_privilege!(ctx, config, session, "create")
325
+ elsif config[:client_reference].respond_to?(:call)
326
+ session = Routes.current_session(ctx, allow_nil: true)
327
+ end
293
328
  body = OAuthProtocol.stringify_keys(ctx.body)
294
329
  client = OAuthProtocol.create_client(
295
330
  ctx,
@@ -301,7 +336,10 @@ module BetterAuth
301
336
  store_client_secret: config[:store_client_secret],
302
337
  prefix: config[:prefix],
303
338
  dynamic_registration: false,
304
- admin: true
339
+ admin: true,
340
+ pairwise_secret: config[:pairwise_secret],
341
+ strip_client_metadata: true,
342
+ reference_id: oauth_client_reference(config, session)
305
343
  )
306
344
  ctx.json(client, status: 201, headers: {"Cache-Control" => "no-store", "Pragma" => "no-cache"})
307
345
  end
@@ -443,6 +481,7 @@ module BetterAuth
443
481
  end
444
482
 
445
483
  def oauth_authorize_flow(ctx, config, query, continue_post_login: false)
484
+ query = oauth_resolve_request_uri!(ctx, config, query)
446
485
  response_type = query["response_type"].to_s
447
486
 
448
487
  client = OAuthProtocol.find_client(ctx, "oauthClient", query["client_id"])
@@ -480,6 +519,10 @@ module BetterAuth
480
519
  raise ctx.redirect(oauth_prompt_redirect(ctx, config, query, "login"))
481
520
  end
482
521
 
522
+ if prompts.include?("login") && !continue_post_login
523
+ raise ctx.redirect(oauth_prompt_redirect(ctx, config, query, "login"))
524
+ end
525
+
483
526
  if prompts.include?("select_account") && !continue_post_login
484
527
  if prompts.include?("none")
485
528
  raise ctx.redirect(OAuthProtocol.redirect_uri_with_params(query["redirect_uri"], error: "account_selection_required", state: query["state"], iss: OAuthProvider.validate_issuer_url(OAuthProtocol.issuer(ctx))))
@@ -499,7 +542,8 @@ module BetterAuth
499
542
  end
500
543
  end
501
544
 
502
- requires_consent = !client_data["skipConsent"] && (prompts.include?("consent") || !oauth_consent_granted?(ctx, client_data["clientId"], session[:user]["id"], scopes))
545
+ consent_reference_id = oauth_consent_reference(config, session, scopes)
546
+ requires_consent = !client_data["skipConsent"] && (prompts.include?("consent") || !oauth_consent_granted?(ctx, client_data["clientId"], session[:user]["id"], scopes, consent_reference_id))
503
547
 
504
548
  if requires_consent
505
549
  if prompts.include?("none")
@@ -512,12 +556,13 @@ module BetterAuth
512
556
  session: session,
513
557
  client: client,
514
558
  scopes: scopes,
559
+ reference_id: consent_reference_id,
515
560
  expires_at: Time.now + 600
516
561
  }
517
562
  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)))
518
563
  end
519
564
 
520
- oauth_redirect_with_code(ctx, config, query, session, client, scopes)
565
+ oauth_redirect_with_code(ctx, config, query, session, client, scopes, reference_id: consent_reference_id)
521
566
  end
522
567
 
523
568
  def oauth_prompt_redirect(ctx, config, query, type, page: nil)
@@ -593,7 +638,7 @@ module BetterAuth
593
638
 
594
639
  def oauth_consent_endpoint(config)
595
640
  Endpoint.new(path: "/oauth2/consent", method: "POST") do |ctx|
596
- Routes.current_session(ctx)
641
+ current_session = Routes.current_session(ctx)
597
642
  body = OAuthProtocol.stringify_keys(ctx.body)
598
643
  consent = config[:store][:consents].delete(body["consent_code"].to_s)
599
644
  raise APIError.new("BAD_REQUEST", message: "invalid consent_code") unless consent
@@ -611,8 +656,9 @@ module BetterAuth
611
656
  raise APIError.new("BAD_REQUEST", message: "invalid_scope")
612
657
  end
613
658
 
614
- oauth_store_consent(ctx, consent[:client], consent[:session], granted_scopes)
615
- redirect = oauth_authorization_redirect(ctx, config, query, consent[:session], consent[:client], granted_scopes)
659
+ reference_id = oauth_consent_reference(config, current_session, granted_scopes) || consent[:reference_id]
660
+ oauth_store_consent(ctx, consent[:client], consent[:session], granted_scopes, reference_id)
661
+ redirect = oauth_authorization_redirect(ctx, config, query, consent[:session], consent[:client], granted_scopes, reference_id: reference_id)
616
662
  ctx.json({redirectURI: redirect})
617
663
  end
618
664
  end
@@ -647,26 +693,32 @@ module BetterAuth
647
693
  issuer: OAuthProvider.validate_issuer_url(OAuthProtocol.issuer(ctx)),
648
694
  prefix: config[:prefix],
649
695
  refresh_token_expires_in: config[:refresh_token_expires_in],
696
+ access_token_expires_in: oauth_access_token_expires_in(config, code[:scopes], machine: false),
650
697
  audience: audience,
651
698
  grant_type: OAuthProtocol::AUTH_CODE_GRANT,
652
699
  custom_token_response_fields: config[:custom_token_response_fields],
653
700
  custom_access_token_claims: config[:custom_access_token_claims],
701
+ custom_id_token_claims: config[:custom_id_token_claims],
654
702
  jwt_access_token: oauth_jwt_access_token?(config, audience),
655
703
  pairwise_secret: config[:pairwise_secret],
656
704
  nonce: code[:nonce],
657
705
  auth_time: code[:auth_time],
658
- reference_id: code[:reference_id]
706
+ reference_id: code[:reference_id],
707
+ filter_id_token_claims_by_scope: true
659
708
  )
660
709
  when OAuthProtocol::CLIENT_CREDENTIALS_GRANT
661
710
  requested = OAuthProtocol.parse_scopes(body["scope"])
662
711
  allowed = OAuthProtocol.parse_scopes(OAuthProtocol.stringify_keys(client)["scopes"] || config[:scopes])
712
+ requested = allowed if requested.empty?
663
713
  unless requested.all? { |scope| allowed.include?(scope) }
664
714
  raise APIError.new("BAD_REQUEST", message: "invalid_scope")
665
715
  end
666
716
 
667
- OAuthProtocol.issue_tokens(ctx, config[:store], model: "oauthAccessToken", client: client, session: {"user" => {}, "session" => {}}, scopes: requested, include_refresh: false, issuer: OAuthProtocol.issuer(ctx), prefix: config[:prefix], audience: audience, grant_type: OAuthProtocol::CLIENT_CREDENTIALS_GRANT, custom_token_response_fields: config[:custom_token_response_fields], custom_access_token_claims: config[:custom_access_token_claims], jwt_access_token: oauth_jwt_access_token?(config, audience), pairwise_secret: config[:pairwise_secret])
717
+ OAuthProtocol.issue_tokens(ctx, config[:store], model: "oauthAccessToken", client: client, session: {"user" => {}, "session" => {}}, scopes: requested, include_refresh: false, issuer: OAuthProtocol.issuer(ctx), prefix: config[:prefix], audience: audience, grant_type: OAuthProtocol::CLIENT_CREDENTIALS_GRANT, custom_token_response_fields: config[:custom_token_response_fields], custom_access_token_claims: config[:custom_access_token_claims], custom_id_token_claims: config[:custom_id_token_claims], jwt_access_token: oauth_jwt_access_token?(config, audience), pairwise_secret: config[:pairwise_secret], access_token_expires_in: oauth_access_token_expires_in(config, requested, machine: true), filter_id_token_claims_by_scope: true)
668
718
  when OAuthProtocol::REFRESH_GRANT
669
- OAuthProtocol.refresh_tokens(ctx, config[:store], model: "oauthAccessToken", client: client, refresh_token: body["refresh_token"], scopes: body["scope"], issuer: OAuthProtocol.issuer(ctx), prefix: config[:prefix], refresh_token_expires_in: config[:refresh_token_expires_in], audience: audience, custom_token_response_fields: config[:custom_token_response_fields], custom_access_token_claims: config[:custom_access_token_claims], jwt_access_token: oauth_jwt_access_token?(config, audience), pairwise_secret: config[:pairwise_secret])
719
+ refresh_record = OAuthProtocol.find_token_by_hint(config[:store], body["refresh_token"].to_s, "refresh_token", prefix: config[:prefix])
720
+ refresh_scopes = OAuthProtocol.parse_scopes(body["scope"] || refresh_record&.fetch("scopes", nil))
721
+ OAuthProtocol.refresh_tokens(ctx, config[:store], model: "oauthAccessToken", client: client, refresh_token: body["refresh_token"], scopes: body["scope"], issuer: OAuthProtocol.issuer(ctx), prefix: config[:prefix], refresh_token_expires_in: config[:refresh_token_expires_in], audience: audience, custom_token_response_fields: config[:custom_token_response_fields], custom_access_token_claims: config[:custom_access_token_claims], custom_id_token_claims: config[:custom_id_token_claims], jwt_access_token: oauth_jwt_access_token?(config, audience), pairwise_secret: config[:pairwise_secret], access_token_expires_in: oauth_access_token_expires_in(config, refresh_scopes, machine: false), filter_id_token_claims_by_scope: true)
670
722
  else
671
723
  raise APIError.new("BAD_REQUEST", message: "unsupported_grant_type")
672
724
  end
@@ -674,8 +726,9 @@ module BetterAuth
674
726
  end
675
727
  end
676
728
 
677
- def oauth_authorization_redirect(ctx, config, query, session, client, scopes)
729
+ def oauth_authorization_redirect(ctx, config, query, session, client, scopes, reference_id: nil)
678
730
  code = Crypto.random_string(32)
731
+ client_reference_id = OAuthProtocol.stringify_keys(client)["referenceId"]
679
732
  OAuthProtocol.store_code(
680
733
  config[:store],
681
734
  code: code,
@@ -686,22 +739,24 @@ module BetterAuth
686
739
  code_challenge: query["code_challenge"],
687
740
  code_challenge_method: query["code_challenge_method"],
688
741
  nonce: query["nonce"],
689
- reference_id: OAuthProtocol.stringify_keys(client)["referenceId"]
742
+ reference_id: reference_id || client_reference_id
690
743
  )
691
744
  OAuthProtocol.redirect_uri_with_params(query["redirect_uri"], code: code, state: query["state"], iss: OAuthProvider.validate_issuer_url(OAuthProtocol.issuer(ctx)))
692
745
  end
693
746
 
694
- def oauth_redirect_with_code(ctx, config, query, session, client, scopes)
695
- raise ctx.redirect(oauth_authorization_redirect(ctx, config, query, session, client, scopes))
747
+ def oauth_redirect_with_code(ctx, config, query, session, client, scopes, reference_id: nil)
748
+ raise ctx.redirect(oauth_authorization_redirect(ctx, config, query, session, client, scopes, reference_id: reference_id))
696
749
  end
697
750
 
698
- def oauth_consent_granted?(ctx, client_id, user_id, scopes)
751
+ def oauth_consent_granted?(ctx, client_id, user_id, scopes, reference_id = nil)
752
+ where = [
753
+ {field: "clientId", value: client_id},
754
+ {field: "userId", value: user_id}
755
+ ]
756
+ where << {field: "referenceId", value: reference_id} if reference_id
699
757
  consent = ctx.context.adapter.find_one(
700
758
  model: "oauthConsent",
701
- where: [
702
- {field: "clientId", value: client_id},
703
- {field: "userId", value: user_id}
704
- ]
759
+ where: where
705
760
  )
706
761
  return false unless consent
707
762
 
@@ -709,17 +764,20 @@ module BetterAuth
709
764
  scopes.all? { |scope| granted.include?(scope) }
710
765
  end
711
766
 
712
- def oauth_store_consent(ctx, client, session, scopes)
767
+ def oauth_store_consent(ctx, client, session, scopes, reference_id = nil)
713
768
  client_id = OAuthProtocol.stringify_keys(client)["clientId"]
714
769
  user_id = session[:user]["id"]
770
+ where = [
771
+ {field: "clientId", value: client_id},
772
+ {field: "userId", value: user_id}
773
+ ]
774
+ where << {field: "referenceId", value: reference_id} if reference_id
715
775
  existing = ctx.context.adapter.find_one(
716
776
  model: "oauthConsent",
717
- where: [
718
- {field: "clientId", value: client_id},
719
- {field: "userId", value: user_id}
720
- ]
777
+ where: where
721
778
  )
722
779
  data = {clientId: client_id, userId: user_id, scopes: scopes}
780
+ data[:referenceId] = reference_id if reference_id
723
781
  if existing
724
782
  ctx.context.adapter.update(model: "oauthConsent", where: [{field: "id", value: existing.fetch("id")}], update: data)
725
783
  else
@@ -727,6 +785,13 @@ module BetterAuth
727
785
  end
728
786
  end
729
787
 
788
+ def oauth_consent_reference(config, session, scopes)
789
+ callback = config.dig(:post_login, :consent_reference_id) || config.dig(:post_login, :consentReferenceId)
790
+ return nil unless callback.respond_to?(:call)
791
+
792
+ callback.call({user: session[:user], session: session[:session], scopes: scopes})
793
+ end
794
+
730
795
  def oauth_introspect_endpoint(config)
731
796
  Endpoint.new(path: "/oauth2/introspect", method: "POST", metadata: {allowed_media_types: ["application/x-www-form-urlencoded", "application/json"]}) do |ctx|
732
797
  client = OAuthProtocol.authenticate_client!(ctx, "oauthClient", store_client_secret: config[:store_client_secret], prefix: config[:prefix])
@@ -738,8 +803,12 @@ module BetterAuth
738
803
  active: true,
739
804
  client_id: token["clientId"],
740
805
  scope: OAuthProtocol.scope_string(token["scope"] || token["scopes"]),
741
- sub: token.dig("user", "id"),
742
- exp: token["expiresAt"]&.to_i
806
+ sub: token["subject"] || token.dig("user", "id"),
807
+ iss: token["issuer"],
808
+ iat: token["issuedAt"]&.to_i,
809
+ exp: token["expiresAt"]&.to_i,
810
+ sid: token["sessionId"],
811
+ aud: token["audience"]
743
812
  })
744
813
  end
745
814
 
@@ -752,6 +821,12 @@ module BetterAuth
752
821
  Endpoint.new(path: "/oauth2/revoke", method: "POST", metadata: {allowed_media_types: ["application/x-www-form-urlencoded", "application/json"]}) do |ctx|
753
822
  OAuthProtocol.authenticate_client!(ctx, "oauthClient", store_client_secret: config[:store_client_secret], prefix: config[:prefix])
754
823
  body = OAuthProtocol.stringify_keys(ctx.body)
824
+ if body["token_type_hint"].to_s == "access_token" && OAuthProtocol.find_token_by_hint(config[:store], body["token"].to_s, "refresh_token", prefix: config[:prefix])
825
+ raise APIError.new("BAD_REQUEST", message: "invalid_request")
826
+ end
827
+ if body["token_type_hint"].to_s == "refresh_token" && OAuthProtocol.find_token_by_hint(config[:store], body["token"].to_s, "access_token", prefix: config[:prefix])
828
+ raise APIError.new("BAD_REQUEST", message: "invalid_request")
829
+ end
755
830
  if (token = OAuthProtocol.find_token_by_hint(config[:store], body["token"].to_s, body["token_type_hint"], prefix: config[:prefix]))
756
831
  token["revoked"] = Time.now
757
832
  end
@@ -761,7 +836,7 @@ module BetterAuth
761
836
 
762
837
  def oauth_userinfo_endpoint(config)
763
838
  Endpoint.new(path: "/oauth2/userinfo", method: "GET") do |ctx|
764
- ctx.json(OAuthProtocol.userinfo(config[:store], ctx.headers["authorization"], additional_claim: config[:custom_user_info_claims] || config[:additional_claim], prefix: config[:prefix]))
839
+ ctx.json(OAuthProtocol.userinfo(config[:store], ctx.headers["authorization"], additional_claim: config[:custom_user_info_claims] || config[:additional_claim], prefix: config[:prefix], jwt_secret: ctx.context.secret))
765
840
  end
766
841
  end
767
842
 
@@ -813,6 +888,28 @@ module BetterAuth
813
888
  )
814
889
  end
815
890
 
891
+ def oauth_resolve_request_uri!(ctx, config, query)
892
+ query = OAuthProtocol.stringify_keys(query)
893
+ return query if query["request_uri"].to_s.empty?
894
+
895
+ resolver = config[:request_uri_resolver]
896
+ unless resolver.respond_to?(:call)
897
+ return oauth_invalid_request_uri!(ctx, query, "request_uri not supported")
898
+ end
899
+
900
+ resolved = resolver.call({request_uri: query["request_uri"], client_id: query["client_id"], context: ctx})
901
+ return oauth_invalid_request_uri!(ctx, query, "request_uri is invalid or expired") unless resolved
902
+
903
+ OAuthProtocol.stringify_keys(resolved)
904
+ end
905
+
906
+ def oauth_invalid_request_uri!(ctx, query, description)
907
+ redirect_uri = query["redirect_uri"]
908
+ raise APIError.new("BAD_REQUEST", message: "invalid_request_uri") if redirect_uri.to_s.empty?
909
+
910
+ raise ctx.redirect(oauth_authorize_error_redirect(ctx, query, "invalid_request_uri", description))
911
+ end
912
+
816
913
  def oauth_jwt_access_token?(config, audience)
817
914
  !!audience && !config[:disable_jwt_plugin] && !config[:disable_jwt_access_tokens]
818
915
  end
@@ -859,6 +956,12 @@ module BetterAuth
859
956
  raise APIError.new("UNAUTHORIZED") unless allowed
860
957
  end
861
958
 
959
+ def oauth_client_reference(config, session)
960
+ return nil unless session && config[:client_reference].respond_to?(:call)
961
+
962
+ config[:client_reference].call({user: session[:user], session: session[:session]})
963
+ end
964
+
862
965
  def oauth_client_update_data(source, admin: false)
863
966
  update = {}
864
967
  update["name"] = source["client_name"] || source["name"] if source.key?("client_name") || source.key?("name")
@@ -957,6 +1060,31 @@ module BetterAuth
957
1060
  (resources.length == 1) ? resources.first : resources
958
1061
  end
959
1062
 
1063
+ def oauth_access_token_expires_in(config, scopes, machine:)
1064
+ base = machine ? config[:m2m_access_token_expires_in] : config[:access_token_expires_in]
1065
+ expirations = normalize_hash(config[:scope_expirations] || {})
1066
+ matches = OAuthProtocol.parse_scopes(scopes).filter_map do |scope|
1067
+ value = expirations[scope.to_sym] || expirations[scope]
1068
+ oauth_duration_seconds(value) if value
1069
+ end
1070
+ ([base.to_i] + matches).compact.min
1071
+ end
1072
+
1073
+ def oauth_duration_seconds(value)
1074
+ return value.to_i if value.is_a?(Numeric)
1075
+
1076
+ match = value.to_s.match(/\A(\d+)([smhd])?\z/)
1077
+ return value.to_i unless match
1078
+
1079
+ amount = match[1].to_i
1080
+ case match[2]
1081
+ when "m" then amount * 60
1082
+ when "h" then amount * 3600
1083
+ when "d" then amount * 86_400
1084
+ else amount
1085
+ end
1086
+ end
1087
+
960
1088
  def oauth_legacy_get_client_endpoint(config)
961
1089
  Endpoint.new(path: "/oauth2/client/:id", method: "GET") do |ctx|
962
1090
  session = Routes.current_session(ctx)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: better_auth-oauth-provider
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sebastian Sala