Compare commits

..

10 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
Elias Schneider
d2b3b7647d release: 0.8.0 2024-10-04 12:11:43 +02:00
Elias Schneider
025378d14e feat: add location based on ip to the audit log 2024-10-04 12:11:10 +02:00
29 changed files with 817 additions and 920 deletions

View File

@@ -23,6 +23,9 @@ jobs:
username: ${{ secrets.DOCKER_REGISTRY_USERNAME }} username: ${{ secrets.DOCKER_REGISTRY_USERNAME }}
password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }} password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
- name: Download GeoLite2 City database
run: MAXMIND_LICENSE_KEY=${{ secrets.MAXMIND_LICENSE_KEY }} sh scripts/download-ip-database.sh
- name: Build and push - name: Build and push
uses: docker/build-push-action@v4 uses: docker/build-push-action@v4
with: with:

View File

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

3
.gitignore vendored
View File

@@ -34,4 +34,5 @@ vite.config.ts.timestamp-*
# Application specific # Application specific
data data
/frontend/tests/.auth /frontend/tests/.auth
pocket-id-backend pocket-id-backend
/backend/GeoLite2-City.mmdb

View File

@@ -1 +1 @@
0.7.1 0.9.0

View File

@@ -1,3 +1,30 @@
## [](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)
### Features
* add location based on ip to the audit log ([025378d](https://github.com/stonith404/pocket-id/commit/025378d14edd2d72da76e90799a0ccdd42cf672c))
## [](https://github.com/stonith404/pocket-id/compare/v0.7.0...v) (2024-10-03) ## [](https://github.com/stonith404/pocket-id/compare/v0.7.0...v) (2024-10-03)

View File

@@ -31,6 +31,7 @@ COPY --from=frontend-builder /app/frontend/package.json ./frontend/package.json
COPY --from=backend-builder /app/backend/pocket-id-backend ./backend/pocket-id-backend COPY --from=backend-builder /app/backend/pocket-id-backend ./backend/pocket-id-backend
COPY --from=backend-builder /app/backend/migrations ./backend/migrations COPY --from=backend-builder /app/backend/migrations ./backend/migrations
COPY --from=backend-builder /app/backend/GeoLite2-City.mmdb ./backend/GeoLite2-City.mmdb
COPY --from=backend-builder /app/backend/email-templates ./backend/email-templates COPY --from=backend-builder /app/backend/email-templates ./backend/email-templates
COPY --from=backend-builder /app/backend/images ./backend/images COPY --from=backend-builder /app/backend/images ./backend/images

View File

@@ -68,6 +68,10 @@ Required tools:
cd .. cd ..
pm2 start pocket-id-backend --name pocket-id-backend pm2 start pocket-id-backend --name pocket-id-backend
# 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
# Start the frontend # Start the frontend
cd ../frontend cd ../frontend
npm install npm install
@@ -94,7 +98,6 @@ You may need the following information:
- **Userinfo URL**: `https://<your-domain>/api/oidc/userinfo` - **Userinfo URL**: `https://<your-domain>/api/oidc/userinfo`
- **Certificate URL**: `https://<your-domain>/.well-known/jwks.json` - **Certificate URL**: `https://<your-domain>/.well-known/jwks.json`
- **OIDC Discovery URL**: `https://<your-domain>/.well-known/openid-configuration` - **OIDC Discovery URL**: `https://<your-domain>/.well-known/openid-configuration`
- **PKCE**: `false` as this is not supported yet.
- **Scopes**: At least `openid email`. Optionally you can add `profile` and `groups`. - **Scopes**: At least `openid email`. Optionally you can add `profile` and `groups`.
### Proxy Services with Pocket ID ### Proxy Services with Pocket ID
@@ -132,6 +135,9 @@ docker compose up -d
cd .. cd ..
pm2 start pocket-id-backend --name pocket-id-backend pm2 start pocket-id-backend --name pocket-id-backend
# Optional: Update the GeoLite2 city database
MAXMIND_LICENSE_KEY=<your-key> sh scripts/download-ip-database.sh
# Start the frontend # Start the frontend
cd ../frontend cd ../frontend
npm install npm install
@@ -145,15 +151,16 @@ docker compose up -d
### Environment variables ### Environment variables
| Variable | Default Value | Recommended to change | Description | | Variable | Default Value | Recommended to change | Description |
| ---------------------- | ----------------------- | --------------------- | --------------------------------------------- | | ---------------------- | ----------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PUBLIC_APP_URL` | `http://localhost` | yes | The URL where you will access the app. | | `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. | | `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. | | `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. | | `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. | | `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. | | `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. |
| `BACKEND_PORT` | `8080` | no | The port on which the backend should listen. | | `PORT` | `3000` | no | The port on which the frontend should listen. |
| `BACKEND_PORT` | `8080` | no | The port on which the backend should listen. |
## Contribute ## Contribute

View File

@@ -9,9 +9,15 @@
<div class="content"> <div class="content">
<h2>New Sign-In Detected</h2> <h2>New Sign-In Detected</h2>
<div class="grid"> <div class="grid">
{{ if and .Data.City .Data.Country }}
<div>
<p class="label">Approximate Location</p>
<p>{{ .Data.City }}, {{ .Data.Country }}</p>
</div>
{{ end }}
<div> <div>
<p class="label">IP Address</p> <p class="label">IP Address</p>
<p>{{ .Data.IPAddress}}</p> <p>{{ .Data.IPAddress }}</p>
</div> </div>
<div> <div>
<p class="label">Device</p> <p class="label">Device</p>
@@ -19,7 +25,7 @@
</div> </div>
<div> <div>
<p class="label">Sign-In Time</p> <p class="label">Sign-In Time</p>
<p>{{ .Data.DateTime.Format "2006-01-02 15:04:05 UTC"}}</p> <p>{{ .Data.DateTime.Format "2006-01-02 15:04:05 UTC" }}</p>
</div> </div>
</div> </div>
<p class="message"> <p class="message">
@@ -27,4 +33,4 @@
safely ignore this message. If not, please review your account and security settings. safely ignore this message. If not, please review your account and security settings.
</p> </p>
</div> </div>
{{ end -}} {{ end -}}

View File

@@ -2,6 +2,9 @@
New Sign-In Detected New Sign-In Detected
==================== ====================
{{ if and .Data.City .Data.Country }}
Approximate Location: {{ .Data.City }}, {{ .Data.Country }}
{{ end }}
IP Address: {{ .Data.IPAddress }} IP Address: {{ .Data.IPAddress }}
Device: {{ .Data.Device }} Device: {{ .Data.Device }}
Time: {{ .Data.DateTime.Format "2006-01-02 15:04:05 UTC"}} Time: {{ .Data.DateTime.Format "2006-01-02 15:04:05 UTC"}}

View File

@@ -7,22 +7,23 @@ require (
github.com/fxamacker/cbor/v2 v2.7.0 github.com/fxamacker/cbor/v2 v2.7.0
github.com/gin-contrib/cors v1.7.2 github.com/gin-contrib/cors v1.7.2
github.com/gin-gonic/gin v1.10.0 github.com/gin-gonic/gin v1.10.0
github.com/go-co-op/gocron/v2 v2.11.0 github.com/go-co-op/gocron/v2 v2.12.1
github.com/go-playground/validator/v10 v10.22.0 github.com/go-playground/validator/v10 v10.22.1
github.com/go-webauthn/webauthn v0.11.1 github.com/go-webauthn/webauthn v0.11.2
github.com/golang-jwt/jwt/v5 v5.2.1 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/google/uuid v1.6.0
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/mileusna/useragent v1.3.4 github.com/mileusna/useragent v1.3.5
golang.org/x/crypto v0.26.0 github.com/oschwald/maxminddb-golang/v2 v2.0.0-beta.1
golang.org/x/crypto v0.27.0
golang.org/x/time v0.6.0 golang.org/x/time v0.6.0
gorm.io/driver/sqlite v1.5.6 gorm.io/driver/sqlite v1.5.6
gorm.io/gorm v1.25.11 gorm.io/gorm v1.25.12
) )
require ( 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/bytedance/sonic/loader v0.2.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect github.com/cloudwego/iasm v0.2.0 // indirect
@@ -30,7 +31,7 @@ require (
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.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/goccy/go-json v0.10.3 // indirect
github.com/google/go-tpm v0.9.1 // indirect github.com/google/go-tpm v0.9.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
@@ -43,22 +44,21 @@ require (
github.com/kr/pretty v0.3.1 // indirect github.com/kr/pretty v0.3.1 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // 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/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // 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/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/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect github.com/ugorji/go/codec v1.2.12 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
go.uber.org/atomic v1.11.0 // indirect go.uber.org/atomic v1.11.0 // indirect
golang.org/x/arch v0.9.0 // indirect golang.org/x/arch v0.10.0 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
golang.org/x/net v0.27.0 // indirect golang.org/x/net v0.29.0 // indirect
golang.org/x/sys v0.23.0 // indirect golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.17.0 // indirect golang.org/x/text v0.18.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // 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.3 h1:W2MGa7RCU1QTeYRTPE3+88mVC0yXmsRQRChiyVocVjU=
github.com/bytedance/sonic v1.12.1/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= 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.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 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM=
github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= 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-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 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= 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.12.1 h1:dCIIBFbzhWKdgXeEifBjHPzgQ1hoWhjS4289Hjjy1uw=
github.com/go-co-op/gocron/v2 v2.11.0/go.mod h1:xY7bJxGazKam1cz04EebrlP4S9q4iWdiAylMGP3jY9w= 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 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 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 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 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 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 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.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-webauthn/webauthn v0.11.1 h1:5G/+dg91/VcaJHTtJUfwIlNJkLwbJCcnUc4W8VtkpzA= github.com/go-webauthn/webauthn v0.11.2 h1:Fgx0/wlmkClTKlnOsdOQ+K5HcHDsDcYIvtYmfhEOSUc=
github.com/go-webauthn/webauthn v0.11.1/go.mod h1:YXRm1WG0OtUyDFaVAgB5KG7kVqW+6dYCJ7FTQH4SxEE= github.com/go-webauthn/webauthn v0.11.2/go.mod h1:aOtudaF94pM71g3jRwTYYwQTG1KyTILTcZqN1srkmD0=
github.com/go-webauthn/x v0.1.12 h1:RjQ5cvApzyU/xLCiP+rub0PE4HBZsLggbxGR5ZpUf/A= github.com/go-webauthn/x v0.1.14 h1:1wrB8jzXAofojJPAaRxnZhRgagvLGnLjhCAwg3kTpT0=
github.com/go-webauthn/x v0.1.12/go.mod h1:XlRcGkNH8PT45TfeJYc6gqpOtiOendHhVmnOxh+5yHs= 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 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 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 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 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.18.1 h1:JML/k+t4tpHCpQTCAD62Nu43NUFzHY4CV3uAuvHGC+Y=
github.com/golang-migrate/migrate/v4 v4.17.1/go.mod h1:m8hinFyWBn0SA4QKHuKh175Pm9wjmxj3S2Mia7dbXzM= 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 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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= 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/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 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 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.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWtuw0=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mileusna/useragent v1.3.4 h1:MiuRRuvGjEie1+yZHO88UBYg8YBC/ddF6T7F56i3PCk= github.com/mileusna/useragent v1.3.5 h1:SJM5NzBmh/hO+4LGeATKpaEX9+b4vcGg2qXGLiNGDws=
github.com/mileusna/useragent v1.3.4/go.mod h1:3d8TOmwL/5I8pJjyVDteHtgDGcefrFUX4ccGOMKNYYc= 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 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 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= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -90,26 +90,26 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/oschwald/maxminddb-golang/v2 v2.0.0-beta.1 h1:UihPOz+oIJ5X0JsO7wEkL50fheCODsoZ9r86mJWfNMc=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/oschwald/maxminddb-golang/v2 v2.0.0-beta.1/go.mod h1:vPpFrres6g9B5+meBwAd9xnp335KFcLEFW7EqJxBHy0=
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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= 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.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.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= 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.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.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.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.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.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.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.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.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 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
@@ -122,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/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 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 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.10.0 h1:S3huipmSclq3PJMNe76NGwkBR504WFkQ5dhzWzP8ZW8=
golang.org/x/arch v0.9.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/arch v0.10.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= 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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.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.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.25.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.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 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 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
@@ -148,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= 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 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE=
gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= 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.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= 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", jwtAuthMiddleware.Add(false), oc.authorizeHandler)
group.POST("/oidc/authorize/new-client", jwtAuthMiddleware.Add(false), oc.authorizeNewClientHandler) 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/userinfo", oc.userInfoHandler)
group.GET("/oidc/clients", jwtAuthMiddleware.Add(true), oc.listClientsHandler) 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) c.JSON(http.StatusOK, response)
} }
func (oc *OidcController) createIDTokenHandler(c *gin.Context) { func (oc *OidcController) createTokensHandler(c *gin.Context) {
var input dto.OidcIdTokenDto var input dto.OidcIdTokenDto
if err := c.ShouldBind(&input); err != nil { if err := c.ShouldBind(&input); err != nil {

View File

@@ -11,6 +11,8 @@ type AuditLogDto struct {
Event model.AuditLogEvent `json:"event"` Event model.AuditLogEvent `json:"event"`
IpAddress string `json:"ipAddress"` IpAddress string `json:"ipAddress"`
Country string `json:"country"`
City string `json:"city"`
Device string `json:"device"` Device string `json:"device"`
UserID string `json:"userID"` UserID string `json:"userID"`
Data model.AuditLogData `json:"data"` Data model.AuditLogData `json:"data"`

View File

@@ -11,6 +11,8 @@ type AuditLog struct {
Event AuditLogEvent Event AuditLogEvent
IpAddress string IpAddress string
Country string
City string
UserAgent string UserAgent string
UserID string UserID string
Data AuditLogData Data AuditLogData

View File

@@ -2,11 +2,13 @@ package service
import ( import (
userAgentParser "github.com/mileusna/useragent" userAgentParser "github.com/mileusna/useragent"
"github.com/oschwald/maxminddb-golang/v2"
"github.com/stonith404/pocket-id/backend/internal/model" "github.com/stonith404/pocket-id/backend/internal/model"
"github.com/stonith404/pocket-id/backend/internal/utils" "github.com/stonith404/pocket-id/backend/internal/utils"
"github.com/stonith404/pocket-id/backend/internal/utils/email" "github.com/stonith404/pocket-id/backend/internal/utils/email"
"gorm.io/gorm" "gorm.io/gorm"
"log" "log"
"net/netip"
) )
type AuditLogService struct { type AuditLogService struct {
@@ -21,9 +23,16 @@ func NewAuditLogService(db *gorm.DB, appConfigService *AppConfigService, emailSe
// Create creates a new audit log entry in the database // Create creates a new audit log entry in the database
func (s *AuditLogService) Create(event model.AuditLogEvent, ipAddress, userAgent, userID string, data model.AuditLogData) model.AuditLog { func (s *AuditLogService) Create(event model.AuditLogEvent, ipAddress, userAgent, userID string, data model.AuditLogData) model.AuditLog {
country, city, err := s.GetIpLocation(ipAddress)
if err != nil {
log.Printf("Failed to get IP location: %v\n", err)
}
auditLog := model.AuditLog{ auditLog := model.AuditLog{
Event: event, Event: event,
IpAddress: ipAddress, IpAddress: ipAddress,
Country: country,
City: city,
UserAgent: userAgent, UserAgent: userAgent,
UserID: userID, UserID: userID,
Data: data, Data: data,
@@ -61,6 +70,8 @@ func (s *AuditLogService) CreateNewSignInWithEmail(ipAddress, userAgent, userID
Email: user.Email, Email: user.Email,
}, NewLoginTemplate, &NewLoginTemplateData{ }, NewLoginTemplate, &NewLoginTemplateData{
IPAddress: ipAddress, IPAddress: ipAddress,
Country: createdAuditLog.Country,
City: createdAuditLog.City,
Device: s.DeviceStringFromUserAgent(userAgent), Device: s.DeviceStringFromUserAgent(userAgent),
DateTime: createdAuditLog.CreatedAt.UTC(), DateTime: createdAuditLog.CreatedAt.UTC(),
}) })
@@ -86,3 +97,29 @@ func (s *AuditLogService) DeviceStringFromUserAgent(userAgent string) string {
ua := userAgentParser.Parse(userAgent) ua := userAgentParser.Parse(userAgent)
return ua.Name + " on " + ua.OS + " " + ua.OSVersion return ua.Name + " on " + ua.OS + " " + ua.OSVersion
} }
func (s *AuditLogService) GetIpLocation(ipAddress string) (country, city string, err error) {
db, err := maxminddb.Open("GeoLite2-City.mmdb")
if err != nil {
return "", "", err
}
defer db.Close()
addr := netip.MustParseAddr(ipAddress)
var record struct {
City struct {
Names map[string]string `maxminddb:"names"`
} `maxminddb:"city"`
Country struct {
Names map[string]string `maxminddb:"names"`
} `maxminddb:"country"`
}
err = db.Lookup(addr).Decode(&record)
if err != nil {
return "", "", err
}
return record.Country.Names["en"], record.City.Names["en"], nil
}

View File

@@ -29,6 +29,8 @@ var NewLoginTemplate = email.Template[NewLoginTemplateData]{
type NewLoginTemplateData struct { type NewLoginTemplateData struct {
IPAddress string IPAddress string
Country string
City string
Device string Device string
DateTime time.Time DateTime time.Time
} }

View File

@@ -3,6 +3,7 @@ package service
import ( import (
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/sha256"
"crypto/x509" "crypto/x509"
"encoding/base64" "encoding/base64"
"encoding/pem" "encoding/pem"
@@ -51,6 +52,7 @@ type AccessTokenJWTClaims struct {
} }
type JWK struct { type JWK struct {
Kid string `json:"kid"`
Kty string `json:"kty"` Kty string `json:"kty"`
Use string `json:"use"` Use string `json:"use"`
Alg string `json:"alg"` Alg string `json:"alg"`
@@ -98,7 +100,15 @@ func (s *JwtService) GenerateAccessToken(user model.User) (string, error) {
}, },
IsAdmin: user.IsAdmin, 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 := jwt.NewWithClaims(jwt.SigningMethodRS256, claim)
token.Header["kid"] = kid
return token.SignedString(s.privateKey) return token.SignedString(s.privateKey)
} }
@@ -137,9 +147,17 @@ func (s *JwtService) GenerateIDToken(userClaims map[string]interface{}, clientID
claims["nonce"] = nonce 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 := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
token.Header["kid"] = kid
return token.SignedString(s.privateKey) return token.SignedString(s.privateKey)
} }
func (s *JwtService) GenerateOauthAccessToken(user model.User, clientID string) (string, error) { func (s *JwtService) GenerateOauthAccessToken(user model.User, clientID string) (string, error) {
claim := jwt.RegisteredClaims{ claim := jwt.RegisteredClaims{
Subject: user.ID, Subject: user.ID,
@@ -148,7 +166,15 @@ func (s *JwtService) GenerateOauthAccessToken(user model.User, clientID string)
Audience: jwt.ClaimStrings{clientID}, Audience: jwt.ClaimStrings{clientID},
Issuer: common.EnvConfig.AppURL, 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 := jwt.NewWithClaims(jwt.SigningMethodRS256, claim)
token.Header["kid"] = kid
return token.SignedString(s.privateKey) return token.SignedString(s.privateKey)
} }
@@ -174,7 +200,13 @@ func (s *JwtService) GetJWK() (JWK, error) {
return JWK{}, errors.New("public key is not initialized") return JWK{}, errors.New("public key is not initialized")
} }
kid, err := s.generateKeyID(s.publicKey)
if err != nil {
return JWK{}, err
}
jwk := JWK{ jwk := JWK{
Kid: kid,
Kty: "RSA", Kty: "RSA",
Use: "sig", Use: "sig",
Alg: "RS256", Alg: "RS256",
@@ -185,6 +217,25 @@ func (s *JwtService) GetJWK() (JWK, error) {
return jwk, nil 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. // generateKeys generates a new RSA key pair and saves them to the specified paths.
func (s *JwtService) generateKeys() error { func (s *JwtService) generateKeys() error {
if err := os.MkdirAll(filepath.Dir(privateKeyPath), 0700); err != nil { if err := os.MkdirAll(filepath.Dir(privateKeyPath), 0700); err != nil {

View File

@@ -0,0 +1,2 @@
ALTER TABLE audit_logs DROP COLUMN country;
ALTER TABLE audit_logs DROP COLUMN city;

View File

@@ -0,0 +1,2 @@
ALTER TABLE audit_logs ADD COLUMN country TEXT;
ALTER TABLE audit_logs ADD COLUMN city TEXT;

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -2,6 +2,8 @@ export type AuditLog = {
id: string; id: string;
event: string; event: string;
ipAddress: string; ipAddress: string;
country?: string;
city?: string;
device: string; device: string;
createdAt: string; createdAt: string;
data: any; data: any;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,31 @@
#!/bin/bash
# Check if the license key environment variable is set
if [ -z "$MAXMIND_LICENSE_KEY" ]; then
echo "Error: MAXMIND_LICENSE_KEY environment variable is not set."
echo "Please set it using 'export MAXMIND_LICENSE_KEY=your_license_key' and try again."
exit 1
fi
echo $MAXMIND_LICENSE_KEY
# GeoLite2 City Database URL
URL="https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=${MAXMIND_LICENSE_KEY}&suffix=tar.gz"
# Download directory
DOWNLOAD_DIR="./geolite2_db"
TARGET_PATH=./backend/GeoLite2-City.mmdb
mkdir -p $DOWNLOAD_DIR
# Download the database
echo "Downloading GeoLite2 City database..."
curl -L -o "$DOWNLOAD_DIR/GeoLite2-City.tar.gz" "$URL"
# Extract the downloaded file
echo "Extracting GeoLite2 City database..."
tar -xzf "$DOWNLOAD_DIR/GeoLite2-City.tar.gz" -C $DOWNLOAD_DIR --strip-components=1
mv "$DOWNLOAD_DIR/GeoLite2-City.mmdb" $TARGET_PATH
# Clean up
rm -rf "$DOWNLOAD_DIR"
echo "GeoLite2 City database downloaded and extracted to $TARGET_PATH"