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 +4 -4
- data/CHANGELOG.md +6 -0
- data/lib/better_auth/oauth_provider/version.rb +1 -1
- data/lib/better_auth/plugins/oauth_provider.rb +157 -29
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 064ea232e262c58fa331b01d271cacace56d0787690411c0c10509573e97575c
|
|
4
|
+
data.tar.gz: fb803328e302b653dcdad4efbf0f40067a798964e0e0a6c3b830933eb297a942
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
@@ -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
|
-
|
|
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
|
-
|
|
615
|
-
|
|
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.
|
|
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:
|
|
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
|
-
|
|
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)
|