Pārlūkot izejas kodu

fix cross folder copy

also update css/js deps and other minor changes

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
Nicola Murino 2 gadi atpakaļ
vecāks
revīzija
3cb53b2c33

+ 1 - 1
docs/full-configuration.md

@@ -1,6 +1,6 @@
 # Configuring SFTPGo
 
-<details><summary><font size=5> Command line option</font></summary>
+<details><summary><font size=5> Command line options</font></summary>
 
 The SFTPGo executable can be used this way:
 

+ 18 - 17
go.mod

@@ -10,14 +10,14 @@ require (
 	github.com/alexedwards/argon2id v0.0.0-20230305115115-4b3c3280a736
 	github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964
 	github.com/aws/aws-sdk-go-v2 v1.17.8
-	github.com/aws/aws-sdk-go-v2/config v1.18.20
-	github.com/aws/aws-sdk-go-v2/credentials v1.13.19
+	github.com/aws/aws-sdk-go-v2/config v1.18.21
+	github.com/aws/aws-sdk-go-v2/credentials v1.13.20
 	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.2
-	github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.61
-	github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.14.8
-	github.com/aws/aws-sdk-go-v2/service/s3 v1.31.2
-	github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.2
-	github.com/aws/aws-sdk-go-v2/service/sts v1.18.8
+	github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.62
+	github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.14.9
+	github.com/aws/aws-sdk-go-v2/service/s3 v1.31.3
+	github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.3
+	github.com/aws/aws-sdk-go-v2/service/sts v1.18.9
 	github.com/bmatcuk/doublestar/v4 v4.6.0
 	github.com/cockroachdb/cockroach-go/v2 v2.3.3
 	github.com/coreos/go-oidc/v3 v3.5.0
@@ -36,7 +36,7 @@ require (
 	github.com/hashicorp/go-hclog v1.5.0
 	github.com/hashicorp/go-plugin v1.4.10-0.20230403150917-e889c1ba1044
 	github.com/hashicorp/go-retryablehttp v0.7.2
-	github.com/jackc/pgx/v5 v5.3.2-0.20230325152211-ca022267dbbf
+	github.com/jackc/pgx/v5 v5.3.2-0.20230411230705-2cf1541bb90a
 	github.com/jlaffaye/ftp v0.0.0-20201112195030-9aae4d151126
 	github.com/klauspost/compress v1.16.4
 	github.com/lestrrat-go/jwx/v2 v2.0.9
@@ -48,11 +48,11 @@ require (
 	github.com/pires/go-proxyproto v0.7.0
 	github.com/pkg/sftp v1.13.6-0.20230213180117-971c283182b6
 	github.com/pquerna/otp v1.4.0
-	github.com/prometheus/client_golang v1.14.0
+	github.com/prometheus/client_golang v1.15.0
 	github.com/robfig/cron/v3 v3.0.1
-	github.com/rs/cors v1.8.3
-	github.com/rs/xid v1.4.0
-	github.com/rs/zerolog v1.29.0
+	github.com/rs/cors v1.9.0
+	github.com/rs/xid v1.5.0
+	github.com/rs/zerolog v1.29.1
 	github.com/sftpgo/sdk v0.1.3-0.20230406132142-15f26d806282
 	github.com/shirou/gopsutil/v3 v3.23.3
 	github.com/spf13/afero v1.9.5
@@ -74,13 +74,13 @@ require (
 	golang.org/x/sys v0.7.0
 	golang.org/x/term v0.7.0
 	golang.org/x/time v0.3.0
-	google.golang.org/api v0.116.0
+	google.golang.org/api v0.118.0
 	gopkg.in/natefinch/lumberjack.v2 v2.2.1
 )
 
 require (
 	cloud.google.com/go v0.110.0 // indirect
-	cloud.google.com/go/compute v1.19.0 // indirect
+	cloud.google.com/go/compute v1.19.1 // indirect
 	cloud.google.com/go/compute/metadata v0.2.3 // indirect
 	cloud.google.com/go/iam v1.0.0 // indirect
 	github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
@@ -94,8 +94,8 @@ require (
 	github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.27 // indirect
 	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.26 // indirect
 	github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.1 // indirect
-	github.com/aws/aws-sdk-go-v2/service/sso v1.12.7 // indirect
-	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.7 // indirect
+	github.com/aws/aws-sdk-go-v2/service/sso v1.12.8 // indirect
+	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.8 // indirect
 	github.com/aws/smithy-go v1.13.5 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/boombuler/barcode v1.0.1 // indirect
@@ -113,6 +113,7 @@ require (
 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 	github.com/golang/protobuf v1.5.3 // indirect
 	github.com/google/go-cmp v0.5.9 // indirect
+	github.com/google/s2a-go v0.1.1 // indirect
 	github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
 	github.com/googleapis/gax-go/v2 v2.8.0 // indirect
 	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
@@ -159,7 +160,7 @@ require (
 	golang.org/x/tools v0.8.0 // indirect
 	golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
-	google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd // indirect
+	google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
 	google.golang.org/grpc v1.54.0 // indirect
 	google.golang.org/protobuf v1.30.0 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect

+ 37 - 34
go.sum

@@ -124,8 +124,8 @@ cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARy
 cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo=
 cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA=
 cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs=
-cloud.google.com/go/compute v1.19.0 h1:+9zda3WGgW1ZSTlVppLCYFIr48Pa35q1uG2N1itbCEQ=
-cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU=
+cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY=
+cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE=
 cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU=
 cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
 cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=
@@ -230,7 +230,7 @@ cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxs
 cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg=
 cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0=
 cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg=
-cloud.google.com/go/kms v1.10.0 h1:Imrtp8792uqNP9bdfPrjtUkjjqOMBcAJ2bdFaAnLhnk=
+cloud.google.com/go/kms v1.10.1 h1:7hm1bRqGCA1GBRQUrp831TwJ9TWhP+tvLuP497CQS2g=
 cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic=
 cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI=
 cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE=
@@ -565,17 +565,17 @@ github.com/aws/aws-sdk-go-v2 v1.17.8/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3eP
 github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 h1:dK82zF6kkPeCo8J1e+tGx4JdvDIQzj7ygIoLg8WMuGs=
 github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10/go.mod h1:VeTZetY5KRJLuD/7fkQXMU6Mw7H5m/KP2J5Iy9osMno=
 github.com/aws/aws-sdk-go-v2/config v1.18.12/go.mod h1:J36fOhj1LQBr+O4hJCiT8FwVvieeoSGOtPuvhKlsNu8=
-github.com/aws/aws-sdk-go-v2/config v1.18.20 h1:yYy+onqmLmDVZtx0mkqbx8aJPl+58V6ivLbLDZ2Qztc=
-github.com/aws/aws-sdk-go-v2/config v1.18.20/go.mod h1:RWjF39RiDevmHw/+VaD8F0A36OPIPTHQQyRx0eZohnw=
+github.com/aws/aws-sdk-go-v2/config v1.18.21 h1:ENTXWKwE8b9YXgQCsruGLhvA9bhg+RqAsL9XEMEsa2c=
+github.com/aws/aws-sdk-go-v2/config v1.18.21/go.mod h1:+jPQiVPz1diRnjj6VGqWcLK6EzNmQ42l7J3OqGTLsSY=
 github.com/aws/aws-sdk-go-v2/credentials v1.13.12/go.mod h1:37HG2MBroXK3jXfxVGtbM2J48ra2+Ltu+tmwr/jO0KA=
-github.com/aws/aws-sdk-go-v2/credentials v1.13.19 h1:FWHJy9uggyQCSEhovtl/6W6rW9P6DSr62GUeY/TS6Eo=
-github.com/aws/aws-sdk-go-v2/credentials v1.13.19/go.mod h1:2m4uvLvl5hvQezVkLeBBUGMEDm5GcUNc3016W6d3NGg=
+github.com/aws/aws-sdk-go-v2/credentials v1.13.20 h1:oZCEFcrMppP/CNiS8myzv9JgOzq2s0d3v3MXYil/mxQ=
+github.com/aws/aws-sdk-go-v2/credentials v1.13.20/go.mod h1:xtZnXErtbZ8YGXC3+8WfajpMBn5Ga/3ojZdxHq6iI8o=
 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.22/go.mod h1:YGSIJyQ6D6FjKMQh16hVFSIUD54L4F7zTGePqYMYYJU=
 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.2 h1:jOzQAesnBFDmz93feqKnsTHsXrlwWORNZMFHMV+WLFU=
 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.2/go.mod h1:cDh1p6XkSGSwSRIArWRc6+UqAQ7x4alQ0QfpVR6f+co=
 github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.51/go.mod h1:7Grl2gV+dx9SWrUIgwwlUvU40t7+lOSbx34XwfmsTkY=
-github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.61 h1:0fHTNkoMAz7jbXSyo0SLubbTJEO+AgvkZ0iWT9DbEsk=
-github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.61/go.mod h1:i8l1At/vjpY8xf1ivKUBJE4+DQyE0gM9Zdg3ZpC/7RU=
+github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.62 h1:LhVbe/UDWvBT/jp5LYAweFVH8s+DNtT07Qp2riWEovU=
+github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.62/go.mod h1:4xCuu1TSwhW5UH6WOdtS4/x/9UfMr2XplzKc86Ffj78=
 github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.28/go.mod h1:3lwChorpIM/BhImY/hy+Z6jekmN92cXGPI1QJasVPYY=
 github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.32 h1:dpbVNUjczQ8Ae3QKHbpHBpfvaVkRdesxpTOe9pTouhU=
 github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.32/go.mod h1:RudqOgadTWdcS3t/erPQo24pcVEoYyqj/kKW5Vya21I=
@@ -601,26 +601,26 @@ github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.22/go.mod h1:QFVbqK
 github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.1 h1:lRWp3bNu5wy0X3a8GS42JvZFlv++AKsMdzEnoiVJrkg=
 github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.1/go.mod h1:VXBHSxdN46bsJrkniN68psSwbyBKsazQfU2yX/iSDso=
 github.com/aws/aws-sdk-go-v2/service/kms v1.20.2/go.mod h1:vdqtUOdVuf5ooy+hJ2GnzqNo94xiAA9s1xbZ1hQgRE0=
-github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.14.8 h1:NLpW2zjplPmay81ZU6SbFZc6oI156PxFvU/zn41WlQE=
-github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.14.8/go.mod h1:oNFgSKrV6y06CQnFXzcYtEOB/EfRP1aHWbddn1TEXG8=
+github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.14.9 h1:OvTETycfHkxIYPgff3hHykG1lH03zBx9G7HH9nLEKBY=
+github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.14.9/go.mod h1:oNFgSKrV6y06CQnFXzcYtEOB/EfRP1aHWbddn1TEXG8=
 github.com/aws/aws-sdk-go-v2/service/s3 v1.30.2/go.mod h1:SXDHd6fI2RhqB7vmAzyYQCTQnpZrIprVJvYxpzW3JAM=
-github.com/aws/aws-sdk-go-v2/service/s3 v1.31.2 h1:iOZoYePk+EuBI1tC7bxeRjO+JvClcYm2fZYW5WPIOMQ=
-github.com/aws/aws-sdk-go-v2/service/s3 v1.31.2/go.mod h1:aSl9/LJltSz1cVusiR/Mu8tvI4Sv/5w/WWrJmmkNii0=
+github.com/aws/aws-sdk-go-v2/service/s3 v1.31.3 h1:MG+2UlhyBL3oCOoHbUQh+Sqr3elN0I5PBe0MtVh0xMg=
+github.com/aws/aws-sdk-go-v2/service/s3 v1.31.3/go.mod h1:aSl9/LJltSz1cVusiR/Mu8tvI4Sv/5w/WWrJmmkNii0=
 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.3/go.mod h1:hqPcyOuLU6yWIbLy3qMnQnmidgKuIEwqIlW6+chYnog=
-github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.2 h1:mRA8bnA0zdTvsGXmoZ6EOmTTmORjEV1uareB4GfzfK0=
-github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.2/go.mod h1:QNYziZIPDbKmKRoTHi9wkgqVidknyiGHfig1UNOojqk=
+github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.3 h1:bqvkwBuoYZ28Aybq10A9uXh7LkPCh7W4nd3l5bf3v5A=
+github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.3/go.mod h1:QNYziZIPDbKmKRoTHi9wkgqVidknyiGHfig1UNOojqk=
 github.com/aws/aws-sdk-go-v2/service/sns v1.20.2/go.mod h1:VN2n9SOMS1lNbh5YD7o+ho0/rgfifSrK//YYNiVVF5E=
 github.com/aws/aws-sdk-go-v2/service/sqs v1.20.2/go.mod h1:1ttxGjUHZliCQMpPss1sU5+Ph/5NvdMFRzr96bv8gm0=
 github.com/aws/aws-sdk-go-v2/service/ssm v1.35.2/go.mod h1:VLSz2SHUKYFSOlXB/GlXoLU6KPYQJAbw7I20TDJdyws=
 github.com/aws/aws-sdk-go-v2/service/sso v1.12.1/go.mod h1:IgV8l3sj22nQDd5qcAGY0WenwCzCphqdbFOpfktZPrI=
-github.com/aws/aws-sdk-go-v2/service/sso v1.12.7 h1:rrYYhsvcvg6CDDoo4GHKtAWBFutS86CpmGvqHJHYL9w=
-github.com/aws/aws-sdk-go-v2/service/sso v1.12.7/go.mod h1:GNIveDnP+aE3jujyUSH5aZ/rktsTM5EvtKnCqBZawdw=
+github.com/aws/aws-sdk-go-v2/service/sso v1.12.8 h1:5cb3D6xb006bPTqEfCNaEA6PPEfBXxxy4NNeX/44kGk=
+github.com/aws/aws-sdk-go-v2/service/sso v1.12.8/go.mod h1:GNIveDnP+aE3jujyUSH5aZ/rktsTM5EvtKnCqBZawdw=
 github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.1/go.mod h1:O1YSOg3aekZibh2SngvCRRG+cRHKKlYgxf/JBF/Kr/k=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.7 h1:Vjpjt3svuJ/u+eKRfycZwqLsLoxyuvvZyHMJSk+3k58=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.7/go.mod h1:44qFP1g7pfd+U+sQHLPalAPKnyfTZjJsYR4xIwsJy5o=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.8 h1:NZaj0ngZMzsubWZbrEFSB4rgSQRbFq38Sd6KBxHuOIU=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.8/go.mod h1:44qFP1g7pfd+U+sQHLPalAPKnyfTZjJsYR4xIwsJy5o=
 github.com/aws/aws-sdk-go-v2/service/sts v1.18.3/go.mod h1:b+psTJn33Q4qGoDaM7ZiOVVG8uVjGI6HaZ8WBHdgDgU=
-github.com/aws/aws-sdk-go-v2/service/sts v1.18.8 h1:SQ8pPoXfzuz4DImO4KAi9xhO4ANG0Ckb5clZ5GoRAPw=
-github.com/aws/aws-sdk-go-v2/service/sts v1.18.8/go.mod h1:yyW88BEPXA2fGFyI2KCcZC3dNpiT0CZAHaF+i656/tQ=
+github.com/aws/aws-sdk-go-v2/service/sts v1.18.9 h1:Qf1aWwnsNkyAoqDqmdM3nHwN78XQjec27LjM6b9vyfI=
+github.com/aws/aws-sdk-go-v2/service/sts v1.18.9/go.mod h1:yyW88BEPXA2fGFyI2KCcZC3dNpiT0CZAHaF+i656/tQ=
 github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
 github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8=
 github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
@@ -817,7 +817,6 @@ github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7
 github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
 github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
 github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
-github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/coreos/go-systemd/v22 v22.4.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
 github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
@@ -1197,6 +1196,8 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLe
 github.com/google/pprof v0.0.0-20220318212150-b2ab0324ddda/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
 github.com/google/pprof v0.0.0-20230111200839-76d1ae5aea2b/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/s2a-go v0.1.1 h1:XJQvZvUdPzOGdf4ZMQc78wYt9XrdIZOl//n03i8P68Q=
+github.com/google/s2a-go v0.1.1/go.mod h1:OJpEgntRZo8ugHpF9hkoLJbS5dSI20XZeXJ9JVywLlM=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
 github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
@@ -1390,8 +1391,8 @@ github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9
 github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
 github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
 github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw=
-github.com/jackc/pgx/v5 v5.3.2-0.20230325152211-ca022267dbbf h1:BLqUs5GEGMJ+h9s63INbbwXUXe95wvhvh1esISGgau0=
-github.com/jackc/pgx/v5 v5.3.2-0.20230325152211-ca022267dbbf/go.mod h1:sU+RaYl9qnhD3Ce+mwnFii6YEPx70mCYghBzKvqq4qo=
+github.com/jackc/pgx/v5 v5.3.2-0.20230411230705-2cf1541bb90a h1:wDHgeTk2JY7s81p4inOAtvzoxk00bvnofbdkisnwsaE=
+github.com/jackc/pgx/v5 v5.3.2-0.20230411230705-2cf1541bb90a/go.mod h1:sU+RaYl9qnhD3Ce+mwnFii6YEPx70mCYghBzKvqq4qo=
 github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
@@ -1749,8 +1750,9 @@ github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqr
 github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
 github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
 github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
-github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
 github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
+github.com/prometheus/client_golang v1.15.0 h1:5fCgGYogn0hFdhyhLbw7hEsWxufKtY9klyvdNfFlFhM=
+github.com/prometheus/client_golang v1.15.0/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
 github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -1809,15 +1811,16 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE
 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/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
-github.com/rs/cors v1.8.3 h1:O+qNyWn7Z+F9M0ILBHgMVPuB1xTOucVd5gtaYyXBpRo=
-github.com/rs/cors v1.8.3/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
+github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE=
+github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
 github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
-github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
 github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
+github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
+github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
 github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
 github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
-github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w=
-github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
+github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc=
+github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -2659,8 +2662,8 @@ google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/
 google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=
 google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=
 google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI=
-google.golang.org/api v0.116.0 h1:09tOPVufPwfm5W4aA8EizGHJ7BcoRDsIareM2a15gO4=
-google.golang.org/api v0.116.0/go.mod h1:9cD4/t6uvd9naoEJFA+M96d0IuB6BqFuyhpw68+mRGg=
+google.golang.org/api v0.118.0 h1:FNfHq9Z2GKULxu7cEhCaB0wWQHg43UpomrrN+24ZRdE=
+google.golang.org/api v0.118.0/go.mod h1:76TtD3vkgmZ66zZzp72bUUklpmQmKlhh6sYtIjYK+5E=
 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.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -2805,8 +2808,8 @@ google.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5/go.mod h1:RGgjbofJ
 google.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
 google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
 google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
-google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd h1:sLpv7bNL1AsX3fdnWh9WVh7ejIzXdOc1RRHGeAmeStU=
-google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=
+google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
+google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
 google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=

+ 1 - 1
internal/common/connection.go

@@ -587,7 +587,7 @@ func (c *BaseConnection) copyFile(virtualSourcePath, virtualTargetPath string, s
 	if ok, _ := c.User.IsFileAllowed(virtualTargetPath); !ok {
 		return fmt.Errorf("file %q is not allowed: %w", virtualTargetPath, c.GetPermissionDeniedError())
 	}
-	if c.isSameResource(virtualSourcePath, virtualSourcePath) {
+	if c.isSameResource(virtualSourcePath, virtualTargetPath) {
 		fs, fsTargetPath, err := c.GetFsAndResolvedPath(virtualTargetPath)
 		if err != nil {
 			return err

+ 64 - 8
internal/common/protocol_test.go

@@ -3428,14 +3428,16 @@ func TestDelayedQuotaUpdater(t *testing.T) {
 func TestPasswordCaching(t *testing.T) {
 	user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
 	assert.NoError(t, err)
-	found, match := dataprovider.CheckCachedPassword(user.Username, defaultPassword)
+	dbUser, err := dataprovider.UserExists(user.Username, "")
+	assert.NoError(t, err)
+	found, match := dataprovider.CheckCachedUserPassword(user.Username, defaultPassword, dbUser.Password)
 	assert.False(t, found)
 	assert.False(t, match)
 
 	user.Password = "wrong"
 	_, _, err = getSftpClient(user)
 	assert.Error(t, err)
-	found, match = dataprovider.CheckCachedPassword(user.Username, defaultPassword)
+	found, match = dataprovider.CheckCachedUserPassword(user.Username, defaultPassword, dbUser.Password)
 	assert.False(t, found)
 	assert.False(t, match)
 	user.Password = ""
@@ -3447,21 +3449,52 @@ func TestPasswordCaching(t *testing.T) {
 		err = checkBasicSFTP(client)
 		assert.NoError(t, err)
 	}
-	found, match = dataprovider.CheckCachedPassword(user.Username, defaultPassword)
+	found, match = dataprovider.CheckCachedUserPassword(user.Username, defaultPassword, dbUser.Password)
 	assert.True(t, found)
 	assert.True(t, match)
 
-	found, match = dataprovider.CheckCachedPassword(user.Username, defaultPassword+"_")
+	found, match = dataprovider.CheckCachedUserPassword(user.Username, defaultPassword+"_", dbUser.Password)
 	assert.True(t, found)
 	assert.False(t, match)
 
-	found, match = dataprovider.CheckCachedPassword(user.Username+"_", defaultPassword)
+	found, match = dataprovider.CheckCachedUserPassword(user.Username+"_", defaultPassword, dbUser.Password)
 	assert.False(t, found)
 	assert.False(t, match)
 
 	user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
 	assert.NoError(t, err)
-	found, match = dataprovider.CheckCachedPassword(user.Username, defaultPassword)
+	// the password was not changed
+	found, match = dataprovider.CheckCachedUserPassword(user.Username, defaultPassword, dbUser.Password)
+	assert.True(t, found)
+	assert.True(t, match)
+	// the password hash will change
+	user.Password = defaultPassword
+	_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
+	assert.NoError(t, err)
+	dbUser, err = dataprovider.UserExists(user.Username, "")
+	assert.NoError(t, err)
+	found, match = dataprovider.CheckCachedUserPassword(user.Username, defaultPassword, dbUser.Password)
+	assert.False(t, found)
+	assert.False(t, match)
+
+	conn, client, err = getSftpClient(user)
+	if assert.NoError(t, err) {
+		defer conn.Close()
+		defer client.Close()
+		err = checkBasicSFTP(client)
+		assert.NoError(t, err)
+	}
+	found, match = dataprovider.CheckCachedUserPassword(user.Username, defaultPassword, dbUser.Password)
+	assert.True(t, found)
+	assert.True(t, match)
+	//change password
+	newPassword := defaultPassword + "mod"
+	user.Password = newPassword
+	_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
+	assert.NoError(t, err)
+	dbUser, err = dataprovider.UserExists(user.Username, "")
+	assert.NoError(t, err)
+	found, match = dataprovider.CheckCachedUserPassword(user.Username, newPassword, dbUser.Password)
 	assert.False(t, found)
 	assert.False(t, match)
 
@@ -3472,8 +3505,31 @@ func TestPasswordCaching(t *testing.T) {
 		err = checkBasicSFTP(client)
 		assert.NoError(t, err)
 	}
+	found, match = dataprovider.CheckCachedUserPassword(user.Username, defaultPassword, dbUser.Password)
+	assert.True(t, found)
+	assert.False(t, match)
+	found, match = dataprovider.CheckCachedUserPassword(user.Username, newPassword, dbUser.Password)
+	assert.True(t, found)
+	assert.True(t, match)
+	// update the password
+	err = dataprovider.UpdateUserPassword(user.Username, defaultPassword, "", "", "")
+	assert.NoError(t, err)
+	dbUser, err = dataprovider.UserExists(user.Username, "")
+	assert.NoError(t, err)
+	// the stored hash does not match
+	found, match = dataprovider.CheckCachedUserPassword(user.Username, defaultPassword, dbUser.Password)
+	assert.False(t, found)
+	assert.False(t, match)
 
-	found, match = dataprovider.CheckCachedPassword(user.Username, defaultPassword)
+	user.Password = defaultPassword
+	conn, client, err = getSftpClient(user)
+	if assert.NoError(t, err) {
+		defer conn.Close()
+		defer client.Close()
+		err = checkBasicSFTP(client)
+		assert.NoError(t, err)
+	}
+	found, match = dataprovider.CheckCachedUserPassword(user.Username, defaultPassword, dbUser.Password)
 	assert.True(t, found)
 	assert.True(t, match)
 
@@ -3481,7 +3537,7 @@ func TestPasswordCaching(t *testing.T) {
 	assert.NoError(t, err)
 	err = os.RemoveAll(user.GetHomeDir())
 	assert.NoError(t, err)
-	found, match = dataprovider.CheckCachedPassword(user.Username, defaultPassword)
+	found, match = dataprovider.CheckCachedUserPassword(user.Username, defaultPassword, dbUser.Password)
 	assert.False(t, found)
 	assert.False(t, match)
 }

+ 13 - 0
internal/dataprovider/admin.go

@@ -424,16 +424,29 @@ func (a *Admin) GetGroupsAsString() string {
 
 // CheckPassword verifies the admin password
 func (a *Admin) CheckPassword(password string) (bool, error) {
+	if config.PasswordCaching {
+		found, match := cachedAdminPasswords.Check(a.Username, password, a.Password)
+		if found {
+			if !match {
+				return false, ErrInvalidCredentials
+			}
+			return match, nil
+		}
+	}
 	if strings.HasPrefix(a.Password, bcryptPwdPrefix) {
 		if err := bcrypt.CompareHashAndPassword([]byte(a.Password), []byte(password)); err != nil {
 			return false, ErrInvalidCredentials
 		}
+		cachedAdminPasswords.Add(a.Username, password, a.Password)
 		return true, nil
 	}
 	match, err := argon2id.ComparePasswordAndHash(password, a.Password)
 	if !match || err != nil {
 		return false, ErrInvalidCredentials
 	}
+	if match && err == nil {
+		cachedAdminPasswords.Add(a.Username, password, a.Password)
+	}
 	return match, err
 }
 

+ 10 - 0
internal/dataprovider/apikey.go

@@ -185,6 +185,15 @@ func (k *APIKey) Authenticate(plainKey string) error {
 		return fmt.Errorf("API key %q is expired, expiration timestamp: %v current timestamp: %v", k.KeyID,
 			k.ExpiresAt, util.GetTimeAsMsSinceEpoch(time.Now()))
 	}
+	if config.PasswordCaching {
+		found, match := cachedAPIKeys.Check(k.KeyID, plainKey, k.Key)
+		if found {
+			if !match {
+				return ErrInvalidCredentials
+			}
+			return nil
+		}
+	}
 	if strings.HasPrefix(k.Key, bcryptPwdPrefix) {
 		if err := bcrypt.CompareHashAndPassword([]byte(k.Key), []byte(plainKey)); err != nil {
 			return ErrInvalidCredentials
@@ -196,5 +205,6 @@ func (k *APIKey) Authenticate(plainKey string) error {
 		}
 	}
 
+	cachedAPIKeys.Add(k.KeyID, plainKey, k.Key)
 	return nil
 }

+ 110 - 17
internal/dataprovider/cachedpassword.go

@@ -15,34 +15,78 @@
 package dataprovider
 
 import (
+	"sort"
 	"sync"
+	"sync/atomic"
+	"time"
+
+	"github.com/drakkan/sftpgo/v2/internal/logger"
+	"github.com/drakkan/sftpgo/v2/internal/util"
 )
 
-var cachedPasswords passwordsCache
+var (
+	cachedUserPasswords  credentialsCache
+	cachedAdminPasswords credentialsCache
+	cachedAPIKeys        credentialsCache
+)
 
 func init() {
-	cachedPasswords = passwordsCache{
-		cache: make(map[string]string),
+	cachedUserPasswords = credentialsCache{
+		name:      "users",
+		sizeLimit: 500,
+		cache:     make(map[string]credentialObject),
+	}
+	cachedAdminPasswords = credentialsCache{
+		name:      "admins",
+		sizeLimit: 100,
+		cache:     make(map[string]credentialObject),
+	}
+	cachedAPIKeys = credentialsCache{
+		name:      "API keys",
+		sizeLimit: 500,
+		cache:     make(map[string]credentialObject),
 	}
 }
 
-type passwordsCache struct {
+// CheckCachedUserPassword is an utility method used only in test cases
+func CheckCachedUserPassword(username, password, hash string) (bool, bool) {
+	return cachedUserPasswords.Check(username, password, hash)
+}
+
+type credentialObject struct {
+	key      string
+	hash     string
+	password string
+	usedAt   *atomic.Int64
+}
+
+type credentialsCache struct {
+	name      string
+	sizeLimit int
 	sync.RWMutex
-	cache map[string]string
+	cache map[string]credentialObject
 }
 
-func (c *passwordsCache) Add(username, password string) {
-	if !config.PasswordCaching || username == "" || password == "" {
+func (c *credentialsCache) Add(username, password, hash string) {
+	if !config.PasswordCaching || username == "" || password == "" || hash == "" {
 		return
 	}
 
 	c.Lock()
 	defer c.Unlock()
 
-	c.cache[username] = password
+	obj := credentialObject{
+		key:      username,
+		hash:     hash,
+		password: password,
+		usedAt:   &atomic.Int64{},
+	}
+	obj.usedAt.Store(util.GetTimeAsMsSinceEpoch(time.Now()))
+
+	c.cache[username] = obj
 }
 
-func (c *passwordsCache) Remove(username string) {
+func (c *credentialsCache) Remove(username string) {
 	if !config.PasswordCaching {
 		return
 	}
@@ -53,24 +97,73 @@ func (c *passwordsCache) Remove(username string) {
 	delete(c.cache, username)
 }
 
-// Check returns if the user is found and if the password match
-func (c *passwordsCache) Check(username, password string) (bool, bool) {
-	if username == "" || password == "" {
+// Check returns if the username is found and if the password match
+func (c *credentialsCache) Check(username, password, hash string) (bool, bool) {
+	if username == "" || password == "" || hash == "" {
 		return false, false
 	}
 
 	c.RLock()
 	defer c.RUnlock()
 
-	pwd, ok := c.cache[username]
+	creds, ok := c.cache[username]
 	if !ok {
 		return false, false
 	}
+	if creds.hash != hash {
+		creds.usedAt.Store(0)
+		return false, false
+	}
+	match := creds.password == password
+	if match {
+		creds.usedAt.Store(util.GetTimeAsMsSinceEpoch(time.Now()))
+	}
+	return true, match
+}
+
+func (c *credentialsCache) count() int {
+	c.RLock()
+	defer c.RUnlock()
 
-	return true, pwd == password
+	return len(c.cache)
 }
 
-// CheckCachedPassword is an utility method used only in test cases
-func CheckCachedPassword(username, password string) (bool, bool) {
-	return cachedPasswords.Check(username, password)
+func (c *credentialsCache) cleanup() {
+	if !config.PasswordCaching {
+		return
+	}
+	if c.count() <= c.sizeLimit {
+		return
+	}
+
+	c.Lock()
+	defer c.Unlock()
+
+	for k, v := range c.cache {
+		if v.usedAt.Load() < util.GetTimeAsMsSinceEpoch(time.Now().Add(-60*time.Minute)) {
+			delete(c.cache, k)
+		}
+	}
+	providerLog(logger.LevelDebug, "size for credentials %q after cleanup: %d", c.name, len(c.cache))
+
+	if len(c.cache) < c.sizeLimit*5 {
+		return
+	}
+	numToRemove := len(c.cache) - c.sizeLimit
+	providerLog(logger.LevelDebug, "additional item to remove from credentials %q: %d", c.name, numToRemove)
+	credentials := make([]credentialObject, 0, len(c.cache))
+	for _, v := range c.cache {
+		credentials = append(credentials, v)
+	}
+	sort.Slice(credentials, func(i, j int) bool {
+		return credentials[i].usedAt.Load() < credentials[j].usedAt.Load()
+	})
+
+	for idx := range credentials {
+		if idx >= numToRemove {
+			break
+		}
+		delete(c.cache, credentials[idx].key)
+	}
+	providerLog(logger.LevelDebug, "size for credentials %q after additional cleanup: %d", c.name, len(c.cache))
 }

+ 7 - 11
internal/dataprovider/dataprovider.go

@@ -1775,6 +1775,7 @@ func DeleteAPIKey(keyID string, executor, ipAddress, role string) error {
 	err = provider.deleteAPIKey(apiKey)
 	if err == nil {
 		executeAction(operationDelete, executor, ipAddress, actionObjectAPIKey, apiKey.KeyID, role, &apiKey)
+		cachedAPIKeys.Remove(keyID)
 	}
 	return err
 }
@@ -1984,6 +1985,7 @@ func DeleteAdmin(username, executor, ipAddress, role string) error {
 	err = provider.deleteAdmin(admin)
 	if err == nil {
 		executeAction(operationDelete, executor, ipAddress, actionObjectAdmin, admin.Username, role, &admin)
+		cachedAdminPasswords.Remove(username)
 	}
 	return err
 }
@@ -2057,7 +2059,6 @@ func UpdateUserPassword(username, plainPwd, executor, ipAddress, role string) er
 		return err
 	}
 	webDAVUsersCache.swap(&user)
-	cachedPasswords.Remove(username)
 	executeAction(operationUpdate, executor, ipAddress, actionObjectUser, username, role, &user)
 	return nil
 }
@@ -2070,7 +2071,6 @@ func UpdateUser(user *User, executor, ipAddress, role string) error {
 	err := provider.updateUser(user)
 	if err == nil {
 		webDAVUsersCache.swap(user)
-		cachedPasswords.Remove(user.Username)
 		executeAction(operationUpdate, executor, ipAddress, actionObjectUser, user.Username, role, user)
 	}
 	return err
@@ -2087,7 +2087,7 @@ func DeleteUser(username, executor, ipAddress, role string) error {
 	if err == nil {
 		RemoveCachedWebDAVUser(user.Username)
 		delayedQuotaUpdater.resetUserQuota(user.Username)
-		cachedPasswords.Remove(username)
+		cachedUserPasswords.Remove(username)
 		executeAction(operationDelete, executor, ipAddress, actionObjectUser, user.Username, role, &user)
 	}
 	return err
@@ -3062,7 +3062,7 @@ func ValidateUser(user *User) error {
 
 func isPasswordOK(user *User, password string) (bool, error) {
 	if config.PasswordCaching {
-		found, match := cachedPasswords.Check(user.Username, password)
+		found, match := cachedUserPasswords.Check(user.Username, password, user.Password)
 		if found {
 			return match, nil
 		}
@@ -3100,7 +3100,7 @@ func isPasswordOK(user *User, password string) (bool, error) {
 		match = fmt.Sprintf("%s%x", md5LDAPPwdPrefix, h.Sum(nil)) == user.Password
 	}
 	if err == nil && match {
-		cachedPasswords.Add(user.Username, password)
+		cachedUserPasswords.Add(user.Username, password, user.Password)
 		if updatePwd {
 			convertUserPassword(user.Username, password)
 		}
@@ -3806,7 +3806,6 @@ func executePreLoginHook(username, loginMethod, ip, protocol string, oidcTokenFi
 	}
 
 	userID := u.ID
-	userPwd := u.Password
 	userUsedQuotaSize := u.UsedQuotaSize
 	userUsedQuotaFiles := u.UsedQuotaFiles
 	userUsedDownloadTransfer := u.UsedDownloadDataTransfer
@@ -3844,9 +3843,6 @@ func executePreLoginHook(username, loginMethod, ip, protocol string, oidcTokenFi
 		err = provider.updateUser(&u)
 		if err == nil {
 			webDAVUsersCache.swap(&u)
-			if u.Password != userPwd {
-				cachedPasswords.Remove(username)
-			}
 		}
 	}
 	if err != nil {
@@ -4081,7 +4077,7 @@ func doExternalAuth(username, password string, pubKey []byte, keyboardInteractiv
 		err = provider.updateUser(&user)
 		if err == nil {
 			webDAVUsersCache.swap(&user)
-			cachedPasswords.Add(user.Username, password)
+			cachedUserPasswords.Add(user.Username, password, user.Password)
 		}
 		return user, err
 	}
@@ -4154,7 +4150,7 @@ func doPluginAuth(username, password string, pubKey []byte, ip, protocol string,
 		err = provider.updateUser(&user)
 		if err == nil {
 			webDAVUsersCache.swap(&user)
-			cachedPasswords.Add(user.Username, password)
+			cachedUserPasswords.Add(user.Username, password, user.Password)
 		}
 		return user, err
 	}

+ 4 - 1
internal/dataprovider/scheduler.go

@@ -100,6 +100,9 @@ func checkDataprovider() {
 func checkCacheUpdates() {
 	checkUserCache()
 	checkIPListEntryCache()
+	cachedUserPasswords.cleanup()
+	cachedAdminPasswords.cleanup()
+	cachedAPIKeys.cleanup()
 }
 
 func checkUserCache() {
@@ -124,11 +127,11 @@ func checkUserCache() {
 				go provider.deleteUser(user, false) //nolint:errcheck
 			}
 			webDAVUsersCache.remove(user.Username)
+			cachedUserPasswords.Remove(user.Username)
 			delayedQuotaUpdater.resetUserQuota(user.Username)
 		} else {
 			webDAVUsersCache.swap(&user)
 		}
-		cachedPasswords.Remove(user.Username)
 	}
 	lastUserCacheUpdate.Store(checkTime)
 	providerLog(logger.LevelDebug, "end user cache check, new update time %v", util.GetTimeFromMsecSinceEpoch(lastUserCacheUpdate.Load()))

+ 3 - 1
internal/sftpd/sftpd_test.go

@@ -3900,7 +3900,9 @@ func TestLoginExternalAuth(t *testing.T) {
 			assert.NoError(t, checkBasicSFTP(client))
 		}
 		if !usePubKey {
-			found, match := dataprovider.CheckCachedPassword(defaultUsername, defaultPassword)
+			dbUser, err := dataprovider.UserExists(defaultUsername, "")
+			assert.NoError(t, err)
+			found, match := dataprovider.CheckCachedUserPassword(defaultUsername, defaultPassword, dbUser.Password)
 			assert.True(t, found)
 			assert.True(t, match)
 		}

+ 1 - 1
static/vendor/datatables/buttons.bootstrap4.min.js

@@ -1,4 +1,4 @@
 /*! Bootstrap integration for DataTables' Buttons
  * ©2016 SpryMedia Ltd - datatables.net/license
  */
-!function(e){"function"==typeof define&&define.amd?define(["jquery","datatables.net-bs4","datatables.net-buttons"],function(t){return e(t,window,document)}):"object"==typeof exports?module.exports=function(t,n){return t=t||window,(n=n||("undefined"!=typeof window?require("jquery"):require("jquery")(t))).fn.dataTable||require("datatables.net-bs4")(t,n),n.fn.dataTable.Buttons||require("datatables.net-buttons")(t,n),e(n,0,t.document)}:e(jQuery,window,document)}(function(e,t,n,o){"use strict";var a=e.fn.dataTable;return e.extend(!0,a.Buttons.defaults,{dom:{container:{className:"dt-buttons btn-group flex-wrap"},button:{className:"btn btn-secondary"},collection:{tag:"div",className:"dropdown-menu",closeButton:!1,button:{tag:"a",className:"dt-button dropdown-item",active:"active",disabled:"disabled"}},splitWrapper:{tag:"div",className:"dt-btn-split-wrapper btn-group",closeButton:!1},splitDropdown:{tag:"button",text:"",className:"btn btn-secondary dt-btn-split-drop dropdown-toggle dropdown-toggle-split",closeButton:!1,align:"split-left",splitAlignClass:"dt-button-split-left"},splitDropdownButton:{tag:"button",className:"dt-btn-split-drop-button btn btn-secondary",closeButton:!1}},buttonCreated:function(t,n){return t.buttons?e('<div class="btn-group"/>').append(n):n}}),a.ext.buttons.collection.className+=" dropdown-toggle",a.ext.buttons.collection.rightAlignClassName="dropdown-menu-right",a});
+!function(e){var o,a;"function"==typeof define&&define.amd?define(["jquery","datatables.net-bs4","datatables.net-buttons"],function(t){return e(t,window,document)}):"object"==typeof exports?(o=require("jquery"),a=function(t,n){n.fn.dataTable||require("datatables.net-bs4")(t,n),n.fn.dataTable.Buttons||require("datatables.net-buttons")(t,n)},"undefined"!=typeof window?module.exports=function(t,n){return t=t||window,n=n||o(t),a(t,n),e(n,0,t.document)}:(a(window,o),module.exports=e(o,window,window.document))):e(jQuery,window,document)}(function(e,t,n,o){"use strict";var a=e.fn.dataTable;return e.extend(!0,a.Buttons.defaults,{dom:{container:{className:"dt-buttons btn-group flex-wrap"},button:{className:"btn btn-secondary"},collection:{tag:"div",className:"dropdown-menu",closeButton:!1,button:{tag:"a",className:"dt-button dropdown-item",active:"active",disabled:"disabled"}},splitWrapper:{tag:"div",className:"dt-btn-split-wrapper btn-group",closeButton:!1},splitDropdown:{tag:"button",text:"",className:"btn btn-secondary dt-btn-split-drop dropdown-toggle dropdown-toggle-split",closeButton:!1,align:"split-left",splitAlignClass:"dt-button-split-left"},splitDropdownButton:{tag:"button",className:"dt-btn-split-drop-button btn btn-secondary",closeButton:!1}},buttonCreated:function(t,n){return t.buttons?e('<div class="btn-group"/>').append(n):n}}),a.ext.buttons.collection.className+=" dropdown-toggle",a.ext.buttons.collection.rightAlignClassName="dropdown-menu-right",a});

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/vendor/datatables/buttons.colVis.min.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/vendor/datatables/dataTables.bootstrap4.min.css


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/vendor/datatables/dataTables.bootstrap4.min.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 1 - 1
static/vendor/datatables/dataTables.buttons.min.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 2 - 2
static/vendor/datatables/dataTables.colReorder.min.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 2 - 2
static/vendor/datatables/dataTables.fixedHeader.min.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 2 - 2
static/vendor/datatables/dataTables.responsive.min.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 2 - 2
static/vendor/datatables/dataTables.select.min.js


+ 24 - 10
static/vendor/datatables/ellipsis.js

@@ -3,25 +3,39 @@
 (function( factory ){
 	if ( typeof define === 'function' && define.amd ) {
 		// AMD
-		define( ['datatables.net'], function ( $ ) {
+		define( ['jquery', 'datatables.net'], function ( $ ) {
 			return factory( $, window, document );
 		} );
 	}
 	else if ( typeof exports === 'object' ) {
 		// CommonJS
-		module.exports = function (root, $) {
-			if ( ! root ) {
-				// CommonJS environments without a window global must pass a
-				// root. This will give an error otherwise
-				root = window;
-			}
-
+		var jq = require('jquery');
+		var cjsRequires = function (root, $) {
 			if ( ! $.fn.dataTable ) {
 				require('datatables.net')(root, $);
 			}
-
-			return factory( $, root, root.document );
 		};
+
+		if (typeof window !== 'undefined') {
+			module.exports = function (root, $) {
+				if ( ! root ) {
+					// CommonJS environments without a window global must pass a
+					// root. This will give an error otherwise
+					root = window;
+				}
+
+				if ( ! $ ) {
+					$ = jq( root );
+				}
+
+				cjsRequires( root, $ );
+				return factory( $, root, root.document );
+			};
+		}
+		else {
+			cjsRequires( window, jq );
+			module.exports = factory( jq, window, window.document );
+		}
 	}
 	else {
 		// Browser

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 1 - 1
static/vendor/datatables/jquery.dataTables.min.js


+ 1 - 1
static/vendor/datatables/responsive.bootstrap4.min.js

@@ -1,4 +1,4 @@
 /*! Bootstrap 4 integration for DataTables' Responsive
  * © SpryMedia Ltd - datatables.net/license
  */
-!function(a){"function"==typeof define&&define.amd?define(["jquery","datatables.net-bs4","datatables.net-responsive"],function(e){return a(e,window,document)}):"object"==typeof exports?module.exports=function(e,d){return e=e||window,(d=d||("undefined"!=typeof window?require("jquery"):require("jquery")(e))).fn.dataTable||require("datatables.net-bs4")(e,d),d.fn.dataTable||require("datatables.net-responsive")(e,d),a(d,0,e.document)}:a(jQuery,window,document)}(function(i,e,d,a){"use strict";var n=i.fn.dataTable,t=n.Responsive.display,s=t.modal,l=i('<div class="modal fade dtr-bs-modal" role="dialog"><div class="modal-dialog" role="document"><div class="modal-content"><div class="modal-header"><button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button></div><div class="modal-body"/></div></div></div>');return t.modal=function(o){return function(e,d,a){var n,t;i.fn.modal?d||(o&&o.header&&(t=(n=l.find("div.modal-header")).find("button").detach(),n.empty().append('<h4 class="modal-title">'+o.header(e)+"</h4>").append(t)),l.find("div.modal-body").empty().append(a()),l.appendTo("body").modal()):s(e,d,a)}},n});
+!function(a){var n,o;"function"==typeof define&&define.amd?define(["jquery","datatables.net-bs4","datatables.net-responsive"],function(e){return a(e,window,document)}):"object"==typeof exports?(n=require("jquery"),o=function(e,d){d.fn.dataTable||require("datatables.net-bs4")(e,d),d.fn.dataTable.Responsive||require("datatables.net-responsive")(e,d)},"undefined"!=typeof window?module.exports=function(e,d){return e=e||window,d=d||n(e),o(e,d),a(d,0,e.document)}:(o(window,n),module.exports=a(n,window,window.document))):a(jQuery,window,document)}(function(i,e,d,a){"use strict";var n=i.fn.dataTable,o=n.Responsive.display,s=o.modal,l=i('<div class="modal fade dtr-bs-modal" role="dialog"><div class="modal-dialog" role="document"><div class="modal-content"><div class="modal-header"><button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button></div><div class="modal-body"/></div></div></div>');return o.modal=function(t){return function(e,d,a){var n,o;i.fn.modal?d||(t&&t.header&&(o=(n=l.find("div.modal-header")).find("button").detach(),n.empty().append('<h4 class="modal-title">'+t.header(e)+"</h4>").append(o)),l.find("div.modal-body").empty().append(a()),l.appendTo("body").modal()):s(e,d,a)}},n});

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/vendor/video-js/video-js.min.css


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 1 - 1
static/vendor/video-js/video.min.js


Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels