diff --git a/README.md b/README.md index 90b7497f..34d46743 100644 --- a/README.md +++ b/README.md @@ -177,6 +177,7 @@ OIDC Provider: --providers.oidc.client-id= Client ID [$PROVIDERS_OIDC_CLIENT_ID] --providers.oidc.client-secret= Client Secret [$PROVIDERS_OIDC_CLIENT_SECRET] --providers.oidc.resource= Optional resource indicator [$PROVIDERS_OIDC_RESOURCE] + --providers.oidc.pkce_required= Optional pkce required indicator [$PROVIDERS_OIDC_PKCE_REQUIRED] Generic OAuth2 Provider: --providers.generic-oauth.auth-url= Auth/Login URL [$PROVIDERS_GENERIC_OAUTH_AUTH_URL] diff --git a/go.mod b/go.mod index f81f9405..dc0b83dc 100644 --- a/go.mod +++ b/go.mod @@ -17,14 +17,13 @@ require ( require ( github.com/containous/alice v0.0.0-20181107144136-d83ebdd94cbd // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/golang/protobuf v1.5.4 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gravitational/trace v1.4.0 // indirect - github.com/jonboulle/clockwork v0.4.0 // indirect github.com/miekg/dns v1.1.59 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pquerna/cachecontrol v0.2.0 // indirect + github.com/stretchr/objx v0.5.1 // indirect github.com/traefik/paerser v0.2.0 // indirect github.com/vulcand/predicate v1.2.0 // indirect golang.org/x/crypto v0.23.0 // indirect @@ -32,11 +31,8 @@ require ( golang.org/x/net v0.25.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.20.0 // indirect - golang.org/x/term v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect golang.org/x/tools v0.21.0 // indirect - google.golang.org/appengine v1.6.8 // indirect - google.golang.org/protobuf v1.34.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 890a978a..66d3809e 100644 --- a/go.sum +++ b/go.sum @@ -611,6 +611,7 @@ github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2 github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= @@ -642,7 +643,6 @@ github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjs github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -666,8 +666,8 @@ github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6Ni github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-acme/lego/v4 v4.13.2 h1:liIHWM9Wr3bmQ5s8UukfPhC4HOOaue9jRkUyrd8Dk7Y= github.com/go-acme/lego/v4 v4.16.1 h1:JxZ93s4KG0jL27rZ30UsIgxap6VGzKuREsSkkyzeoCQ= +github.com/go-acme/lego/v4 v4.16.1/go.mod h1:AVvwdPned/IWpD/ihHhMsKnveF7HHYAz/CmtXi7OZoE= github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= @@ -676,8 +676,10 @@ github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmn github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo= +github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U= +github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= github.com/go-kit/kit v0.10.1-0.20200915143503-439c4d2ed3ea h1:CnEQOUv4ilElSwFB9g/lVmz206oLE4aNZDYngIY1Gvg= +github.com/go-kit/kit v0.10.1-0.20200915143503-439c4d2ed3ea/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= @@ -716,10 +718,7 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -739,9 +738,9 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -783,7 +782,6 @@ github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57Q github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf h1:C1GPyPJrOlJlIrcaBBiBpDsqZena2Ks8spa5xZqr1XQ= github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf/go.mod h1:zXqxTI6jXDdKnlf8s+nT+3c8LrwUEy3yNpO4XJL90lA= github.com/gravitational/trace v1.4.0 h1:TtTeMElVwMX21Udb1nmK2tpWYAAMJoyjevzKOaxIFZQ= github.com/gravitational/trace v1.4.0/go.mod h1:g79NZzwCjWS/VVubYowaFAQsTjVTohGi0hFbIWSyGoY= @@ -795,10 +793,7 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= -github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= -github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= @@ -812,8 +807,11 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= @@ -821,8 +819,6 @@ github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WV github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= -github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs= github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= @@ -838,7 +834,6 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -851,6 +846,7 @@ github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qq github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= @@ -864,6 +860,8 @@ github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcD github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0= +github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= @@ -872,6 +870,7 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= @@ -879,8 +878,6 @@ github.com/thomseddon/go-flags v1.4.1-0.20190507184247-a3629c504486 h1:hk17f4niA github.com/thomseddon/go-flags v1.4.1-0.20190507184247-a3629c504486/go.mod h1:NK9eZpNBmSKVxvyB/MExg6jW0Bo9hQyAuCP+b8MJFow= github.com/traefik/paerser v0.2.0 h1:zqCLGSXoNlcBd+mzqSCLjon/I6phqIjeJL2xFB2ysgQ= github.com/traefik/paerser v0.2.0/go.mod h1:afzaVcgF8A+MpTnPG4wBr4whjanCSYA6vK5RwaYVtRc= -github.com/traefik/traefik/v2 v2.10.4 h1:0fIQTVHwciZ7lS1FsMFbkwgt2mlz9N3X1nLv6vl15bg= -github.com/traefik/traefik/v2 v2.10.4/go.mod h1:ovx/gjYqjcg81kwW8h8dfQ8xqPs1TmKayOqk2CJ7o1I= github.com/traefik/traefik/v2 v2.11.2 h1:BuvZD0uraRgM9pkx1yI/sRO0yB/Ov4OCuqlywvsE5+Q= github.com/traefik/traefik/v2 v2.11.2/go.mod h1:xWigO+RC0cQt24GqWCTeBeFiG6XqjCvpIn84v05wLtQ= github.com/vulcand/predicate v1.2.0 h1:uFsW1gcnnR7R+QTID+FVcs0sSYlIGntoGOTb3rQJt50= @@ -915,8 +912,6 @@ golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= @@ -977,8 +972,6 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= -golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1039,8 +1032,6 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= @@ -1073,8 +1064,6 @@ golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= -golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= -golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1093,7 +1082,6 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1176,8 +1164,6 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= @@ -1191,11 +1177,7 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= -golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1212,8 +1194,6 @@ golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= @@ -1285,8 +1265,6 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= -golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg= -golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1368,10 +1346,7 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -1563,11 +1538,6 @@ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/internal/cookie/cookie.go b/internal/cookie/cookie.go new file mode 100644 index 00000000..c0eb1c9e --- /dev/null +++ b/internal/cookie/cookie.go @@ -0,0 +1,88 @@ +package cookie + +import ( + "errors" + "net/http" + "time" +) + +// CookieStore interface defines methods for setting and getting cookies +type CookieStore interface { + SetCookie(name, value string, opts ...CookieOption) + GetCookie(name string) (string, error) + DeleteCookie(name string) +} + +// CookieOption is a function type that modifies cookie attributes +type CookieOption func(*http.Cookie) + +// WithMaxAge sets the max age of the cookie +func WithMaxAge(seconds int) CookieOption { + return func(c *http.Cookie) { + c.MaxAge = seconds + } +} + +// WithSameSite sets the SameSite attribute of the cookie +func WithSameSite(v http.SameSite) CookieOption { + return func(c *http.Cookie) { + c.SameSite = v + } +} + +// CookieStoreImpl is a concrete implementation of the CookieStore interface +type CookieStoreImpl struct { + writer http.ResponseWriter + request *http.Request + secure bool +} + +// NewCookieStore creates a new instance of CookieStoreImpl +func NewCookieStore(w http.ResponseWriter, r *http.Request, secure bool) *CookieStoreImpl { + return &CookieStoreImpl{ + writer: w, + request: r, + secure: secure, + } +} + +// SetCookie sets a cookie with the given name, value, and attributes +func (c *CookieStoreImpl) SetCookie(name, value string, opts ...CookieOption) { + cookie := &http.Cookie{ + Name: name, + Value: value, + Path: "/", + Secure: c.secure, + HttpOnly: true, + SameSite: http.SameSiteLaxMode, + } + + // Apply any provided options + for _, opt := range opts { + opt(cookie) + } + + http.SetCookie(c.writer, cookie) +} + +// DeleteCookie removes a cookie with the given name +func (c *CookieStoreImpl) DeleteCookie(name string) { + cookie := &http.Cookie{ + Name: name, + Value: "", + Path: "/", + MaxAge: -1, + Expires: time.Unix(0, 0), + } + + http.SetCookie(c.writer, cookie) +} + +// GetCookie retrieves the value of the cookie with the given name +func (c *CookieStoreImpl) GetCookie(name string) (string, error) { + cookie, err := c.request.Cookie(name) + if err != nil { + return "", errors.New("cookie not found") + } + return cookie.Value, nil +} diff --git a/internal/pkce/verifier.go b/internal/pkce/verifier.go new file mode 100644 index 00000000..37ca9354 --- /dev/null +++ b/internal/pkce/verifier.go @@ -0,0 +1,78 @@ +package pkce + +import ( + "crypto/rand" + "crypto/sha256" + "encoding/base64" + "fmt" + "io" +) + +// CodeVerifier represents a PKCE code verifier as defined in RFC 7636 +type CodeVerifier struct { + Value string +} + +const ( + // MinVerifierLength is the minimum allowed length for a code verifier + MinVerifierLength = 32 + // MaxVerifierLength is the maximum allowed length for a code verifier + MaxVerifierLength = 96 + // DefaultVerifierLength is the default length for generated code verifiers + DefaultVerifierLength = 64 +) + +// CreateCodeVerifier generates a new code verifier with default length +func CreateCodeVerifier() (*CodeVerifier, error) { + return CreateCodeVerifierWithLength(DefaultVerifierLength) +} + +// CreateCodeVerifierWithLength generates a new code verifier with specified length +func CreateCodeVerifierWithLength(length int) (*CodeVerifier, error) { + if length < MinVerifierLength || length > MaxVerifierLength { + return nil, fmt.Errorf("code verifier length must be between %d and %d", MinVerifierLength, MaxVerifierLength) + } + + secureRandomString, err := generateSecureRandomString(length) + if err != nil { + return nil, fmt.Errorf("failed to create code verifier: %w", err) + } + return &CodeVerifier{Value: secureRandomString}, nil +} + +// CreateCodeVerifierWithCode creates a code verifier from an existing code +func CreateCodeVerifierWithCode(code string) (*CodeVerifier, error) { + if len(code) < MinVerifierLength || len(code) > MaxVerifierLength { + return nil, fmt.Errorf("code verifier length must be between %d and %d", MinVerifierLength, MaxVerifierLength) + } + return &CodeVerifier{Value: code}, nil +} + +// String returns the string representation of the code verifier +func (v *CodeVerifier) String() string { + return v.Value +} + +// CodeChallengeS256 generates the S256 PKCE code challenge as defined in RFC 7636 +func (v *CodeVerifier) CodeChallengeS256() string { + h := sha256.New() + h.Write([]byte(v.Value)) + return encode(h.Sum(nil)) +} + +// GenerateNonce generates a cryptographically secure nonce +func GenerateNonce() (string, error) { + return generateSecureRandomString(DefaultVerifierLength) +} + +func generateSecureRandomString(length int) (string, error) { + bytes := make([]byte, length) + if _, err := io.ReadFull(rand.Reader, bytes); err != nil { + return "", fmt.Errorf("failed to generate secure random string: %w", err) + } + return base64.RawURLEncoding.EncodeToString(bytes), nil +} + +func encode(msg []byte) string { + return base64.RawURLEncoding.EncodeToString(msg) +} diff --git a/internal/provider/generic_oauth.go b/internal/provider/generic_oauth.go index a6bba510..e2263929 100644 --- a/internal/provider/generic_oauth.go +++ b/internal/provider/generic_oauth.go @@ -7,6 +7,7 @@ import ( "fmt" "net/http" + "github.com/thomseddon/traefik-forward-auth/internal/cookie" "golang.org/x/oauth2" ) @@ -52,12 +53,12 @@ func (o *GenericOAuth) Setup() error { } // GetLoginURL provides the login url for the given redirect uri and state -func (o *GenericOAuth) GetLoginURL(redirectURI, state string) string { - return o.OAuthGetLoginURL(redirectURI, state) +func (o *GenericOAuth) GetLoginURL(redirectURI, state string, _ cookie.CookieStore) (string, error) { + return o.OAuthGetLoginURL(redirectURI, state), nil } // ExchangeCode exchanges the given redirect uri and code for a token -func (o *GenericOAuth) ExchangeCode(redirectURI, code string) (string, error) { +func (o *GenericOAuth) ExchangeCode(redirectURI, code string, _ cookie.CookieStore) (string, error) { token, err := o.OAuthExchangeCode(redirectURI, code) if err != nil { return "", err diff --git a/internal/provider/generic_oauth_test.go b/internal/provider/generic_oauth_test.go index 1c2f2899..69688c7c 100644 --- a/internal/provider/generic_oauth_test.go +++ b/internal/provider/generic_oauth_test.go @@ -53,7 +53,8 @@ func TestGenericOAuthGetLoginURL(t *testing.T) { } // Check url - uri, err := url.Parse(p.GetLoginURL("http://example.com/_oauth", "state")) + loginURL, _ := p.GetLoginURL("http://example.com/_oauth", "state", nil) + uri, err := url.Parse(loginURL) assert.Nil(err) assert.Equal("https", uri.Scheme) assert.Equal("provider.com", uri.Host) @@ -104,7 +105,7 @@ func TestGenericOAuthExchangeCode(t *testing.T) { // AuthStyleInHeader is attempted p.Config.Endpoint.AuthStyle = oauth2.AuthStyleInParams - token, err := p.ExchangeCode("http://example.com/_oauth", "code") + token, err := p.ExchangeCode("http://example.com/_oauth", "code", nil) assert.Nil(err) assert.Equal("123456789", token) } diff --git a/internal/provider/google.go b/internal/provider/google.go index 1c0d6d10..6b4e3301 100644 --- a/internal/provider/google.go +++ b/internal/provider/google.go @@ -6,6 +6,8 @@ import ( "fmt" "net/http" "net/url" + + "github.com/thomseddon/traefik-forward-auth/internal/cookie" ) // Google provider @@ -53,7 +55,7 @@ func (g *Google) Setup() error { } // GetLoginURL provides the login url for the given redirect uri and state -func (g *Google) GetLoginURL(redirectURI, state string) string { +func (g *Google) GetLoginURL(redirectURI, state string, _ cookie.CookieStore) (string, error) { q := url.Values{} q.Set("client_id", g.ClientID) q.Set("response_type", "code") @@ -68,11 +70,11 @@ func (g *Google) GetLoginURL(redirectURI, state string) string { u = *g.LoginURL u.RawQuery = q.Encode() - return u.String() + return u.String(), nil } // ExchangeCode exchanges the given redirect uri and code for a token -func (g *Google) ExchangeCode(redirectURI, code string) (string, error) { +func (g *Google) ExchangeCode(redirectURI, code string, _ cookie.CookieStore) (string, error) { form := url.Values{} form.Set("client_id", g.ClientID) form.Set("client_secret", g.ClientSecret) diff --git a/internal/provider/google_test.go b/internal/provider/google_test.go index 64243fc0..4c4e7ef5 100644 --- a/internal/provider/google_test.go +++ b/internal/provider/google_test.go @@ -68,7 +68,8 @@ func TestGoogleGetLoginURL(t *testing.T) { } // Check url - uri, err := url.Parse(p.GetLoginURL("http://example.com/_oauth", "state")) + loginUrl, _ := p.GetLoginURL("http://example.com/_oauth", "state", nil) + uri, err := url.Parse(loginUrl) assert.Nil(err) assert.Equal("https", uri.Scheme) assert.Equal("google.com", uri.Host) @@ -116,7 +117,7 @@ func TestGoogleExchangeCode(t *testing.T) { }, } - token, err := p.ExchangeCode("http://example.com/_oauth", "code") + token, err := p.ExchangeCode("http://example.com/_oauth", "code", nil) assert.Nil(err) assert.Equal("123456789", token) } diff --git a/internal/provider/oidc.go b/internal/provider/oidc.go index 5e17a580..8fe3714d 100644 --- a/internal/provider/oidc.go +++ b/internal/provider/oidc.go @@ -3,16 +3,28 @@ package provider import ( "context" "errors" + "fmt" + "net/http" + "strings" "github.com/coreos/go-oidc" + "github.com/thomseddon/traefik-forward-auth/internal/cookie" + "github.com/thomseddon/traefik-forward-auth/internal/pkce" "golang.org/x/oauth2" ) +const ( + cookieNameNonce = "oidc-nonce" + cookieNamePkceCode = "oidc-pkce-code" + cookieMaxAge = 600 // 10 minutes +) + // OIDC provider type OIDC struct { IssuerURL string `long:"issuer-url" env:"ISSUER_URL" description:"Issuer URL"` ClientID string `long:"client-id" env:"CLIENT_ID" description:"Client ID"` ClientSecret string `long:"client-secret" env:"CLIENT_SECRET" description:"Client Secret" json:"-"` + PkceRequired bool `long:"pkce-required" env:"PKCE_REQUIRED" description:"Optional pkce required indicator"` OAuthProvider @@ -27,9 +39,9 @@ func (o *OIDC) Name() string { // Setup performs validation and setup func (o *OIDC) Setup() error { - // Check parms - if o.IssuerURL == "" || o.ClientID == "" || o.ClientSecret == "" { - return errors.New("providers.oidc.issuer-url, providers.oidc.client-id, providers.oidc.client-secret must be set") + // Check params + if err := o.checkParams(); err != nil { + return err } var err error @@ -60,13 +72,55 @@ func (o *OIDC) Setup() error { } // GetLoginURL provides the login url for the given redirect uri and state -func (o *OIDC) GetLoginURL(redirectURI, state string) string { - return o.OAuthGetLoginURL(redirectURI, state) +func (o *OIDC) GetLoginURL(redirectURI, state string, cookieStore cookie.CookieStore) (string, error) { + var opts []oauth2.AuthCodeOption + + // Generate and store nonce + nonce, err := pkce.GenerateNonce() + if err != nil { + return "", fmt.Errorf("failed to generate nonce: %w", err) + } + + cookieStore.SetCookie(cookieNameNonce, nonce, + cookie.WithMaxAge(cookieMaxAge), + cookie.WithSameSite(http.SameSiteStrictMode), + ) + + opts = append(opts, oauth2.SetAuthURLParam("nonce", nonce)) + + if o.PkceRequired { + pkceVerifier, err := pkce.CreateCodeVerifier() + if err != nil { + return "", fmt.Errorf("failed to create PKCE verifier: %w", err) + } + + opts = append(opts, + oauth2.SetAuthURLParam("code_challenge_method", "S256"), + oauth2.SetAuthURLParam("code_challenge", pkceVerifier.CodeChallengeS256()), + ) + + cookieStore.SetCookie(cookieNamePkceCode, pkceVerifier.String(), + cookie.WithMaxAge(cookieMaxAge), + cookie.WithSameSite(http.SameSiteStrictMode), + ) + } + return o.OAuthGetLoginURL(redirectURI, state, opts...), nil } // ExchangeCode exchanges the given redirect uri and code for a token -func (o *OIDC) ExchangeCode(redirectURI, code string) (string, error) { - token, err := o.OAuthExchangeCode(redirectURI, code) +func (o *OIDC) ExchangeCode(redirectURI, code string, cookieStore cookie.CookieStore) (string, error) { + var opts []oauth2.AuthCodeOption + + if o.PkceRequired { + pkceCode, err := cookieStore.GetCookie(cookieNamePkceCode) + if err != nil { + return "", err + } + cookieStore.DeleteCookie(cookieNamePkceCode) + opts = append(opts, oauth2.SetAuthURLParam("code_verifier", pkceCode)) + } + + token, err := o.OAuthExchangeCode(redirectURI, code, opts...) if err != nil { return "", err } @@ -77,6 +131,23 @@ func (o *OIDC) ExchangeCode(redirectURI, code string) (string, error) { return "", errors.New("Missing id_token") } + // Verify nonce + idToken, err := o.verifier.Verify(o.ctx, rawIDToken) + if err != nil { + return "", err + } + + nonce, err := cookieStore.GetCookie(cookieNameNonce) + if err != nil { + return "", errors.New("nonce not found") + } + + cookieStore.DeleteCookie(cookieNameNonce) + + if idToken.Nonce != nonce { + return "", errors.New("nonce verification failed") + } + return rawIDToken, nil } @@ -97,3 +168,25 @@ func (o *OIDC) GetUser(token string) (User, error) { return user, nil } + +func (o *OIDC) checkParams() error { + if o.IssuerURL == "" || o.ClientID == "" || (o.ClientSecret == "" && !o.PkceRequired) { + var emptyFields []string + + if o.IssuerURL == "" { + emptyFields = append(emptyFields, "providers.oidc.issuer-url") + } + + if o.ClientID == "" { + emptyFields = append(emptyFields, "providers.oidc.client-id") + } + + if o.ClientSecret == "" && !o.PkceRequired { + emptyFields = append(emptyFields, "providers.oidc.client-secret") + } + + return errors.New(strings.Join(emptyFields, ", ") + " must be set") + } + + return nil +} diff --git a/internal/provider/oidc_test.go b/internal/provider/oidc_test.go index d514d37c..7a7ae43d 100644 --- a/internal/provider/oidc_test.go +++ b/internal/provider/oidc_test.go @@ -13,9 +13,29 @@ import ( "time" "github.com/stretchr/testify/assert" - jose "gopkg.in/square/go-jose.v2" + "github.com/stretchr/testify/mock" + "github.com/thomseddon/traefik-forward-auth/internal/cookie" + "gopkg.in/square/go-jose.v2" ) +// MockCookieStore is a mock implementation of the CookieStore interface +type MockCookieStore struct { + mock.Mock +} + +func (m *MockCookieStore) SetCookie(name, value string, opts ...cookie.CookieOption) { + m.Called(name, value) +} + +func (m *MockCookieStore) GetCookie(name string) (string, error) { + args := m.Called(name) + return args.String(0), args.Error(1) +} + +func (m *MockCookieStore) DeleteCookie(name string) { + m.Called(name) +} + // Tests func TestOIDCName(t *testing.T) { @@ -29,7 +49,24 @@ func TestOIDCSetup(t *testing.T) { err := p.Setup() if assert.Error(err) { - assert.Equal("providers.oidc.issuer-url, providers.oidc.client-id, providers.oidc.client-secret must be set", err.Error()) + assert.Equal( + "providers.oidc.issuer-url, providers.oidc.client-id, providers.oidc.client-secret must be set", + err.Error(), + ) + } + + p.IssuerURL = "url" + + err = p.Setup() + if assert.Error(err) { + assert.Equal("providers.oidc.client-id, providers.oidc.client-secret must be set", err.Error()) + } + + p.ClientID = "id" + + err = p.Setup() + if assert.Error(err) { + assert.Equal("providers.oidc.client-secret must be set", err.Error()) } } @@ -39,8 +76,15 @@ func TestOIDCGetLoginURL(t *testing.T) { provider, server, serverURL, _ := setupOIDCTest(t, nil) defer server.Close() - // Check url - uri, err := url.Parse(provider.GetLoginURL("http://example.com/_oauth", "state")) + mockCookieStore := new(MockCookieStore) + + // Use mock.Anything for all parameters since we're passing cookie options now + mockCookieStore.On("SetCookie", cookieNameNonce, mock.Anything, mock.Anything).Return() + mockCookieStore.On("SetCookie", cookieNamePkceCode, mock.Anything, mock.Anything).Return() + + // Check URL without PKCE + loginUrl, _ := provider.GetLoginURL("http://example.com/_oauth", "state", mockCookieStore) + uri, err := url.Parse(loginUrl) assert.Nil(err) assert.Equal(serverURL.Scheme, uri.Scheme) assert.Equal(serverURL.Host, uri.Host) @@ -48,25 +92,55 @@ func TestOIDCGetLoginURL(t *testing.T) { // Check query string qs := uri.Query() + + // Capture the nonce from the cookie store + capturedNonce := qs.Get("nonce") expectedQs := url.Values{ "client_id": []string{"idtest"}, "redirect_uri": []string{"http://example.com/_oauth"}, "response_type": []string{"code"}, "scope": []string{"openid profile email"}, "state": []string{"state"}, + "nonce": []string{capturedNonce}, } assert.Equal(expectedQs, qs) - // Calling the method should not modify the underlying config - assert.Equal("", provider.Config.RedirectURL) + // Test with PkceRequired config option + provider.PkceRequired = true + + // Check URL with PKCE + loginUrl, _ = provider.GetLoginURL("http://example.com/_oauth", "state", mockCookieStore) + uri, err = url.Parse(loginUrl) + assert.Nil(err) + assert.Equal(serverURL.Scheme, uri.Scheme) + assert.Equal(serverURL.Host, uri.Host) + assert.Equal("/auth", uri.Path) + + // Check query string + qs = uri.Query() + + // Capture the nonce and code challenge from the query string + capturedNonce = qs.Get("nonce") + capturedCodeChallenge := qs.Get("code_challenge") + + expectedQs = url.Values{ + "client_id": []string{"idtest"}, + "code_challenge": []string{capturedCodeChallenge}, + "code_challenge_method": []string{"S256"}, + "redirect_uri": []string{"http://example.com/_oauth"}, + "response_type": []string{"code"}, + "scope": []string{"openid profile email"}, + "state": []string{"state"}, + "nonce": []string{capturedNonce}, + } + assert.Equal(expectedQs, qs) - // // Test with resource config option - // provider.Resource = "resourcetest" - // Check url - uri, err = url.Parse(provider.GetLoginURL("http://example.com/_oauth", "state")) + // Check URL with resource + loginUrl, _ = provider.GetLoginURL("http://example.com/_oauth", "state", mockCookieStore) + uri, err = url.Parse(loginUrl) assert.Nil(err) assert.Equal(serverURL.Scheme, uri.Scheme) assert.Equal(serverURL.Host, uri.Host) @@ -84,25 +158,51 @@ func TestOIDCGetLoginURL(t *testing.T) { } assert.Equal(expectedQs, qs) - // Calling the method should not modify the underlying config + // Ensure the underlying config is not modified assert.Equal("", provider.Config.RedirectURL) + + // Verify that SetCookie was called as expected + mockCookieStore.AssertCalled(t, "SetCookie", cookieNameNonce, mock.Anything, mock.Anything) + if provider.PkceRequired { + mockCookieStore.AssertCalled(t, "SetCookie", cookieNamePkceCode, mock.Anything, mock.Anything) + } } func TestOIDCExchangeCode(t *testing.T) { assert := assert.New(t) - provider, server, _, _ := setupOIDCTest(t, map[string]map[string]string{ - "token": { - "code": "code", - "grant_type": "authorization_code", - "redirect_uri": "http://example.com/_oauth", + mockCookieStore := new(MockCookieStore) + + // Simulate the behavior of the cookie store + mockCookieStore.On("GetCookie", cookieNamePkceCode).Return("mockPkceCode", nil) + mockCookieStore.On("GetCookie", cookieNameNonce).Return("mockNonce", nil) + mockCookieStore.On("DeleteCookie", cookieNamePkceCode).Return() + mockCookieStore.On("DeleteCookie", cookieNameNonce).Return() + + provider, server, _, _ := setupOIDCTest( + t, map[string]map[string]string{ + "token": { + "code": "code", + "grant_type": "authorization_code", + "redirect_uri": "http://example.com/_oauth", + "code_verifier": "mockPkceCode", // Add PKCE verifier + }, }, - }) + ) defer server.Close() - token, err := provider.ExchangeCode("http://example.com/_oauth", "code") - assert.Nil(err) - assert.Equal("id_123456789", token) + // Enable PKCE for the test + provider.PkceRequired = true + + token, err := provider.ExchangeCode("http://example.com/_oauth", "code", mockCookieStore) + assert.NoError(err) + assert.NotEmpty(token) + + // Verify cookie store interactions + mockCookieStore.AssertCalled(t, "GetCookie", cookieNamePkceCode) + mockCookieStore.AssertCalled(t, "GetCookie", cookieNameNonce) + mockCookieStore.AssertCalled(t, "DeleteCookie", cookieNamePkceCode) + mockCookieStore.AssertCalled(t, "DeleteCookie", cookieNameNonce) } func TestOIDCGetUser(t *testing.T) { @@ -112,14 +212,16 @@ func TestOIDCGetUser(t *testing.T) { defer server.Close() // Generate JWT - token := key.sign(t, []byte(`{ + token := key.sign( + t, []byte(`{ "iss": "`+serverURL.String()+`", "exp":`+strconv.FormatInt(time.Now().Add(time.Hour).Unix(), 10)+`, "aud": "idtest", "sub": "1", "email": "example@example.com", "email_verified": true - }`)) + }`), + ) // Get user user, err := provider.GetUser(token) @@ -189,12 +291,14 @@ func (s *OIDCServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/.well-known/openid-configuration" { // Open id config w.Header().Set("Content-Type", "application/json") - fmt.Fprint(w, `{ - "issuer":"`+s.url.String()+`", - "authorization_endpoint":"`+s.url.String()+`/auth", - "token_endpoint":"`+s.url.String()+`/token", - "jwks_uri":"`+s.url.String()+`/jwks" - }`) + fmt.Fprint( + w, `{ + "issuer":"`+s.url.String()+`", + "authorization_endpoint":"`+s.url.String()+`/auth", + "token_endpoint":"`+s.url.String()+`/token", + "jwks_uri":"`+s.url.String()+`/jwks" + }`, + ) } else if r.URL.Path == "/token" { // Token request // Check body @@ -204,11 +308,21 @@ func (s *OIDCServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } + // Create a signed JWT token for testing + idToken := s.key.sign(s.t, []byte(`{ + "iss": "`+s.url.String()+`", + "sub": "test_subject", + "aud": "idtest", + "exp": `+strconv.FormatInt(time.Now().Add(time.Hour).Unix(), 10)+`, + "iat": `+strconv.FormatInt(time.Now().Unix(), 10)+`, + "nonce": "mockNonce" + }`)) + w.Header().Set("Content-Type", "application/json") - fmt.Fprint(w, `{ - "access_token":"123456789", - "id_token":"id_123456789" - }`) + fmt.Fprintf(w, `{ + "access_token": "123456789", + "id_token": "%s" + }`, idToken) } else if r.URL.Path == "/jwks" { // Key request w.Header().Set("Content-Type", "application/json") @@ -257,10 +371,12 @@ func (k *rsaKey) publicJWK(t *testing.T) string { // sign creates a JWS using the private key from the provided payload. func (k *rsaKey) sign(t *testing.T, payload []byte) string { - signer, err := jose.NewSigner(jose.SigningKey{ - Algorithm: k.alg, - Key: k.key, - }, nil) + signer, err := jose.NewSigner( + jose.SigningKey{ + Algorithm: k.alg, + Key: k.key, + }, nil, + ) if err != nil { t.Fatal(err) } diff --git a/internal/provider/providers.go b/internal/provider/providers.go index ac863df3..d287d03d 100644 --- a/internal/provider/providers.go +++ b/internal/provider/providers.go @@ -4,6 +4,7 @@ import ( "context" // "net/url" + "github.com/thomseddon/traefik-forward-auth/internal/cookie" "golang.org/x/oauth2" ) @@ -17,8 +18,8 @@ type Providers struct { // Provider is used to authenticate users type Provider interface { Name() string - GetLoginURL(redirectURI, state string) string - ExchangeCode(redirectURI, code string) (string, error) + GetLoginURL(redirectURI, state string, cookieStore cookie.CookieStore) (string, error) + ExchangeCode(redirectURI, code string, cookieStore cookie.CookieStore) (string, error) GetUser(token string) (User, error) Setup() error } @@ -49,18 +50,18 @@ func (p *OAuthProvider) ConfigCopy(redirectURI string) oauth2.Config { } // OAuthGetLoginURL provides a base "GetLoginURL" for proiders using OAauth2 -func (p *OAuthProvider) OAuthGetLoginURL(redirectURI, state string) string { +func (p *OAuthProvider) OAuthGetLoginURL(redirectURI, state string, opts ...oauth2.AuthCodeOption) string { config := p.ConfigCopy(redirectURI) if p.Resource != "" { return config.AuthCodeURL(state, oauth2.SetAuthURLParam("resource", p.Resource)) } - return config.AuthCodeURL(state) + return config.AuthCodeURL(state, opts...) } // OAuthExchangeCode provides a base "ExchangeCode" for proiders using OAauth2 -func (p *OAuthProvider) OAuthExchangeCode(redirectURI, code string) (*oauth2.Token, error) { +func (p *OAuthProvider) OAuthExchangeCode(redirectURI, code string, opts ...oauth2.AuthCodeOption) (*oauth2.Token, error) { config := p.ConfigCopy(redirectURI) - return config.Exchange(p.ctx, code) + return config.Exchange(p.ctx, code, opts...) } diff --git a/internal/server.go b/internal/server.go index b8f37a09..eec85a7f 100644 --- a/internal/server.go +++ b/internal/server.go @@ -5,6 +5,7 @@ import ( "net/url" "github.com/sirupsen/logrus" + "github.com/thomseddon/traefik-forward-auth/internal/cookie" "github.com/thomseddon/traefik-forward-auth/internal/provider" muxhttp "github.com/traefik/traefik/v2/pkg/muxer/http" ) @@ -169,8 +170,9 @@ func (s *Server) AuthCallbackHandler() http.HandlerFunc { // Clear CSRF cookie http.SetCookie(w, ClearCSRFCookie(r, c)) + cookieStore := cookie.NewCookieStore(w, r, config.InsecureCookie) // Exchange code for token - token, err := p.ExchangeCode(redirectUri(r), r.URL.Query().Get("code")) + token, err := p.ExchangeCode(redirectUri(r), r.URL.Query().Get("code"), cookieStore) if err != nil { logger.WithField("error", err).Error("Code exchange failed with provider") http.Error(w, "Service unavailable", 503) @@ -234,8 +236,16 @@ func (s *Server) authRedirect(logger *logrus.Entry, w http.ResponseWriter, r *ht "\"insecure-cookie\" config option to permit cookies via http.") } + cookieStore := cookie.NewCookieStore(w, r, config.InsecureCookie) // Forward them on - loginURL := p.GetLoginURL(redirectUri(r), MakeState(r, p, nonce)) + loginURL, err := p.GetLoginURL(redirectUri(r), MakeState(r, p, nonce), cookieStore) + + if err != nil { + logger.WithField("error", err).Error("Get login url failed") + http.Error(w, "Service unavailable", 503) + return + } + http.Redirect(w, r, loginURL, http.StatusTemporaryRedirect) logger.WithFields(logrus.Fields{