瀏覽代碼

check that the jwt token is used by the same IP for which it

Signed-off-by: Nicola Murino <[email protected]>
Nicola Murino 3 年之前
父節點
當前提交
d955ddcef9
共有 13 個文件被更改,包括 154 次插入118 次删除
  1. 1 1
      .github/workflows/development.yml
  2. 1 1
      .github/workflows/release.yml
  3. 1 4
      README.md
  4. 18 18
      go.mod
  5. 36 36
      go.sum
  6. 8 6
      httpd/auth_utils.go
  7. 33 1
      httpd/httpd_test.go
  8. 7 7
      httpd/internal_test.go
  9. 20 21
      httpd/middleware.go
  10. 1 1
      httpd/oidc.go
  11. 1 1
      httpd/oidc_test.go
  12. 24 19
      httpd/server.go
  13. 3 2
      sftpd/server.go

+ 1 - 1
.github/workflows/development.yml

@@ -382,7 +382,7 @@ jobs:
           gzip output/man/man1/*
           cp sftpgo output/
 
-      - uses: uraimo/run-on-arch-action@v2.1.1
+      - uses: uraimo/run-on-arch-action@v2
         if: ${{ matrix.arch != 'amd64' }}
         name: Build for ${{ matrix.arch }}
         id: build

+ 1 - 1
.github/workflows/release.yml

@@ -326,7 +326,7 @@ jobs:
         env:
           SFTPGO_VERSION: ${{ steps.get_version.outputs.SFTPGO_VERSION }}
 
-      - uses: uraimo/run-on-arch-action@v2.1.1
+      - uses: uraimo/run-on-arch-action@v2
         if: ${{ matrix.arch != 'amd64' }}
         name: Build for ${{ matrix.arch }}
         id: build

+ 1 - 4
README.md

@@ -11,8 +11,6 @@ Several storage backends are supported: local filesystem, encrypted local filesy
 
 ## Features
 
-<details>
-
 - Support for serving local filesystem, encrypted local filesystem, S3 Compatible Object Storage, Google Cloud Storage, Azure Blob Storage or other SFTP accounts over SFTP/SCP/FTP/WebDAV.
 - Virtual folders are supported: a virtual folder can use any of the supported storage backends. So you can have, for example, an S3 user that exposes a GCS bucket (or part of it) on a specified path and an encrypted local filesystem on another one. Virtual folders can be private or shared among multiple users, for shared virtual folders you can define different quota limits for each user.
 - Configurable [custom commands and/or HTTP hooks](./docs/custom-actions.md) on file upload, pre-upload, download, pre-download, delete, pre-delete, rename, mkdir, rmdir on SSH commands and on user add, update and delete.
@@ -42,6 +40,7 @@ Several storage backends are supported: local filesystem, encrypted local filesy
 - Per-user and per-directory shell like patterns filters: files can be allowed, denied or hidden based on shell like patterns.
 - Automatically terminating idle connections.
 - Automatic blocklist management using the built-in [defender](./docs/defender.md).
+- Geo-IP filtering using a [plugin](https://github.com/sftpgo/sftpgo-plugin-geoipfilter).
 - Atomic uploads are configurable.
 - Per-user files/folders ownership mapping: you can map all the users to the system account that runs SFTPGo (all platforms are supported) or you can run SFTPGo as root user and map each user or group of users to a different system account (\*NIX only).
 - Support for Git repositories over SSH.
@@ -60,8 +59,6 @@ Several storage backends are supported: local filesystem, encrypted local filesy
 - Log files are accurate and they are saved in the easily parsable JSON format ([more information](./docs/logs.md)).
 - SFTPGo supports a [plugin system](./docs/plugins.md) and therefore can be extended using external plugins.
 
-</details>
-
 ## Platforms
 
 SFTPGo is developed and tested on Linux. After each commit, the code is automatically built and tested on Linux, macOS and Windows using a [GitHub Action](./.github/workflows/development.yml). The test cases are regularly manually executed and passed on FreeBSD. Other *BSD variants should work too.

+ 18 - 18
go.mod

@@ -8,12 +8,12 @@ require (
 	github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0
 	github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962
 	github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387
-	github.com/aws/aws-sdk-go-v2 v1.16.0
-	github.com/aws/aws-sdk-go-v2/config v1.15.1
-	github.com/aws/aws-sdk-go-v2/credentials v1.11.0
-	github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.1
-	github.com/aws/aws-sdk-go-v2/service/s3 v1.26.1
-	github.com/aws/aws-sdk-go-v2/service/sts v1.16.1
+	github.com/aws/aws-sdk-go-v2 v1.16.1
+	github.com/aws/aws-sdk-go-v2/config v1.15.2
+	github.com/aws/aws-sdk-go-v2/credentials v1.11.1
+	github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.2
+	github.com/aws/aws-sdk-go-v2/service/s3 v1.26.2
+	github.com/aws/aws-sdk-go-v2/service/sts v1.16.2
 	github.com/cockroachdb/cockroach-go/v2 v2.2.8
 	github.com/coreos/go-oidc/v3 v3.1.0
 	github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001
@@ -75,17 +75,17 @@ require (
 	cloud.google.com/go/compute v1.5.0 // indirect
 	cloud.google.com/go/iam v0.3.0 // indirect
 	github.com/Azure/azure-sdk-for-go/sdk/internal v0.9.1 // indirect
-	github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.0 // indirect
-	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.1 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.7 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.1 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/ini v1.3.8 // indirect
-	github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.0 // indirect
-	github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.1 // indirect
-	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.1 // indirect
-	github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.1 // indirect
-	github.com/aws/aws-sdk-go-v2/service/sso v1.11.1 // indirect
-	github.com/aws/smithy-go v1.11.1 // indirect
+	github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1 // indirect
+	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.2 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.8 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.2 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/ini v1.3.9 // indirect
+	github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1 // indirect
+	github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.2 // indirect
+	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.2 // indirect
+	github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.2 // indirect
+	github.com/aws/aws-sdk-go-v2/service/sso v1.11.2 // indirect
+	github.com/aws/smithy-go v1.11.2 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/boombuler/barcode v1.0.1 // indirect
 	github.com/cenkalti/backoff v2.2.1+incompatible // indirect
@@ -147,7 +147,7 @@ require (
 	golang.org/x/tools v0.1.10 // indirect
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
-	google.golang.org/genproto v0.0.0-20220323144105-ec3c684e5b14 // indirect
+	google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb // indirect
 	google.golang.org/grpc v1.45.0 // indirect
 	google.golang.org/protobuf v1.28.0 // indirect
 	gopkg.in/ini.v1 v1.66.4 // indirect

+ 36 - 36
go.sum

@@ -137,51 +137,51 @@ github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZo
 github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
 github.com/aws/aws-sdk-go v1.40.34/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
 github.com/aws/aws-sdk-go-v2 v1.9.0/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
-github.com/aws/aws-sdk-go-v2 v1.16.0 h1:cBAYjiiexRAg9v2z9vb6IdxAa7ef4KCtjW7w7e3GxGo=
-github.com/aws/aws-sdk-go-v2 v1.16.0/go.mod h1:lJYcuZZEHWNIb6ugJjbQY1fykdoobWbOS7kJYb4APoI=
-github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.0 h1:J/tiyHbl07LL4/1i0rFrW5pbLMvo7M6JrekBUNpLeT4=
-github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.0/go.mod h1:ohZjRmiToJ4NybwWTGOCbzlUQU8dxSHxYKzuX7k5l6Y=
+github.com/aws/aws-sdk-go-v2 v1.16.1 h1:udzee98w8H6ikRgtFdVN9JzzYEbi/quFfSvduZETJIU=
+github.com/aws/aws-sdk-go-v2 v1.16.1/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU=
+github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1 h1:SdK4Ppk5IzLs64ZMvr6MrSficMtjY2oS0WOORXTlxwU=
+github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1/go.mod h1:n8Bs1ElDD2wJ9kCRTczA83gYbBmjSwZp3umc6zF4EeM=
 github.com/aws/aws-sdk-go-v2/config v1.7.0/go.mod h1:w9+nMZ7soXCe5nT46Ri354SNhXDQ6v+V5wqDjnZE+GY=
-github.com/aws/aws-sdk-go-v2/config v1.15.1 h1:hTIZFepYESYyowQUBo47lu69WSxsYqGUILY9Nu8+7pY=
-github.com/aws/aws-sdk-go-v2/config v1.15.1/go.mod h1:MZHGbuW2WnqIOQQBKu2ZkhTjuutZSTnn56TDq4QyydE=
+github.com/aws/aws-sdk-go-v2/config v1.15.2 h1:4oGcm1yqqtTc2Z8YpwehwjSiBA3TR0iZbFCgNlXcVFQ=
+github.com/aws/aws-sdk-go-v2/config v1.15.2/go.mod h1:S1p1xf7DGVp0srNq0BakyxfirOldPQeDVlx7+fllyok=
 github.com/aws/aws-sdk-go-v2/credentials v1.4.0/go.mod h1:dgGR+Qq7Wjcd4AOAW5Rf5Tnv3+x7ed6kETXyS9WCuAY=
-github.com/aws/aws-sdk-go-v2/credentials v1.11.0 h1:gc4Uhs80s60nmLon5Z4JXWinX2BkAGT0YROoUT8h8U4=
-github.com/aws/aws-sdk-go-v2/credentials v1.11.0/go.mod h1:EdV1ZFgtZ4XM5RDHWcRWK8H+xW5duNVBqWj2oLu7tRo=
+github.com/aws/aws-sdk-go-v2/credentials v1.11.1 h1:uR323+M7ca3v2GKXbFSwWbNA3kLjjFzaalL6W4rpB9s=
+github.com/aws/aws-sdk-go-v2/credentials v1.11.1/go.mod h1:pYrHWfKUoWTmbr+xTf6ZoWeyyvLAQ5BPT3aL+nKlTpE=
 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.5.0/go.mod h1:CpNzHK9VEFUCknu50kkB8z58AH2B5DvPP7ea1LHve/Y=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.1 h1:F9Je1nq5YXfMOv6451NHvMf6U0iTWeMnsG0MMIQoUmk=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.1/go.mod h1:Yph0XsTbQ5GGZ2+mO1a03P/SO9fdX3t1nejIp2tq79g=
-github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.1 h1:2lGuOytsLs4N2z1UmZ9s7BuhHMcZxNkm612YsLHK/8g=
-github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.1/go.mod h1:hkzjqerOQhhBGAL/DdmKLsP8hGZvtyvukCLvrU5twz4=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.7 h1:KUErSJgdqmqAPBWAp6Zx9CjL0YXfytXJeXcsWnuCM1c=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.7/go.mod h1:oB9nZcxH1cGq7NPGurVJwxrO2vmJ9mmEBayCwcAlmT8=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.1 h1:feVfa9eJonhJiss7g51ikjNB2DrUzbNZNvPL8pw/54k=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.1/go.mod h1:K4vz7lRYCyLYpYAMCLObODahFgARdD3YVa0MvQte9Co=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.2 h1:+AULPOLHEDjH2TcNKpixl4gt26hFOdlUuuisZUBFczA=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.2/go.mod h1:jmsqNRVo2XlUTNXG/NF7hM7o2gd2jhfg8vdJ135d4XA=
+github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.2 h1:PTFTblDWY/HdQ6ix5+to1uLARgLLuYbzKGLQnIdE5Us=
+github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.2/go.mod h1:j4OwU2Gb7yaQaidJRpdlIRYX93jBCWhVYIgMlPjf89o=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.8 h1:CDaO90VZVBAL1sK87S5oSPIrp7yZqORv1hPIi2UsTMk=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.8/go.mod h1:LnTQMTqbKsbtt+UI5+wPsB7jedW+2ZgozoPG8k6cMxg=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.2 h1:XXR3cdOcKRCTZf6ctcqpMf+go1BdzTm6+T9Ul5zxcMI=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.2/go.mod h1:1x4ZP3Z8odssdhuLI+/1Tqw6Pt/VAaP4Tr8EUxHvPXE=
 github.com/aws/aws-sdk-go-v2/internal/ini v1.2.2/go.mod h1:BQV0agm+JEhqR+2RT5e1XTFIDcAAV0eW6z2trp+iduw=
-github.com/aws/aws-sdk-go-v2/internal/ini v1.3.8 h1:adr3PfiggFtqgFofAMUFCtdvwzpf3QxPES4ezK4M3iI=
-github.com/aws/aws-sdk-go-v2/internal/ini v1.3.8/go.mod h1:wLbQYt36AJqaRZUQiCNXzbtkNigyPfKHrotHuIDiCy8=
-github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.0 h1:uhb7moM7VjqIEpWzTpCvceLDSwrWpaleXm39OnVjuLE=
-github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.0/go.mod h1:pA2St3Pu2Ldy6fBPY45Azoh1WBG4oS7eIKOd4XN7Meg=
-github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.1 h1:iq77O8kBduROlzJ6mhN8zqxXxctDZ1PnWY0kyCfYMGc=
-github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.1/go.mod h1:1R7cjoiEG9cgMCBpIOuCyZWT0Dn87vNUeAaC8Reiaow=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.3.9 h1:8umg6LSQ/b0+ZTq+Ro8K7VLGVwd7kiYQtIACpf2N/Yo=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.3.9/go.mod h1:kASRBzoVW4I8KUmGCjsowAqVor9QU9DuTUABVducrTY=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1 h1:T4pFel53bkHjL2mMo+4DKE6r6AuoZnM0fg7k1/ratr4=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1/go.mod h1:GeUru+8VzrTXV/83XyMJ80KpH8xO89VPoUileyNQ+tc=
+github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.2 h1:VoMBHtQZygRs8mcQNDrfmn09vFH2ccjf79nGJ0xuUfo=
+github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.2/go.mod h1:2Fzbfwkx7z4yue1Lz6KDSKG84UpOcUKFl3VAtSF/gcg=
 github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.0/go.mod h1:R1KK+vY8AfalhG1AOu5e35pOD2SdoPKQCFLTvnxiohk=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.1 h1:B/SPX7J+Y0Yrcjv60Nhbh1gC2uBN47SfN8JYre6Mp4M=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.1/go.mod h1:2Hhr9Eh1gJzDatwACX/ozAZ/ljq5vzvPRu5cdu25tzc=
-github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.1 h1:sjASzLVAG9okVe5HgLur36itaOY4UC90VZNXAtcn0+s=
-github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.1/go.mod h1:ELGakx1J1qEJqWkwiN0jh3CMDTN6v17kOgN8kZgo6LQ=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.2 h1:RrN7V0r8+lUUKZM4OAoCOIZqjPLZPOl6wuwMd2QIryI=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.2/go.mod h1:7hwSi01X5Yj9H0qLQljrn8OSdLwwSym1aQCfGn1tDQQ=
+github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.2 h1:yxr9h06slG9fdVmO3CpBVuFVD73AeUHLmBxhCr3T3+E=
+github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.2/go.mod h1:rvV/Jr4T8H3kMMw/9fFQw9kxqb70YKihA0oWuUFd3K8=
 github.com/aws/aws-sdk-go-v2/service/kms v1.5.0/go.mod h1:w7JuP9Oq1IKMFQPkNe3V6s9rOssXzOVEMNEqK1L1bao=
-github.com/aws/aws-sdk-go-v2/service/s3 v1.26.1 h1:FSoVdUKHxAzZKXuemm+7vVj3eOUY5u01SSwwkfhWpqA=
-github.com/aws/aws-sdk-go-v2/service/s3 v1.26.1/go.mod h1:ZS+sYP2DfetYS7n42MLAoz8x50T/w5su+rZmhaqTubg=
+github.com/aws/aws-sdk-go-v2/service/s3 v1.26.2 h1:Op/A+5+D1K0bmwH3BStYbp/7iod9Rdfm9898A0qYxLc=
+github.com/aws/aws-sdk-go-v2/service/s3 v1.26.2/go.mod h1:Ao1W746VIMdV1WhEkjeVa5JzlaE1JkxJ46facHX9kzs=
 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.6.0/go.mod h1:B+7C5UKdVq1ylkI/A6O8wcurFtaux0R1njePNPtKwoA=
 github.com/aws/aws-sdk-go-v2/service/ssm v1.10.0/go.mod h1:4dXS5YNqI3SNbetQ7X7vfsMlX6ZnboJA2dulBwJx7+g=
 github.com/aws/aws-sdk-go-v2/service/sso v1.4.0/go.mod h1:+1fpWnL96DL23aXPpMGbsmKe8jLTEfbjuQoA4WS1VaA=
-github.com/aws/aws-sdk-go-v2/service/sso v1.11.1 h1:DyHctRsJIAWIvom1Itb4T84D2jwpIu+KIi3d0SFaswg=
-github.com/aws/aws-sdk-go-v2/service/sso v1.11.1/go.mod h1:CvFTucADIx7U/M44vjLs/ZttpQHdpxwK+62+dUGhDeY=
+github.com/aws/aws-sdk-go-v2/service/sso v1.11.2 h1:8fVz1c9B/63w7O0kxbrCTT69iV4DgXnFumarPCZ3Cns=
+github.com/aws/aws-sdk-go-v2/service/sso v1.11.2/go.mod h1:GdCj3+FzI3D5tauOzz8n3YjN70XvgZz82PVVtJXmDds=
 github.com/aws/aws-sdk-go-v2/service/sts v1.7.0/go.mod h1:0qcSMCyASQPN2sk/1KQLQ2Fh6yq8wm0HSDAimPhzCoM=
-github.com/aws/aws-sdk-go-v2/service/sts v1.16.1 h1:xsOtPAvHqhvQvBza5ohaUcfq1LceH2lZKMUGZJKiZiM=
-github.com/aws/aws-sdk-go-v2/service/sts v1.16.1/go.mod h1:Aq2/Qggh2oemSfyHH+EO4UBbgWG6zFCXLHYI4ILTY7w=
+github.com/aws/aws-sdk-go-v2/service/sts v1.16.2 h1:qgK5htfKByTiPxS/diZ/mTCfDwGAVuyjRdqu6VoCh80=
+github.com/aws/aws-sdk-go-v2/service/sts v1.16.2/go.mod h1:RoMljzynmRe3jyOsRgqIMTzyhpAv6XNxu549M1X4Mdo=
 github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
-github.com/aws/smithy-go v1.11.1 h1:IQ+lPZVkSM3FRtyaDox41R8YS6iwPMYIreejOgPW49g=
-github.com/aws/smithy-go v1.11.1/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM=
+github.com/aws/smithy-go v1.11.2 h1:eG/N+CcUMAvsdffgMvjMKwfyDzIkjM6pfxMJ8Mzc6mE=
+github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM=
 github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@@ -1131,8 +1131,8 @@ google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2
 google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
 google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
 google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
-google.golang.org/genproto v0.0.0-20220323144105-ec3c684e5b14 h1:17TOyVD+9MLIDtDJW9PdtMuVT7gNLEkN+G/xFYjZmr8=
-google.golang.org/genproto v0.0.0-20220323144105-ec3c684e5b14/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
+google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb h1:0m9wktIpOxGw+SSKmydXWB3Z3GTfcPP6+q75HCQa6HI=
+google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
 google.golang.org/grpc v1.8.0/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=

+ 8 - 6
httpd/auth_utils.go

@@ -159,11 +159,11 @@ func (c *jwtTokenClaims) hasPerm(perm string) bool {
 	return util.IsStringInSlice(perm, c.Permissions)
 }
 
-func (c *jwtTokenClaims) createToken(tokenAuth *jwtauth.JWTAuth, audience tokenAudience) (jwt.Token, string, error) {
+func (c *jwtTokenClaims) createToken(tokenAuth *jwtauth.JWTAuth, audience tokenAudience, ip string) (jwt.Token, string, error) {
 	claims := c.asMap()
 	now := time.Now().UTC()
 
-	claims[jwt.JwtIDKey] = xid.New().String()
+	claims[jwt.JwtIDKey] = fmt.Sprintf("%s%s", xid.New().String(), ip)
 	claims[jwt.NotBeforeKey] = now.Add(-30 * time.Second)
 	claims[jwt.ExpirationKey] = now.Add(tokenDuration)
 	claims[jwt.AudienceKey] = audience
@@ -171,8 +171,8 @@ func (c *jwtTokenClaims) createToken(tokenAuth *jwtauth.JWTAuth, audience tokenA
 	return tokenAuth.Encode(claims)
 }
 
-func (c *jwtTokenClaims) createTokenResponse(tokenAuth *jwtauth.JWTAuth, audience tokenAudience) (map[string]interface{}, error) {
-	token, tokenString, err := c.createToken(tokenAuth, audience)
+func (c *jwtTokenClaims) createTokenResponse(tokenAuth *jwtauth.JWTAuth, audience tokenAudience, ip string) (map[string]interface{}, error) {
+	token, tokenString, err := c.createToken(tokenAuth, audience, ip)
 	if err != nil {
 		return nil, err
 	}
@@ -184,8 +184,10 @@ func (c *jwtTokenClaims) createTokenResponse(tokenAuth *jwtauth.JWTAuth, audienc
 	return response, nil
 }
 
-func (c *jwtTokenClaims) createAndSetCookie(w http.ResponseWriter, r *http.Request, tokenAuth *jwtauth.JWTAuth, audience tokenAudience) error {
-	resp, err := c.createTokenResponse(tokenAuth, audience)
+func (c *jwtTokenClaims) createAndSetCookie(w http.ResponseWriter, r *http.Request, tokenAuth *jwtauth.JWTAuth,
+	audience tokenAudience, ip string,
+) error {
+	resp, err := c.createTokenResponse(tokenAuth, audience, ip)
 	if err != nil {
 		return err
 	}

+ 33 - 1
httpd/httpd_test.go

@@ -8713,6 +8713,38 @@ func TestWebClientMaxConnections(t *testing.T) {
 	common.Config.MaxTotalConnections = oldValue
 }
 
+func TestTokenInvalidIPAddress(t *testing.T) {
+	user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
+	assert.NoError(t, err)
+
+	webToken, err := getJWTWebClientTokenFromTestServerWithAddr(defaultUsername, defaultPassword, "1.1.1.1")
+	assert.NoError(t, err)
+
+	req, err := http.NewRequest(http.MethodGet, webClientFilesPath, nil)
+	assert.NoError(t, err)
+	req.RemoteAddr = "1.1.1.2"
+	req.RequestURI = webClientFilesPath
+	setJWTCookieForReq(req, webToken)
+	rr := executeRequest(req)
+	checkResponseCode(t, http.StatusFound, rr)
+
+	apiToken, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
+	assert.NoError(t, err)
+
+	req, err = http.NewRequest(http.MethodGet, userDirsPath+"/?path=%2F", nil)
+	assert.NoError(t, err)
+	req.RemoteAddr = "2.2.2.2"
+	setBearerForReq(req, apiToken)
+	rr = executeRequest(req)
+	checkResponseCode(t, http.StatusUnauthorized, rr)
+	assert.Contains(t, rr.Body.String(), "Your token is not valid")
+
+	_, err = httpdtest.RemoveUser(user, http.StatusOK)
+	assert.NoError(t, err)
+	err = os.RemoveAll(user.GetHomeDir())
+	assert.NoError(t, err)
+}
+
 func TestDefender(t *testing.T) {
 	oldConfig := config.GetCommonConfig()
 
@@ -17500,7 +17532,7 @@ func getJWTAPIUserTokenFromTestServer(username, password string) (string, error)
 	req.SetBasicAuth(username, password)
 	rr := executeRequest(req)
 	if rr.Code != http.StatusOK {
-		return "", fmt.Errorf("unexpected  status code %v", rr.Code)
+		return "", fmt.Errorf("unexpected status code %v", rr.Code)
 	}
 	responseHolder := make(map[string]interface{})
 	err := render.DecodeJSON(rr.Body, &responseHolder)

+ 7 - 7
httpd/internal_test.go

@@ -629,7 +629,7 @@ func TestUpdateWebAdminInvalidClaims(t *testing.T) {
 		Permissions: admin.Permissions,
 		Signature:   admin.GetSignature(),
 	}
-	token, err := c.createTokenResponse(server.tokenAuth, tokenAudienceWebAdmin)
+	token, err := c.createTokenResponse(server.tokenAuth, tokenAudienceWebAdmin, "")
 	assert.NoError(t, err)
 
 	form := make(url.Values)
@@ -746,7 +746,7 @@ func TestCreateTokenError(t *testing.T) {
 	}
 	req, _ := http.NewRequest(http.MethodGet, tokenPath, nil)
 
-	server.generateAndSendToken(rr, req, admin)
+	server.generateAndSendToken(rr, req, admin, "")
 	assert.Equal(t, http.StatusInternalServerError, rr.Code)
 
 	rr = httptest.NewRecorder()
@@ -778,7 +778,7 @@ func TestCreateTokenError(t *testing.T) {
 	assert.Equal(t, http.StatusOK, rr.Code, rr.Body.String())
 	req, _ = http.NewRequest(http.MethodPost, webAdminSetupPath, nil)
 	rr = httptest.NewRecorder()
-	server.loginAdmin(rr, req, &admin, false, nil)
+	server.loginAdmin(rr, req, &admin, false, nil, "")
 	// req with no POST body
 	req, _ = http.NewRequest(http.MethodGet, webAdminLoginPath+"?a=a%C3%AO%GG", nil)
 	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
@@ -1991,7 +1991,7 @@ func TestWebUserInvalidClaims(t *testing.T) {
 		Permissions: nil,
 		Signature:   user.GetSignature(),
 	}
-	token, err := c.createTokenResponse(server.tokenAuth, tokenAudienceWebClient)
+	token, err := c.createTokenResponse(server.tokenAuth, tokenAudienceWebClient, "")
 	assert.NoError(t, err)
 
 	req, _ := http.NewRequest(http.MethodGet, webClientFilesPath, nil)
@@ -2066,7 +2066,7 @@ func TestInvalidClaims(t *testing.T) {
 		Permissions: nil,
 		Signature:   user.GetSignature(),
 	}
-	token, err := c.createTokenResponse(server.tokenAuth, tokenAudienceWebClient)
+	token, err := c.createTokenResponse(server.tokenAuth, tokenAudienceWebClient, "")
 	assert.NoError(t, err)
 	form := make(url.Values)
 	form.Set(csrfFormToken, createCSRFToken())
@@ -2086,7 +2086,7 @@ func TestInvalidClaims(t *testing.T) {
 		Permissions: nil,
 		Signature:   admin.GetSignature(),
 	}
-	token, err = c.createTokenResponse(server.tokenAuth, tokenAudienceWebAdmin)
+	token, err = c.createTokenResponse(server.tokenAuth, tokenAudienceWebAdmin, "")
 	assert.NoError(t, err)
 	form = make(url.Values)
 	form.Set(csrfFormToken, createCSRFToken())
@@ -2134,7 +2134,7 @@ func TestSigningKey(t *testing.T) {
 		Permissions: nil,
 		Signature:   user.GetSignature(),
 	}
-	token, err := c.createTokenResponse(server1.tokenAuth, tokenAudienceWebClient)
+	token, err := c.createTokenResponse(server1.tokenAuth, tokenAudienceWebClient, "")
 	assert.NoError(t, err)
 	accessToken := token["access_token"].(string)
 	assert.NotEmpty(t, accessToken)

+ 20 - 21
httpd/middleware.go

@@ -42,33 +42,29 @@ func validateJWTToken(w http.ResponseWriter, r *http.Request, audience tokenAudi
 
 	isAPIToken := (audience == tokenAudienceAPI || audience == tokenAudienceAPIUser)
 
-	if err != nil || token == nil {
-		logger.Debug(logSender, "", "error getting jwt token: %v", err)
+	doRedirect := func(message string, err error) {
 		if isAPIToken {
-			sendAPIResponse(w, r, err, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
+			sendAPIResponse(w, r, err, message, http.StatusUnauthorized)
 		} else {
 			http.Redirect(w, r, redirectPath, http.StatusFound)
 		}
+	}
+
+	if err != nil || token == nil {
+		logger.Debug(logSender, "", "error getting jwt token: %v", err)
+		doRedirect(http.StatusText(http.StatusUnauthorized), err)
 		return errInvalidToken
 	}
 
 	err = jwt.Validate(token)
 	if err != nil {
 		logger.Debug(logSender, "", "error validating jwt token: %v", err)
-		if isAPIToken {
-			sendAPIResponse(w, r, err, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
-		} else {
-			http.Redirect(w, r, redirectPath, http.StatusFound)
-		}
+		doRedirect(http.StatusText(http.StatusUnauthorized), err)
 		return errInvalidToken
 	}
 	if isTokenInvalidated(r) {
 		logger.Debug(logSender, "", "the token has been invalidated")
-		if isAPIToken {
-			sendAPIResponse(w, r, nil, "Your token is no longer valid", http.StatusUnauthorized)
-		} else {
-			http.Redirect(w, r, redirectPath, http.StatusFound)
-		}
+		doRedirect("Your token is no longer valid", nil)
 		return errInvalidToken
 	}
 	// a user with a partial token will be always redirected to the appropriate two factor auth page
@@ -77,11 +73,13 @@ func validateJWTToken(w http.ResponseWriter, r *http.Request, audience tokenAudi
 	}
 	if !util.IsStringInSlice(audience, token.Audience()) {
 		logger.Debug(logSender, "", "the token is not valid for audience %#v", audience)
-		if isAPIToken {
-			sendAPIResponse(w, r, nil, "Your token audience is not valid", http.StatusUnauthorized)
-		} else {
-			http.Redirect(w, r, redirectPath, http.StatusFound)
-		}
+		doRedirect("Your token audience is not valid", nil)
+		return errInvalidToken
+	}
+	ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
+	if ipAddr != "" && !strings.Contains(token.JwtID(), ipAddr) {
+		logger.Debug(logSender, "", "the token with id %#v is not valid for the ip address %#v", token.JwtID(), ipAddr)
+		doRedirect("Your token is not valid", nil)
 		return errInvalidToken
 	}
 	return nil
@@ -382,7 +380,8 @@ func authenticateAdminWithAPIKey(username, keyID string, tokenAuth *jwtauth.JWTA
 	if !admin.Filters.AllowAPIKeyAuth {
 		return fmt.Errorf("API key authentication disabled for admin %#v", admin.Username)
 	}
-	if err := admin.CanLogin(util.GetIPFromRemoteAddress(r.RemoteAddr)); err != nil {
+	ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
+	if err := admin.CanLogin(ipAddr); err != nil {
 		return err
 	}
 	c := jwtTokenClaims{
@@ -392,7 +391,7 @@ func authenticateAdminWithAPIKey(username, keyID string, tokenAuth *jwtauth.JWTA
 		APIKeyID:    keyID,
 	}
 
-	resp, err := c.createTokenResponse(tokenAuth, tokenAudienceAPI)
+	resp, err := c.createTokenResponse(tokenAuth, tokenAudienceAPI, ipAddr)
 	if err != nil {
 		return err
 	}
@@ -446,7 +445,7 @@ func authenticateUserWithAPIKey(username, keyID string, tokenAuth *jwtauth.JWTAu
 		APIKeyID:    keyID,
 	}
 
-	resp, err := c.createTokenResponse(tokenAuth, tokenAudienceAPIUser)
+	resp, err := c.createTokenResponse(tokenAuth, tokenAudienceAPIUser, ipAddr)
 	if err != nil {
 		updateLoginMetrics(&user, dataprovider.LoginMethodPassword, ipAddr, common.ErrInternalFailure)
 		return err

+ 1 - 1
httpd/oidc.go

@@ -496,7 +496,7 @@ func (s *httpdServer) oidcTokenAuthenticator(audience tokenAudience) func(next h
 				Username:    token.Username,
 				Permissions: token.Permissions,
 			}
-			_, tokenString, err := jwtTokenClaims.createToken(s.tokenAuth, audience)
+			_, tokenString, err := jwtTokenClaims.createToken(s.tokenAuth, audience, util.GetIPFromRemoteAddress(r.RemoteAddr))
 			if err != nil {
 				setFlashMessage(w, r, "Unable to create cookie")
 				if audience == tokenAudienceWebAdmin {

+ 1 - 1
httpd/oidc_test.go

@@ -666,7 +666,7 @@ func TestSkipOIDCAuth(t *testing.T) {
 	jwtTokenClaims := jwtTokenClaims{
 		Username: "user",
 	}
-	_, tokenString, err := jwtTokenClaims.createToken(server.tokenAuth, tokenAudienceWebClient)
+	_, tokenString, err := jwtTokenClaims.createToken(server.tokenAuth, tokenAudienceWebClient, "")
 	assert.NoError(t, err)
 	rr := httptest.NewRecorder()
 	r, err := http.NewRequest(http.MethodGet, webClientLogoutPath, nil)

+ 24 - 19
httpd/server.go

@@ -413,6 +413,7 @@ func (s *httpdServer) handleWebAdminTwoFactorRecoveryPost(w http.ResponseWriter,
 		s.renderTwoFactorRecoveryPage(w, "Two factory authentication is not enabled")
 		return
 	}
+	ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
 	for idx, code := range admin.Filters.RecoveryCodes {
 		if err := code.Secret.Decrypt(); err != nil {
 			s.renderInternalServerErrorPage(w, r, fmt.Errorf("unable to decrypt recovery code: %w", err))
@@ -424,13 +425,13 @@ func (s *httpdServer) handleWebAdminTwoFactorRecoveryPost(w http.ResponseWriter,
 				return
 			}
 			admin.Filters.RecoveryCodes[idx].Used = true
-			err = dataprovider.UpdateAdmin(&admin, dataprovider.ActionExecutorSelf, util.GetIPFromRemoteAddress(r.RemoteAddr))
+			err = dataprovider.UpdateAdmin(&admin, dataprovider.ActionExecutorSelf, ipAddr)
 			if err != nil {
 				logger.Warn(logSender, "", "unable to set the recovery code %#v as used: %v", recoveryCode, err)
 				s.renderInternalServerErrorPage(w, r, errors.New("unable to set the recovery code as used"))
 				return
 			}
-			s.loginAdmin(w, r, &admin, true, s.renderTwoFactorRecoveryPage)
+			s.loginAdmin(w, r, &admin, true, s.renderTwoFactorRecoveryPage, ipAddr)
 			return
 		}
 	}
@@ -478,7 +479,7 @@ func (s *httpdServer) handleWebAdminTwoFactorPost(w http.ResponseWriter, r *http
 		s.renderTwoFactorPage(w, "Invalid authentication code")
 		return
 	}
-	s.loginAdmin(w, r, &admin, true, s.renderTwoFactorPage)
+	s.loginAdmin(w, r, &admin, true, s.renderTwoFactorPage, util.GetIPFromRemoteAddress(r.RemoteAddr))
 }
 
 func (s *httpdServer) handleWebAdminLoginPost(w http.ResponseWriter, r *http.Request) {
@@ -497,12 +498,13 @@ func (s *httpdServer) handleWebAdminLoginPost(w http.ResponseWriter, r *http.Req
 		s.renderAdminLoginPage(w, err.Error())
 		return
 	}
-	admin, err := dataprovider.CheckAdminAndPass(username, password, util.GetIPFromRemoteAddress(r.RemoteAddr))
+	ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
+	admin, err := dataprovider.CheckAdminAndPass(username, password, ipAddr)
 	if err != nil {
 		s.renderAdminLoginPage(w, err.Error())
 		return
 	}
-	s.loginAdmin(w, r, &admin, false, s.renderAdminLoginPage)
+	s.loginAdmin(w, r, &admin, false, s.renderAdminLoginPage, ipAddr)
 }
 
 func (s *httpdServer) renderAdminLoginPage(w http.ResponseWriter, error string) {
@@ -585,7 +587,7 @@ func (s *httpdServer) handleWebAdminPasswordResetPost(w http.ResponseWriter, r *
 		return
 	}
 
-	s.loginAdmin(w, r, admin, false, s.renderResetPwdPage)
+	s.loginAdmin(w, r, admin, false, s.renderResetPwdPage, util.GetIPFromRemoteAddress(r.RemoteAddr))
 }
 
 func (s *httpdServer) handleWebAdminSetupPost(w http.ResponseWriter, r *http.Request) {
@@ -629,12 +631,13 @@ func (s *httpdServer) handleWebAdminSetupPost(w http.ResponseWriter, r *http.Req
 		Status:      1,
 		Permissions: []string{dataprovider.PermAdminAny},
 	}
-	err = dataprovider.AddAdmin(&admin, username, util.GetIPFromRemoteAddress(r.RemoteAddr))
+	ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
+	err = dataprovider.AddAdmin(&admin, username, ipAddr)
 	if err != nil {
 		s.renderAdminSetupPage(w, r, username, err.Error())
 		return
 	}
-	s.loginAdmin(w, r, &admin, false, nil)
+	s.loginAdmin(w, r, &admin, false, nil, ipAddr)
 }
 
 func (s *httpdServer) loginUser(
@@ -655,7 +658,7 @@ func (s *httpdServer) loginUser(
 		audience = tokenAudienceWebClientPartial
 	}
 
-	err := c.createAndSetCookie(w, r, s.tokenAuth, audience)
+	err := c.createAndSetCookie(w, r, s.tokenAuth, audience, ipAddr)
 	if err != nil {
 		logger.Warn(logSender, connectionID, "unable to set user login cookie %v", err)
 		updateLoginMetrics(user, dataprovider.LoginMethodPassword, ipAddr, common.ErrInternalFailure)
@@ -676,7 +679,7 @@ func (s *httpdServer) loginUser(
 
 func (s *httpdServer) loginAdmin(
 	w http.ResponseWriter, r *http.Request, admin *dataprovider.Admin,
-	isSecondFactorAuth bool, errorFunc func(w http.ResponseWriter, error string),
+	isSecondFactorAuth bool, errorFunc func(w http.ResponseWriter, error string), ip string,
 ) {
 	c := jwtTokenClaims{
 		Username:    admin.Username,
@@ -689,7 +692,7 @@ func (s *httpdServer) loginAdmin(
 		audience = tokenAudienceWebAdminPartial
 	}
 
-	err := c.createAndSetCookie(w, r, s.tokenAuth, audience)
+	err := c.createAndSetCookie(w, r, s.tokenAuth, audience, ip)
 	if err != nil {
 		logger.Warn(logSender, "", "unable to set admin login cookie %v", err)
 		if errorFunc == nil {
@@ -803,7 +806,7 @@ func (s *httpdServer) generateAndSendUserToken(w http.ResponseWriter, r *http.Re
 		RequiredTwoFactorProtocols: user.Filters.TwoFactorAuthProtocols,
 	}
 
-	resp, err := c.createTokenResponse(s.tokenAuth, tokenAudienceAPIUser)
+	resp, err := c.createTokenResponse(s.tokenAuth, tokenAudienceAPIUser, ipAddr)
 
 	if err != nil {
 		updateLoginMetrics(&user, dataprovider.LoginMethodPassword, ipAddr, common.ErrInternalFailure)
@@ -823,7 +826,8 @@ func (s *httpdServer) getToken(w http.ResponseWriter, r *http.Request) {
 		sendAPIResponse(w, r, nil, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
 		return
 	}
-	admin, err := dataprovider.CheckAdminAndPass(username, password, util.GetIPFromRemoteAddress(r.RemoteAddr))
+	ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
+	admin, err := dataprovider.CheckAdminAndPass(username, password, ipAddr)
 	if err != nil {
 		w.Header().Set(common.HTTPAuthenticationHeader, basicRealm)
 		sendAPIResponse(w, r, err, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
@@ -854,17 +858,17 @@ func (s *httpdServer) getToken(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 
-	s.generateAndSendToken(w, r, admin)
+	s.generateAndSendToken(w, r, admin, ipAddr)
 }
 
-func (s *httpdServer) generateAndSendToken(w http.ResponseWriter, r *http.Request, admin dataprovider.Admin) {
+func (s *httpdServer) generateAndSendToken(w http.ResponseWriter, r *http.Request, admin dataprovider.Admin, ip string) {
 	c := jwtTokenClaims{
 		Username:    admin.Username,
 		Permissions: admin.Permissions,
 		Signature:   admin.GetSignature(),
 	}
 
-	resp, err := c.createTokenResponse(s.tokenAuth, tokenAudienceAPI)
+	resp, err := c.createTokenResponse(s.tokenAuth, tokenAudienceAPI, ip)
 
 	if err != nil {
 		sendAPIResponse(w, r, err, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@@ -914,7 +918,7 @@ func (s *httpdServer) refreshClientToken(w http.ResponseWriter, r *http.Request,
 
 	tokenClaims.Permissions = user.Filters.WebClient
 	logger.Debug(logSender, "", "cookie refreshed for user %#v", user.Username)
-	tokenClaims.createAndSetCookie(w, r, s.tokenAuth, tokenAudienceWebClient) //nolint:errcheck
+	tokenClaims.createAndSetCookie(w, r, s.tokenAuth, tokenAudienceWebClient, util.GetIPFromRemoteAddress(r.RemoteAddr)) //nolint:errcheck
 }
 
 func (s *httpdServer) refreshAdminToken(w http.ResponseWriter, r *http.Request, tokenClaims jwtTokenClaims) {
@@ -930,13 +934,14 @@ func (s *httpdServer) refreshAdminToken(w http.ResponseWriter, r *http.Request,
 		logger.Debug(logSender, "", "signature mismatch for admin %#v, unable to refresh cookie", admin.Username)
 		return
 	}
-	if !admin.CanLoginFromIP(util.GetIPFromRemoteAddress(r.RemoteAddr)) {
+	ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
+	if !admin.CanLoginFromIP(ipAddr) {
 		logger.Debug(logSender, "", "admin %#v cannot login from %v, unable to refresh cookie", admin.Username, r.RemoteAddr)
 		return
 	}
 	tokenClaims.Permissions = admin.Permissions
 	logger.Debug(logSender, "", "cookie refreshed for admin %#v", admin.Username)
-	tokenClaims.createAndSetCookie(w, r, s.tokenAuth, tokenAudienceWebAdmin) //nolint:errcheck
+	tokenClaims.createAndSetCookie(w, r, s.tokenAuth, tokenAudienceWebAdmin, ipAddr) //nolint:errcheck
 }
 
 func (s *httpdServer) updateContextFromCookie(r *http.Request) *http.Request {

+ 3 - 2
sftpd/server.go

@@ -4,6 +4,7 @@ import (
 	"bytes"
 	"encoding/hex"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"io"
 	"net"
@@ -509,7 +510,7 @@ func (c *Configuration) AcceptInboundConnection(conn net.Conn, config *ssh.Serve
 		go func(in <-chan *ssh.Request, counter int64) {
 			for req := range in {
 				ok := false
-				connID := fmt.Sprintf("%v_%v", connectionID, counter)
+				connID := fmt.Sprintf("%s_%d", connectionID, counter)
 
 				switch req.Type {
 				case "subsystem":
@@ -879,7 +880,7 @@ func (c *Configuration) validatePublicKeyCredentials(conn ssh.ConnMetadata, pubK
 			return nil, err
 		}
 		if !c.certChecker.IsUserAuthority(cert.SignatureKey) {
-			err = fmt.Errorf("ssh: certificate signed by unrecognized authority")
+			err = errors.New("ssh: certificate signed by unrecognized authority")
 			user.Username = conn.User()
 			updateLoginMetrics(&user, ipAddr, method, err)
 			return nil, err