diff --git a/go.mod b/go.mod index 701ef19f..a8a1508d 100644 --- a/go.mod +++ b/go.mod @@ -9,15 +9,15 @@ require ( github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5 github.com/alexedwards/argon2id v1.0.0 github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964 - github.com/aws/aws-sdk-go-v2 v1.25.0 - github.com/aws/aws-sdk-go-v2/config v1.27.0 - github.com/aws/aws-sdk-go-v2/credentials v1.17.0 - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.0 - github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.2 - github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.20.1 - github.com/aws/aws-sdk-go-v2/service/s3 v1.50.1 - github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.27.1 - github.com/aws/aws-sdk-go-v2/service/sts v1.27.0 + github.com/aws/aws-sdk-go-v2 v1.25.1 + github.com/aws/aws-sdk-go-v2/config v1.27.2 + github.com/aws/aws-sdk-go-v2/credentials v1.17.2 + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.1 + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.4 + github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.20.3 + github.com/aws/aws-sdk-go-v2/service/s3 v1.50.3 + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.27.3 + github.com/aws/aws-sdk-go-v2/service/sts v1.27.2 github.com/bmatcuk/doublestar/v4 v4.6.1 github.com/cockroachdb/cockroach-go/v2 v2.3.6 github.com/coreos/go-oidc/v3 v3.9.0 @@ -38,7 +38,7 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.5 github.com/jackc/pgx/v5 v5.5.3 github.com/jlaffaye/ftp v0.0.0-20201112195030-9aae4d151126 - github.com/klauspost/compress v1.17.6 + github.com/klauspost/compress v1.17.7 github.com/lestrrat-go/jwx/v2 v2.0.20 github.com/lithammer/shortuuid/v3 v3.0.7 github.com/mattn/go-sqlite3 v1.14.22 @@ -74,7 +74,7 @@ require ( golang.org/x/sys v0.17.0 golang.org/x/term v0.17.0 golang.org/x/time v0.5.0 - google.golang.org/api v0.165.0 + google.golang.org/api v0.166.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 ) @@ -85,18 +85,18 @@ require ( cloud.google.com/go/iam v1.1.6 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 // indirect github.com/ajg/form v1.5.1 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.0 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.0 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.0 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.1 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.1 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.0 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.0 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.0 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.0 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.19.0 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.22.0 // indirect - github.com/aws/smithy-go v1.20.0 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.19.2 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.22.2 // indirect + github.com/aws/smithy-go v1.20.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/boombuler/barcode v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect @@ -173,9 +173,9 @@ require ( golang.org/x/tools v0.18.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240213162025-012b6fc9bca9 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9 // indirect + google.golang.org/genproto v0.0.0-20240221002015-b0ce06bbee7c // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240221002015-b0ce06bbee7c // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c // indirect google.golang.org/grpc v1.61.1 // indirect google.golang.org/protobuf v1.32.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index abef5690..c29435f9 100644 --- a/go.sum +++ b/go.sum @@ -33,48 +33,48 @@ github.com/alexedwards/argon2id v1.0.0 h1:wJzDx66hqWX7siL/SRUmgz3F8YMrd/nfX/xHHc github.com/alexedwards/argon2id v1.0.0/go.mod h1:tYKkqIjzXvZdzPvADMWOEZ+l6+BD6CtBXMj5fnJppiw= github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964 h1:I9YN9WMo3SUh7p/4wKeNvD/IQla3U3SUa61U7ul+xM4= github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964/go.mod h1:eFiR01PwTcpbzXtdMces7zxg6utvFM5puiWHpWB8D/k= -github.com/aws/aws-sdk-go-v2 v1.25.0 h1:sv7+1JVJxOu/dD/sz/csHX7jFqmP001TIY7aytBWDSQ= -github.com/aws/aws-sdk-go-v2 v1.25.0/go.mod h1:G104G1Aho5WqF+SR3mDIobTABQzpYV0WxMsKxlMggOA= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.0 h1:2UO6/nT1lCZq1LqM67Oa4tdgP1CvL1sLSxvuD+VrOeE= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.0/go.mod h1:5zGj2eA85ClyedTDK+Whsu+w9yimnVIZvhvBKrDquM8= -github.com/aws/aws-sdk-go-v2/config v1.27.0 h1:J5sdGCAHuWKIXLeXiqr8II/adSvetkx0qdZwdbXXpb0= -github.com/aws/aws-sdk-go-v2/config v1.27.0/go.mod h1:cfh8v69nuSUohNFMbIISP2fhmblGmYEOKs5V53HiHnk= -github.com/aws/aws-sdk-go-v2/credentials v1.17.0 h1:lMW2x6sKBsiAJrpi1doOXqWFyEPoE886DTb1X0wb7So= -github.com/aws/aws-sdk-go-v2/credentials v1.17.0/go.mod h1:uT41FIH8cCIxOdUYIL0PYyHlL1NoneDuDSCwg5VE/5o= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.0 h1:xWCwjjvVz2ojYTP4kBKUuUh9ZrXfcAXpflhOUUeXg1k= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.0/go.mod h1:j3fACuqXg4oMTQOR2yY7m0NmJY0yBK4L4sLsRXq1Ins= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.2 h1:VEekE/fJWqAWYozxFQ07B+h8NdvTPAYhV13xIBenuO0= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.2/go.mod h1:8vozqAHmDNmoD4YbuDKIfpnLbByzngczL4My1RELLVo= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.0 h1:NPs/EqVO+ajwOoq56EfcGKa3L3ruWuazkIw1BqxwOPw= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.0/go.mod h1:D+duLy2ylgatV+yTlQ8JTuLfDD0BnFvnQRc+o6tbZ4M= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.0 h1:ks7KGMVUMoDzcxNWUlEdI+/lokMFD136EL6DWmUOV80= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.0/go.mod h1:hL6BWM/d/qz113fVitZjbXR0E+RCTU1+x+1Idyn5NgE= +github.com/aws/aws-sdk-go-v2 v1.25.1 h1:P7hU6A5qEdmajGwvae/zDkOq+ULLC9tQBTwqqiwFGpI= +github.com/aws/aws-sdk-go-v2 v1.25.1/go.mod h1:Evoc5AsmtveRt1komDwIsjHFyrP5tDuF1D1U+6z6pNo= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1 h1:gTK2uhtAPtFcdRRJilZPx8uJLL2J85xK11nKtWL0wfU= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1/go.mod h1:sxpLb+nZk7tIfCWChfd+h4QwHNUR57d8hA1cleTkjJo= +github.com/aws/aws-sdk-go-v2/config v1.27.2 h1:XnMKB9JRjfnxg9ZkUic4MiapnWJISWRo8HVM+7nx9qQ= +github.com/aws/aws-sdk-go-v2/config v1.27.2/go.mod h1:z/XIktFoVIKNEqX/811vx4eHetrC3tAkgJKL1ZY/KM4= +github.com/aws/aws-sdk-go-v2/credentials v1.17.2 h1:tCZXWtH0HiIEZ50NJ7/QEaXmuzEd36L+2JUiZkp2nsc= +github.com/aws/aws-sdk-go-v2/credentials v1.17.2/go.mod h1:7Zo+D6q4auSIo3p4EItuTKTk7J+RqjASISZqLvmUgpc= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.1 h1:lk1ZZFbdb24qpOwVC1AwYNrswUjAxeyey6kFBVANudQ= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.1/go.mod h1:/xJ6x1NehNGCX4tvGzzj2bq5TBOT/Yxq+qbL9Jpx2Vk= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.4 h1:yuhSpqtahkrC8kRCU5v4gEaTDy/ccTIPIkufIRF7YTk= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.4/go.mod h1:q3SxgP2WD9YRLCybtyse8EgO3vKKWVmxlTmBNeRXPyk= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.1 h1:evvi7FbTAoFxdP/mixmP7LIYzQWAmzBcwNB/es9XPNc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.1/go.mod h1:rH61DT6FDdikhPghymripNUCsf+uVF4Cnk4c4DBKH64= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.1 h1:RAnaIrbxPtlXNVI/OIlh1sidTQ3e1qM6LRjs7N0bE0I= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.1/go.mod h1:nbgAGkH5lk0RZRMh6A4K/oG6Xj11eC/1CyDow+DUAFI= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.0 h1:TkbRExyKSVHELwG9gz2+gql37jjec2R5vus9faTomwE= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.0/go.mod h1:T3/9xMKudHhnj8it5EqIrhvv11tVZqWYkKcot+BFStc= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.0 h1:a33HuFlO0KsveiP90IUJh8Xr/cx9US2PqkSroaLc+o8= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.0/go.mod h1:SxIkWpByiGbhbHYTo9CMTUnx2G4p4ZQMrDPcRRy//1c= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.0 h1:UiSyK6ent6OKpkMJN3+k5HZ4sk4UfchEaaW5wv7SblQ= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.0/go.mod h1:l7kzl8n8DXoRyFz5cIMG70HnPauWa649TUhgw8Rq6lo= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.0 h1:SHN/umDLTmFTmYfI+gkanz6da3vK8Kvj/5wkqnTHbuA= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.0/go.mod h1:l8gPU5RYGOFHJqWEpPMoRTP0VoaWQSkJdKo+hwWnnDA= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.0 h1:l5puwOHr7IxECuPMIuZG7UKOzAnF24v6t4l+Z5Moay4= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.0/go.mod h1:Oov79flWa/n7Ni+lQC3z+VM7PoRM47omRqbJU9B5Y7E= -github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.20.1 h1:eHNChn4Sp+g1hdz4rkx96n1l/LpJEQLDuFB0V+fA/yg= -github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.20.1/go.mod h1:9ev55pJx9xNX3UAOKzZmbmaTbwwuLTCemOJPsd7rUz8= -github.com/aws/aws-sdk-go-v2/service/s3 v1.50.1 h1:bjpWJEXch7moIt3PX2r5XpGROsletl7enqG1Q3Te1Dc= -github.com/aws/aws-sdk-go-v2/service/s3 v1.50.1/go.mod h1:1o/W6JFUuREj2ExoQ21vHJgO7wakvjhol91M9eknFgs= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.27.1 h1:ss/HbHbONu0uscM549++4YanT6MnjNN0BGhE5pZRfG4= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.27.1/go.mod h1:JsJDZFHwLGZu6dxhV9EV1gJrMnCeE4GEXubSZA59xdA= -github.com/aws/aws-sdk-go-v2/service/sso v1.19.0 h1:u6OkVDxtBPnxPkZ9/63ynEe+8kHbtS5IfaC4PzVxzWM= -github.com/aws/aws-sdk-go-v2/service/sso v1.19.0/go.mod h1:YqbU3RS/pkDVu+v+Nwxvn0i1WB0HkNWEePWbmODEbbs= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.22.0 h1:6DL0qu5+315wbsAEEmzK+P9leRwNbkp+lGjPC+CEvb8= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.22.0/go.mod h1:olUAyg+FaoFaL/zFaeQQONjOZ9HXoxgvI/c7mQTYz7M= -github.com/aws/aws-sdk-go-v2/service/sts v1.27.0 h1:cjTRjh700H36MQ8M0LnDn33W3JmwC77mdxIIyPWCdpM= -github.com/aws/aws-sdk-go-v2/service/sts v1.27.0/go.mod h1:nXfOBMWPokIbOY+Gi7a1psWMSvskUCemZzI+SMB7Akc= -github.com/aws/smithy-go v1.20.0 h1:6+kZsCXZwKxZS9RfISnPc4EXlHoyAkm2hPuM8X2BrrQ= -github.com/aws/smithy-go v1.20.0/go.mod h1:uo5RKksAl4PzhqaAbjd4rLgFoq5koTsQKYuGe7dklGc= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.1 h1:rtYJd3w6IWCTVS8vmMaiXjW198noh2PBm5CiXyJea9o= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.1/go.mod h1:zvXu+CTlib30LUy4LTNFc6HTZ/K6zCae5YIHTdX9wIo= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 h1:EyBZibRTVAs6ECHZOw5/wlylS9OcTzwyjeQMudmREjE= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1/go.mod h1:JKpmtYhhPs7D97NL/ltqz7yCkERFW5dOlHyVl66ZYF8= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.1 h1:5Wxh862HkXL9CbQ83BIkWKLIgQapGeuh5zG2G9OZtQk= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.1/go.mod h1:V7GLA01pNUxMCYSQsibdVrqUrNIYIT/9lCOyR8ExNvQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.1 h1:cVP8mng1RjDyI3JN/AXFCn5FHNlsBaBH0/MBtG1bg0o= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.1/go.mod h1:C8sQjoyAsdfjC7hpy4+S6B92hnFzx0d0UAyHicaOTIE= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.1 h1:OYmmIcyw19f7x0qLBLQ3XsrCZSSyLhxd9GXng5evsN4= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.1/go.mod h1:s5rqdn74Vdg10k61Pwf4ZHEApOSD6CKRe6qpeHDq32I= +github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.20.3 h1:uOHZ8HCjUHrRUi+sezA1yCeJVwa4Yy91tZDrWn1sT8w= +github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.20.3/go.mod h1:TzgisXFoXgCssgP11SzC6KrvcyCErz5c3w++m3xFOfo= +github.com/aws/aws-sdk-go-v2/service/s3 v1.50.3 h1:Cv/HH7sLzEdJMYQi4MCNHxZeyubQNOOIdVc0VU0lo3Q= +github.com/aws/aws-sdk-go-v2/service/s3 v1.50.3/go.mod h1:lTW7O4iMAnO2o7H3XJTvqaWFZCH6zIPs+eP7RdG/yp0= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.27.3 h1:LP38dk6XSNKyWAr3ZNEVECBPjEnoP+/SGvOfX0tRy+U= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.27.3/go.mod h1:RA3ERghFSivbTf0Sbsxv/grUuLMcyAjm0F/PylJMmEs= +github.com/aws/aws-sdk-go-v2/service/sso v1.19.2 h1:pnj8llQoBAHD4UmbM8UM5GdfycFJKMhgPSeaOyRaZ34= +github.com/aws/aws-sdk-go-v2/service/sso v1.19.2/go.mod h1:x6/tCd1o/AOKQR+iYnjrzhJxD+w0xRN34asGPaSV7ew= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.22.2 h1:L4yhKxW6HbTSQ08OsvPJuaspaLE40qMgprgXUNFUiMg= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.22.2/go.mod h1:lZB123q0SVQ3dfIbEOcGzhQHrwVBcHVReNS9tm20oU4= +github.com/aws/aws-sdk-go-v2/service/sts v1.27.2 h1:Dr+7r/p20XpN+1U5tVNZfA2bLq0kQ9IjVBM0iAyMMLg= +github.com/aws/aws-sdk-go-v2/service/sts v1.27.2/go.mod h1:ozhhG9/NB5c9jcmhGq6tX9dpp21LYdmRWRQVppASim4= +github.com/aws/smithy-go v1.20.1 h1:4SZlSlMr36UEqC7XOyRVb27XMeZubNcBNN+9IgEPIQw= +github.com/aws/smithy-go v1.20.1/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= @@ -252,8 +252,8 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI= -github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= +github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -529,8 +529,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -google.golang.org/api v0.165.0 h1:zd5d4JIIIaYYsfVy1HzoXYZ9rWCSBxxAglbczzo7Bgc= -google.golang.org/api v0.165.0/go.mod h1:2OatzO7ZDQsoS7IFf3rvsE17/TldiU3F/zxFHeqUB5o= +google.golang.org/api v0.166.0 h1:6m4NUwrZYhAaVIHZWxaKjw1L1vNAjtMwORmKRyEEo24= +google.golang.org/api v0.166.0/go.mod h1:4FcBc686KFi7QI/U51/2GKKevfZMpM17sCdibqe/bSA= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= @@ -538,12 +538,12 @@ google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= -google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= -google.golang.org/genproto/googleapis/api v0.0.0-20240213162025-012b6fc9bca9 h1:4++qSzdWBUy9/2x8L5KZgwZw+mjJZ2yDSCGMVM0YzRs= -google.golang.org/genproto/googleapis/api v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:PVreiBMirk8ypES6aw9d4p6iiBNSIfZEBqr3UGoAi2E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9 h1:hZB7eLIaYlW9qXRfCq/qDaPdbeY3757uARz5Vvfv+cY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:YUWgXUFRPfoYK1IHMuxH5K6nPEXSCzIMljnQ59lLRCk= +google.golang.org/genproto v0.0.0-20240221002015-b0ce06bbee7c h1:Zmyn5CV/jxzKnF+3d+xzbomACPwLQqVpLTpyXN5uTaQ= +google.golang.org/genproto v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= +google.golang.org/genproto/googleapis/api v0.0.0-20240221002015-b0ce06bbee7c h1:9g7erC9qu44ks7UK4gDNlnk4kOxZG707xKm4jVniy6o= +google.golang.org/genproto/googleapis/api v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c h1:NUsgEN92SQQqzfA+YtqYNqYmB3DMMYLlIwUZAQFVFbo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= diff --git a/internal/dataprovider/admin.go b/internal/dataprovider/admin.go index 2d994987..dac80ea4 100644 --- a/internal/dataprovider/admin.go +++ b/internal/dataprovider/admin.go @@ -199,6 +199,10 @@ type AdminFilters struct { AllowList []string `json:"allow_list,omitempty"` // API key auth allows to impersonate this administrator with an API key AllowAPIKeyAuth bool `json:"allow_api_key_auth,omitempty"` + // A password change is required at the next login + RequirePasswordChange bool `json:"require_password_change,omitempty"` + // Require two factor authentication + RequireTwoFactor bool `json:"require_two_factor"` // Time-based one time passwords configuration TOTPConfig AdminTOTPConfig `json:"totp_config,omitempty"` // Recovery codes to use if the user loses access to their second factor auth device. @@ -615,6 +619,8 @@ func (a *Admin) getACopy() Admin { filters := AdminFilters{} filters.AllowList = make([]string, len(a.Filters.AllowList)) filters.AllowAPIKeyAuth = a.Filters.AllowAPIKeyAuth + filters.RequirePasswordChange = a.Filters.RequirePasswordChange + filters.RequireTwoFactor = a.Filters.RequireTwoFactor filters.TOTPConfig.Enabled = a.Filters.TOTPConfig.Enabled filters.TOTPConfig.ConfigName = a.Filters.TOTPConfig.ConfigName filters.TOTPConfig.Secret = a.Filters.TOTPConfig.Secret.Clone() diff --git a/internal/httpd/api_admin.go b/internal/httpd/api_admin.go index b26e2512..b958478a 100644 --- a/internal/httpd/api_admin.go +++ b/internal/httpd/api_admin.go @@ -150,6 +150,8 @@ func updateAdmin(w http.ResponseWriter, r *http.Request) { sendAPIResponse(w, r, errors.New("you cannot add/change your role"), "", http.StatusBadRequest) return } + updatedAdmin.Filters.RequirePasswordChange = admin.Filters.RequirePasswordChange + updatedAdmin.Filters.RequireTwoFactor = admin.Filters.RequireTwoFactor } updatedAdmin.ID = admin.ID updatedAdmin.Username = admin.Username @@ -317,6 +319,7 @@ func doChangeAdminPassword(r *http.Request, currentPassword, newPassword, confir } admin.Password = newPassword + admin.Filters.RequirePasswordChange = false return dataprovider.UpdateAdmin(&admin, dataprovider.ActionExecutorSelf, util.GetIPFromRemoteAddress(r.RemoteAddr), claims.Role) } diff --git a/internal/httpd/api_mfa.go b/internal/httpd/api_mfa.go index fbd5a361..0b7e282e 100644 --- a/internal/httpd/api_mfa.go +++ b/internal/httpd/api_mfa.go @@ -122,23 +122,25 @@ func saveTOTPConfig(w http.ResponseWriter, r *http.Request) { code := getNewRecoveryCode() recoveryCodes = append(recoveryCodes, dataprovider.RecoveryCode{Secret: kms.NewPlainSecret(code)}) } + baseURL := webBaseClientPath if claims.hasUserAudience() { if err := saveUserTOTPConfig(claims.Username, r, recoveryCodes); err != nil { sendAPIResponse(w, r, err, "", getRespStatus(err)) return } - if claims.MustSetTwoFactorAuth { - // force logout - defer func() { - c := jwtTokenClaims{} - c.removeCookie(w, r, webBaseClientPath) - }() - } } else { if err := saveAdminTOTPConfig(claims.Username, r, recoveryCodes); err != nil { sendAPIResponse(w, r, err, "", getRespStatus(err)) return } + baseURL = webBasePath + } + if claims.MustSetTwoFactorAuth { + // force logout + defer func() { + c := jwtTokenClaims{} + c.removeCookie(w, r, baseURL) + }() } sendAPIResponse(w, r, nil, "TOTP configuration saved", http.StatusOK) @@ -303,6 +305,9 @@ func saveAdminTOTPConfig(username string, r *http.Request, recoveryCodes []datap if err != nil { return util.NewValidationError(fmt.Sprintf("unable to decode JSON body: %v", err)) } + if !admin.Filters.TOTPConfig.Enabled && admin.Filters.RequireTwoFactor { + return util.NewValidationError("two-factor authentication must be enabled") + } if admin.Filters.TOTPConfig.Enabled { if admin.CountUnusedRecoveryCodes() < 5 && admin.Filters.TOTPConfig.Enabled { admin.Filters.RecoveryCodes = recoveryCodes diff --git a/internal/httpd/api_utils.go b/internal/httpd/api_utils.go index d3e74f94..d198536b 100644 --- a/internal/httpd/api_utils.go +++ b/internal/httpd/api_utils.go @@ -830,6 +830,7 @@ func handleResetPassword(r *http.Request, code, newPassword, confirmPassword str return &admin, &user, util.NewValidationError("unable to associate the confirmation code with an existing admin") } admin.Password = newPassword + admin.Filters.RequirePasswordChange = false err = dataprovider.UpdateAdmin(&admin, dataprovider.ActionExecutorSelf, ipAddr, admin.Role) if err != nil { return &admin, &user, util.NewGenericError(fmt.Sprintf("unable to set the new password: %v", err)) diff --git a/internal/httpd/httpd_test.go b/internal/httpd/httpd_test.go index 894ef21a..500732bd 100644 --- a/internal/httpd/httpd_test.go +++ b/internal/httpd/httpd_test.go @@ -3501,6 +3501,194 @@ func TestTwoFactorRequirementsGroupLevel(t *testing.T) { assert.NoError(t, err) } +func TestAdminMustChangePasswordRequirement(t *testing.T) { + admin := getTestAdmin() + admin.Username = altAdminUsername + admin.Password = altAdminPassword + admin.Filters.RequirePasswordChange = true + admin, _, err := httpdtest.AddAdmin(admin, http.StatusCreated) + assert.NoError(t, err) + + token, _, err := httpdtest.GetToken(altAdminUsername, altAdminPassword) + assert.NoError(t, err) + httpdtest.SetJWTToken(token) + + _, _, err = httpdtest.GetUsers(0, 0, http.StatusForbidden) + assert.NoError(t, err) + _, _, err = httpdtest.GetStatus(http.StatusForbidden) + assert.NoError(t, err) + + _, err = httpdtest.ChangeAdminPassword(altAdminPassword, defaultTokenAuthPass, http.StatusOK) + assert.NoError(t, err) + + httpdtest.SetJWTToken("") + + admin, _, err = httpdtest.GetAdminByUsername(admin.Username, http.StatusOK) + assert.NoError(t, err) + assert.False(t, admin.Filters.RequirePasswordChange) + + // get a new token + token, _, err = httpdtest.GetToken(altAdminUsername, defaultTokenAuthPass) + assert.NoError(t, err) + httpdtest.SetJWTToken(token) + + _, _, err = httpdtest.GetUsers(0, 0, http.StatusOK) + assert.NoError(t, err) + + desc := xid.New().String() + admin.Filters.RequirePasswordChange = true + admin.Filters.RequireTwoFactor = true + admin.Description = desc + _, _, err = httpdtest.UpdateAdmin(admin, http.StatusOK) + if assert.Error(t, err) { + assert.ErrorContains(t, err, "require password change mismatch") + } + admin, _, err = httpdtest.GetAdminByUsername(admin.Username, http.StatusOK) + assert.NoError(t, err) + assert.False(t, admin.Filters.RequirePasswordChange) + assert.False(t, admin.Filters.RequireTwoFactor) + assert.Equal(t, desc, admin.Description) + + httpdtest.SetJWTToken("") + + admin.Filters.RequirePasswordChange = true + _, _, err = httpdtest.UpdateAdmin(admin, http.StatusOK) + assert.NoError(t, err) + // test the same for the WebAdmin + webToken, err := getJWTWebTokenFromTestServer(altAdminUsername, defaultTokenAuthPass) + assert.NoError(t, err) + req, err := http.NewRequest(http.MethodGet, webUsersPath, nil) + assert.NoError(t, err) + req.RequestURI = webUsersPath + setJWTCookieForReq(req, webToken) + rr := executeRequest(req) + checkResponseCode(t, http.StatusForbidden, rr) + + csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath) + assert.NoError(t, err) + form := make(url.Values) + form.Set(csrfFormToken, csrfToken) + form.Set("current_password", defaultTokenAuthPass) + form.Set("new_password1", altAdminPassword) + form.Set("new_password2", altAdminPassword) + req, err = http.NewRequest(http.MethodPost, webChangeAdminPwdPath, bytes.NewBuffer([]byte(form.Encode()))) + assert.NoError(t, err) + req.RemoteAddr = defaultRemoteAddr + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + setJWTCookieForReq(req, webToken) + rr = executeRequest(req) + checkResponseCode(t, http.StatusFound, rr) + assert.Equal(t, webLoginPath, rr.Header().Get("Location")) + + admin, _, err = httpdtest.GetAdminByUsername(admin.Username, http.StatusOK) + assert.NoError(t, err) + assert.False(t, admin.Filters.RequirePasswordChange) + + webToken, err = getJWTWebTokenFromTestServer(altAdminUsername, altAdminPassword) + assert.NoError(t, err) + req, err = http.NewRequest(http.MethodGet, webUsersPath, nil) + assert.NoError(t, err) + req.RequestURI = webUsersPath + setJWTCookieForReq(req, webToken) + rr = executeRequest(req) + checkResponseCode(t, http.StatusOK, rr) + + _, err = httpdtest.RemoveAdmin(admin, http.StatusOK) + assert.NoError(t, err) +} + +func TestAdminTwoFactorRequirements(t *testing.T) { + admin := getTestAdmin() + admin.Username = altAdminUsername + admin.Password = altAdminPassword + admin.Filters.RequireTwoFactor = true + admin, _, err := httpdtest.AddAdmin(admin, http.StatusCreated) + assert.NoError(t, err) + + token, err := getJWTAPITokenFromTestServer(altAdminUsername, altAdminPassword) + assert.NoError(t, err) + req, err := http.NewRequest(http.MethodGet, serverStatusPath, nil) + assert.NoError(t, err) + setBearerForReq(req, token) + rr := executeRequest(req) + checkResponseCode(t, http.StatusForbidden, rr) + assert.Contains(t, rr.Body.String(), "Two-factor authentication requirements not met") + + webToken, err := getJWTWebTokenFromTestServer(altAdminUsername, altAdminPassword) + assert.NoError(t, err) + req, err = http.NewRequest(http.MethodGet, webFoldersPath, nil) + assert.NoError(t, err) + req.RequestURI = webFoldersPath + setJWTCookieForReq(req, webToken) + rr = executeRequest(req) + checkResponseCode(t, http.StatusForbidden, rr) + assert.Contains(t, rr.Body.String(), util.I18nError2FARequiredGeneric) + // add TOTP config + configName, key, _, err := mfa.GenerateTOTPSecret(mfa.GetAvailableTOTPConfigNames()[0], altAdminUsername) + assert.NoError(t, err) + adminTOTPConfig := dataprovider.AdminTOTPConfig{ + Enabled: true, + ConfigName: configName, + Secret: kms.NewPlainSecret(key.Secret()), + } + asJSON, err := json.Marshal(adminTOTPConfig) + assert.NoError(t, err) + req, err = http.NewRequest(http.MethodPost, adminTOTPSavePath, bytes.NewBuffer(asJSON)) + assert.NoError(t, err) + setBearerForReq(req, token) + rr = executeRequest(req) + checkResponseCode(t, http.StatusOK, rr) + admin, _, err = httpdtest.GetAdminByUsername(altAdminUsername, http.StatusOK) + assert.NoError(t, err) + assert.True(t, admin.Filters.TOTPConfig.Enabled) + + passcode, err := generateTOTPPasscode(key.Secret()) + assert.NoError(t, err) + req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%v%v", httpBaseURL, tokenPath), nil) + assert.NoError(t, err) + req.Header.Set("X-SFTPGO-OTP", passcode) + req.SetBasicAuth(altAdminUsername, altAdminPassword) + resp, err := httpclient.GetHTTPClient().Do(req) + assert.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + responseHolder := make(map[string]any) + err = render.DecodeJSON(resp.Body, &responseHolder) + assert.NoError(t, err) + token = responseHolder["access_token"].(string) + assert.NotEmpty(t, token) + err = resp.Body.Close() + assert.NoError(t, err) + + req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%v%v", httpBaseURL, serverStatusPath), nil) + assert.NoError(t, err) + setBearerForReq(req, token) + resp, err = httpclient.GetHTTPClient().Do(req) + assert.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + err = resp.Body.Close() + assert.NoError(t, err) + // try to disable 2FA + disableReq := map[string]any{ + "enabled": false, + } + asJSON, err = json.Marshal(disableReq) + assert.NoError(t, err) + req, err = http.NewRequest(http.MethodPost, fmt.Sprintf("%v%v", httpBaseURL, adminTOTPSavePath), bytes.NewBuffer(asJSON)) + assert.NoError(t, err) + setBearerForReq(req, token) + resp, err = httpclient.GetHTTPClient().Do(req) + assert.NoError(t, err) + assert.Equal(t, http.StatusBadRequest, resp.StatusCode) + bodyResp, err := io.ReadAll(resp.Body) + assert.NoError(t, err) + assert.Contains(t, string(bodyResp), "two-factor authentication must be enabled") + err = resp.Body.Close() + assert.NoError(t, err) + + _, err = httpdtest.RemoveAdmin(admin, http.StatusOK) + assert.NoError(t, err) +} + func TestLoginUserAPITOTP(t *testing.T) { user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated) assert.NoError(t, err) @@ -19835,6 +20023,20 @@ func TestAdminUpdateSelfMock(t *testing.T) { assert.Contains(t, rr.Body.String(), util.I18nErrorAdminSelfDisable) form.Set("status", "1") + form.Set("require_two_factor", "1") + form.Set("require_password_change", "1") + req, _ = http.NewRequest(http.MethodPost, path.Join(webAdminPath, defaultTokenAuthUser), bytes.NewBuffer([]byte(form.Encode()))) + req.RemoteAddr = defaultRemoteAddr + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + setJWTCookieForReq(req, token) + rr = executeRequest(req) + checkResponseCode(t, http.StatusSeeOther, rr) + + admin, _, err = httpdtest.GetAdminByUsername(defaultTokenAuthUser, http.StatusOK) + assert.NoError(t, err) + assert.False(t, admin.Filters.RequirePasswordChange) + assert.False(t, admin.Filters.RequireTwoFactor) + form.Set("role", "my role") req, _ = http.NewRequest(http.MethodPost, path.Join(webAdminPath, defaultTokenAuthUser), bytes.NewBuffer([]byte(form.Encode()))) req.RemoteAddr = defaultRemoteAddr @@ -24375,6 +24577,7 @@ func TestAdminForgotPassword(t *testing.T) { a := getTestAdmin() a.Username = altAdminUsername a.Password = altAdminPassword + a.Filters.RequirePasswordChange = true admin, _, err := httpdtest.AddAdmin(a, http.StatusCreated) assert.NoError(t, err) @@ -24514,6 +24717,10 @@ func TestAdminForgotPassword(t *testing.T) { rr = executeRequest(req) checkResponseCode(t, http.StatusNotFound, rr) + admin, _, err = httpdtest.GetAdminByUsername(admin.Username, http.StatusOK) + assert.NoError(t, err) + assert.False(t, admin.Filters.RequirePasswordChange) + _, err = httpdtest.RemoveAdmin(admin, http.StatusOK) assert.NoError(t, err) } diff --git a/internal/httpd/internal_test.go b/internal/httpd/internal_test.go index 5be25ca1..1dfb8975 100644 --- a/internal/httpd/internal_test.go +++ b/internal/httpd/internal_test.go @@ -1554,6 +1554,14 @@ func TestJWTTokenValidation(t *testing.T) { fn.ServeHTTP(rr, req.WithContext(ctx)) assert.Equal(t, http.StatusBadRequest, rr.Code) + fn = server.checkAuthRequirements(r) + rr = httptest.NewRecorder() + req, _ = http.NewRequest(http.MethodPost, webGroupsPath, nil) + req.RequestURI = webGroupsPath + ctx = jwtauth.NewContext(req.Context(), token, errTest) + fn.ServeHTTP(rr, req.WithContext(ctx)) + assert.Equal(t, http.StatusBadRequest, rr.Code) + rr = httptest.NewRecorder() req, _ = http.NewRequest(http.MethodPost, userSharesPath, nil) req.RequestURI = userSharesPath diff --git a/internal/httpd/middleware.go b/internal/httpd/middleware.go index 523b6c59..8c98f02b 100644 --- a/internal/httpd/middleware.go +++ b/internal/httpd/middleware.go @@ -223,7 +223,11 @@ func (s *httpdServer) checkAuthRequirements(next http.Handler) http.Handler { _, claims, err := jwtauth.FromContext(r.Context()) if err != nil { if isWebRequest(r) { - s.renderClientBadRequestPage(w, r, err) + if isWebClientRequest(r) { + s.renderClientBadRequestPage(w, r, err) + } else { + s.renderBadRequestPage(w, r, err) + } } else { sendAPIResponse(w, r, err, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } @@ -234,16 +238,23 @@ func (s *httpdServer) checkAuthRequirements(next http.Handler) http.Handler { if tokenClaims.MustSetTwoFactorAuth || tokenClaims.MustChangePassword { var err error if tokenClaims.MustSetTwoFactorAuth { - protocols := strings.Join(tokenClaims.RequiredTwoFactorProtocols, ", ") - err = util.NewI18nError( - util.NewGenericError( - fmt.Sprintf("Two-factor authentication requirements not met, please configure two-factor authentication for the following protocols: %v", - protocols)), - util.I18nError2FARequired, - util.I18nErrorArgs(map[string]any{ - "val": protocols, - }), - ) + if len(tokenClaims.RequiredTwoFactorProtocols) > 0 { + protocols := strings.Join(tokenClaims.RequiredTwoFactorProtocols, ", ") + err = util.NewI18nError( + util.NewGenericError( + fmt.Sprintf("Two-factor authentication requirements not met, please configure two-factor authentication for the following protocols: %v", + protocols)), + util.I18nError2FARequired, + util.I18nErrorArgs(map[string]any{ + "val": protocols, + }), + ) + } else { + err = util.NewI18nError( + util.NewGenericError("Two-factor authentication requirements not met, please configure two-factor authentication"), + util.I18nError2FARequiredGeneric, + ) + } } else { err = util.NewI18nError( util.NewGenericError("Password change required. Please set a new password to continue to use your account"), @@ -251,7 +262,11 @@ func (s *httpdServer) checkAuthRequirements(next http.Handler) http.Handler { ) } if isWebRequest(r) { - s.renderClientForbiddenPage(w, r, err) + if isWebClientRequest(r) { + s.renderClientForbiddenPage(w, r, err) + } else { + s.renderForbiddenPage(w, r, err) + } } else { sendAPIResponse(w, r, err, "", http.StatusForbidden) } diff --git a/internal/httpd/server.go b/internal/httpd/server.go index 4a77b24b..4fddeb19 100644 --- a/internal/httpd/server.go +++ b/internal/httpd/server.go @@ -794,6 +794,8 @@ func (s *httpdServer) loginAdmin( Role: admin.Role, Signature: admin.GetSignature(), HideUserPageSections: admin.Filters.Preferences.HideUserPageSections, + MustSetTwoFactorAuth: admin.Filters.RequireTwoFactor && !admin.Filters.TOTPConfig.Enabled, + MustChangePassword: admin.Filters.RequirePasswordChange, } audience := tokenAudienceWebAdmin @@ -982,10 +984,12 @@ func (s *httpdServer) getToken(w http.ResponseWriter, r *http.Request) { func (s *httpdServer) generateAndSendToken(w http.ResponseWriter, r *http.Request, admin dataprovider.Admin, ip string) { c := jwtTokenClaims{ - Username: admin.Username, - Permissions: admin.Permissions, - Role: admin.Role, - Signature: admin.GetSignature(), + Username: admin.Username, + Permissions: admin.Permissions, + Role: admin.Role, + Signature: admin.GetSignature(), + MustSetTwoFactorAuth: admin.Filters.RequireTwoFactor && !admin.Filters.TOTPConfig.Enabled, + MustChangePassword: admin.Filters.RequirePasswordChange, } resp, err := c.createTokenResponse(s.tokenAuth, tokenAudienceAPI, ip) @@ -1318,7 +1322,7 @@ func (s *httpdServer) initializeRouter() { router.With(forbidAPIKeyAuthentication).Get(logoutPath, s.logout) router.With(forbidAPIKeyAuthentication).Get(adminProfilePath, getAdminProfile) - router.With(forbidAPIKeyAuthentication).Put(adminProfilePath, updateAdminProfile) + router.With(forbidAPIKeyAuthentication, s.checkAuthRequirements).Put(adminProfilePath, updateAdminProfile) router.With(forbidAPIKeyAuthentication).Put(adminPwdPath, changeAdminPassword) // admin TOTP APIs router.With(forbidAPIKeyAuthentication).Get(adminTOTPConfigsPath, getTOTPConfigs) @@ -1328,62 +1332,6 @@ func (s *httpdServer) initializeRouter() { router.With(forbidAPIKeyAuthentication).Get(admin2FARecoveryCodesPath, getRecoveryCodes) router.With(forbidAPIKeyAuthentication).Post(admin2FARecoveryCodesPath, generateRecoveryCodes) - router.With(s.checkPerm(dataprovider.PermAdminViewServerStatus)). - Get(serverStatusPath, func(w http.ResponseWriter, r *http.Request) { - r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) - render.JSON(w, r, getServicesStatus()) - }) - - router.With(s.checkPerm(dataprovider.PermAdminViewConnections)).Get(activeConnectionsPath, getActiveConnections) - router.With(s.checkPerm(dataprovider.PermAdminCloseConnections)). - Delete(activeConnectionsPath+"/{connectionID}", handleCloseConnection) - router.With(s.checkPerm(dataprovider.PermAdminQuotaScans)).Get(quotasBasePath+"/users/scans", getUsersQuotaScans) - router.With(s.checkPerm(dataprovider.PermAdminQuotaScans)).Post(quotasBasePath+"/users/{username}/scan", startUserQuotaScan) - router.With(s.checkPerm(dataprovider.PermAdminQuotaScans)).Get(quotasBasePath+"/folders/scans", getFoldersQuotaScans) - router.With(s.checkPerm(dataprovider.PermAdminQuotaScans)).Post(quotasBasePath+"/folders/{name}/scan", startFolderQuotaScan) - router.With(s.checkPerm(dataprovider.PermAdminViewUsers)).Get(userPath, getUsers) - router.With(s.checkPerm(dataprovider.PermAdminAddUsers)).Post(userPath, addUser) - router.With(s.checkPerm(dataprovider.PermAdminViewUsers)).Get(userPath+"/{username}", getUserByUsername) //nolint:goconst - router.With(s.checkPerm(dataprovider.PermAdminChangeUsers)).Put(userPath+"/{username}", updateUser) - router.With(s.checkPerm(dataprovider.PermAdminDeleteUsers)).Delete(userPath+"/{username}", deleteUser) - router.With(s.checkPerm(dataprovider.PermAdminChangeUsers)).Put(userPath+"/{username}/2fa/disable", disableUser2FA) - router.With(s.checkPerm(dataprovider.PermAdminManageFolders)).Get(folderPath, getFolders) - router.With(s.checkPerm(dataprovider.PermAdminManageFolders)).Get(folderPath+"/{name}", getFolderByName) //nolint:goconst - router.With(s.checkPerm(dataprovider.PermAdminManageFolders)).Post(folderPath, addFolder) - router.With(s.checkPerm(dataprovider.PermAdminManageFolders)).Put(folderPath+"/{name}", updateFolder) - router.With(s.checkPerm(dataprovider.PermAdminManageFolders)).Delete(folderPath+"/{name}", deleteFolder) - router.With(s.checkPerm(dataprovider.PermAdminManageGroups)).Get(groupPath, getGroups) - router.With(s.checkPerm(dataprovider.PermAdminManageGroups)).Get(groupPath+"/{name}", getGroupByName) - router.With(s.checkPerm(dataprovider.PermAdminManageGroups)).Post(groupPath, addGroup) - router.With(s.checkPerm(dataprovider.PermAdminManageGroups)).Put(groupPath+"/{name}", updateGroup) - router.With(s.checkPerm(dataprovider.PermAdminManageGroups)).Delete(groupPath+"/{name}", deleteGroup) - router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Get(dumpDataPath, dumpData) - router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Get(loadDataPath, loadData) - router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Post(loadDataPath, loadDataFromRequest) - router.With(s.checkPerm(dataprovider.PermAdminChangeUsers)).Put(quotasBasePath+"/users/{username}/usage", - updateUserQuotaUsage) - router.With(s.checkPerm(dataprovider.PermAdminChangeUsers)).Put(quotasBasePath+"/users/{username}/transfer-usage", - updateUserTransferQuotaUsage) - router.With(s.checkPerm(dataprovider.PermAdminChangeUsers)).Put(quotasBasePath+"/folders/{name}/usage", - updateFolderQuotaUsage) - router.With(s.checkPerm(dataprovider.PermAdminViewDefender)).Get(defenderHosts, getDefenderHosts) - router.With(s.checkPerm(dataprovider.PermAdminViewDefender)).Get(defenderHosts+"/{id}", getDefenderHostByID) - router.With(s.checkPerm(dataprovider.PermAdminManageDefender)).Delete(defenderHosts+"/{id}", deleteDefenderHostByID) - router.With(s.checkPerm(dataprovider.PermAdminManageAdmins)).Get(adminPath, getAdmins) - router.With(s.checkPerm(dataprovider.PermAdminManageAdmins)).Post(adminPath, addAdmin) - router.With(s.checkPerm(dataprovider.PermAdminManageAdmins)).Get(adminPath+"/{username}", getAdminByUsername) - router.With(s.checkPerm(dataprovider.PermAdminManageAdmins)).Put(adminPath+"/{username}", updateAdmin) - router.With(s.checkPerm(dataprovider.PermAdminManageAdmins)).Delete(adminPath+"/{username}", deleteAdmin) - router.With(s.checkPerm(dataprovider.PermAdminManageAdmins)).Put(adminPath+"/{username}/2fa/disable", disableAdmin2FA) - router.With(s.checkPerm(dataprovider.PermAdminRetentionChecks)).Get(retentionChecksPath, getRetentionChecks) - router.With(s.checkPerm(dataprovider.PermAdminRetentionChecks)).Post(retentionBasePath+"/{username}/check", - startRetentionCheck) - router.With(s.checkPerm(dataprovider.PermAdminViewEvents), compressor.Handler). - Get(fsEventsPath, searchFsEvents) - router.With(s.checkPerm(dataprovider.PermAdminViewEvents), compressor.Handler). - Get(providerEventsPath, searchProviderEvents) - router.With(s.checkPerm(dataprovider.PermAdminViewEvents), compressor.Handler). - Get(logEventsPath, searchLogEvents) router.With(forbidAPIKeyAuthentication, s.checkPerm(dataprovider.PermAdminManageAPIKeys)). Get(apiKeysPath, getAPIKeys) router.With(forbidAPIKeyAuthentication, s.checkPerm(dataprovider.PermAdminManageAPIKeys)). @@ -1394,27 +1342,88 @@ func (s *httpdServer) initializeRouter() { Put(apiKeysPath+"/{id}", updateAPIKey) router.With(forbidAPIKeyAuthentication, s.checkPerm(dataprovider.PermAdminManageAPIKeys)). Delete(apiKeysPath+"/{id}", deleteAPIKey) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Get(eventActionsPath, getEventActions) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Get(eventActionsPath+"/{name}", getEventActionByName) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Post(eventActionsPath, addEventAction) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Put(eventActionsPath+"/{name}", updateEventAction) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Delete(eventActionsPath+"/{name}", deleteEventAction) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Get(eventRulesPath, getEventRules) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Get(eventRulesPath+"/{name}", getEventRuleByName) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Post(eventRulesPath, addEventRule) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Put(eventRulesPath+"/{name}", updateEventRule) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Delete(eventRulesPath+"/{name}", deleteEventRule) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Post(eventRulesPath+"/run/{name}", runOnDemandRule) - router.With(s.checkPerm(dataprovider.PermAdminManageRoles)).Get(rolesPath, getRoles) - router.With(s.checkPerm(dataprovider.PermAdminManageRoles)).Post(rolesPath, addRole) - router.With(s.checkPerm(dataprovider.PermAdminManageRoles)).Get(rolesPath+"/{name}", getRoleByName) - router.With(s.checkPerm(dataprovider.PermAdminManageRoles)).Put(rolesPath+"/{name}", updateRole) - router.With(s.checkPerm(dataprovider.PermAdminManageRoles)).Delete(rolesPath+"/{name}", deleteRole) - router.With(s.checkPerm(dataprovider.PermAdminManageIPLists), compressor.Handler).Get(ipListsPath+"/{type}", getIPListEntries) //nolint:goconst - router.With(s.checkPerm(dataprovider.PermAdminManageIPLists)).Post(ipListsPath+"/{type}", addIPListEntry) - router.With(s.checkPerm(dataprovider.PermAdminManageIPLists)).Get(ipListsPath+"/{type}/{ipornet}", getIPListEntry) //nolint:goconst - router.With(s.checkPerm(dataprovider.PermAdminManageIPLists)).Put(ipListsPath+"/{type}/{ipornet}", updateIPListEntry) - router.With(s.checkPerm(dataprovider.PermAdminManageIPLists)).Delete(ipListsPath+"/{type}/{ipornet}", deleteIPListEntry) + + router.Group(func(router chi.Router) { + router.Use(s.checkAuthRequirements) + + router.With(s.checkPerm(dataprovider.PermAdminViewServerStatus)). + Get(serverStatusPath, func(w http.ResponseWriter, r *http.Request) { + r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) + render.JSON(w, r, getServicesStatus()) + }) + + router.With(s.checkPerm(dataprovider.PermAdminViewConnections)).Get(activeConnectionsPath, getActiveConnections) + router.With(s.checkPerm(dataprovider.PermAdminCloseConnections)). + Delete(activeConnectionsPath+"/{connectionID}", handleCloseConnection) + router.With(s.checkPerm(dataprovider.PermAdminQuotaScans)).Get(quotasBasePath+"/users/scans", getUsersQuotaScans) + router.With(s.checkPerm(dataprovider.PermAdminQuotaScans)).Post(quotasBasePath+"/users/{username}/scan", startUserQuotaScan) + router.With(s.checkPerm(dataprovider.PermAdminQuotaScans)).Get(quotasBasePath+"/folders/scans", getFoldersQuotaScans) + router.With(s.checkPerm(dataprovider.PermAdminQuotaScans)).Post(quotasBasePath+"/folders/{name}/scan", startFolderQuotaScan) + router.With(s.checkPerm(dataprovider.PermAdminViewUsers)).Get(userPath, getUsers) + router.With(s.checkPerm(dataprovider.PermAdminAddUsers)).Post(userPath, addUser) + router.With(s.checkPerm(dataprovider.PermAdminViewUsers)).Get(userPath+"/{username}", getUserByUsername) //nolint:goconst + router.With(s.checkPerm(dataprovider.PermAdminChangeUsers)).Put(userPath+"/{username}", updateUser) + router.With(s.checkPerm(dataprovider.PermAdminDeleteUsers)).Delete(userPath+"/{username}", deleteUser) + router.With(s.checkPerm(dataprovider.PermAdminChangeUsers)).Put(userPath+"/{username}/2fa/disable", disableUser2FA) + router.With(s.checkPerm(dataprovider.PermAdminManageFolders)).Get(folderPath, getFolders) + router.With(s.checkPerm(dataprovider.PermAdminManageFolders)).Get(folderPath+"/{name}", getFolderByName) //nolint:goconst + router.With(s.checkPerm(dataprovider.PermAdminManageFolders)).Post(folderPath, addFolder) + router.With(s.checkPerm(dataprovider.PermAdminManageFolders)).Put(folderPath+"/{name}", updateFolder) + router.With(s.checkPerm(dataprovider.PermAdminManageFolders)).Delete(folderPath+"/{name}", deleteFolder) + router.With(s.checkPerm(dataprovider.PermAdminManageGroups)).Get(groupPath, getGroups) + router.With(s.checkPerm(dataprovider.PermAdminManageGroups)).Get(groupPath+"/{name}", getGroupByName) + router.With(s.checkPerm(dataprovider.PermAdminManageGroups)).Post(groupPath, addGroup) + router.With(s.checkPerm(dataprovider.PermAdminManageGroups)).Put(groupPath+"/{name}", updateGroup) + router.With(s.checkPerm(dataprovider.PermAdminManageGroups)).Delete(groupPath+"/{name}", deleteGroup) + router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Get(dumpDataPath, dumpData) + router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Get(loadDataPath, loadData) + router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Post(loadDataPath, loadDataFromRequest) + router.With(s.checkPerm(dataprovider.PermAdminChangeUsers)).Put(quotasBasePath+"/users/{username}/usage", + updateUserQuotaUsage) + router.With(s.checkPerm(dataprovider.PermAdminChangeUsers)).Put(quotasBasePath+"/users/{username}/transfer-usage", + updateUserTransferQuotaUsage) + router.With(s.checkPerm(dataprovider.PermAdminChangeUsers)).Put(quotasBasePath+"/folders/{name}/usage", + updateFolderQuotaUsage) + router.With(s.checkPerm(dataprovider.PermAdminViewDefender)).Get(defenderHosts, getDefenderHosts) + router.With(s.checkPerm(dataprovider.PermAdminViewDefender)).Get(defenderHosts+"/{id}", getDefenderHostByID) + router.With(s.checkPerm(dataprovider.PermAdminManageDefender)).Delete(defenderHosts+"/{id}", deleteDefenderHostByID) + router.With(s.checkPerm(dataprovider.PermAdminManageAdmins)).Get(adminPath, getAdmins) + router.With(s.checkPerm(dataprovider.PermAdminManageAdmins)).Post(adminPath, addAdmin) + router.With(s.checkPerm(dataprovider.PermAdminManageAdmins)).Get(adminPath+"/{username}", getAdminByUsername) + router.With(s.checkPerm(dataprovider.PermAdminManageAdmins)).Put(adminPath+"/{username}", updateAdmin) + router.With(s.checkPerm(dataprovider.PermAdminManageAdmins)).Delete(adminPath+"/{username}", deleteAdmin) + router.With(s.checkPerm(dataprovider.PermAdminManageAdmins)).Put(adminPath+"/{username}/2fa/disable", disableAdmin2FA) + router.With(s.checkPerm(dataprovider.PermAdminRetentionChecks)).Get(retentionChecksPath, getRetentionChecks) + router.With(s.checkPerm(dataprovider.PermAdminRetentionChecks)).Post(retentionBasePath+"/{username}/check", + startRetentionCheck) + router.With(s.checkPerm(dataprovider.PermAdminViewEvents), compressor.Handler). + Get(fsEventsPath, searchFsEvents) + router.With(s.checkPerm(dataprovider.PermAdminViewEvents), compressor.Handler). + Get(providerEventsPath, searchProviderEvents) + router.With(s.checkPerm(dataprovider.PermAdminViewEvents), compressor.Handler). + Get(logEventsPath, searchLogEvents) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Get(eventActionsPath, getEventActions) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Get(eventActionsPath+"/{name}", getEventActionByName) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Post(eventActionsPath, addEventAction) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Put(eventActionsPath+"/{name}", updateEventAction) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Delete(eventActionsPath+"/{name}", deleteEventAction) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Get(eventRulesPath, getEventRules) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Get(eventRulesPath+"/{name}", getEventRuleByName) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Post(eventRulesPath, addEventRule) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Put(eventRulesPath+"/{name}", updateEventRule) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Delete(eventRulesPath+"/{name}", deleteEventRule) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Post(eventRulesPath+"/run/{name}", runOnDemandRule) + router.With(s.checkPerm(dataprovider.PermAdminManageRoles)).Get(rolesPath, getRoles) + router.With(s.checkPerm(dataprovider.PermAdminManageRoles)).Post(rolesPath, addRole) + router.With(s.checkPerm(dataprovider.PermAdminManageRoles)).Get(rolesPath+"/{name}", getRoleByName) + router.With(s.checkPerm(dataprovider.PermAdminManageRoles)).Put(rolesPath+"/{name}", updateRole) + router.With(s.checkPerm(dataprovider.PermAdminManageRoles)).Delete(rolesPath+"/{name}", deleteRole) + router.With(s.checkPerm(dataprovider.PermAdminManageIPLists), compressor.Handler).Get(ipListsPath+"/{type}", getIPListEntries) //nolint:goconst + router.With(s.checkPerm(dataprovider.PermAdminManageIPLists)).Post(ipListsPath+"/{type}", addIPListEntry) + router.With(s.checkPerm(dataprovider.PermAdminManageIPLists)).Get(ipListsPath+"/{type}/{ipornet}", getIPListEntry) //nolint:goconst + router.With(s.checkPerm(dataprovider.PermAdminManageIPLists)).Put(ipListsPath+"/{type}/{ipornet}", updateIPListEntry) + router.With(s.checkPerm(dataprovider.PermAdminManageIPLists)).Delete(ipListsPath+"/{type}/{ipornet}", deleteIPListEntry) + }) }) s.router.Get(userTokenPath, s.getUserToken) @@ -1675,8 +1684,9 @@ func (s *httpdServer) setupWebAdminRoutes() { router.Use(jwtAuthenticatorWebAdmin) router.Get(webLogoutPath, s.handleWebAdminLogout) - router.With(s.refreshCookie, s.requireBuiltinLogin).Get(webAdminProfilePath, s.handleWebAdminProfile) - router.With(s.requireBuiltinLogin).Post(webAdminProfilePath, s.handleWebAdminProfilePost) + router.With(s.refreshCookie, s.checkAuthRequirements, s.requireBuiltinLogin).Get( + webAdminProfilePath, s.handleWebAdminProfile) + router.With(s.checkAuthRequirements, s.requireBuiltinLogin).Post(webAdminProfilePath, s.handleWebAdminProfilePost) router.With(s.refreshCookie, s.requireBuiltinLogin).Get(webChangeAdminPwdPath, s.handleWebAdminChangePwd) router.With(s.requireBuiltinLogin).Post(webChangeAdminPwdPath, s.handleWebAdminChangePwdPost) @@ -1689,153 +1699,157 @@ func (s *httpdServer) setupWebAdminRoutes() { getRecoveryCodes) router.With(verifyCSRFHeader, s.requireBuiltinLogin).Post(webAdminRecoveryCodesPath, generateRecoveryCodes) - router.With(s.checkPerm(dataprovider.PermAdminViewUsers), s.refreshCookie). - Get(webUsersPath, s.handleGetWebUsers) - router.With(s.checkPerm(dataprovider.PermAdminViewUsers), compressor.Handler, s.refreshCookie). - Get(webUsersPath+jsonAPISuffix, getAllUsers) - router.With(s.checkPerm(dataprovider.PermAdminAddUsers), s.refreshCookie). - Get(webUserPath, s.handleWebAddUserGet) - router.With(s.checkPerm(dataprovider.PermAdminChangeUsers), s.refreshCookie). - Get(webUserPath+"/{username}", s.handleWebUpdateUserGet) - router.With(s.checkPerm(dataprovider.PermAdminAddUsers)).Post(webUserPath, s.handleWebAddUserPost) - router.With(s.checkPerm(dataprovider.PermAdminChangeUsers)).Post(webUserPath+"/{username}", - s.handleWebUpdateUserPost) - router.With(s.checkPerm(dataprovider.PermAdminManageGroups), s.refreshCookie). - Get(webGroupsPath, s.handleWebGetGroups) - router.With(s.checkPerm(dataprovider.PermAdminManageGroups), compressor.Handler, s.refreshCookie). - Get(webGroupsPath+jsonAPISuffix, getAllGroups) - router.With(s.checkPerm(dataprovider.PermAdminManageGroups), s.refreshCookie). - Get(webGroupPath, s.handleWebAddGroupGet) - router.With(s.checkPerm(dataprovider.PermAdminManageGroups)).Post(webGroupPath, s.handleWebAddGroupPost) - router.With(s.checkPerm(dataprovider.PermAdminManageGroups), s.refreshCookie). - Get(webGroupPath+"/{name}", s.handleWebUpdateGroupGet) - router.With(s.checkPerm(dataprovider.PermAdminManageGroups)).Post(webGroupPath+"/{name}", - s.handleWebUpdateGroupPost) - router.With(s.checkPerm(dataprovider.PermAdminManageGroups), verifyCSRFHeader). - Delete(webGroupPath+"/{name}", deleteGroup) - router.With(s.checkPerm(dataprovider.PermAdminViewConnections), s.refreshCookie). - Get(webConnectionsPath, s.handleWebGetConnections) - router.With(s.checkPerm(dataprovider.PermAdminViewConnections), s.refreshCookie). - Get(webConnectionsPath+jsonAPISuffix, getActiveConnections) - router.With(s.checkPerm(dataprovider.PermAdminManageFolders), s.refreshCookie). - Get(webFoldersPath, s.handleWebGetFolders) - router.With(s.checkPerm(dataprovider.PermAdminManageFolders), compressor.Handler, s.refreshCookie). - Get(webFoldersPath+jsonAPISuffix, getAllFolders) - router.With(s.checkPerm(dataprovider.PermAdminManageFolders), s.refreshCookie). - Get(webFolderPath, s.handleWebAddFolderGet) - router.With(s.checkPerm(dataprovider.PermAdminManageFolders)).Post(webFolderPath, s.handleWebAddFolderPost) - router.With(s.checkPerm(dataprovider.PermAdminViewServerStatus), s.refreshCookie). - Get(webStatusPath, s.handleWebGetStatus) - router.With(s.checkPerm(dataprovider.PermAdminManageAdmins), s.refreshCookie). - Get(webAdminsPath, s.handleGetWebAdmins) - router.With(s.checkPerm(dataprovider.PermAdminManageAdmins), compressor.Handler, s.refreshCookie). - Get(webAdminsPath+jsonAPISuffix, getAllAdmins) - router.With(s.checkPerm(dataprovider.PermAdminManageAdmins), s.refreshCookie). - Get(webAdminPath, s.handleWebAddAdminGet) - router.With(s.checkPerm(dataprovider.PermAdminManageAdmins), s.refreshCookie). - Get(webAdminPath+"/{username}", s.handleWebUpdateAdminGet) - router.With(s.checkPerm(dataprovider.PermAdminManageAdmins)).Post(webAdminPath, s.handleWebAddAdminPost) - router.With(s.checkPerm(dataprovider.PermAdminManageAdmins)).Post(webAdminPath+"/{username}", - s.handleWebUpdateAdminPost) - router.With(s.checkPerm(dataprovider.PermAdminManageAdmins), verifyCSRFHeader). - Delete(webAdminPath+"/{username}", deleteAdmin) - router.With(s.checkPerm(dataprovider.PermAdminCloseConnections), verifyCSRFHeader). - Delete(webConnectionsPath+"/{connectionID}", handleCloseConnection) - router.With(s.checkPerm(dataprovider.PermAdminManageFolders), s.refreshCookie). - Get(webFolderPath+"/{name}", s.handleWebUpdateFolderGet) - router.With(s.checkPerm(dataprovider.PermAdminManageFolders)).Post(webFolderPath+"/{name}", - s.handleWebUpdateFolderPost) - router.With(s.checkPerm(dataprovider.PermAdminManageFolders), verifyCSRFHeader). - Delete(webFolderPath+"/{name}", deleteFolder) - router.With(s.checkPerm(dataprovider.PermAdminQuotaScans), verifyCSRFHeader). - Post(webScanVFolderPath+"/{name}", startFolderQuotaScan) - router.With(s.checkPerm(dataprovider.PermAdminDeleteUsers), verifyCSRFHeader). - Delete(webUserPath+"/{username}", deleteUser) - router.With(s.checkPerm(dataprovider.PermAdminQuotaScans), verifyCSRFHeader). - Post(webQuotaScanPath+"/{username}", startUserQuotaScan) - router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Get(webMaintenancePath, s.handleWebMaintenance) - router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Get(webBackupPath, dumpData) - router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Post(webRestorePath, s.handleWebRestore) - router.With(s.checkPerm(dataprovider.PermAdminManageSystem), s.refreshCookie). - Get(webTemplateUser, s.handleWebTemplateUserGet) - router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Post(webTemplateUser, s.handleWebTemplateUserPost) - router.With(s.checkPerm(dataprovider.PermAdminManageSystem), s.refreshCookie). - Get(webTemplateFolder, s.handleWebTemplateFolderGet) - router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Post(webTemplateFolder, s.handleWebTemplateFolderPost) - router.With(s.checkPerm(dataprovider.PermAdminViewDefender)).Get(webDefenderPath, s.handleWebDefenderPage) - router.With(s.checkPerm(dataprovider.PermAdminViewDefender)).Get(webDefenderHostsPath, getDefenderHosts) - router.With(s.checkPerm(dataprovider.PermAdminManageDefender)).Delete(webDefenderHostsPath+"/{id}", - deleteDefenderHostByID) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), compressor.Handler, s.refreshCookie). - Get(webAdminEventActionsPath+jsonAPISuffix, getAllActions) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), s.refreshCookie). - Get(webAdminEventActionsPath, s.handleWebGetEventActions) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), s.refreshCookie). - Get(webAdminEventActionPath, s.handleWebAddEventActionGet) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Post(webAdminEventActionPath, - s.handleWebAddEventActionPost) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), s.refreshCookie). - Get(webAdminEventActionPath+"/{name}", s.handleWebUpdateEventActionGet) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Post(webAdminEventActionPath+"/{name}", - s.handleWebUpdateEventActionPost) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), verifyCSRFHeader). - Delete(webAdminEventActionPath+"/{name}", deleteEventAction) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), compressor.Handler, s.refreshCookie). - Get(webAdminEventRulesPath+jsonAPISuffix, getAllRules) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), s.refreshCookie). - Get(webAdminEventRulesPath, s.handleWebGetEventRules) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), s.refreshCookie). - Get(webAdminEventRulePath, s.handleWebAddEventRuleGet) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Post(webAdminEventRulePath, - s.handleWebAddEventRulePost) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), s.refreshCookie). - Get(webAdminEventRulePath+"/{name}", s.handleWebUpdateEventRuleGet) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Post(webAdminEventRulePath+"/{name}", - s.handleWebUpdateEventRulePost) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), verifyCSRFHeader). - Delete(webAdminEventRulePath+"/{name}", deleteEventRule) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), verifyCSRFHeader). - Post(webAdminEventRulePath+"/run/{name}", runOnDemandRule) - router.With(s.checkPerm(dataprovider.PermAdminManageRoles), s.refreshCookie). - Get(webAdminRolesPath, s.handleWebGetRoles) - router.With(s.checkPerm(dataprovider.PermAdminManageRoles), compressor.Handler, s.refreshCookie). - Get(webAdminRolesPath+jsonAPISuffix, getAllRoles) - router.With(s.checkPerm(dataprovider.PermAdminManageRoles), s.refreshCookie). - Get(webAdminRolePath, s.handleWebAddRoleGet) - router.With(s.checkPerm(dataprovider.PermAdminManageRoles)).Post(webAdminRolePath, s.handleWebAddRolePost) - router.With(s.checkPerm(dataprovider.PermAdminManageRoles), s.refreshCookie). - Get(webAdminRolePath+"/{name}", s.handleWebUpdateRoleGet) - router.With(s.checkPerm(dataprovider.PermAdminManageRoles)).Post(webAdminRolePath+"/{name}", - s.handleWebUpdateRolePost) - router.With(s.checkPerm(dataprovider.PermAdminManageRoles), verifyCSRFHeader). - Delete(webAdminRolePath+"/{name}", deleteRole) - router.With(s.checkPerm(dataprovider.PermAdminViewEvents), s.refreshCookie).Get(webEventsPath, - s.handleWebGetEvents) - router.With(s.checkPerm(dataprovider.PermAdminViewEvents), compressor.Handler, s.refreshCookie). - Get(webEventsFsSearchPath, searchFsEvents) - router.With(s.checkPerm(dataprovider.PermAdminViewEvents), compressor.Handler, s.refreshCookie). - Get(webEventsProviderSearchPath, searchProviderEvents) - router.With(s.checkPerm(dataprovider.PermAdminViewEvents), compressor.Handler, s.refreshCookie). - Get(webEventsLogSearchPath, searchLogEvents) - router.With(s.checkPerm(dataprovider.PermAdminManageIPLists)).Get(webIPListsPath, s.handleWebIPListsPage) - router.With(s.checkPerm(dataprovider.PermAdminManageIPLists), compressor.Handler, s.refreshCookie). - Get(webIPListsPath+"/{type}", getIPListEntries) - router.With(s.checkPerm(dataprovider.PermAdminManageIPLists), s.refreshCookie).Get(webIPListPath+"/{type}", - s.handleWebAddIPListEntryGet) - router.With(s.checkPerm(dataprovider.PermAdminManageIPLists)).Post(webIPListPath+"/{type}", - s.handleWebAddIPListEntryPost) - router.With(s.checkPerm(dataprovider.PermAdminManageIPLists), s.refreshCookie).Get(webIPListPath+"/{type}/{ipornet}", - s.handleWebUpdateIPListEntryGet) - router.With(s.checkPerm(dataprovider.PermAdminManageIPLists)).Post(webIPListPath+"/{type}/{ipornet}", - s.handleWebUpdateIPListEntryPost) - router.With(s.checkPerm(dataprovider.PermAdminManageIPLists), verifyCSRFHeader). - Delete(webIPListPath+"/{type}/{ipornet}", deleteIPListEntry) - router.With(s.checkPerm(dataprovider.PermAdminManageSystem), s.refreshCookie).Get(webConfigsPath, s.handleWebConfigs) - router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Post(webConfigsPath, s.handleWebConfigsPost) - router.With(s.checkPerm(dataprovider.PermAdminManageSystem), verifyCSRFHeader, s.refreshCookie). - Post(webConfigsPath+"/smtp/test", testSMTPConfig) - router.With(s.checkPerm(dataprovider.PermAdminManageSystem), verifyCSRFHeader, s.refreshCookie). - Post(webOAuth2TokenPath, handleSMTPOAuth2TokenRequestPost) + router.Group(func(router chi.Router) { + router.Use(s.checkAuthRequirements) + + router.With(s.checkPerm(dataprovider.PermAdminViewUsers), s.refreshCookie). + Get(webUsersPath, s.handleGetWebUsers) + router.With(s.checkPerm(dataprovider.PermAdminViewUsers), compressor.Handler, s.refreshCookie). + Get(webUsersPath+jsonAPISuffix, getAllUsers) + router.With(s.checkPerm(dataprovider.PermAdminAddUsers), s.refreshCookie). + Get(webUserPath, s.handleWebAddUserGet) + router.With(s.checkPerm(dataprovider.PermAdminChangeUsers), s.refreshCookie). + Get(webUserPath+"/{username}", s.handleWebUpdateUserGet) + router.With(s.checkPerm(dataprovider.PermAdminAddUsers)).Post(webUserPath, s.handleWebAddUserPost) + router.With(s.checkPerm(dataprovider.PermAdminChangeUsers)).Post(webUserPath+"/{username}", + s.handleWebUpdateUserPost) + router.With(s.checkPerm(dataprovider.PermAdminManageGroups), s.refreshCookie). + Get(webGroupsPath, s.handleWebGetGroups) + router.With(s.checkPerm(dataprovider.PermAdminManageGroups), compressor.Handler, s.refreshCookie). + Get(webGroupsPath+jsonAPISuffix, getAllGroups) + router.With(s.checkPerm(dataprovider.PermAdminManageGroups), s.refreshCookie). + Get(webGroupPath, s.handleWebAddGroupGet) + router.With(s.checkPerm(dataprovider.PermAdminManageGroups)).Post(webGroupPath, s.handleWebAddGroupPost) + router.With(s.checkPerm(dataprovider.PermAdminManageGroups), s.refreshCookie). + Get(webGroupPath+"/{name}", s.handleWebUpdateGroupGet) + router.With(s.checkPerm(dataprovider.PermAdminManageGroups)).Post(webGroupPath+"/{name}", + s.handleWebUpdateGroupPost) + router.With(s.checkPerm(dataprovider.PermAdminManageGroups), verifyCSRFHeader). + Delete(webGroupPath+"/{name}", deleteGroup) + router.With(s.checkPerm(dataprovider.PermAdminViewConnections), s.refreshCookie). + Get(webConnectionsPath, s.handleWebGetConnections) + router.With(s.checkPerm(dataprovider.PermAdminViewConnections), s.refreshCookie). + Get(webConnectionsPath+jsonAPISuffix, getActiveConnections) + router.With(s.checkPerm(dataprovider.PermAdminManageFolders), s.refreshCookie). + Get(webFoldersPath, s.handleWebGetFolders) + router.With(s.checkPerm(dataprovider.PermAdminManageFolders), compressor.Handler, s.refreshCookie). + Get(webFoldersPath+jsonAPISuffix, getAllFolders) + router.With(s.checkPerm(dataprovider.PermAdminManageFolders), s.refreshCookie). + Get(webFolderPath, s.handleWebAddFolderGet) + router.With(s.checkPerm(dataprovider.PermAdminManageFolders)).Post(webFolderPath, s.handleWebAddFolderPost) + router.With(s.checkPerm(dataprovider.PermAdminViewServerStatus), s.refreshCookie). + Get(webStatusPath, s.handleWebGetStatus) + router.With(s.checkPerm(dataprovider.PermAdminManageAdmins), s.refreshCookie). + Get(webAdminsPath, s.handleGetWebAdmins) + router.With(s.checkPerm(dataprovider.PermAdminManageAdmins), compressor.Handler, s.refreshCookie). + Get(webAdminsPath+jsonAPISuffix, getAllAdmins) + router.With(s.checkPerm(dataprovider.PermAdminManageAdmins), s.refreshCookie). + Get(webAdminPath, s.handleWebAddAdminGet) + router.With(s.checkPerm(dataprovider.PermAdminManageAdmins), s.refreshCookie). + Get(webAdminPath+"/{username}", s.handleWebUpdateAdminGet) + router.With(s.checkPerm(dataprovider.PermAdminManageAdmins)).Post(webAdminPath, s.handleWebAddAdminPost) + router.With(s.checkPerm(dataprovider.PermAdminManageAdmins)).Post(webAdminPath+"/{username}", + s.handleWebUpdateAdminPost) + router.With(s.checkPerm(dataprovider.PermAdminManageAdmins), verifyCSRFHeader). + Delete(webAdminPath+"/{username}", deleteAdmin) + router.With(s.checkPerm(dataprovider.PermAdminCloseConnections), verifyCSRFHeader). + Delete(webConnectionsPath+"/{connectionID}", handleCloseConnection) + router.With(s.checkPerm(dataprovider.PermAdminManageFolders), s.refreshCookie). + Get(webFolderPath+"/{name}", s.handleWebUpdateFolderGet) + router.With(s.checkPerm(dataprovider.PermAdminManageFolders)).Post(webFolderPath+"/{name}", + s.handleWebUpdateFolderPost) + router.With(s.checkPerm(dataprovider.PermAdminManageFolders), verifyCSRFHeader). + Delete(webFolderPath+"/{name}", deleteFolder) + router.With(s.checkPerm(dataprovider.PermAdminQuotaScans), verifyCSRFHeader). + Post(webScanVFolderPath+"/{name}", startFolderQuotaScan) + router.With(s.checkPerm(dataprovider.PermAdminDeleteUsers), verifyCSRFHeader). + Delete(webUserPath+"/{username}", deleteUser) + router.With(s.checkPerm(dataprovider.PermAdminQuotaScans), verifyCSRFHeader). + Post(webQuotaScanPath+"/{username}", startUserQuotaScan) + router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Get(webMaintenancePath, s.handleWebMaintenance) + router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Get(webBackupPath, dumpData) + router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Post(webRestorePath, s.handleWebRestore) + router.With(s.checkPerm(dataprovider.PermAdminManageSystem), s.refreshCookie). + Get(webTemplateUser, s.handleWebTemplateUserGet) + router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Post(webTemplateUser, s.handleWebTemplateUserPost) + router.With(s.checkPerm(dataprovider.PermAdminManageSystem), s.refreshCookie). + Get(webTemplateFolder, s.handleWebTemplateFolderGet) + router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Post(webTemplateFolder, s.handleWebTemplateFolderPost) + router.With(s.checkPerm(dataprovider.PermAdminViewDefender)).Get(webDefenderPath, s.handleWebDefenderPage) + router.With(s.checkPerm(dataprovider.PermAdminViewDefender)).Get(webDefenderHostsPath, getDefenderHosts) + router.With(s.checkPerm(dataprovider.PermAdminManageDefender)).Delete(webDefenderHostsPath+"/{id}", + deleteDefenderHostByID) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), compressor.Handler, s.refreshCookie). + Get(webAdminEventActionsPath+jsonAPISuffix, getAllActions) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), s.refreshCookie). + Get(webAdminEventActionsPath, s.handleWebGetEventActions) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), s.refreshCookie). + Get(webAdminEventActionPath, s.handleWebAddEventActionGet) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Post(webAdminEventActionPath, + s.handleWebAddEventActionPost) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), s.refreshCookie). + Get(webAdminEventActionPath+"/{name}", s.handleWebUpdateEventActionGet) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Post(webAdminEventActionPath+"/{name}", + s.handleWebUpdateEventActionPost) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), verifyCSRFHeader). + Delete(webAdminEventActionPath+"/{name}", deleteEventAction) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), compressor.Handler, s.refreshCookie). + Get(webAdminEventRulesPath+jsonAPISuffix, getAllRules) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), s.refreshCookie). + Get(webAdminEventRulesPath, s.handleWebGetEventRules) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), s.refreshCookie). + Get(webAdminEventRulePath, s.handleWebAddEventRuleGet) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Post(webAdminEventRulePath, + s.handleWebAddEventRulePost) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), s.refreshCookie). + Get(webAdminEventRulePath+"/{name}", s.handleWebUpdateEventRuleGet) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Post(webAdminEventRulePath+"/{name}", + s.handleWebUpdateEventRulePost) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), verifyCSRFHeader). + Delete(webAdminEventRulePath+"/{name}", deleteEventRule) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), verifyCSRFHeader). + Post(webAdminEventRulePath+"/run/{name}", runOnDemandRule) + router.With(s.checkPerm(dataprovider.PermAdminManageRoles), s.refreshCookie). + Get(webAdminRolesPath, s.handleWebGetRoles) + router.With(s.checkPerm(dataprovider.PermAdminManageRoles), compressor.Handler, s.refreshCookie). + Get(webAdminRolesPath+jsonAPISuffix, getAllRoles) + router.With(s.checkPerm(dataprovider.PermAdminManageRoles), s.refreshCookie). + Get(webAdminRolePath, s.handleWebAddRoleGet) + router.With(s.checkPerm(dataprovider.PermAdminManageRoles)).Post(webAdminRolePath, s.handleWebAddRolePost) + router.With(s.checkPerm(dataprovider.PermAdminManageRoles), s.refreshCookie). + Get(webAdminRolePath+"/{name}", s.handleWebUpdateRoleGet) + router.With(s.checkPerm(dataprovider.PermAdminManageRoles)).Post(webAdminRolePath+"/{name}", + s.handleWebUpdateRolePost) + router.With(s.checkPerm(dataprovider.PermAdminManageRoles), verifyCSRFHeader). + Delete(webAdminRolePath+"/{name}", deleteRole) + router.With(s.checkPerm(dataprovider.PermAdminViewEvents), s.refreshCookie).Get(webEventsPath, + s.handleWebGetEvents) + router.With(s.checkPerm(dataprovider.PermAdminViewEvents), compressor.Handler, s.refreshCookie). + Get(webEventsFsSearchPath, searchFsEvents) + router.With(s.checkPerm(dataprovider.PermAdminViewEvents), compressor.Handler, s.refreshCookie). + Get(webEventsProviderSearchPath, searchProviderEvents) + router.With(s.checkPerm(dataprovider.PermAdminViewEvents), compressor.Handler, s.refreshCookie). + Get(webEventsLogSearchPath, searchLogEvents) + router.With(s.checkPerm(dataprovider.PermAdminManageIPLists)).Get(webIPListsPath, s.handleWebIPListsPage) + router.With(s.checkPerm(dataprovider.PermAdminManageIPLists), compressor.Handler, s.refreshCookie). + Get(webIPListsPath+"/{type}", getIPListEntries) + router.With(s.checkPerm(dataprovider.PermAdminManageIPLists), s.refreshCookie).Get(webIPListPath+"/{type}", + s.handleWebAddIPListEntryGet) + router.With(s.checkPerm(dataprovider.PermAdminManageIPLists)).Post(webIPListPath+"/{type}", + s.handleWebAddIPListEntryPost) + router.With(s.checkPerm(dataprovider.PermAdminManageIPLists), s.refreshCookie).Get(webIPListPath+"/{type}/{ipornet}", + s.handleWebUpdateIPListEntryGet) + router.With(s.checkPerm(dataprovider.PermAdminManageIPLists)).Post(webIPListPath+"/{type}/{ipornet}", + s.handleWebUpdateIPListEntryPost) + router.With(s.checkPerm(dataprovider.PermAdminManageIPLists), verifyCSRFHeader). + Delete(webIPListPath+"/{type}/{ipornet}", deleteIPListEntry) + router.With(s.checkPerm(dataprovider.PermAdminManageSystem), s.refreshCookie).Get(webConfigsPath, s.handleWebConfigs) + router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Post(webConfigsPath, s.handleWebConfigsPost) + router.With(s.checkPerm(dataprovider.PermAdminManageSystem), verifyCSRFHeader, s.refreshCookie). + Post(webConfigsPath+"/smtp/test", testSMTPConfig) + router.With(s.checkPerm(dataprovider.PermAdminManageSystem), verifyCSRFHeader, s.refreshCookie). + Post(webOAuth2TokenPath, handleSMTPOAuth2TokenRequestPost) + }) }) } } diff --git a/internal/httpd/webadmin.go b/internal/httpd/webadmin.go index 639ca98c..f163d502 100644 --- a/internal/httpd/webadmin.go +++ b/internal/httpd/webadmin.go @@ -210,12 +210,13 @@ type changePasswordPage struct { type mfaPage struct { basePage - TOTPConfigs []string - TOTPConfig dataprovider.AdminTOTPConfig - GenerateTOTPURL string - ValidateTOTPURL string - SaveTOTPURL string - RecCodesURL string + TOTPConfigs []string + TOTPConfig dataprovider.AdminTOTPConfig + GenerateTOTPURL string + ValidateTOTPURL string + SaveTOTPURL string + RecCodesURL string + RequireTwoFactor bool } type maintenancePage struct { @@ -786,6 +787,7 @@ func (s *httpdServer) renderMFAPage(w http.ResponseWriter, r *http.Request) { return } data.TOTPConfig = admin.Filters.TOTPConfig + data.RequireTwoFactor = admin.Filters.RequireTwoFactor renderAdminTemplate(w, templateMFA, data) } @@ -1741,6 +1743,8 @@ func getAdminFromPostFields(r *http.Request) (dataprovider.Admin, error) { admin.Role = strings.TrimSpace(r.Form.Get("role")) admin.Filters.AllowList = getSliceFromDelimitedValues(r.Form.Get("allowed_ip"), ",") admin.Filters.AllowAPIKeyAuth = r.Form.Get("allow_api_key_auth") != "" + admin.Filters.RequireTwoFactor = r.Form.Get("require_two_factor") != "" + admin.Filters.RequirePasswordChange = r.Form.Get("require_password_change") != "" admin.AdditionalInfo = r.Form.Get("additional_info") admin.Description = r.Form.Get("description") admin.Filters.Preferences.HideUserPageSections = getAdminHiddenUserPageSections(r) @@ -3016,6 +3020,8 @@ func (s *httpdServer) handleWebUpdateAdminPost(w http.ResponseWriter, r *http.Re ), false) return } + updatedAdmin.Filters.RequirePasswordChange = admin.Filters.RequirePasswordChange + updatedAdmin.Filters.RequireTwoFactor = admin.Filters.RequireTwoFactor } err = dataprovider.UpdateAdmin(&updatedAdmin, claims.Username, ipAddr, claims.Role) if err != nil { diff --git a/internal/httpdtest/httpdtest.go b/internal/httpdtest/httpdtest.go index 953c6116..71ffa12b 100644 --- a/internal/httpdtest/httpdtest.go +++ b/internal/httpdtest/httpdtest.go @@ -1970,6 +1970,12 @@ func compareAdminFilters(expected, actual dataprovider.AdminFilters) error { if expected.Preferences.DefaultUsersExpiration != actual.Preferences.DefaultUsersExpiration { return errors.New("default users expiration mismatch") } + if expected.RequirePasswordChange != actual.RequirePasswordChange { + return errors.New("require password change mismatch") + } + if expected.RequireTwoFactor != actual.RequireTwoFactor { + return errors.New("require two factor mismatch") + } return nil } diff --git a/internal/util/i18n.go b/internal/util/i18n.go index 540b1a20..85ffda1c 100644 --- a/internal/util/i18n.go +++ b/internal/util/i18n.go @@ -159,6 +159,7 @@ const ( I18nErrorShareExpired = "share.expired" I18nErrorLoginFromIPDenied = "login.ip_not_allowed" I18nError2FARequired = "login.two_factor_required" + I18nError2FARequiredGeneric = "login.two_factor_required_generic" I18nErrorNoOIDCFeature = "general.no_oidc_feature" I18nErrorNoPermissions = "general.no_permissions" I18nErrorShareBrowsePaths = "share.browsable_multiple_paths" diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index ba904785..bce6e97a 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -6003,6 +6003,10 @@ components: allow_api_key_auth: type: boolean description: 'API key auth allows to impersonate this administrator with an API key' + require_two_factor: + type: boolean + require_password_change: + type: boolean totp_config: $ref: '#/components/schemas/AdminTOTPConfig' recovery_codes: diff --git a/static/locales/en/translation.json b/static/locales/en/translation.json index 57891c9a..75940d1e 100644 --- a/static/locales/en/translation.json +++ b/static/locales/en/translation.json @@ -99,6 +99,7 @@ "reset_ok_login_error": "The password reset completed successfully but an unexpected error occurred while signing in", "ip_not_allowed": "Login is not allowed from this IP address", "two_factor_required": "Set up two-factor authentication, it is required for the following protocols: {{val}}", + "two_factor_required_generic": "Set up two-factor authentication, it is mandatory for your account", "link": "Go to {{link}}" }, "theme": { @@ -386,6 +387,7 @@ "msg_enabled": "Two-factor authentication is enabled", "msg_disabled": "Secure Your Account", "msg_info": "Two-factor authentication adds an extra layer of security to your account. To log in you'll need to provide an additional authentication code.", + "require": "Two-factor authentication is required for this account", "require_for": "Require 2FA for", "generate": "Generate new secret key", "recovery_codes": "Recovery codes", @@ -727,7 +729,8 @@ "user_page_pref_help": "You can hide some sections from the user page. These are not security settings and are not enforced server side in any way. They are only intended to simplify the add/update user page", "hide_sections": "Hide sections", "default_users_expiration": "Default users expiration", - "default_users_expiration_help": "Default expiration for new users as number of days" + "default_users_expiration_help": "Default expiration for new users as number of days", + "require_pwd_change_help": "A password change is required at the next login" }, "connections": { "view_manage": "View and manage connections", diff --git a/static/locales/it/translation.json b/static/locales/it/translation.json index 26673f6f..97252b62 100644 --- a/static/locales/it/translation.json +++ b/static/locales/it/translation.json @@ -99,6 +99,7 @@ "reset_ok_login_error": "La reimpostazione della password è stata completata correttamente ma si è verificato un errore imprevisto durante l'accesso", "ip_not_allowed": "L'accesso non è consentito da questo indirizzo IP", "two_factor_required": "Configura l'autenticazione a due fattori, è obbligatoria per i seguenti protocolli: {{val}}", + "two_factor_required_generic": "Configura l'autenticazione a due fattori, è obbligatoria per il tuo account", "link": "Vai a {{link}}" }, "theme": { @@ -386,6 +387,7 @@ "msg_enabled": "L'autenticazione a due fattori è abilitata", "msg_disabled": "Metti il tuo account in sicurezza", "msg_info": "L'autenticazione a due fattori aggiunge un ulteriore livello di sicurezza al tuo account. Per accedere dovrai fornire un ulteriore codice di autenticazione.", + "require": "L'autenticazione a due fattori è obbligatoria per questo account", "require_for": "Richiedi 2FA per", "generate": "Genera una nuova chiave segreta", "recovery_codes": "Codici di ripristino", @@ -727,7 +729,8 @@ "user_page_pref_help": "Puoi nascondere alcune sezioni dalla pagina utente. Queste non sono impostazioni di sicurezza e non vengono verificate lato server. Hanno il solo scopo di semplificare la pagina di creazione/modifica utenti", "hide_sections": "Nascondi sezioni", "default_users_expiration": "Scadenza predefinita utenti", - "default_users_expiration_help": "Scadenza predefinita per i nuovi utenti espressa in numero di giorni" + "default_users_expiration_help": "Scadenza predefinita per i nuovi utenti espressa in numero di giorni", + "require_pwd_change_help": "Il cambio password è obbligatorio al prossimo accesso" }, "connections": { "view_manage": "Visualizza e gestisci connessioni attive", diff --git a/templates/webadmin/admin.html b/templates/webadmin/admin.html index cbdc83f5..baf5957d 100644 --- a/templates/webadmin/admin.html +++ b/templates/webadmin/admin.html @@ -42,6 +42,18 @@ explicit grant from the SFTPGo Team (support@sftpgo.com). +