Compare commits

..

8 Commits

Author SHA1 Message Date
Elias Schneider
2587058ded release: 0.9.0 2024-10-18 08:23:55 +02:00
Elias Schneider
ff06bf0b34 feat: add environment variable to change the caddy port in Docker 2024-10-18 08:23:06 +02:00
Elias Schneider
11ed661f86 feat: use improve table for users and audit logs 2024-10-16 08:49:19 +02:00
Elias Schneider
29748cc6c7 fix: allow copy to clipboard for client secret 2024-10-13 15:55:17 +02:00
Elias Schneider
edfb99d221 release: 0.8.1 2024-10-11 20:53:47 +02:00
Elias Schneider
282ff82b0c fix: add key id to JWK 2024-10-11 20:52:31 +02:00
Elias Schneider
9d5f83da78 chore: dump dependencies 2024-10-04 14:15:04 +02:00
Elias Schneider
896da812a3 ci/cd: create dummy GeoLite2 City database for e2e tests 2024-10-04 12:17:32 +02:00
16 changed files with 703 additions and 917 deletions

View File

@@ -16,6 +16,9 @@ jobs:
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Create dummy GeoLite2 City database
run: touch ./backend/GeoLite2-City.mmdb
- name: Build Docker Image
run: docker build -t stonith404/pocket-id .
- name: Run Docker Container

View File

@@ -1 +1 @@
0.8.0
0.9.0

View File

@@ -1,3 +1,23 @@
## [](https://github.com/stonith404/pocket-id/compare/v0.8.1...v) (2024-10-18)
### Features
* add environment variable to change the caddy port in Docker ([ff06bf0](https://github.com/stonith404/pocket-id/commit/ff06bf0b34496ce472ba6d3ebd4ea249f21c0ec3))
* use improve table for users and audit logs ([11ed661](https://github.com/stonith404/pocket-id/commit/11ed661f86a512f78f66d604a10c1d47d39f2c39))
### Bug Fixes
* allow copy to clipboard for client secret ([29748cc](https://github.com/stonith404/pocket-id/commit/29748cc6c7b7e5a6b54bfe837e0b1a98fa1ad594))
## [](https://github.com/stonith404/pocket-id/compare/v0.8.0...v) (2024-10-11)
### Bug Fixes
* add key id to JWK ([282ff82](https://github.com/stonith404/pocket-id/commit/282ff82b0c7e2414b3528c8ca325758245b8ae61))
## [](https://github.com/stonith404/pocket-id/compare/v0.7.1...v) (2024-10-04)

View File

@@ -68,7 +68,7 @@ Required tools:
cd ..
pm2 start pocket-id-backend --name pocket-id-backend
# Optional: Download the GeoLite2 city database.
# Optional: Download the GeoLite2 city database.
# If not downloaded the ip location in the audit log will be empty.
MAXMIND_LICENSE_KEY=<your-key> sh scripts/download-ip-database.sh
@@ -151,15 +151,16 @@ docker compose up -d
### Environment variables
| Variable | Default Value | Recommended to change | Description |
| ---------------------- | ----------------------- | --------------------- | --------------------------------------------- |
| `PUBLIC_APP_URL` | `http://localhost` | yes | The URL where you will access the app. |
| `TRUST_PROXY` | `false` | yes | Whether the app is behind a reverse proxy. |
| `DB_PATH` | `data/pocket-id.db` | no | The path to the SQLite database. |
| `UPLOAD_PATH` | `data/uploads` | no | The path where the uploaded files are stored. |
| `INTERNAL_BACKEND_URL` | `http://localhost:8080` | no | The URL where the backend is accessible. |
| `PORT` | `3000` | no | The port on which the frontend should listen. |
| `BACKEND_PORT` | `8080` | no | The port on which the backend should listen. |
| Variable | Default Value | Recommended to change | Description |
| ---------------------- | ----------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PUBLIC_APP_URL` | `http://localhost` | yes | The URL where you will access the app. |
| `TRUST_PROXY` | `false` | yes | Whether the app is behind a reverse proxy. |
| `DB_PATH` | `data/pocket-id.db` | no | The path to the SQLite database. |
| `UPLOAD_PATH` | `data/uploads` | no | The path where the uploaded files are stored. |
| `INTERNAL_BACKEND_URL` | `http://localhost:8080` | no | The URL where the backend is accessible. |
| `CADDY_PORT` | `80` | no | The port on which Caddy should listen. Caddy is only active inside the Docker container. If you want to change the exposed port of the container then you sould change this variable. |
| `PORT` | `3000` | no | The port on which the frontend should listen. |
| `BACKEND_PORT` | `8080` | no | The port on which the backend should listen. |
## Contribute

View File

@@ -7,23 +7,23 @@ require (
github.com/fxamacker/cbor/v2 v2.7.0
github.com/gin-contrib/cors v1.7.2
github.com/gin-gonic/gin v1.10.0
github.com/go-co-op/gocron/v2 v2.11.0
github.com/go-playground/validator/v10 v10.22.0
github.com/go-webauthn/webauthn v0.11.1
github.com/go-co-op/gocron/v2 v2.12.1
github.com/go-playground/validator/v10 v10.22.1
github.com/go-webauthn/webauthn v0.11.2
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/golang-migrate/migrate/v4 v4.17.1
github.com/golang-migrate/migrate/v4 v4.18.1
github.com/google/uuid v1.6.0
github.com/joho/godotenv v1.5.1
github.com/mileusna/useragent v1.3.4
github.com/mileusna/useragent v1.3.5
github.com/oschwald/maxminddb-golang/v2 v2.0.0-beta.1
golang.org/x/crypto v0.26.0
golang.org/x/crypto v0.27.0
golang.org/x/time v0.6.0
gorm.io/driver/sqlite v1.5.6
gorm.io/gorm v1.25.11
gorm.io/gorm v1.25.12
)
require (
github.com/bytedance/sonic v1.12.1 // indirect
github.com/bytedance/sonic v1.12.3 // indirect
github.com/bytedance/sonic/loader v0.2.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
@@ -31,7 +31,7 @@ require (
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-webauthn/x v0.1.12 // indirect
github.com/go-webauthn/x v0.1.14 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/google/go-tpm v0.9.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
@@ -44,22 +44,21 @@ require (
github.com/kr/pretty v0.3.1 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.22 // indirect
github.com/mattn/go-sqlite3 v1.14.23 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.uber.org/atomic v1.11.0 // indirect
golang.org/x/arch v0.9.0 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/sys v0.23.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/arch v0.10.0 // indirect
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
golang.org/x/net v0.29.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.18.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -1,5 +1,5 @@
github.com/bytedance/sonic v1.12.1 h1:jWl5Qz1fy7X1ioY74WqO0KjAMtAGQs4sYnjiEBiyX24=
github.com/bytedance/sonic v1.12.1/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
github.com/bytedance/sonic v1.12.3 h1:W2MGa7RCU1QTeYRTPE3+88mVC0yXmsRQRChiyVocVjU=
github.com/bytedance/sonic v1.12.3/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM=
github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
@@ -23,26 +23,26 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-co-op/gocron/v2 v2.11.0 h1:IOowNA6SzwdRFnD4/Ol3Kj6G2xKfsoiiGq2Jhhm9bvE=
github.com/go-co-op/gocron/v2 v2.11.0/go.mod h1:xY7bJxGazKam1cz04EebrlP4S9q4iWdiAylMGP3jY9w=
github.com/go-co-op/gocron/v2 v2.12.1 h1:dCIIBFbzhWKdgXeEifBjHPzgQ1hoWhjS4289Hjjy1uw=
github.com/go-co-op/gocron/v2 v2.12.1/go.mod h1:xY7bJxGazKam1cz04EebrlP4S9q4iWdiAylMGP3jY9w=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-webauthn/webauthn v0.11.1 h1:5G/+dg91/VcaJHTtJUfwIlNJkLwbJCcnUc4W8VtkpzA=
github.com/go-webauthn/webauthn v0.11.1/go.mod h1:YXRm1WG0OtUyDFaVAgB5KG7kVqW+6dYCJ7FTQH4SxEE=
github.com/go-webauthn/x v0.1.12 h1:RjQ5cvApzyU/xLCiP+rub0PE4HBZsLggbxGR5ZpUf/A=
github.com/go-webauthn/x v0.1.12/go.mod h1:XlRcGkNH8PT45TfeJYc6gqpOtiOendHhVmnOxh+5yHs=
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-webauthn/webauthn v0.11.2 h1:Fgx0/wlmkClTKlnOsdOQ+K5HcHDsDcYIvtYmfhEOSUc=
github.com/go-webauthn/webauthn v0.11.2/go.mod h1:aOtudaF94pM71g3jRwTYYwQTG1KyTILTcZqN1srkmD0=
github.com/go-webauthn/x v0.1.14 h1:1wrB8jzXAofojJPAaRxnZhRgagvLGnLjhCAwg3kTpT0=
github.com/go-webauthn/x v0.1.14/go.mod h1:UuVvFZ8/NbOnkDz3y1NaxtUN87pmtpC1PQ+/5BBQRdc=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMnX7wLTPnnYO4=
github.com/golang-migrate/migrate/v4 v4.17.1/go.mod h1:m8hinFyWBn0SA4QKHuKh175Pm9wjmxj3S2Mia7dbXzM=
github.com/golang-migrate/migrate/v4 v4.18.1 h1:JML/k+t4tpHCpQTCAD62Nu43NUFzHY4CV3uAuvHGC+Y=
github.com/golang-migrate/migrate/v4 v4.18.1/go.mod h1:HAX6m3sQgcdO81tdjn5exv20+3Kb13cmGli1hrD6hks=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-tpm v0.9.1 h1:0pGc4X//bAlmZzMKf8iz6IsDo1nYTbYJ6FZN/rg4zdM=
@@ -79,10 +79,10 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mileusna/useragent v1.3.4 h1:MiuRRuvGjEie1+yZHO88UBYg8YBC/ddF6T7F56i3PCk=
github.com/mileusna/useragent v1.3.4/go.mod h1:3d8TOmwL/5I8pJjyVDteHtgDGcefrFUX4ccGOMKNYYc=
github.com/mattn/go-sqlite3 v1.14.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWtuw0=
github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mileusna/useragent v1.3.5 h1:SJM5NzBmh/hO+4LGeATKpaEX9+b4vcGg2qXGLiNGDws=
github.com/mileusna/useragent v1.3.5/go.mod h1:3d8TOmwL/5I8pJjyVDteHtgDGcefrFUX4ccGOMKNYYc=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -92,26 +92,24 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/oschwald/maxminddb-golang/v2 v2.0.0-beta.1 h1:UihPOz+oIJ5X0JsO7wEkL50fheCODsoZ9r86mJWfNMc=
github.com/oschwald/maxminddb-golang/v2 v2.0.0-beta.1/go.mod h1:vPpFrres6g9B5+meBwAd9xnp335KFcLEFW7EqJxBHy0=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
@@ -124,20 +122,20 @@ go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/arch v0.9.0 h1:ub9TgUInamJ8mrZIGlBG6/4TqWeMszd4N8lNorbrr6k=
golang.org/x/arch v0.9.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/arch v0.10.0 h1:S3huipmSclq3PJMNe76NGwkBR504WFkQ5dhzWzP8ZW8=
golang.org/x/arch v0.10.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
@@ -150,6 +148,6 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE=
gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=

View File

@@ -18,7 +18,7 @@ func NewOidcController(group *gin.RouterGroup, jwtAuthMiddleware *middleware.Jwt
group.POST("/oidc/authorize", jwtAuthMiddleware.Add(false), oc.authorizeHandler)
group.POST("/oidc/authorize/new-client", jwtAuthMiddleware.Add(false), oc.authorizeNewClientHandler)
group.POST("/oidc/token", oc.createIDTokenHandler)
group.POST("/oidc/token", oc.createTokensHandler)
group.GET("/oidc/userinfo", oc.userInfoHandler)
group.GET("/oidc/clients", jwtAuthMiddleware.Add(true), oc.listClientsHandler)
@@ -91,7 +91,7 @@ func (oc *OidcController) authorizeNewClientHandler(c *gin.Context) {
c.JSON(http.StatusOK, response)
}
func (oc *OidcController) createIDTokenHandler(c *gin.Context) {
func (oc *OidcController) createTokensHandler(c *gin.Context) {
var input dto.OidcIdTokenDto
if err := c.ShouldBind(&input); err != nil {

View File

@@ -3,6 +3,7 @@ package service
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
@@ -51,6 +52,7 @@ type AccessTokenJWTClaims struct {
}
type JWK struct {
Kid string `json:"kid"`
Kty string `json:"kty"`
Use string `json:"use"`
Alg string `json:"alg"`
@@ -98,7 +100,15 @@ func (s *JwtService) GenerateAccessToken(user model.User) (string, error) {
},
IsAdmin: user.IsAdmin,
}
kid, err := s.generateKeyID(s.publicKey)
if err != nil {
return "", errors.New("failed to generate key ID: " + err.Error())
}
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claim)
token.Header["kid"] = kid
return token.SignedString(s.privateKey)
}
@@ -137,9 +147,17 @@ func (s *JwtService) GenerateIDToken(userClaims map[string]interface{}, clientID
claims["nonce"] = nonce
}
kid, err := s.generateKeyID(s.publicKey)
if err != nil {
return "", errors.New("failed to generate key ID: " + err.Error())
}
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
token.Header["kid"] = kid
return token.SignedString(s.privateKey)
}
func (s *JwtService) GenerateOauthAccessToken(user model.User, clientID string) (string, error) {
claim := jwt.RegisteredClaims{
Subject: user.ID,
@@ -148,7 +166,15 @@ func (s *JwtService) GenerateOauthAccessToken(user model.User, clientID string)
Audience: jwt.ClaimStrings{clientID},
Issuer: common.EnvConfig.AppURL,
}
kid, err := s.generateKeyID(s.publicKey)
if err != nil {
return "", errors.New("failed to generate key ID: " + err.Error())
}
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claim)
token.Header["kid"] = kid
return token.SignedString(s.privateKey)
}
@@ -174,7 +200,13 @@ func (s *JwtService) GetJWK() (JWK, error) {
return JWK{}, errors.New("public key is not initialized")
}
kid, err := s.generateKeyID(s.publicKey)
if err != nil {
return JWK{}, err
}
jwk := JWK{
Kid: kid,
Kty: "RSA",
Use: "sig",
Alg: "RS256",
@@ -185,6 +217,25 @@ func (s *JwtService) GetJWK() (JWK, error) {
return jwk, nil
}
// GenerateKeyID generates a Key ID for the public key using the first 8 bytes of the SHA-256 hash of the public key.
func (s *JwtService) generateKeyID(publicKey *rsa.PublicKey) (string, error) {
pubASN1, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
return "", errors.New("failed to marshal public key: " + err.Error())
}
// Compute SHA-256 hash of the public key
hash := sha256.New()
hash.Write(pubASN1)
hashed := hash.Sum(nil)
// Truncate the hash to the first 8 bytes for a shorter Key ID
shortHash := hashed[:8]
// Return Base64 encoded truncated hash as Key ID
return base64.RawURLEncoding.EncodeToString(shortHash), nil
}
// generateKeys generates a new RSA key pair and saves them to the specified paths.
func (s *JwtService) generateKeys() error {
if err := os.MkdirAll(filepath.Dir(privateKeyPath), 0700); err != nil {

File diff suppressed because it is too large Load Diff

View File

@@ -12,46 +12,46 @@
"format": "prettier --write ."
},
"devDependencies": {
"@playwright/test": "^1.46.1",
"@sveltejs/adapter-auto": "^3.2.4",
"@sveltejs/adapter-node": "^5.2.2",
"@sveltejs/kit": "^2.5.24",
"@playwright/test": "^1.47.2",
"@sveltejs/adapter-auto": "^3.2.5",
"@sveltejs/adapter-node": "^5.2.5",
"@sveltejs/kit": "^2.6.1",
"@sveltejs/vite-plugin-svelte": "^3.1.2",
"@types/eslint": "^9.6.0",
"@types/jsonwebtoken": "^9.0.6",
"@types/node": "^22.5.0",
"@types/eslint": "^9.6.1",
"@types/jsonwebtoken": "^9.0.7",
"@types/node": "^22.7.4",
"autoprefixer": "^10.4.20",
"cbor-js": "^0.1.0",
"eslint": "^9.9.1",
"eslint": "^9.11.1",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.40.0",
"globals": "^15.9.0",
"postcss": "^8.4.41",
"eslint-plugin-svelte": "^2.44.1",
"globals": "^15.10.0",
"postcss": "^8.4.47",
"prettier": "^3.3.3",
"prettier-plugin-svelte": "^3.2.6",
"prettier-plugin-tailwindcss": "^0.6.6",
"svelte": "^5.0.0-next.1",
"svelte-check": "^3.8.6",
"tailwindcss": "^3.4.10",
"prettier-plugin-svelte": "^3.2.7",
"prettier-plugin-tailwindcss": "^0.6.8",
"svelte": "^5.0.0-next.262",
"svelte-check": "^4.0.4",
"tailwindcss": "^3.4.13",
"tslib": "^2.7.0",
"typescript": "^5.5.4",
"typescript-eslint": "^8.2.0",
"vite": "^5.4.2"
"typescript": "^5.6.2",
"typescript-eslint": "^8.8.0",
"vite": "^5.4.8"
},
"type": "module",
"dependencies": {
"@simplewebauthn/browser": "^10.0.0",
"axios": "^1.7.5",
"bits-ui": "^0.21.15",
"axios": "^1.7.7",
"bits-ui": "^0.21.16",
"clsx": "^2.1.1",
"crypto": "^1.0.1",
"formsnap": "^1.0.1",
"jsonwebtoken": "^9.0.2",
"lucide-svelte": "^0.435.0",
"lucide-svelte": "^0.447.0",
"mode-watcher": "^0.4.1",
"svelte-sonner": "^0.3.27",
"sveltekit-superforms": "^2.17.0",
"tailwind-merge": "^2.5.2",
"svelte-sonner": "^0.3.28",
"sveltekit-superforms": "^2.19.0",
"tailwind-merge": "^2.5.3",
"tailwind-variants": "^0.2.1",
"zod": "^3.23.8"
}

View File

@@ -11,12 +11,14 @@
let {
items,
selectedIds = $bindable(),
withoutSearch = false,
fetchItems,
columns,
rows
}: {
items: Paginated<T>;
selectedIds?: string[];
withoutSearch?: boolean;
fetchItems: (search: string, page: number, limit: number) => Promise<Paginated<T>>;
columns: (string | { label: string; hidden?: boolean })[];
rows: Snippet<[{ item: T }]>;
@@ -65,12 +67,14 @@
</script>
<div class="w-full">
{#if !withoutSearch}
<Input
class="mb-4 max-w-sm"
placeholder={'Search...'}
type="text"
oninput={(e) => onSearch((e.target as HTMLInputElement).value)}
/>
{/if}
<Table.Root>
<Table.Header>
<Table.Row>

View File

@@ -26,7 +26,7 @@
'OIDC Discovery URL': `https://${$page.url.hostname}/.well-known/openid-configuration`,
'Token URL': `https://${$page.url.hostname}/api/oidc/token`,
'Userinfo URL': `https://${$page.url.hostname}/api/oidc/userinfo`,
'Certificate URL': `https://${$page.url.hostname}/.well-known/jwks.json`,
'Certificate URL': `https://${$page.url.hostname}/.well-known/jwks.json`
};
async function updateClient(updatedClient: OidcClientCreateWithLogo) {
@@ -95,10 +95,16 @@
</div>
<div class="mb-2 mt-1 flex items-center">
<Label class="w-44">Client secret</Label>
<span class="text-muted-foreground text-sm" data-testid="client-secret"
>{$clientSecretStore ?? '••••••••••••••••••••••••••••••••'}</span
>
{#if !$clientSecretStore}
{#if $clientSecretStore}
<CopyToClipboard value={$clientSecretStore}>
<span class="text-muted-foreground text-sm" data-testid="client-secret">
{$clientSecretStore}
</span>
</CopyToClipboard>
{:else}
<span class="text-muted-foreground text-sm" data-testid="client-secret"
>••••••••••••••••••••••••••••••••</span
>
<Button
class="ml-2"
onclick={createClientSecret}

View File

@@ -1,16 +1,14 @@
<script lang="ts">
import { page } from '$app/stores';
import AdvancedTable from '$lib/components/advanced-table.svelte';
import { openConfirmDialog } from '$lib/components/confirm-dialog/';
import { Badge } from '$lib/components/ui/badge/index';
import { Button } from '$lib/components/ui/button';
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
import { Input } from '$lib/components/ui/input';
import * as Pagination from '$lib/components/ui/pagination';
import * as Table from '$lib/components/ui/table';
import UserService from '$lib/services/user-service';
import type { Paginated, PaginationRequest } from '$lib/types/pagination.type';
import type { Paginated } from '$lib/types/pagination.type';
import type { User } from '$lib/types/user.type';
import { debounced } from '$lib/utils/debounce-util';
import { axiosErrorToast } from '$lib/utils/error-util';
import { LucideLink, LucidePencil, LucideTrash } from 'lucide-svelte';
import Ellipsis from 'lucide-svelte/icons/ellipsis';
@@ -19,23 +17,17 @@
let { users: initialUsers }: { users: Paginated<User> } = $props();
let users = $state<Paginated<User>>(initialUsers);
let oneTimeLink = $state<string | null>(null);
$effect(() => {
users = initialUsers;
});
let oneTimeLink = $state<string | null>(null);
const userService = new UserService();
let pagination = $state<PaginationRequest>({
page: 1,
limit: 10
});
let search = $state('');
const debouncedSearch = debounced(async (searchValue: string) => {
users = await userService.list(searchValue, pagination);
}, 400);
function fetchItems(search: string, page: number, limit: number) {
return userService.list(search, { page, limit });
}
async function deleteUser(user: User) {
openConfirmDialog({
@@ -47,7 +39,7 @@
action: async () => {
try {
await userService.remove(user.id);
users = await userService.list(search, pagination);
users = await userService.list();
} catch (e) {
axiosErrorToast(e);
}
@@ -67,105 +59,51 @@
}
</script>
<Input
type="search"
placeholder="Search users"
bind:value={search}
on:input={(e) => debouncedSearch((e.target as HTMLInputElement).value)}
/>
<Table.Root>
<Table.Header>
<Table.Row>
<Table.Head class="hidden md:table-cell">First name</Table.Head>
<Table.Head class="hidden md:table-cell">Last name</Table.Head>
<Table.Head>Email</Table.Head>
<Table.Head>Username</Table.Head>
<Table.Head class="hidden lg:table-cell">Role</Table.Head>
<Table.Head>
<span class="sr-only">Actions</span>
</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{#if users.data.length === 0}
<Table.Row>
<Table.Cell colspan={6} class="text-center">No users found</Table.Cell>
</Table.Row>
{:else}
{#each users.data as user}
<Table.Row>
<Table.Cell class="hidden md:table-cell">{user.firstName}</Table.Cell>
<Table.Cell class="hidden md:table-cell">{user.lastName}</Table.Cell>
<Table.Cell>{user.email}</Table.Cell>
<Table.Cell>{user.username}</Table.Cell>
<Table.Cell class="hidden lg:table-cell">
<Badge variant="outline">{user.isAdmin ? 'Admin' : 'User'}</Badge>
</Table.Cell>
<Table.Cell>
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild let:builder>
<Button aria-haspopup="true" size="icon" variant="ghost" builders={[builder]}>
<Ellipsis class="h-4 w-4" />
<span class="sr-only">Toggle menu</span>
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end">
<DropdownMenu.Item on:click={() => createOneTimeAccessToken(user.id)}
><LucideLink class="mr-2 h-4 w-4" />One-time link</DropdownMenu.Item
>
<DropdownMenu.Item href="/settings/admin/users/{user.id}"
><LucidePencil class="mr-2 h-4 w-4" /> Edit</DropdownMenu.Item
>
<DropdownMenu.Item
class="text-red-500 focus:!text-red-700"
on:click={() => deleteUser(user)}
><LucideTrash class="mr-2 h-4 w-4" />Delete</DropdownMenu.Item
>
</DropdownMenu.Content>
</DropdownMenu.Root>
</Table.Cell>
</Table.Row>
{/each}
{/if}
</Table.Body>
</Table.Root>
{#if users?.data?.length ?? 0 > 0}
<Pagination.Root
class="mt-5"
count={users.pagination.totalItems}
perPage={pagination.limit}
onPageChange={async (p) =>
(users = await userService.list(search, {
page: p,
limit: pagination.limit
}))}
bind:page={users.pagination.currentPage}
let:pages
let:currentPage
>
<Pagination.Content class="flex justify-end">
<Pagination.Item>
<Pagination.PrevButton />
</Pagination.Item>
{#each pages as page (page.key)}
{#if page.type === 'ellipsis'}
<Pagination.Item>
<Pagination.Ellipsis />
</Pagination.Item>
{:else}
<Pagination.Item>
<Pagination.Link {page} isActive={users.pagination.currentPage === page.value}>
{page.value}
</Pagination.Link>
</Pagination.Item>
{/if}
{/each}
<Pagination.Item>
<Pagination.NextButton />
</Pagination.Item>
</Pagination.Content>
</Pagination.Root>
{/if}
<AdvancedTable
items={users}
{fetchItems}
columns={[
'First name',
'Last name',
'Email',
'Username',
'Role',
{ label: 'Actions', hidden: true }
]}
withoutSearch
>
{#snippet rows({ item })}
<Table.Cell>{item.firstName}</Table.Cell>
<Table.Cell>{item.lastName}</Table.Cell>
<Table.Cell>{item.email}</Table.Cell>
<Table.Cell>{item.username}</Table.Cell>
<Table.Cell class="hidden lg:table-cell">
<Badge variant="outline">{item.isAdmin ? 'Admin' : 'User'}</Badge>
</Table.Cell>
<Table.Cell>
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild let:builder>
<Button aria-haspopup="true" size="icon" variant="ghost" builders={[builder]}>
<Ellipsis class="h-4 w-4" />
<span class="sr-only">Toggle menu</span>
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end">
<DropdownMenu.Item on:click={() => createOneTimeAccessToken(item.id)}
><LucideLink class="mr-2 h-4 w-4" />One-time link</DropdownMenu.Item
>
<DropdownMenu.Item href="/settings/admin/users/{item.id}"
><LucidePencil class="mr-2 h-4 w-4" /> Edit</DropdownMenu.Item
>
<DropdownMenu.Item
class="text-red-500 focus:!text-red-700"
on:click={() => deleteUser(item)}
><LucideTrash class="mr-2 h-4 w-4" />Delete</DropdownMenu.Item
>
</DropdownMenu.Content>
</DropdownMenu.Root>
</Table.Cell>
{/snippet}
</AdvancedTable>
<OneTimeLinkModal {oneTimeLink} />

View File

@@ -1,20 +1,22 @@
<script lang="ts">
import AdvancedTable from '$lib/components/advanced-table.svelte';
import { Badge } from '$lib/components/ui/badge';
import * as Pagination from '$lib/components/ui/pagination';
import * as Table from '$lib/components/ui/table';
import AuditLogService from '$lib/services/audit-log-service';
import type { AuditLog } from '$lib/types/audit-log.type';
import type { Paginated, PaginationRequest } from '$lib/types/pagination.type';
import type { Paginated } from '$lib/types/pagination.type';
let { auditLogs: initialAuditLog }: { auditLogs: Paginated<AuditLog> } = $props();
let auditLogs = $state<Paginated<AuditLog>>(initialAuditLog);
const auditLogService = new AuditLogService();
let pagination = $state<PaginationRequest>({
page: 1,
limit: 15
});
async function fetchItems(search: string, page: number, limit: number) {
return await auditLogService.list({
page,
limit
});
}
function toFriendlyEventString(event: string) {
const words = event.split('_');
@@ -25,73 +27,22 @@
}
</script>
<Table.Root>
<Table.Header class="whitespace-nowrap">
<Table.Row>
<Table.Head>Time</Table.Head>
<Table.Head>Event</Table.Head>
<Table.Head>Approximate Location</Table.Head>
<Table.Head>IP Address</Table.Head>
<Table.Head>Device</Table.Head>
<Table.Head>Client</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body class="whitespace-nowrap">
{#if auditLogs.data.length === 0}
<Table.Row>
<Table.Cell colspan={6} class="text-center">No logs found</Table.Cell>
</Table.Row>
{:else}
{#each auditLogs.data as auditLog}
<Table.Row>
<Table.Cell>{new Date(auditLog.createdAt).toLocaleString()}</Table.Cell>
<Table.Cell>
<Badge variant="outline">{toFriendlyEventString(auditLog.event)}</Badge>
</Table.Cell>
<Table.Cell>{auditLog.city && auditLog.country ? `${auditLog.city}, ${auditLog.country}` : 'Unknown'}</Table.Cell>
<Table.Cell>{auditLog.ipAddress}</Table.Cell>
<Table.Cell>{auditLog.device}</Table.Cell>
<Table.Cell>{auditLog.data.clientName}</Table.Cell>
</Table.Row>
{/each}
{/if}
</Table.Body>
</Table.Root>
{#if auditLogs?.data?.length ?? 0 > 0}
<Pagination.Root
class="mt-5"
count={auditLogs.pagination.totalItems}
perPage={pagination.limit}
onPageChange={async (p) =>
(auditLogs = await auditLogService.list({
page: p,
limit: pagination.limit
}))}
bind:page={auditLogs.pagination.currentPage}
let:pages
let:currentPage
>
<Pagination.Content class="flex justify-end">
<Pagination.Item>
<Pagination.PrevButton />
</Pagination.Item>
{#each pages as page (page.key)}
{#if page.type === 'ellipsis'}
<Pagination.Item>
<Pagination.Ellipsis />
</Pagination.Item>
{:else}
<Pagination.Item>
<Pagination.Link {page} isActive={auditLogs.pagination.currentPage === page.value}>
{page.value}
</Pagination.Link>
</Pagination.Item>
{/if}
{/each}
<Pagination.Item>
<Pagination.NextButton />
</Pagination.Item>
</Pagination.Content>
</Pagination.Root>
{/if}
<AdvancedTable
items={auditLogs}
{fetchItems}
columns={['Time', 'Event', 'Approximate Location', 'IP Address', 'Device', 'Client']}
withoutSearch
>
{#snippet rows({ item })}
<Table.Cell>{new Date(item.createdAt).toLocaleString()}</Table.Cell>
<Table.Cell>
<Badge variant="outline">{toFriendlyEventString(item.event)}</Badge>
</Table.Cell>
<Table.Cell
>{item.city && item.country ? `${item.city}, ${item.country}` : 'Unknown'}</Table.Cell
>
<Table.Cell>{item.ipAddress}</Table.Cell>
<Table.Cell>{item.device}</Table.Cell>
<Table.Cell>{item.data.clientName}</Table.Cell>
{/snippet}
</AdvancedTable>

View File

@@ -1,4 +1,4 @@
:80 {
:{$CADDY_PORT:80} {
reverse_proxy /api/* http://localhost:{$BACKEND_PORT:8080}
reverse_proxy /.well-known/* http://localhost:{$BACKEND_PORT:8080}
reverse_proxy /* http://localhost:{$PORT:3000}

View File

@@ -1,4 +1,4 @@
:80 {
:{$CADDY_PORT:80} {
reverse_proxy /api/* http://localhost:{$BACKEND_PORT:8080} {
trusted_proxies 0.0.0.0/0
}