From 47b7f1e6f2812908fecef75fd4bf2c9e692f1ba4 Mon Sep 17 00:00:00 2001 From: Stavros Date: Sat, 6 Jun 2026 18:01:59 +0300 Subject: [PATCH] feat: add back support for request oidc param --- frontend/src/pages/login-page.tsx | 6 +++- frontend/src/pages/totp-page.tsx | 6 +++- go.mod | 5 ++++ go.sum | 10 +++++++ internal/controller/oidc_controller.go | 41 +++++++++++++++++++------- internal/service/oidc_service.go | 37 ++++++++++++++++++----- 6 files changed, 84 insertions(+), 21 deletions(-) diff --git a/frontend/src/pages/login-page.tsx b/frontend/src/pages/login-page.tsx index b46ac998..c070936f 100644 --- a/frontend/src/pages/login-page.tsx +++ b/frontend/src/pages/login-page.tsx @@ -126,7 +126,11 @@ export const LoginPage = () => { }); redirectTimer.current = window.setTimeout(() => { - window.location.replace(`/continue${compiledParams}`); + if (screenParams.login_for === "oidc") { + window.location.replace(`/oidc/authorize${compiledParams}`); + } else { + window.location.replace(`/continue${compiledParams}`); + } }, 500); }, onError: (error: AxiosError) => { diff --git a/frontend/src/pages/totp-page.tsx b/frontend/src/pages/totp-page.tsx index 3b16d615..b4a4c1f9 100644 --- a/frontend/src/pages/totp-page.tsx +++ b/frontend/src/pages/totp-page.tsx @@ -42,7 +42,11 @@ export const TotpPage = () => { }); redirectTimer.current = window.setTimeout(() => { - window.location.replace(`/continue${compiledParams}`); + if (screenParams.login_for === "oidc") { + window.location.replace(`/oidc/authorize${compiledParams}`); + } else { + window.location.replace(`/continue${compiledParams}`); + } }, 500); }, onError: () => { diff --git a/go.mod b/go.mod index e7c0d2d3..4d1cfa3a 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/google/go-querystring v1.2.0 github.com/google/uuid v1.6.0 github.com/jackc/pgx/v5 v5.10.0 + github.com/lestrrat-go/jwx/v4 v4.0.2 github.com/mdp/qrterminal/v3 v3.2.1 github.com/pquerna/otp v1.5.0 github.com/rs/zerolog v1.35.1 @@ -86,6 +87,7 @@ require ( github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-yaml v1.19.2 // indirect github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect + github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/go-cmp v0.7.0 // indirect @@ -101,6 +103,8 @@ require ( github.com/klauspost/compress v1.18.5 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/lestrrat-go/dsig v1.3.0 // indirect + github.com/lestrrat-go/option/v3 v3.0.0-alpha1 // indirect github.com/lucasb-eyer/go-colorful v1.3.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -140,6 +144,7 @@ require ( github.com/tailscale/wireguard-go v0.0.0-20260527010701-b48af7099cad // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.1 // indirect + github.com/valyala/fastjson v1.6.10 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect diff --git a/go.sum b/go.sum index 9cd35e7f..4e542fc8 100644 --- a/go.sum +++ b/go.sum @@ -216,6 +216,8 @@ github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg= github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA= github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= @@ -301,6 +303,12 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lestrrat-go/dsig v1.3.0 h1:phjMOCXvYzhuIgn7Voe2rex8z166vGfxRxmqM25P9/Q= +github.com/lestrrat-go/dsig v1.3.0/go.mod h1:RD2eOaidyPvpc7IJQoO3Qq52RWdy8ZcJs8lrOnoa1Kc= +github.com/lestrrat-go/jwx/v4 v4.0.2 h1:T3lzN2dynOt6SuowT08ZWo/cPs3YsB0GHZSXKvfE0uQ= +github.com/lestrrat-go/jwx/v4 v4.0.2/go.mod h1:F2a0rSyXsqLAL0orBZGOXrzQGv018Tx4eiEWWYR7Yzo= +github.com/lestrrat-go/option/v3 v3.0.0-alpha1 h1:dvdzLwm/Ba5CJUF3jQP7w/iNYSLfy7yyh9XXNa1WjxI= +github.com/lestrrat-go/option/v3 v3.0.0-alpha1/go.mod h1:5KSg20dfsKkNJtjDmaQRLZVXuUrzuCCcz/gbDK0pfKk= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= @@ -453,6 +461,8 @@ github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8 github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/valyala/fastjson v1.6.10 h1:/yjJg8jaVQdYR3arGxPE2X5z89xrlhS0eGXdv+ADTh4= +github.com/valyala/fastjson v1.6.10/go.mod h1:e6FubmQouUNP73jtMLmcbxS6ydWIpOfhz34TSfO3JaE= github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/weppos/publicsuffix-go v0.50.3 h1:eT5dcjHQcVDNc0igpFEsGHKIip30feuB2zuuI9eJxiE= diff --git a/internal/controller/oidc_controller.go b/internal/controller/oidc_controller.go index 969c5e8e..aaaf5755 100644 --- a/internal/controller/oidc_controller.go +++ b/internal/controller/oidc_controller.go @@ -117,15 +117,36 @@ func (controller *OIDCController) authorize(c *gin.Context) { var req service.AuthorizeRequest - err := c.ShouldBindWith(&req, binding.Query) + reqQueries := c.Request.URL.Query() - if err != nil { - controller.authorizeError(c, authorizeErrorParams{ - err: err, - reason: "Failed to bind JSON", - reasonPublic: "The client provided an invalid authorization request", - }) - return + if reqQueries.Get("request") != "" { + requestObject, err := controller.oidc.DecodeAuthorizeJWT(reqQueries.Get("request")) + + if err != nil { + controller.authorizeError(c, authorizeErrorParams{ + err: err, + reason: "Failed to decode request object", + reasonPublic: "The client provided an invalid request object", + }) + return + } + + req = *requestObject + } else { + var queryReq service.AuthorizeRequest + + err := c.ShouldBindWith(&queryReq, binding.Query) + + if err != nil { + controller.authorizeError(c, authorizeErrorParams{ + err: err, + reason: "Failed to bind query parameters", + reasonPublic: "The client provided invalid query parameters", + }) + return + } + + req = queryReq } client, ok := controller.oidc.GetClient(req.ClientID) @@ -139,9 +160,7 @@ func (controller *OIDCController) authorize(c *gin.Context) { return } - // TODO: handle request= parameter with JWTs - - err = controller.oidc.ValidateAuthorizeParams(req) + err := controller.oidc.ValidateAuthorizeParams(req) if err != nil { controller.log.App.Warn().Err(err).Msg("Failed to validate authorize params") diff --git a/internal/service/oidc_service.go b/internal/service/oidc_service.go index a1a0fad0..486cd810 100644 --- a/internal/service/oidc_service.go +++ b/internal/service/oidc_service.go @@ -20,6 +20,7 @@ import ( "slices" "github.com/go-jose/go-jose/v4" + "github.com/golang-jwt/jwt/v5" "github.com/steveiliop56/ding" "github.com/tinyauthapp/tinyauth/internal/model" "github.com/tinyauthapp/tinyauth/internal/repository" @@ -106,14 +107,15 @@ type TokenResponse struct { } type AuthorizeRequest struct { - Scope string `form:"scope" binding:"required"` - ResponseType string `form:"response_type" binding:"required"` - ClientID string `form:"client_id" binding:"required"` - RedirectURI string `form:"redirect_uri" binding:"required"` - State string `form:"state"` - Nonce string `form:"nonce"` - CodeChallenge string `form:"code_challenge"` - CodeChallengeMethod string `form:"code_challenge_method"` + jwt.Claims + Scope string `form:"scope" binding:"required" json:"scope"` + ResponseType string `form:"response_type" binding:"required" json:"response_type"` + ClientID string `form:"client_id" binding:"required" json:"client_id"` + RedirectURI string `form:"redirect_uri" binding:"required" json:"redirect_uri"` + State string `form:"state" json:"state"` + Nonce string `form:"nonce" json:"nonce"` + CodeChallenge string `form:"code_challenge" json:"code_challenge"` + CodeChallengeMethod string `form:"code_challenge_method" json:"code_challenge_method"` } type AuthorizeCodeEntry struct { @@ -883,3 +885,22 @@ func (service *OIDCService) GetAuthorizeRequestByTicket(ticket string) (*Authori func (service *OIDCService) DeleteAuthorizeRequestTicket(ticket string) { service.caches.authorize.Delete(ticket) } + +// TODO: support signed request objects in the future +func (service *OIDCService) DecodeAuthorizeJWT(tokenString string) (*AuthorizeRequest, error) { + var req AuthorizeRequest + + token, _, err := jwt.NewParser().ParseUnverified(tokenString, &req) + + if err != nil { + return nil, fmt.Errorf("failed to parse authorize request jwt: %w", err) + } + + claims, ok := token.Claims.(*AuthorizeRequest) + + if !ok { + return nil, errors.New("failed to parse claims from authorize request jwt") + } + + return claims, nil +}