mirror of
https://github.com/pocket-id/pocket-id.git
synced 2025-12-16 18:23:03 +03:00
Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20eba1378e | ||
|
|
a6ae7ae287 | ||
|
|
840a672fc3 | ||
|
|
7446f853fc | ||
|
|
652ee6ad5d | ||
|
|
dca9e7a11a | ||
|
|
816c198a42 | ||
|
|
339837bec4 | ||
|
|
39b46e99a9 | ||
|
|
dc9e64de3d | ||
|
|
6207e10279 | ||
|
|
7550333fe2 | ||
|
|
3de1301fa8 | ||
|
|
c3980d3d28 | ||
|
|
4d0fff821e | ||
|
|
2e66211b7f | ||
|
|
2071d002fc | ||
|
|
0d071694cd | ||
|
|
39e403d00f | ||
|
|
4e858420e9 | ||
|
|
2d78349b38 | ||
|
|
9ed2adb0f8 | ||
|
|
43790dc1be | ||
|
|
7fbc356d8d | ||
|
|
9b77e8b7c1 | ||
|
|
bea115866f | ||
|
|
626f87d592 | ||
|
|
0751540d7d | ||
|
|
7c04bda5b7 | ||
|
|
98add37390 | ||
|
|
3dda2e16e9 | ||
|
|
3a6fce5c4b | ||
|
|
07ee087c3d | ||
|
|
d66cf70d50 | ||
|
|
fb8cc0bb22 | ||
|
|
0bae7e4f53 | ||
|
|
974b7b3c34 | ||
|
|
15cde6ac66 | ||
|
|
e864d5dcbf | ||
|
|
c6ab2b252c | ||
|
|
7350e3486d | ||
|
|
96303ded2b | ||
|
|
d06257ec9b | ||
|
|
19ef4833e9 | ||
|
|
e2c38138be | ||
|
|
13b02a072f | ||
|
|
430421e98b | ||
|
|
61e71ad43b | ||
|
|
4db44e4818 | ||
|
|
9ab178712a | ||
|
|
ecd74b794f | ||
|
|
5afd651434 | ||
|
|
2d3cba6308 | ||
|
|
e607fe424a | ||
|
|
8ae446322a | ||
|
|
37a835b44e | ||
|
|
75f531fbc6 | ||
|
|
28346da731 | ||
|
|
a1b20f0e74 | ||
|
|
7497f4ad40 | ||
|
|
b530d646ac | ||
|
|
77985800ae | ||
|
|
ea21eba281 | ||
|
|
66edb18f2c | ||
|
|
dab37c5967 | ||
|
|
781ff7ae7b | ||
|
|
04c7f180de | ||
|
|
5c452ceef0 | ||
|
|
8cd834a503 | ||
|
|
a65ce56b42 | ||
|
|
4a97986f52 |
@@ -1,4 +1,4 @@
|
|||||||
# See the README for more information: https://github.com/stonith404/pocket-id?tab=readme-ov-file#environment-variables
|
# See the README for more information: https://github.com/pocket-id/pocket-id?tab=readme-ov-file#environment-variables
|
||||||
PUBLIC_APP_URL=http://localhost
|
PUBLIC_APP_URL=http://localhost
|
||||||
TRUST_PROXY=false
|
TRUST_PROXY=false
|
||||||
MAXMIND_LICENSE_KEY=
|
MAXMIND_LICENSE_KEY=
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04 # Using an older version because of https://github.com/actions/runner-images/issues/11471
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- name: checkout code
|
- name: checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
@@ -17,7 +20,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
ghcr.io/${{ github.repository }}
|
ghcr.io/${{ github.repository }}
|
||||||
${{ github.repository }}
|
|
||||||
tags: |
|
tags: |
|
||||||
type=semver,pattern={{version}},prefix=v
|
type=semver,pattern={{version}},prefix=v
|
||||||
type=semver,pattern={{major}}.{{minor}},prefix=v
|
type=semver,pattern={{major}}.{{minor}},prefix=v
|
||||||
|
|||||||
15
.github/workflows/e2e-tests.yml
vendored
15
.github/workflows/e2e-tests.yml
vendored
@@ -2,8 +2,17 @@ name: E2E Tests
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
|
paths-ignore:
|
||||||
|
- "docs/**"
|
||||||
|
- "**.md"
|
||||||
|
- ".github/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
|
paths-ignore:
|
||||||
|
- "docs/**"
|
||||||
|
- "**.md"
|
||||||
|
- ".github/**"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
@@ -15,7 +24,7 @@ jobs:
|
|||||||
- name: Build and export
|
- name: Build and export
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
tags: stonith404/pocket-id:test
|
tags: pocket-id/pocket-id:test
|
||||||
outputs: type=docker,dest=/tmp/docker-image.tar
|
outputs: type=docker,dest=/tmp/docker-image.tar
|
||||||
|
|
||||||
- name: Upload Docker image artifact
|
- name: Upload Docker image artifact
|
||||||
@@ -56,7 +65,7 @@ jobs:
|
|||||||
docker run -d --name pocket-id-sqlite \
|
docker run -d --name pocket-id-sqlite \
|
||||||
-p 80:80 \
|
-p 80:80 \
|
||||||
-e APP_ENV=test \
|
-e APP_ENV=test \
|
||||||
stonith404/pocket-id:test
|
pocket-id/pocket-id:test
|
||||||
|
|
||||||
- name: Run Playwright tests
|
- name: Run Playwright tests
|
||||||
working-directory: ./frontend
|
working-directory: ./frontend
|
||||||
@@ -129,7 +138,7 @@ jobs:
|
|||||||
-e APP_ENV=test \
|
-e APP_ENV=test \
|
||||||
-e DB_PROVIDER=postgres \
|
-e DB_PROVIDER=postgres \
|
||||||
-e POSTGRES_CONNECTION_STRING=postgresql://postgres:postgres@pocket-id-db:5432/pocket-id \
|
-e POSTGRES_CONNECTION_STRING=postgresql://postgres:postgres@pocket-id-db:5432/pocket-id \
|
||||||
stonith404/pocket-id:test
|
pocket-id/pocket-id:test
|
||||||
|
|
||||||
- name: Run Playwright tests
|
- name: Run Playwright tests
|
||||||
working-directory: ./frontend
|
working-directory: ./frontend
|
||||||
|
|||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -38,11 +38,6 @@ data
|
|||||||
pocket-id-backend
|
pocket-id-backend
|
||||||
/backend/GeoLite2-City.mmdb
|
/backend/GeoLite2-City.mmdb
|
||||||
|
|
||||||
# Generated files
|
|
||||||
docs/build
|
|
||||||
docs/.docusaurus
|
|
||||||
docs/.cache-loader
|
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.env.local
|
.env.local
|
||||||
|
|||||||
109
CHANGELOG.md
109
CHANGELOG.md
@@ -1,3 +1,112 @@
|
|||||||
|
## [](https://github.com/pocket-id/pocket-id/compare/v0.35.0...v) (2025-02-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add validation that `PUBLIC_APP_URL` can't contain a path ([a6ae7ae](https://github.com/pocket-id/pocket-id/commit/a6ae7ae28713f7fc8018ae2aa7572986df3e1a5b))
|
||||||
|
* binary profile picture can't be imported from LDAP ([840a672](https://github.com/pocket-id/pocket-id/commit/840a672fc35ca8476caf86d7efaba9d54bce86aa))
|
||||||
|
|
||||||
|
## [](https://github.com/pocket-id/pocket-id/compare/v0.34.0...v) (2025-02-19)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add ability to upload a profile picture ([#244](https://github.com/pocket-id/pocket-id/issues/244)) ([652ee6a](https://github.com/pocket-id/pocket-id/commit/652ee6ad5d6c46f0d35c955ff7bb9bdac6240ca6))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* app config strings starting with a number are parsed incorrectly ([816c198](https://github.com/pocket-id/pocket-id/commit/816c198a42c189cb1f2d94885d2e3623e47e2848))
|
||||||
|
* emails do not get rendered correctly in Gmail ([dca9e7a](https://github.com/pocket-id/pocket-id/commit/dca9e7a11a3ba5d3b43a937f11cb9d16abad2db5))
|
||||||
|
|
||||||
|
## [](https://github.com/pocket-id/pocket-id/compare/v0.33.0...v) (2025-02-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add LDAP group membership attribute ([#236](https://github.com/pocket-id/pocket-id/issues/236)) ([39b46e9](https://github.com/pocket-id/pocket-id/commit/39b46e99a9b930ea39cf640c3080530cfff5be6e))
|
||||||
|
|
||||||
|
## [](https://github.com/pocket-id/pocket-id/compare/v0.32.0...v) (2025-02-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add end session endpoint ([#232](https://github.com/pocket-id/pocket-id/issues/232)) ([7550333](https://github.com/pocket-id/pocket-id/commit/7550333fe2ff6424f3168f63c5179d76767532fd))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* alignment of OIDC client details ([c3980d3](https://github.com/pocket-id/pocket-id/commit/c3980d3d28a7158a4dc9369af41f185b891e485e))
|
||||||
|
* layout of OIDC client details page on mobile ([3de1301](https://github.com/pocket-id/pocket-id/commit/3de1301fa84b3ab4fff4242d827c7794d44910f2))
|
||||||
|
* show "Sync Now" and "Test Email" button even if UI config is disabled ([4d0fff8](https://github.com/pocket-id/pocket-id/commit/4d0fff821e2245050ce631b4465969510466dfae))
|
||||||
|
|
||||||
|
## [](https://github.com/pocket-id/pocket-id/compare/v0.31.0...v) (2025-02-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add ability to set custom Geolite DB URL ([2071d00](https://github.com/pocket-id/pocket-id/commit/2071d002fc5c3b5ff7a3fca6a5c99f5517196853))
|
||||||
|
|
||||||
|
## [](https://github.com/pocket-id/pocket-id/compare/v0.30.0...v) (2025-02-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add ability to override the UI configuration with environment variables ([4e85842](https://github.com/pocket-id/pocket-id/commit/4e858420e9d9713e19f3b35c45c882403717f72f))
|
||||||
|
* add warning for only having one passkey configured ([#220](https://github.com/pocket-id/pocket-id/issues/220)) ([39e403d](https://github.com/pocket-id/pocket-id/commit/39e403d00f3870f9e960427653a1d9697da27a6f))
|
||||||
|
* display source in user and group table ([#225](https://github.com/pocket-id/pocket-id/issues/225)) ([9ed2adb](https://github.com/pocket-id/pocket-id/commit/9ed2adb0f8da13725fd9a4ef6a7798c377d13513))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* user linking in ldap group sync ([#222](https://github.com/pocket-id/pocket-id/issues/222)) ([2d78349](https://github.com/pocket-id/pocket-id/commit/2d78349b381d7ca10f47d3c03cef685a576b1b49))
|
||||||
|
|
||||||
|
## [](https://github.com/pocket-id/pocket-id/compare/v0.29.0...v) (2025-02-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add custom ldap search filters ([#216](https://github.com/pocket-id/pocket-id/issues/216)) ([626f87d](https://github.com/pocket-id/pocket-id/commit/626f87d59211f4129098b91dc1d020edb4aca692))
|
||||||
|
* update host configuration to allow external access ([#218](https://github.com/pocket-id/pocket-id/issues/218)) ([bea1158](https://github.com/pocket-id/pocket-id/commit/bea115866fd8e4b15d3281c422d2fb72312758b1))
|
||||||
|
|
||||||
|
## [](https://github.com/pocket-id/pocket-id/compare/v0.28.1...v) (2025-02-05)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add JSON support in custom claims ([15cde6a](https://github.com/pocket-id/pocket-id/commit/15cde6ac66bc857ac28df545a37c1f4341977595))
|
||||||
|
* add option to disable Caddy in the Docker container ([e864d5d](https://github.com/pocket-id/pocket-id/commit/e864d5dcbff1ef28dc6bf120e4503093a308c5c8))
|
||||||
|
|
||||||
|
## [](https://github.com/stonith404/pocket-id/compare/v0.28.0...v) (2025-02-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* don't return error page if version info fetching failed ([d06257e](https://github.com/stonith404/pocket-id/commit/d06257ec9b5e46e25e40c174b4bef02dca0a1ea3))
|
||||||
|
|
||||||
|
## [](https://github.com/stonith404/pocket-id/compare/v0.27.2...v) (2025-02-03)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* allow LDAP users and groups to be deleted if LDAP gets disabled ([9ab1787](https://github.com/stonith404/pocket-id/commit/9ab178712aa3cc71546a89226e67b7ba91245251))
|
||||||
|
* map allowed groups to OIDC clients ([#202](https://github.com/stonith404/pocket-id/issues/202)) ([13b02a0](https://github.com/stonith404/pocket-id/commit/13b02a072f20ce10e12fd8b897cbf42a908f3291))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **caddy:** trusted_proxies for IPv6 enabled hosts ([#189](https://github.com/stonith404/pocket-id/issues/189)) ([37a835b](https://github.com/stonith404/pocket-id/commit/37a835b44e308622f6862de494738dd2bfb58ef0))
|
||||||
|
* missing user service dependency ([61e71ad](https://github.com/stonith404/pocket-id/commit/61e71ad43b8f0f498133d3eb2381382e7bc642b9))
|
||||||
|
* non LDAP user group can't be updated after update ([ecd74b7](https://github.com/stonith404/pocket-id/commit/ecd74b794f1ffb7da05bce0046fb8d096b039409))
|
||||||
|
* use cursor pointer on clickable elements ([7798580](https://github.com/stonith404/pocket-id/commit/77985800ae9628104e03e7f2e803b7ed9eaaf4e0))
|
||||||
|
|
||||||
|
## [](https://github.com/stonith404/pocket-id/compare/v0.27.1...v) (2025-01-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* smtp hello for tls connections ([#180](https://github.com/stonith404/pocket-id/issues/180)) ([781ff7a](https://github.com/stonith404/pocket-id/commit/781ff7ae7b84b13892e7a565b7a78f20c52ee2c9))
|
||||||
|
|
||||||
## [](https://github.com/stonith404/pocket-id/compare/v0.27.0...v) (2025-01-24)
|
## [](https://github.com/stonith404/pocket-id/compare/v0.27.0...v) (2025-01-24)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -69,5 +69,5 @@ We are using [Playwright](https://playwright.dev) for end-to-end testing.
|
|||||||
|
|
||||||
The tests can be run like this:
|
The tests can be run like this:
|
||||||
1. Start the backend normally
|
1. Start the backend normally
|
||||||
2. Start the frontend in production mode with `npm run build && node build/index.js`
|
2. Start the frontend in production mode with `npm run build && node --env-file=.env build/index.js`
|
||||||
3. Run the tests with `npm run test`
|
3. Run the tests with `npm run test`
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Stage 1: Build Frontend
|
# Stage 1: Build Frontend
|
||||||
FROM node:20-alpine AS frontend-builder
|
FROM node:22-alpine AS frontend-builder
|
||||||
WORKDIR /app/frontend
|
WORKDIR /app/frontend
|
||||||
COPY ./frontend/package*.json ./
|
COPY ./frontend/package*.json ./
|
||||||
RUN npm ci
|
RUN npm ci
|
||||||
@@ -20,7 +20,7 @@ WORKDIR /app/backend/cmd
|
|||||||
RUN CGO_ENABLED=1 GOOS=linux go build -o /app/backend/pocket-id-backend .
|
RUN CGO_ENABLED=1 GOOS=linux go build -o /app/backend/pocket-id-backend .
|
||||||
|
|
||||||
# Stage 3: Production Image
|
# Stage 3: Production Image
|
||||||
FROM node:20-alpine
|
FROM node:22-alpine
|
||||||
# Delete default node user
|
# Delete default node user
|
||||||
RUN deluser --remove-home node
|
RUN deluser --remove-home node
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Pocket ID is a simple OIDC provider that allows users to authenticate with their passkeys to your services.
|
Pocket ID is a simple OIDC provider that allows users to authenticate with their passkeys to your services.
|
||||||
|
|
||||||
→ Try out the [Demo](https://pocket-id.eliasschneider.com)
|
→ Try out the [Demo](https://demo.pocket-id.org)
|
||||||
|
|
||||||
<img src="https://github.com/user-attachments/assets/96ac549d-b897-404a-8811-f42b16ea58e2" width="1200"/>
|
<img src="https://github.com/user-attachments/assets/96ac549d-b897-404a-8811-f42b16ea58e2" width="1200"/>
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ Additionally, what makes Pocket ID special is that it only supports [passkey](ht
|
|||||||
|
|
||||||
Pocket ID can be set up in multiple ways. The easiest and recommended way is to use Docker.
|
Pocket ID can be set up in multiple ways. The easiest and recommended way is to use Docker.
|
||||||
|
|
||||||
Visit the [documentation](https://stonith404.github.io/pocket-id) for the setup guide and more information.
|
Visit the [documentation](https://docs.pocket-id.org) for the setup guide and more information.
|
||||||
|
|
||||||
## Contribute
|
## Contribute
|
||||||
|
|
||||||
|
|||||||
@@ -7,4 +7,4 @@ SQLITE_DB_PATH=data/pocket-id.db
|
|||||||
POSTGRES_CONNECTION_STRING=postgresql://postgres:postgres@localhost:5432/pocket-id
|
POSTGRES_CONNECTION_STRING=postgresql://postgres:postgres@localhost:5432/pocket-id
|
||||||
UPLOAD_PATH=data/uploads
|
UPLOAD_PATH=data/uploads
|
||||||
PORT=8080
|
PORT=8080
|
||||||
HOST=localhost
|
HOST=0.0.0.0
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/stonith404/pocket-id/backend/internal/bootstrap"
|
"github.com/pocket-id/pocket-id/backend/internal/bootstrap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -1,58 +1,59 @@
|
|||||||
module github.com/stonith404/pocket-id/backend
|
module github.com/pocket-id/pocket-id/backend
|
||||||
|
|
||||||
go 1.23.1
|
go 1.23.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/caarlos0/env/v11 v11.2.2
|
github.com/caarlos0/env/v11 v11.3.1
|
||||||
|
github.com/disintegration/imaging v1.6.2
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0
|
github.com/fxamacker/cbor/v2 v2.7.0
|
||||||
github.com/gin-gonic/gin v1.10.0
|
github.com/gin-gonic/gin v1.10.0
|
||||||
github.com/go-co-op/gocron/v2 v2.12.1
|
github.com/go-co-op/gocron/v2 v2.15.0
|
||||||
github.com/go-playground/validator/v10 v10.22.1
|
github.com/go-ldap/ldap/v3 v3.4.10
|
||||||
|
github.com/go-playground/validator/v10 v10.24.0
|
||||||
github.com/go-webauthn/webauthn v0.11.2
|
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.18.1
|
github.com/golang-migrate/migrate/v4 v4.18.2
|
||||||
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.5
|
github.com/mileusna/useragent v1.3.5
|
||||||
github.com/oschwald/maxminddb-golang/v2 v2.0.0-beta.1
|
github.com/oschwald/maxminddb-golang/v2 v2.0.0-beta.2
|
||||||
golang.org/x/crypto v0.31.0
|
golang.org/x/crypto v0.32.0
|
||||||
golang.org/x/time v0.6.0
|
golang.org/x/image v0.24.0
|
||||||
|
golang.org/x/time v0.9.0
|
||||||
gorm.io/driver/postgres v1.5.11
|
gorm.io/driver/postgres v1.5.11
|
||||||
gorm.io/driver/sqlite v1.5.6
|
gorm.io/driver/sqlite v1.5.7
|
||||||
gorm.io/gorm v1.25.12
|
gorm.io/gorm v1.25.12
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
|
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
|
||||||
github.com/bytedance/sonic v1.12.3 // indirect
|
github.com/bytedance/sonic v1.12.8 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.2.0 // indirect
|
github.com/bytedance/sonic/loader v0.2.3 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.5 // indirect
|
github.com/gin-contrib/sse v1.0.0 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
|
github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
|
||||||
github.com/go-ldap/ldap/v3 v3.4.10 // 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.14 // indirect
|
github.com/go-webauthn/x v0.1.16 // indirect
|
||||||
github.com/goccy/go-json v0.10.3 // indirect
|
github.com/goccy/go-json v0.10.4 // indirect
|
||||||
github.com/google/go-tpm v0.9.1 // indirect
|
github.com/google/go-tpm v0.9.3 // indirect
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
github.com/jackc/pgx/v5 v5.5.5 // indirect
|
github.com/jackc/pgx/v5 v5.7.2 // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/jonboulle/clockwork v0.4.0 // indirect
|
github.com/jonboulle/clockwork v0.5.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||||
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/lib/pq v1.10.9 // indirect
|
github.com/lib/pq v1.10.9 // 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.23 // indirect
|
github.com/mattn/go-sqlite3 v1.14.24 // 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
|
||||||
@@ -62,12 +63,12 @@ require (
|
|||||||
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.10.0 // indirect
|
golang.org/x/arch v0.13.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
|
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
|
||||||
golang.org/x/net v0.33.0 // indirect
|
golang.org/x/net v0.34.0 // indirect
|
||||||
golang.org/x/sync v0.10.0 // indirect
|
golang.org/x/sync v0.11.0 // indirect
|
||||||
golang.org/x/sys v0.28.0 // indirect
|
golang.org/x/sys v0.29.0 // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
golang.org/x/text v0.22.0 // indirect
|
||||||
google.golang.org/protobuf v1.34.2 // indirect
|
google.golang.org/protobuf v1.36.4 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
145
backend/go.sum
145
backend/go.sum
@@ -4,24 +4,26 @@ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+
|
|||||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
|
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
|
||||||
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||||
github.com/bytedance/sonic v1.12.3 h1:W2MGa7RCU1QTeYRTPE3+88mVC0yXmsRQRChiyVocVjU=
|
github.com/bytedance/sonic v1.12.8 h1:4xYRVRlXIgvSZ4e8iVTlMF5szgpXd4AfvuWgA8I8lgs=
|
||||||
github.com/bytedance/sonic v1.12.3/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
|
github.com/bytedance/sonic v1.12.8/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8=
|
||||||
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.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0=
|
||||||
github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||||
github.com/caarlos0/env/v11 v11.2.2 h1:95fApNrUyueipoZN/EhA8mMxiNxrBwDa+oAZrMWl3Kg=
|
github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
|
||||||
github.com/caarlos0/env/v11 v11.2.2/go.mod h1:JBfcdeQiBoI3Zh1QRAWfe+tpiNTmDtcCj/hHHHMx0vc=
|
github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
|
||||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
|
||||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dhui/dktest v0.4.3 h1:wquqUxAFdcUgabAVLvSCOKOlag5cIZuaOjYIBOWdsR0=
|
github.com/dhui/dktest v0.4.4 h1:+I4s6JRE1yGuqflzwqG+aIaMdgXIorCf5P98JnaAWa8=
|
||||||
github.com/dhui/dktest v0.4.3/go.mod h1:zNK8IwktWzQRm6I/l2Wjp7MakiyaFWv4G1hjmodmMTs=
|
github.com/dhui/dktest v0.4.4/go.mod h1:4+22R4lgsdAXrDyaH4Nqx2JEz2hLp49MqQmm9HLCQhM=
|
||||||
|
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||||
|
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4=
|
github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4=
|
||||||
@@ -34,16 +36,16 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2
|
|||||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4=
|
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4=
|
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
|
||||||
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-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk=
|
github.com/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk=
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
github.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||||
github.com/go-co-op/gocron/v2 v2.12.1 h1:dCIIBFbzhWKdgXeEifBjHPzgQ1hoWhjS4289Hjjy1uw=
|
github.com/go-co-op/gocron/v2 v2.15.0 h1:Kpvo71VSihE+RImmpA+3ta5CcMhoRzMGw4dJawrj4zo=
|
||||||
github.com/go-co-op/gocron/v2 v2.12.1/go.mod h1:xY7bJxGazKam1cz04EebrlP4S9q4iWdiAylMGP3jY9w=
|
github.com/go-co-op/gocron/v2 v2.15.0/go.mod h1:ZF70ZwEqz0OO4RBXE1sNxnANy/zvwLcattWEFsqpKig=
|
||||||
github.com/go-ldap/ldap/v3 v3.4.10 h1:ot/iwPOhfpNVgB1o+AVXljizWZ9JTp7YF5oeyONmcJU=
|
github.com/go-ldap/ldap/v3 v3.4.10 h1:ot/iwPOhfpNVgB1o+AVXljizWZ9JTp7YF5oeyONmcJU=
|
||||||
github.com/go-ldap/ldap/v3 v3.4.10/go.mod h1:JXh4Uxgi40P6E9rdsYqpUtbW46D9UTjJ9QSwGRznplY=
|
github.com/go-ldap/ldap/v3 v3.4.10/go.mod h1:JXh4Uxgi40P6E9rdsYqpUtbW46D9UTjJ9QSwGRznplY=
|
||||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
@@ -56,24 +58,24 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
|||||||
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.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
|
github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
|
||||||
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
|
||||||
github.com/go-webauthn/webauthn v0.11.2 h1:Fgx0/wlmkClTKlnOsdOQ+K5HcHDsDcYIvtYmfhEOSUc=
|
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/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.16 h1:EaVXZntpyHviN9ykjdRBQIw9B0Ed3LO5FW7mDiMQEa8=
|
||||||
github.com/go-webauthn/x v0.1.14/go.mod h1:UuVvFZ8/NbOnkDz3y1NaxtUN87pmtpC1PQ+/5BBQRdc=
|
github.com/go-webauthn/x v0.1.16/go.mod h1:jhYjfwe/AVYaUs2mUXArj7vvZj+SpooQPyyQGNab+Us=
|
||||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
|
||||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
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.18.1 h1:JML/k+t4tpHCpQTCAD62Nu43NUFzHY4CV3uAuvHGC+Y=
|
github.com/golang-migrate/migrate/v4 v4.18.2 h1:2VSCMz7x7mjyTXx3m2zPokOY82LTRgxK1yQYKo6wWQ8=
|
||||||
github.com/golang-migrate/migrate/v4 v4.18.1/go.mod h1:HAX6m3sQgcdO81tdjn5exv20+3Kb13cmGli1hrD6hks=
|
github.com/golang-migrate/migrate/v4 v4.18.2/go.mod h1:2CM6tJvn2kqPXwnXO/d3rAQYiyoIm180VsO8PRX6Rpk=
|
||||||
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.3 h1:+yx0/anQuGzi+ssRqeD6WpXjW2L/V0dItUayO0i9sRc=
|
||||||
github.com/google/go-tpm v0.9.1/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
|
github.com/google/go-tpm v0.9.3/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
@@ -85,20 +87,27 @@ github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brv
|
|||||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
|
github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI=
|
||||||
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
|
||||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
|
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
|
||||||
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
|
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
|
||||||
|
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
|
||||||
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
|
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
|
||||||
|
github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
|
||||||
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
|
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
|
||||||
|
github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
|
||||||
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
|
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
|
||||||
|
github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
|
||||||
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
|
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
|
||||||
|
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
|
||||||
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
@@ -106,13 +115,13 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
|||||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
|
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
|
||||||
github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc=
|
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
@@ -124,8 +133,8 @@ 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.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWtuw0=
|
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||||
github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.24/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 h1:SJM5NzBmh/hO+4LGeATKpaEX9+b4vcGg2qXGLiNGDws=
|
||||||
github.com/mileusna/useragent v1.3.5/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=
|
||||||
@@ -145,8 +154,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
|
|||||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||||
github.com/oschwald/maxminddb-golang/v2 v2.0.0-beta.1 h1:UihPOz+oIJ5X0JsO7wEkL50fheCODsoZ9r86mJWfNMc=
|
github.com/oschwald/maxminddb-golang/v2 v2.0.0-beta.2 h1:jG+FaCBv3h6GD5F+oenTfe3+0NmX8sCKjni5k3A5Dek=
|
||||||
github.com/oschwald/maxminddb-golang/v2 v2.0.0-beta.1/go.mod h1:vPpFrres6g9B5+meBwAd9xnp335KFcLEFW7EqJxBHy0=
|
github.com/oschwald/maxminddb-golang/v2 v2.0.0-beta.2/go.mod h1:rHaQJ5SjfCdL4sqCKa3FhklRcaXga2/qyvmQuA+ZJ6M=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
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/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=
|
||||||
@@ -162,14 +171,16 @@ github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99
|
|||||||
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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.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=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||||
@@ -189,20 +200,22 @@ 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.10.0 h1:S3huipmSclq3PJMNe76NGwkBR504WFkQ5dhzWzP8ZW8=
|
golang.org/x/arch v0.13.0 h1:KCkqVVV1kGg0X87TFysjCJ8MxtZEIU4Ja/yXGeoECdA=
|
||||||
golang.org/x/arch v0.10.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
golang.org/x/arch v0.13.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
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/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
|
||||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
|
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
|
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||||
|
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
|
||||||
|
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
||||||
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
|
||||||
|
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
@@ -218,20 +231,18 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
|||||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
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/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
|
||||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||||
|
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||||
|
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
|
||||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
|
||||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
|
||||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||||
|
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
@@ -243,10 +254,9 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
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/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
|
||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||||
|
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
@@ -264,12 +274,11 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
|||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
|
||||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||||
|
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||||
|
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
@@ -277,8 +286,8 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
|||||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
@@ -288,8 +297,8 @@ 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/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314=
|
gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314=
|
||||||
gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
||||||
gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE=
|
gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
|
||||||
gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
||||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||||
gorm.io/gorm v1.25.12/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=
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
package bootstrap
|
package bootstrap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/stonith404/pocket-id/backend/internal/common"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/utils"
|
|
||||||
"github.com/stonith404/pocket-id/backend/resources"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/utils"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/resources"
|
||||||
)
|
)
|
||||||
|
|
||||||
// initApplicationImages copies the images from the images directory to the application-images directory
|
// initApplicationImages copies the images from the images directory to the application-images directory
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package bootstrap
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/service"
|
"github.com/pocket-id/pocket-id/backend/internal/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Bootstrap() {
|
func Bootstrap() {
|
||||||
|
|||||||
@@ -3,20 +3,21 @@ package bootstrap
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/golang-migrate/migrate/v4"
|
"github.com/golang-migrate/migrate/v4"
|
||||||
"github.com/golang-migrate/migrate/v4/database"
|
"github.com/golang-migrate/migrate/v4/database"
|
||||||
postgresMigrate "github.com/golang-migrate/migrate/v4/database/postgres"
|
postgresMigrate "github.com/golang-migrate/migrate/v4/database/postgres"
|
||||||
sqliteMigrate "github.com/golang-migrate/migrate/v4/database/sqlite3"
|
sqliteMigrate "github.com/golang-migrate/migrate/v4/database/sqlite3"
|
||||||
"github.com/golang-migrate/migrate/v4/source/iofs"
|
"github.com/golang-migrate/migrate/v4/source/iofs"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/common"
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
"github.com/stonith404/pocket-id/backend/resources"
|
"github.com/pocket-id/pocket-id/backend/resources"
|
||||||
"gorm.io/driver/postgres"
|
"gorm.io/driver/postgres"
|
||||||
"gorm.io/driver/sqlite"
|
"gorm.io/driver/sqlite"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/logger"
|
"gorm.io/gorm/logger"
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func newDatabase() (db *gorm.DB) {
|
func newDatabase() (db *gorm.DB) {
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/common"
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/controller"
|
"github.com/pocket-id/pocket-id/backend/internal/controller"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/job"
|
"github.com/pocket-id/pocket-id/backend/internal/job"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/middleware"
|
"github.com/pocket-id/pocket-id/backend/internal/middleware"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/service"
|
"github.com/pocket-id/pocket-id/backend/internal/service"
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@@ -38,11 +38,11 @@ func initRouter(db *gorm.DB, appConfigService *service.AppConfigService) {
|
|||||||
auditLogService := service.NewAuditLogService(db, appConfigService, emailService, geoLiteService)
|
auditLogService := service.NewAuditLogService(db, appConfigService, emailService, geoLiteService)
|
||||||
jwtService := service.NewJwtService(appConfigService)
|
jwtService := service.NewJwtService(appConfigService)
|
||||||
webauthnService := service.NewWebAuthnService(db, jwtService, auditLogService, appConfigService)
|
webauthnService := service.NewWebAuthnService(db, jwtService, auditLogService, appConfigService)
|
||||||
userService := service.NewUserService(db, jwtService, auditLogService, emailService)
|
userService := service.NewUserService(db, jwtService, auditLogService, emailService, appConfigService)
|
||||||
customClaimService := service.NewCustomClaimService(db)
|
customClaimService := service.NewCustomClaimService(db)
|
||||||
oidcService := service.NewOidcService(db, jwtService, appConfigService, auditLogService, customClaimService)
|
oidcService := service.NewOidcService(db, jwtService, appConfigService, auditLogService, customClaimService)
|
||||||
testService := service.NewTestService(db, appConfigService)
|
testService := service.NewTestService(db, appConfigService, jwtService)
|
||||||
userGroupService := service.NewUserGroupService(db)
|
userGroupService := service.NewUserGroupService(db, appConfigService)
|
||||||
ldapService := service.NewLdapService(db, appConfigService, userService, userGroupService)
|
ldapService := service.NewLdapService(db, appConfigService, userService, userGroupService)
|
||||||
|
|
||||||
rateLimitMiddleware := middleware.NewRateLimitMiddleware()
|
rateLimitMiddleware := middleware.NewRateLimitMiddleware()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package common
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"github.com/caarlos0/env/v11"
|
"github.com/caarlos0/env/v11"
|
||||||
_ "github.com/joho/godotenv/autoload"
|
_ "github.com/joho/godotenv/autoload"
|
||||||
@@ -10,8 +11,9 @@ import (
|
|||||||
type DbProvider string
|
type DbProvider string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DbProviderSqlite DbProvider = "sqlite"
|
DbProviderSqlite DbProvider = "sqlite"
|
||||||
DbProviderPostgres DbProvider = "postgres"
|
DbProviderPostgres DbProvider = "postgres"
|
||||||
|
MaxMindGeoLiteCityUrl string = "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=%s&suffix=tar.gz"
|
||||||
)
|
)
|
||||||
|
|
||||||
type EnvConfigSchema struct {
|
type EnvConfigSchema struct {
|
||||||
@@ -25,6 +27,8 @@ type EnvConfigSchema struct {
|
|||||||
Host string `env:"HOST"`
|
Host string `env:"HOST"`
|
||||||
MaxMindLicenseKey string `env:"MAXMIND_LICENSE_KEY"`
|
MaxMindLicenseKey string `env:"MAXMIND_LICENSE_KEY"`
|
||||||
GeoLiteDBPath string `env:"GEOLITE_DB_PATH"`
|
GeoLiteDBPath string `env:"GEOLITE_DB_PATH"`
|
||||||
|
GeoLiteDBUrl string `env:"GEOLITE_DB_URL"`
|
||||||
|
UiConfigDisabled bool `env:"PUBLIC_UI_CONFIG_DISABLED"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var EnvConfig = &EnvConfigSchema{
|
var EnvConfig = &EnvConfigSchema{
|
||||||
@@ -35,9 +39,11 @@ var EnvConfig = &EnvConfigSchema{
|
|||||||
UploadPath: "data/uploads",
|
UploadPath: "data/uploads",
|
||||||
AppURL: "http://localhost",
|
AppURL: "http://localhost",
|
||||||
Port: "8080",
|
Port: "8080",
|
||||||
Host: "localhost",
|
Host: "0.0.0.0",
|
||||||
MaxMindLicenseKey: "",
|
MaxMindLicenseKey: "",
|
||||||
GeoLiteDBPath: "data/GeoLite2-City.mmdb",
|
GeoLiteDBPath: "data/GeoLite2-City.mmdb",
|
||||||
|
GeoLiteDBUrl: MaxMindGeoLiteCityUrl,
|
||||||
|
UiConfigDisabled: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -56,4 +62,12 @@ func init() {
|
|||||||
if EnvConfig.DbProvider == DbProviderSqlite && EnvConfig.SqliteDBPath == "" {
|
if EnvConfig.DbProvider == DbProviderSqlite && EnvConfig.SqliteDBPath == "" {
|
||||||
log.Fatal("Missing SQLITE_DB_PATH environment variable")
|
log.Fatal("Missing SQLITE_DB_PATH environment variable")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parsedAppUrl, err := url.Parse(EnvConfig.AppURL)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("PUBLIC_APP_URL is not a valid URL")
|
||||||
|
}
|
||||||
|
if parsedAppUrl.Path != "" {
|
||||||
|
log.Fatal("PUBLIC_APP_URL must not contain a path")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,13 @@ type TokenInvalidOrExpiredError struct{}
|
|||||||
func (e *TokenInvalidOrExpiredError) Error() string { return "token is invalid or expired" }
|
func (e *TokenInvalidOrExpiredError) Error() string { return "token is invalid or expired" }
|
||||||
func (e *TokenInvalidOrExpiredError) HttpStatusCode() int { return 400 }
|
func (e *TokenInvalidOrExpiredError) HttpStatusCode() int { return 400 }
|
||||||
|
|
||||||
|
type TokenInvalidError struct{}
|
||||||
|
|
||||||
|
func (e *TokenInvalidError) Error() string {
|
||||||
|
return "Token is invalid"
|
||||||
|
}
|
||||||
|
func (e *TokenInvalidError) HttpStatusCode() int { return 400 }
|
||||||
|
|
||||||
type OidcMissingAuthorizationError struct{}
|
type OidcMissingAuthorizationError struct{}
|
||||||
|
|
||||||
func (e *OidcMissingAuthorizationError) Error() string { return "missing authorization" }
|
func (e *OidcMissingAuthorizationError) Error() string { return "missing authorization" }
|
||||||
@@ -176,3 +183,39 @@ func (e *LdapUserGroupUpdateError) Error() string {
|
|||||||
return "LDAP user groups can't be updated"
|
return "LDAP user groups can't be updated"
|
||||||
}
|
}
|
||||||
func (e *LdapUserGroupUpdateError) HttpStatusCode() int { return http.StatusForbidden }
|
func (e *LdapUserGroupUpdateError) HttpStatusCode() int { return http.StatusForbidden }
|
||||||
|
|
||||||
|
type OidcAccessDeniedError struct{}
|
||||||
|
|
||||||
|
func (e *OidcAccessDeniedError) Error() string {
|
||||||
|
return "You're not allowed to access this service"
|
||||||
|
}
|
||||||
|
func (e *OidcAccessDeniedError) HttpStatusCode() int { return http.StatusForbidden }
|
||||||
|
|
||||||
|
type OidcClientIdNotMatchingError struct{}
|
||||||
|
|
||||||
|
func (e *OidcClientIdNotMatchingError) Error() string {
|
||||||
|
return "Client id in request doesn't match client id in token"
|
||||||
|
}
|
||||||
|
func (e *OidcClientIdNotMatchingError) HttpStatusCode() int { return http.StatusBadRequest }
|
||||||
|
|
||||||
|
type OidcNoCallbackURLError struct{}
|
||||||
|
|
||||||
|
func (e *OidcNoCallbackURLError) Error() string {
|
||||||
|
return "No callback URL provided"
|
||||||
|
}
|
||||||
|
func (e *OidcNoCallbackURLError) HttpStatusCode() int { return http.StatusBadRequest }
|
||||||
|
|
||||||
|
type UiConfigDisabledError struct{}
|
||||||
|
|
||||||
|
func (e *UiConfigDisabledError) Error() string {
|
||||||
|
return "The configuration can't be changed since the UI configuration is disabled"
|
||||||
|
}
|
||||||
|
func (e *UiConfigDisabledError) HttpStatusCode() int { return http.StatusForbidden }
|
||||||
|
|
||||||
|
type InvalidUUIDError struct{}
|
||||||
|
|
||||||
|
func (e *InvalidUUIDError) Error() string {
|
||||||
|
return "Invalid UUID"
|
||||||
|
}
|
||||||
|
|
||||||
|
type InvalidEmailError struct{}
|
||||||
|
|||||||
@@ -2,13 +2,14 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/common"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/dto"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/middleware"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/service"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/utils"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/dto"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/middleware"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/service"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewAppConfigController(
|
func NewAppConfigController(
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/stonith404/pocket-id/backend/internal/dto"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/middleware"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/utils"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/dto"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/middleware"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/utils"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/service"
|
"github.com/pocket-id/pocket-id/backend/internal/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewAuditLogController(group *gin.RouterGroup, auditLogService *service.AuditLogService, jwtAuthMiddleware *middleware.JwtAuthMiddleware) {
|
func NewAuditLogController(group *gin.RouterGroup, auditLogService *service.AuditLogService, jwtAuthMiddleware *middleware.JwtAuthMiddleware) {
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/dto"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/middleware"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/service"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/dto"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/middleware"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewCustomClaimController(group *gin.RouterGroup, jwtAuthMiddleware *middleware.JwtAuthMiddleware, customClaimService *service.CustomClaimService) {
|
func NewCustomClaimController(group *gin.RouterGroup, jwtAuthMiddleware *middleware.JwtAuthMiddleware, customClaimService *service.CustomClaimService) {
|
||||||
|
|||||||
@@ -1,22 +1,30 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/dto"
|
"github.com/pocket-id/pocket-id/backend/internal/utils/cookie"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/middleware"
|
"log"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/service"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/utils"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/dto"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/middleware"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/service"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewOidcController(group *gin.RouterGroup, jwtAuthMiddleware *middleware.JwtAuthMiddleware, fileSizeLimitMiddleware *middleware.FileSizeLimitMiddleware, oidcService *service.OidcService, jwtService *service.JwtService) {
|
func NewOidcController(group *gin.RouterGroup, jwtAuthMiddleware *middleware.JwtAuthMiddleware, fileSizeLimitMiddleware *middleware.FileSizeLimitMiddleware, oidcService *service.OidcService, jwtService *service.JwtService) {
|
||||||
oc := &OidcController{oidcService: oidcService, jwtService: jwtService}
|
oc := &OidcController{oidcService: oidcService, jwtService: jwtService}
|
||||||
|
|
||||||
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/authorization-required", jwtAuthMiddleware.Add(false), oc.authorizationConfirmationRequiredHandler)
|
||||||
|
|
||||||
group.POST("/oidc/token", oc.createTokensHandler)
|
group.POST("/oidc/token", oc.createTokensHandler)
|
||||||
group.GET("/oidc/userinfo", oc.userInfoHandler)
|
group.GET("/oidc/userinfo", oc.userInfoHandler)
|
||||||
|
group.POST("/oidc/end-session", oc.EndSessionHandler)
|
||||||
|
group.GET("/oidc/end-session", oc.EndSessionHandler)
|
||||||
|
|
||||||
group.GET("/oidc/clients", jwtAuthMiddleware.Add(true), oc.listClientsHandler)
|
group.GET("/oidc/clients", jwtAuthMiddleware.Add(true), oc.listClientsHandler)
|
||||||
group.POST("/oidc/clients", jwtAuthMiddleware.Add(true), oc.createClientHandler)
|
group.POST("/oidc/clients", jwtAuthMiddleware.Add(true), oc.createClientHandler)
|
||||||
@@ -24,6 +32,7 @@ func NewOidcController(group *gin.RouterGroup, jwtAuthMiddleware *middleware.Jwt
|
|||||||
group.PUT("/oidc/clients/:id", jwtAuthMiddleware.Add(true), oc.updateClientHandler)
|
group.PUT("/oidc/clients/:id", jwtAuthMiddleware.Add(true), oc.updateClientHandler)
|
||||||
group.DELETE("/oidc/clients/:id", jwtAuthMiddleware.Add(true), oc.deleteClientHandler)
|
group.DELETE("/oidc/clients/:id", jwtAuthMiddleware.Add(true), oc.deleteClientHandler)
|
||||||
|
|
||||||
|
group.PUT("/oidc/clients/:id/allowed-user-groups", jwtAuthMiddleware.Add(true), oc.updateAllowedUserGroupsHandler)
|
||||||
group.POST("/oidc/clients/:id/secret", jwtAuthMiddleware.Add(true), oc.createClientSecretHandler)
|
group.POST("/oidc/clients/:id/secret", jwtAuthMiddleware.Add(true), oc.createClientSecretHandler)
|
||||||
|
|
||||||
group.GET("/oidc/clients/:id/logo", oc.getClientLogoHandler)
|
group.GET("/oidc/clients/:id/logo", oc.getClientLogoHandler)
|
||||||
@@ -57,25 +66,20 @@ func (oc *OidcController) authorizeHandler(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, response)
|
c.JSON(http.StatusOK, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (oc *OidcController) authorizeNewClientHandler(c *gin.Context) {
|
func (oc *OidcController) authorizationConfirmationRequiredHandler(c *gin.Context) {
|
||||||
var input dto.AuthorizeOidcClientRequestDto
|
var input dto.AuthorizationRequiredDto
|
||||||
if err := c.ShouldBindJSON(&input); err != nil {
|
if err := c.ShouldBindJSON(&input); err != nil {
|
||||||
c.Error(err)
|
c.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
code, callbackURL, err := oc.oidcService.AuthorizeNewClient(input, c.GetString("userID"), c.ClientIP(), c.Request.UserAgent())
|
hasAuthorizedClient, err := oc.oidcService.HasAuthorizedClient(input.ClientID, c.GetString("userID"), input.Scope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Error(err)
|
c.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response := dto.AuthorizeOidcClientResponseDto{
|
c.JSON(http.StatusOK, gin.H{"authorizationRequired": !hasAuthorizedClient})
|
||||||
Code: code,
|
|
||||||
CallbackURL: callbackURL,
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, response)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (oc *OidcController) createTokensHandler(c *gin.Context) {
|
func (oc *OidcController) createTokensHandler(c *gin.Context) {
|
||||||
@@ -124,6 +128,44 @@ func (oc *OidcController) userInfoHandler(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, claims)
|
c.JSON(http.StatusOK, claims)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (oc *OidcController) EndSessionHandler(c *gin.Context) {
|
||||||
|
var input dto.OidcLogoutDto
|
||||||
|
|
||||||
|
// Bind query parameters to the struct
|
||||||
|
if c.Request.Method == http.MethodGet {
|
||||||
|
if err := c.ShouldBindQuery(&input); err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if c.Request.Method == http.MethodPost {
|
||||||
|
// Bind form parameters to the struct
|
||||||
|
if err := c.ShouldBind(&input); err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callbackURL, err := oc.oidcService.ValidateEndSession(input, c.GetString("userID"))
|
||||||
|
if err != nil {
|
||||||
|
// If the validation fails, the user has to confirm the logout manually and doesn't get redirected
|
||||||
|
log.Printf("Error getting logout callback URL, the user has to confirm the logout manually: %v", err)
|
||||||
|
c.Redirect(http.StatusFound, common.EnvConfig.AppURL+"/logout")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// The validation was successful, so we can log out and redirect the user to the callback URL without confirmation
|
||||||
|
cookie.AddAccessTokenCookie(c, 0, "")
|
||||||
|
|
||||||
|
logoutCallbackURL, _ := url.Parse(callbackURL)
|
||||||
|
if input.State != "" {
|
||||||
|
q := logoutCallbackURL.Query()
|
||||||
|
q.Set("state", input.State)
|
||||||
|
logoutCallbackURL.RawQuery = q.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Redirect(http.StatusFound, logoutCallbackURL.String())
|
||||||
|
}
|
||||||
|
|
||||||
func (oc *OidcController) getClientHandler(c *gin.Context) {
|
func (oc *OidcController) getClientHandler(c *gin.Context) {
|
||||||
clientId := c.Param("id")
|
clientId := c.Param("id")
|
||||||
client, err := oc.oidcService.GetClient(clientId)
|
client, err := oc.oidcService.GetClient(clientId)
|
||||||
@@ -134,7 +176,7 @@ func (oc *OidcController) getClientHandler(c *gin.Context) {
|
|||||||
|
|
||||||
// Return a different DTO based on the user's role
|
// Return a different DTO based on the user's role
|
||||||
if c.GetBool("userIsAdmin") {
|
if c.GetBool("userIsAdmin") {
|
||||||
clientDto := dto.OidcClientDto{}
|
clientDto := dto.OidcClientWithAllowedUserGroupsDto{}
|
||||||
err = dto.MapStruct(client, &clientDto)
|
err = dto.MapStruct(client, &clientDto)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
c.JSON(http.StatusOK, clientDto)
|
c.JSON(http.StatusOK, clientDto)
|
||||||
@@ -191,7 +233,7 @@ func (oc *OidcController) createClientHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var clientDto dto.OidcClientDto
|
var clientDto dto.OidcClientWithAllowedUserGroupsDto
|
||||||
if err := dto.MapStruct(client, &clientDto); err != nil {
|
if err := dto.MapStruct(client, &clientDto); err != nil {
|
||||||
c.Error(err)
|
c.Error(err)
|
||||||
return
|
return
|
||||||
@@ -223,7 +265,7 @@ func (oc *OidcController) updateClientHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var clientDto dto.OidcClientDto
|
var clientDto dto.OidcClientWithAllowedUserGroupsDto
|
||||||
if err := dto.MapStruct(client, &clientDto); err != nil {
|
if err := dto.MapStruct(client, &clientDto); err != nil {
|
||||||
c.Error(err)
|
c.Error(err)
|
||||||
return
|
return
|
||||||
@@ -278,3 +320,25 @@ func (oc *OidcController) deleteClientLogoHandler(c *gin.Context) {
|
|||||||
|
|
||||||
c.Status(http.StatusNoContent)
|
c.Status(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (oc *OidcController) updateAllowedUserGroupsHandler(c *gin.Context) {
|
||||||
|
var input dto.OidcUpdateAllowedUserGroupsDto
|
||||||
|
if err := c.ShouldBindJSON(&input); err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
oidcClient, err := oc.oidcService.UpdateAllowedUserGroups(c.Param("id"), input)
|
||||||
|
if err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var oidcClientDto dto.OidcClientDto
|
||||||
|
if err := dto.MapStruct(oidcClient, &oidcClientDto); err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, oidcClientDto)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/service"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewTestController(group *gin.RouterGroup, testService *service.TestService) {
|
func NewTestController(group *gin.RouterGroup, testService *service.TestService) {
|
||||||
@@ -37,5 +38,7 @@ func (tc *TestController) resetAndSeedHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tc.TestService.SetJWTKeys()
|
||||||
|
|
||||||
c.Status(http.StatusNoContent)
|
c.Status(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/stonith404/pocket-id/backend/internal/utils/cookie"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/utils/cookie"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/common"
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/dto"
|
"github.com/pocket-id/pocket-id/backend/internal/dto"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/middleware"
|
"github.com/pocket-id/pocket-id/backend/internal/middleware"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/service"
|
"github.com/pocket-id/pocket-id/backend/internal/service"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/utils"
|
"github.com/pocket-id/pocket-id/backend/internal/utils"
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -29,6 +30,11 @@ func NewUserController(group *gin.RouterGroup, jwtAuthMiddleware *middleware.Jwt
|
|||||||
group.PUT("/users/me", jwtAuthMiddleware.Add(false), uc.updateCurrentUserHandler)
|
group.PUT("/users/me", jwtAuthMiddleware.Add(false), uc.updateCurrentUserHandler)
|
||||||
group.DELETE("/users/:id", jwtAuthMiddleware.Add(true), uc.deleteUserHandler)
|
group.DELETE("/users/:id", jwtAuthMiddleware.Add(true), uc.deleteUserHandler)
|
||||||
|
|
||||||
|
group.GET("/users/:id/profile-picture.png", uc.getUserProfilePictureHandler)
|
||||||
|
group.GET("/users/me/profile-picture.png", jwtAuthMiddleware.Add(false), uc.getCurrentUserProfilePictureHandler)
|
||||||
|
group.PUT("/users/:id/profile-picture", jwtAuthMiddleware.Add(true), uc.updateUserProfilePictureHandler)
|
||||||
|
group.PUT("/users/me/profile-picture", jwtAuthMiddleware.Add(false), uc.updateUserProfilePictureHandler)
|
||||||
|
|
||||||
group.POST("/users/:id/one-time-access-token", jwtAuthMiddleware.Add(true), uc.createOneTimeAccessTokenHandler)
|
group.POST("/users/:id/one-time-access-token", jwtAuthMiddleware.Add(true), uc.createOneTimeAccessTokenHandler)
|
||||||
group.POST("/one-time-access-token/:token", rateLimitMiddleware.Add(rate.Every(10*time.Second), 5), uc.exchangeOneTimeAccessTokenHandler)
|
group.POST("/one-time-access-token/:token", rateLimitMiddleware.Add(rate.Every(10*time.Second), 5), uc.exchangeOneTimeAccessTokenHandler)
|
||||||
group.POST("/one-time-access-token/setup", uc.getSetupAccessTokenHandler)
|
group.POST("/one-time-access-token/setup", uc.getSetupAccessTokenHandler)
|
||||||
@@ -141,6 +147,74 @@ func (uc *UserController) updateCurrentUserHandler(c *gin.Context) {
|
|||||||
uc.updateUser(c, true)
|
uc.updateUser(c, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (uc *UserController) getUserProfilePictureHandler(c *gin.Context) {
|
||||||
|
userID := c.Param("id")
|
||||||
|
|
||||||
|
picture, size, err := uc.userService.GetProfilePicture(userID)
|
||||||
|
if err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.DataFromReader(http.StatusOK, size, "image/png", picture, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uc *UserController) getCurrentUserProfilePictureHandler(c *gin.Context) {
|
||||||
|
userID := c.GetString("userID")
|
||||||
|
|
||||||
|
picture, size, err := uc.userService.GetProfilePicture(userID)
|
||||||
|
if err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.DataFromReader(http.StatusOK, size, "image/png", picture, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uc *UserController) updateUserProfilePictureHandler(c *gin.Context) {
|
||||||
|
userID := c.GetString("userID")
|
||||||
|
fileHeader, err := c.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
file, err := fileHeader.Open()
|
||||||
|
if err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
if err := uc.userService.UpdateProfilePicture(userID, file); err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uc *UserController) updateCurrentUserProfilePictureHandler(c *gin.Context) {
|
||||||
|
userID := c.GetString("userID")
|
||||||
|
fileHeader, err := c.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
file, err := fileHeader.Open()
|
||||||
|
if err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
if err := uc.userService.UpdateProfilePicture(userID, file); err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
func (uc *UserController) createOneTimeAccessTokenHandler(c *gin.Context) {
|
func (uc *UserController) createOneTimeAccessTokenHandler(c *gin.Context) {
|
||||||
var input dto.OneTimeAccessTokenCreateDto
|
var input dto.OneTimeAccessTokenCreateDto
|
||||||
if err := c.ShouldBindJSON(&input); err != nil {
|
if err := c.ShouldBindJSON(&input); err != nil {
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/dto"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/middleware"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/service"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/utils"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/dto"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/middleware"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/service"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewUserGroupController(group *gin.RouterGroup, jwtAuthMiddleware *middleware.JwtAuthMiddleware, userGroupService *service.UserGroupService) {
|
func NewUserGroupController(group *gin.RouterGroup, jwtAuthMiddleware *middleware.JwtAuthMiddleware, userGroupService *service.UserGroupService) {
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/go-webauthn/webauthn/protocol"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/common"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/dto"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/middleware"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/utils/cookie"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-webauthn/webauthn/protocol"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/dto"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/middleware"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/utils/cookie"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/service"
|
"github.com/pocket-id/pocket-id/backend/internal/service"
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/common"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/service"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewWellKnownController(group *gin.RouterGroup, jwtService *service.JwtService) {
|
func NewWellKnownController(group *gin.RouterGroup, jwtService *service.JwtService) {
|
||||||
@@ -34,9 +35,10 @@ func (wkc *WellKnownController) openIDConfigurationHandler(c *gin.Context) {
|
|||||||
"authorization_endpoint": appUrl + "/authorize",
|
"authorization_endpoint": appUrl + "/authorize",
|
||||||
"token_endpoint": appUrl + "/api/oidc/token",
|
"token_endpoint": appUrl + "/api/oidc/token",
|
||||||
"userinfo_endpoint": appUrl + "/api/oidc/userinfo",
|
"userinfo_endpoint": appUrl + "/api/oidc/userinfo",
|
||||||
|
"end_session_endpoint": appUrl + "/api/oidc/end-session",
|
||||||
"jwks_uri": appUrl + "/.well-known/jwks.json",
|
"jwks_uri": appUrl + "/.well-known/jwks.json",
|
||||||
"scopes_supported": []string{"openid", "profile", "email"},
|
"scopes_supported": []string{"openid", "profile", "email"},
|
||||||
"claims_supported": []string{"sub", "given_name", "family_name", "name", "email", "email_verified", "preferred_username"},
|
"claims_supported": []string{"sub", "given_name", "family_name", "name", "email", "email_verified", "preferred_username", "picture"},
|
||||||
"response_types_supported": []string{"code", "id_token"},
|
"response_types_supported": []string{"code", "id_token"},
|
||||||
"subject_types_supported": []string{"public"},
|
"subject_types_supported": []string{"public"},
|
||||||
"id_token_signing_alg_values_supported": []string{"RS256"},
|
"id_token_signing_alg_values_supported": []string{"RS256"},
|
||||||
|
|||||||
@@ -28,12 +28,16 @@ type AppConfigUpdateDto struct {
|
|||||||
LdapBindDn string `json:"ldapBindDn"`
|
LdapBindDn string `json:"ldapBindDn"`
|
||||||
LdapBindPassword string `json:"ldapBindPassword"`
|
LdapBindPassword string `json:"ldapBindPassword"`
|
||||||
LdapBase string `json:"ldapBase"`
|
LdapBase string `json:"ldapBase"`
|
||||||
|
LdapUserSearchFilter string `json:"ldapUserSearchFilter"`
|
||||||
|
LdapUserGroupSearchFilter string `json:"ldapUserGroupSearchFilter"`
|
||||||
LdapSkipCertVerify string `json:"ldapSkipCertVerify"`
|
LdapSkipCertVerify string `json:"ldapSkipCertVerify"`
|
||||||
LdapAttributeUserUniqueIdentifier string `json:"ldapAttributeUserUniqueIdentifier"`
|
LdapAttributeUserUniqueIdentifier string `json:"ldapAttributeUserUniqueIdentifier"`
|
||||||
LdapAttributeUserUsername string `json:"ldapAttributeUserUsername"`
|
LdapAttributeUserUsername string `json:"ldapAttributeUserUsername"`
|
||||||
LdapAttributeUserEmail string `json:"ldapAttributeUserEmail"`
|
LdapAttributeUserEmail string `json:"ldapAttributeUserEmail"`
|
||||||
LdapAttributeUserFirstName string `json:"ldapAttributeUserFirstName"`
|
LdapAttributeUserFirstName string `json:"ldapAttributeUserFirstName"`
|
||||||
LdapAttributeUserLastName string `json:"ldapAttributeUserLastName"`
|
LdapAttributeUserLastName string `json:"ldapAttributeUserLastName"`
|
||||||
|
LdapAttributeUserProfilePicture string `json:"ldapAttributeUserProfilePicture"`
|
||||||
|
LdapAttributeGroupMember string `json:"ldapAttributeGroupMember"`
|
||||||
LdapAttributeGroupUniqueIdentifier string `json:"ldapAttributeGroupUniqueIdentifier"`
|
LdapAttributeGroupUniqueIdentifier string `json:"ldapAttributeGroupUniqueIdentifier"`
|
||||||
LdapAttributeGroupName string `json:"ldapAttributeGroupName"`
|
LdapAttributeGroupName string `json:"ldapAttributeGroupName"`
|
||||||
LdapAttributeAdminGroup string `json:"ldapAttributeAdminGroup"`
|
LdapAttributeAdminGroup string `json:"ldapAttributeAdminGroup"`
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package dto
|
package dto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/stonith404/pocket-id/backend/internal/model"
|
"github.com/pocket-id/pocket-id/backend/internal/model"
|
||||||
datatype "github.com/stonith404/pocket-id/backend/internal/model/types"
|
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AuditLogDto struct {
|
type AuditLogDto struct {
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ package dto
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/model/types"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MapStructList maps a list of source structs to a list of destination structs
|
// MapStructList maps a list of source structs to a list of destination structs
|
||||||
|
|||||||
@@ -8,17 +8,27 @@ type PublicOidcClientDto struct {
|
|||||||
|
|
||||||
type OidcClientDto struct {
|
type OidcClientDto struct {
|
||||||
PublicOidcClientDto
|
PublicOidcClientDto
|
||||||
CallbackURLs []string `json:"callbackURLs"`
|
CallbackURLs []string `json:"callbackURLs"`
|
||||||
IsPublic bool `json:"isPublic"`
|
LogoutCallbackURLs []string `json:"logoutCallbackURLs"`
|
||||||
PkceEnabled bool `json:"pkceEnabled"`
|
IsPublic bool `json:"isPublic"`
|
||||||
CreatedBy UserDto `json:"createdBy"`
|
PkceEnabled bool `json:"pkceEnabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OidcClientWithAllowedUserGroupsDto struct {
|
||||||
|
PublicOidcClientDto
|
||||||
|
CallbackURLs []string `json:"callbackURLs"`
|
||||||
|
LogoutCallbackURLs []string `json:"logoutCallbackURLs"`
|
||||||
|
IsPublic bool `json:"isPublic"`
|
||||||
|
PkceEnabled bool `json:"pkceEnabled"`
|
||||||
|
AllowedUserGroups []UserGroupDtoWithUserCount `json:"allowedUserGroups"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OidcClientCreateDto struct {
|
type OidcClientCreateDto struct {
|
||||||
Name string `json:"name" binding:"required,max=50"`
|
Name string `json:"name" binding:"required,max=50"`
|
||||||
CallbackURLs []string `json:"callbackURLs" binding:"required"`
|
CallbackURLs []string `json:"callbackURLs" binding:"required"`
|
||||||
IsPublic bool `json:"isPublic"`
|
LogoutCallbackURLs []string `json:"logoutCallbackURLs"`
|
||||||
PkceEnabled bool `json:"pkceEnabled"`
|
IsPublic bool `json:"isPublic"`
|
||||||
|
PkceEnabled bool `json:"pkceEnabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthorizeOidcClientRequestDto struct {
|
type AuthorizeOidcClientRequestDto struct {
|
||||||
@@ -35,6 +45,11 @@ type AuthorizeOidcClientResponseDto struct {
|
|||||||
CallbackURL string `json:"callbackURL"`
|
CallbackURL string `json:"callbackURL"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AuthorizationRequiredDto struct {
|
||||||
|
ClientID string `json:"clientID" binding:"required"`
|
||||||
|
Scope string `json:"scope" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
type OidcCreateTokensDto struct {
|
type OidcCreateTokensDto struct {
|
||||||
GrantType string `form:"grant_type" binding:"required"`
|
GrantType string `form:"grant_type" binding:"required"`
|
||||||
Code string `form:"code" binding:"required"`
|
Code string `form:"code" binding:"required"`
|
||||||
@@ -42,3 +57,14 @@ type OidcCreateTokensDto struct {
|
|||||||
ClientSecret string `form:"client_secret"`
|
ClientSecret string `form:"client_secret"`
|
||||||
CodeVerifier string `form:"code_verifier"`
|
CodeVerifier string `form:"code_verifier"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OidcUpdateAllowedUserGroupsDto struct {
|
||||||
|
UserGroupIDs []string `json:"userGroupIds" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OidcLogoutDto struct {
|
||||||
|
IdTokenHint string `form:"id_token_hint"`
|
||||||
|
ClientId string `form:"client_id"`
|
||||||
|
PostLogoutRedirectUri string `form:"post_logout_redirect_uri"`
|
||||||
|
State string `form:"state"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package dto
|
package dto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
datatype "github.com/stonith404/pocket-id/backend/internal/model/types"
|
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserGroupDtoWithUsers struct {
|
type UserGroupDtoWithUsers struct {
|
||||||
@@ -33,7 +33,3 @@ type UserGroupCreateDto struct {
|
|||||||
type UserGroupUpdateUsersDto struct {
|
type UserGroupUpdateUsersDto struct {
|
||||||
UserIDs []string `json:"userIds" binding:"required"`
|
UserIDs []string `json:"userIds" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AssignUserToGroupDto struct {
|
|
||||||
UserID string `json:"userId" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package dto
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/go-webauthn/webauthn/protocol"
|
"github.com/go-webauthn/webauthn/protocol"
|
||||||
datatype "github.com/stonith404/pocket-id/backend/internal/model/types"
|
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WebauthnCredentialDto struct {
|
type WebauthnCredentialDto struct {
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
package job
|
package job
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/go-co-op/gocron/v2"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/model"
|
|
||||||
datatype "github.com/stonith404/pocket-id/backend/internal/model/types"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-co-op/gocron/v2"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/model"
|
||||||
|
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RegisterDbCleanupJobs(db *gorm.DB) {
|
func RegisterDbCleanupJobs(db *gorm.DB) {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/go-co-op/gocron/v2"
|
"github.com/go-co-op/gocron/v2"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/service"
|
"github.com/pocket-id/pocket-id/backend/internal/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LdapJobs struct {
|
type LdapJobs struct {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package middleware
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/common"
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CorsMiddleware struct{}
|
type CorsMiddleware struct{}
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ package middleware
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gin-gonic/gin/binding"
|
"github.com/gin-gonic/gin/binding"
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/common"
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ErrorHandlerMiddleware struct{}
|
type ErrorHandlerMiddleware struct{}
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ package middleware
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/common"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FileSizeLimitMiddleware struct{}
|
type FileSizeLimitMiddleware struct{}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/common"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/service"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/utils/cookie"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/service"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/utils/cookie"
|
||||||
)
|
)
|
||||||
|
|
||||||
type JwtAuthMiddleware struct {
|
type JwtAuthMiddleware struct {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/stonith404/pocket-id/backend/internal/common"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -35,12 +35,16 @@ type AppConfig struct {
|
|||||||
LdapBindDn AppConfigVariable
|
LdapBindDn AppConfigVariable
|
||||||
LdapBindPassword AppConfigVariable
|
LdapBindPassword AppConfigVariable
|
||||||
LdapBase AppConfigVariable
|
LdapBase AppConfigVariable
|
||||||
|
LdapUserSearchFilter AppConfigVariable
|
||||||
|
LdapUserGroupSearchFilter AppConfigVariable
|
||||||
LdapSkipCertVerify AppConfigVariable
|
LdapSkipCertVerify AppConfigVariable
|
||||||
LdapAttributeUserUniqueIdentifier AppConfigVariable
|
LdapAttributeUserUniqueIdentifier AppConfigVariable
|
||||||
LdapAttributeUserUsername AppConfigVariable
|
LdapAttributeUserUsername AppConfigVariable
|
||||||
LdapAttributeUserEmail AppConfigVariable
|
LdapAttributeUserEmail AppConfigVariable
|
||||||
LdapAttributeUserFirstName AppConfigVariable
|
LdapAttributeUserFirstName AppConfigVariable
|
||||||
LdapAttributeUserLastName AppConfigVariable
|
LdapAttributeUserLastName AppConfigVariable
|
||||||
|
LdapAttributeUserProfilePicture AppConfigVariable
|
||||||
|
LdapAttributeGroupMember AppConfigVariable
|
||||||
LdapAttributeGroupUniqueIdentifier AppConfigVariable
|
LdapAttributeGroupUniqueIdentifier AppConfigVariable
|
||||||
LdapAttributeGroupName AppConfigVariable
|
LdapAttributeGroupName AppConfigVariable
|
||||||
LdapAttributeAdminGroup AppConfigVariable
|
LdapAttributeAdminGroup AppConfigVariable
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/google/uuid"
|
|
||||||
model "github.com/stonith404/pocket-id/backend/internal/model/types"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
model "github.com/pocket-id/pocket-id/backend/internal/model/types"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Base contains common columns for all tables.
|
// Base contains common columns for all tables.
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import (
|
|||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
datatype "github.com/stonith404/pocket-id/backend/internal/model/types"
|
|
||||||
|
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -36,16 +37,18 @@ type OidcAuthorizationCode struct {
|
|||||||
type OidcClient struct {
|
type OidcClient struct {
|
||||||
Base
|
Base
|
||||||
|
|
||||||
Name string `sortable:"true"`
|
Name string `sortable:"true"`
|
||||||
Secret string
|
Secret string
|
||||||
CallbackURLs CallbackURLs
|
CallbackURLs UrlList
|
||||||
ImageType *string
|
LogoutCallbackURLs UrlList
|
||||||
HasLogo bool `gorm:"-"`
|
ImageType *string
|
||||||
IsPublic bool
|
HasLogo bool `gorm:"-"`
|
||||||
PkceEnabled bool
|
IsPublic bool
|
||||||
|
PkceEnabled bool
|
||||||
|
|
||||||
CreatedByID string
|
AllowedUserGroups []UserGroup `gorm:"many2many:oidc_clients_allowed_user_groups;"`
|
||||||
CreatedBy User
|
CreatedByID string
|
||||||
|
CreatedBy User
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *OidcClient) AfterFind(_ *gorm.DB) (err error) {
|
func (c *OidcClient) AfterFind(_ *gorm.DB) (err error) {
|
||||||
@@ -54,9 +57,9 @@ func (c *OidcClient) AfterFind(_ *gorm.DB) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type CallbackURLs []string
|
type UrlList []string
|
||||||
|
|
||||||
func (cu *CallbackURLs) Scan(value interface{}) error {
|
func (cu *UrlList) Scan(value interface{}) error {
|
||||||
if v, ok := value.([]byte); ok {
|
if v, ok := value.([]byte); ok {
|
||||||
return json.Unmarshal(v, cu)
|
return json.Unmarshal(v, cu)
|
||||||
} else {
|
} else {
|
||||||
@@ -64,6 +67,6 @@ func (cu *CallbackURLs) Scan(value interface{}) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cu CallbackURLs) Value() (driver.Value, error) {
|
func (cu UrlList) Value() (driver.Value, error) {
|
||||||
return json.Marshal(cu)
|
return json.Marshal(cu)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ package datatype
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/common"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DateTime custom type for time.Time to store date as unix timestamp for sqlite and as date for postgres
|
// DateTime custom type for time.Time to store date as unix timestamp for sqlite and as date for postgres
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package model
|
|||||||
import (
|
import (
|
||||||
"github.com/go-webauthn/webauthn/protocol"
|
"github.com/go-webauthn/webauthn/protocol"
|
||||||
"github.com/go-webauthn/webauthn/webauthn"
|
"github.com/go-webauthn/webauthn/webauthn"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/model/types"
|
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ import (
|
|||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/go-webauthn/webauthn/protocol"
|
|
||||||
datatype "github.com/stonith404/pocket-id/backend/internal/model/types"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-webauthn/webauthn/protocol"
|
||||||
|
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WebauthnSession struct {
|
type WebauthnSession struct {
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/common"
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/dto"
|
"github.com/pocket-id/pocket-id/backend/internal/dto"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/model"
|
"github.com/pocket-id/pocket-id/backend/internal/model"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/utils"
|
"github.com/pocket-id/pocket-id/backend/internal/utils"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -119,6 +119,7 @@ var defaultDbConfig = model.AppConfig{
|
|||||||
LdapEnabled: model.AppConfigVariable{
|
LdapEnabled: model.AppConfigVariable{
|
||||||
Key: "ldapEnabled",
|
Key: "ldapEnabled",
|
||||||
Type: "bool",
|
Type: "bool",
|
||||||
|
IsPublic: true,
|
||||||
DefaultValue: "false",
|
DefaultValue: "false",
|
||||||
},
|
},
|
||||||
LdapUrl: model.AppConfigVariable{
|
LdapUrl: model.AppConfigVariable{
|
||||||
@@ -137,6 +138,16 @@ var defaultDbConfig = model.AppConfig{
|
|||||||
Key: "ldapBase",
|
Key: "ldapBase",
|
||||||
Type: "string",
|
Type: "string",
|
||||||
},
|
},
|
||||||
|
LdapUserSearchFilter: model.AppConfigVariable{
|
||||||
|
Key: "ldapUserSearchFilter",
|
||||||
|
Type: "string",
|
||||||
|
DefaultValue: "(objectClass=person)",
|
||||||
|
},
|
||||||
|
LdapUserGroupSearchFilter: model.AppConfigVariable{
|
||||||
|
Key: "ldapUserGroupSearchFilter",
|
||||||
|
Type: "string",
|
||||||
|
DefaultValue: "(objectClass=groupOfNames)",
|
||||||
|
},
|
||||||
LdapSkipCertVerify: model.AppConfigVariable{
|
LdapSkipCertVerify: model.AppConfigVariable{
|
||||||
Key: "ldapSkipCertVerify",
|
Key: "ldapSkipCertVerify",
|
||||||
Type: "bool",
|
Type: "bool",
|
||||||
@@ -162,6 +173,15 @@ var defaultDbConfig = model.AppConfig{
|
|||||||
Key: "ldapAttributeUserLastName",
|
Key: "ldapAttributeUserLastName",
|
||||||
Type: "string",
|
Type: "string",
|
||||||
},
|
},
|
||||||
|
LdapAttributeUserProfilePicture: model.AppConfigVariable{
|
||||||
|
Key: "ldapAttributeUserProfilePicture",
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
LdapAttributeGroupMember: model.AppConfigVariable{
|
||||||
|
Key: "ldapAttributeGroupMember",
|
||||||
|
Type: "string",
|
||||||
|
DefaultValue: "member",
|
||||||
|
},
|
||||||
LdapAttributeGroupUniqueIdentifier: model.AppConfigVariable{
|
LdapAttributeGroupUniqueIdentifier: model.AppConfigVariable{
|
||||||
Key: "ldapAttributeGroupUniqueIdentifier",
|
Key: "ldapAttributeGroupUniqueIdentifier",
|
||||||
Type: "string",
|
Type: "string",
|
||||||
@@ -177,12 +197,15 @@ var defaultDbConfig = model.AppConfig{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *AppConfigService) UpdateAppConfig(input dto.AppConfigUpdateDto) ([]model.AppConfigVariable, error) {
|
func (s *AppConfigService) UpdateAppConfig(input dto.AppConfigUpdateDto) ([]model.AppConfigVariable, error) {
|
||||||
var savedConfigVariables []model.AppConfigVariable
|
if common.EnvConfig.UiConfigDisabled {
|
||||||
|
return nil, &common.UiConfigDisabledError{}
|
||||||
|
}
|
||||||
|
|
||||||
tx := s.db.Begin()
|
tx := s.db.Begin()
|
||||||
rt := reflect.ValueOf(input).Type()
|
rt := reflect.ValueOf(input).Type()
|
||||||
rv := reflect.ValueOf(input)
|
rv := reflect.ValueOf(input)
|
||||||
|
|
||||||
|
var savedConfigVariables []model.AppConfigVariable
|
||||||
for i := 0; i < rt.NumField(); i++ {
|
for i := 0; i < rt.NumField(); i++ {
|
||||||
field := rt.Field(i)
|
field := rt.Field(i)
|
||||||
key := field.Tag.Get("json")
|
key := field.Tag.Get("json")
|
||||||
@@ -243,9 +266,13 @@ func (s *AppConfigService) ListAppConfig(showAll bool) ([]model.AppConfigVariabl
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the value to the default value if it is empty
|
|
||||||
for i := range configuration {
|
for i := range configuration {
|
||||||
if configuration[i].Value == "" && configuration[i].DefaultValue != "" {
|
if common.EnvConfig.UiConfigDisabled {
|
||||||
|
// Set the value to the environment variable if the UI config is disabled
|
||||||
|
configuration[i].Value = s.getConfigVariableFromEnvironmentVariable(configuration[i].Key, configuration[i].DefaultValue)
|
||||||
|
|
||||||
|
} else if configuration[i].Value == "" && configuration[i].DefaultValue != "" {
|
||||||
|
// Set the value to the default value if it is empty
|
||||||
configuration[i].Value = configuration[i].DefaultValue
|
configuration[i].Value = configuration[i].DefaultValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -344,12 +371,25 @@ func (s *AppConfigService) LoadDbConfigFromDb() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if storedConfigVar.Value == "" && storedConfigVar.DefaultValue != "" {
|
if common.EnvConfig.UiConfigDisabled {
|
||||||
|
storedConfigVar.Value = s.getConfigVariableFromEnvironmentVariable(currentConfigVar.Key, storedConfigVar.DefaultValue)
|
||||||
|
} else if storedConfigVar.Value == "" && storedConfigVar.DefaultValue != "" {
|
||||||
storedConfigVar.Value = storedConfigVar.DefaultValue
|
storedConfigVar.Value = storedConfigVar.DefaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
dbConfigField.Set(reflect.ValueOf(storedConfigVar))
|
dbConfigField.Set(reflect.ValueOf(storedConfigVar))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *AppConfigService) getConfigVariableFromEnvironmentVariable(key, fallbackValue string) string {
|
||||||
|
environmentVariableName := utils.CamelCaseToScreamingSnakeCase(key)
|
||||||
|
|
||||||
|
if value, exists := os.LookupEnv(environmentVariableName); exists {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallbackValue
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
userAgentParser "github.com/mileusna/useragent"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/model"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/utils"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/utils/email"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
userAgentParser "github.com/mileusna/useragent"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/model"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/utils"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/utils/email"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AuditLogService struct {
|
type AuditLogService struct {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/stonith404/pocket-id/backend/internal/common"
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/dto"
|
"github.com/pocket-id/pocket-id/backend/internal/dto"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/model"
|
"github.com/pocket-id/pocket-id/backend/internal/model"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/common"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/model"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/utils/email"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
htemplate "html/template"
|
htemplate "html/template"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"mime/quotedprintable"
|
"mime/quotedprintable"
|
||||||
@@ -17,6 +13,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
ttemplate "text/template"
|
ttemplate "text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/model"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/utils/email"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
var netDialer = &net.Dialer{
|
var netDialer = &net.Dialer{
|
||||||
@@ -89,16 +90,31 @@ func SendEmail[V any](srv *EmailService, toEmail email.Address, template email.T
|
|||||||
)
|
)
|
||||||
c.Body(body)
|
c.Body(body)
|
||||||
|
|
||||||
// Set up the TLS configuration
|
// Connect to the SMTP server
|
||||||
|
client, err := srv.getSmtpClient()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to connect to SMTP server: %w", err)
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
// Send the email
|
||||||
|
if err := srv.sendEmailContent(client, toEmail, c); err != nil {
|
||||||
|
return fmt.Errorf("send email content: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *EmailService) getSmtpClient() (client *smtp.Client, err error) {
|
||||||
|
port := srv.appConfigService.DbConfig.SmtpPort.Value
|
||||||
|
smtpAddress := srv.appConfigService.DbConfig.SmtpHost.Value + ":" + port
|
||||||
|
|
||||||
tlsConfig := &tls.Config{
|
tlsConfig := &tls.Config{
|
||||||
InsecureSkipVerify: srv.appConfigService.DbConfig.SmtpSkipCertVerify.Value == "true",
|
InsecureSkipVerify: srv.appConfigService.DbConfig.SmtpSkipCertVerify.Value == "true",
|
||||||
ServerName: srv.appConfigService.DbConfig.SmtpHost.Value,
|
ServerName: srv.appConfigService.DbConfig.SmtpHost.Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect to the SMTP server
|
// Connect to the SMTP server
|
||||||
port := srv.appConfigService.DbConfig.SmtpPort.Value
|
|
||||||
smtpAddress := srv.appConfigService.DbConfig.SmtpHost.Value + ":" + port
|
|
||||||
var client *smtp.Client
|
|
||||||
if srv.appConfigService.DbConfig.SmtpTls.Value == "false" {
|
if srv.appConfigService.DbConfig.SmtpTls.Value == "false" {
|
||||||
client, err = srv.connectToSmtpServer(smtpAddress)
|
client, err = srv.connectToSmtpServer(smtpAddress)
|
||||||
} else if port == "465" {
|
} else if port == "465" {
|
||||||
@@ -112,24 +128,14 @@ func SendEmail[V any](srv *EmailService, toEmail email.Address, template email.T
|
|||||||
tlsConfig,
|
tlsConfig,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to connect to SMTP server: %w", err)
|
return nil, fmt.Errorf("failed to connect to SMTP server: %w", err)
|
||||||
}
|
|
||||||
defer client.Close()
|
|
||||||
|
|
||||||
// Set the hello message manually as for example Google rejects the default "localhost" value
|
|
||||||
hostname, err := os.Hostname()
|
|
||||||
if err == nil {
|
|
||||||
if err := client.Hello(hostname); err != nil {
|
|
||||||
return fmt.Errorf("failed to say hello to SMTP server: %w", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set up the authentication if user or password are set
|
||||||
smtpUser := srv.appConfigService.DbConfig.SmtpUser.Value
|
smtpUser := srv.appConfigService.DbConfig.SmtpUser.Value
|
||||||
smtpPassword := srv.appConfigService.DbConfig.SmtpPassword.Value
|
smtpPassword := srv.appConfigService.DbConfig.SmtpPassword.Value
|
||||||
|
|
||||||
// Set up the authentication if user or password are set
|
|
||||||
if smtpUser != "" || smtpPassword != "" {
|
if smtpUser != "" || smtpPassword != "" {
|
||||||
auth := smtp.PlainAuth("",
|
auth := smtp.PlainAuth("",
|
||||||
srv.appConfigService.DbConfig.SmtpUser.Value,
|
srv.appConfigService.DbConfig.SmtpUser.Value,
|
||||||
@@ -137,16 +143,11 @@ func SendEmail[V any](srv *EmailService, toEmail email.Address, template email.T
|
|||||||
srv.appConfigService.DbConfig.SmtpHost.Value,
|
srv.appConfigService.DbConfig.SmtpHost.Value,
|
||||||
)
|
)
|
||||||
if err := client.Auth(auth); err != nil {
|
if err := client.Auth(auth); err != nil {
|
||||||
return fmt.Errorf("failed to authenticate SMTP client: %w", err)
|
return nil, fmt.Errorf("failed to authenticate SMTP client: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the email
|
return client, err
|
||||||
if err := srv.sendEmailContent(client, toEmail, c); err != nil {
|
|
||||||
return fmt.Errorf("send email content: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv *EmailService) connectToSmtpServer(serverAddr string) (*smtp.Client, error) {
|
func (srv *EmailService) connectToSmtpServer(serverAddr string) (*smtp.Client, error) {
|
||||||
@@ -155,6 +156,15 @@ func (srv *EmailService) connectToSmtpServer(serverAddr string) (*smtp.Client, e
|
|||||||
return nil, fmt.Errorf("failed to connect to SMTP server: %w", err)
|
return nil, fmt.Errorf("failed to connect to SMTP server: %w", err)
|
||||||
}
|
}
|
||||||
client, err := smtp.NewClient(conn, srv.appConfigService.DbConfig.SmtpHost.Value)
|
client, err := smtp.NewClient(conn, srv.appConfigService.DbConfig.SmtpHost.Value)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, fmt.Errorf("failed to create SMTP client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := srv.sendHelloCommand(client); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to say hello to SMTP server: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return client, err
|
return client, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,6 +184,10 @@ func (srv *EmailService) connectToSmtpServerUsingImplicitTLS(serverAddr string,
|
|||||||
return nil, fmt.Errorf("failed to create SMTP client: %w", err)
|
return nil, fmt.Errorf("failed to create SMTP client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := srv.sendHelloCommand(client); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to say hello to SMTP server: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,12 +203,26 @@ func (srv *EmailService) connectToSmtpServerUsingStartTLS(serverAddr string, tls
|
|||||||
return nil, fmt.Errorf("failed to create SMTP client: %w", err)
|
return nil, fmt.Errorf("failed to create SMTP client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := srv.sendHelloCommand(client); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to say hello to SMTP server: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := client.StartTLS(tlsConfig); err != nil {
|
if err := client.StartTLS(tlsConfig); err != nil {
|
||||||
return nil, fmt.Errorf("failed to start TLS: %w", err)
|
return nil, fmt.Errorf("failed to start TLS: %w", err)
|
||||||
}
|
}
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (srv *EmailService) sendHelloCommand(client *smtp.Client) error {
|
||||||
|
hostname, err := os.Hostname()
|
||||||
|
if err == nil {
|
||||||
|
if err := client.Hello(hostname); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (srv *EmailService) sendEmailContent(client *smtp.Client, toEmail email.Address, c *email.Composer) error {
|
func (srv *EmailService) sendEmailContent(client *smtp.Client, toEmail email.Address, c *email.Composer) error {
|
||||||
if err := client.Mail(srv.appConfigService.DbConfig.SmtpFrom.Value); err != nil {
|
if err := client.Mail(srv.appConfigService.DbConfig.SmtpFrom.Value); err != nil {
|
||||||
return fmt.Errorf("failed to set sender: %w", err)
|
return fmt.Errorf("failed to set sender: %w", err)
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/utils/email"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/utils/email"
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -17,11 +17,12 @@ import (
|
|||||||
|
|
||||||
"github.com/oschwald/maxminddb-golang/v2"
|
"github.com/oschwald/maxminddb-golang/v2"
|
||||||
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/common"
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GeoLiteService struct {
|
type GeoLiteService struct {
|
||||||
mutex sync.Mutex
|
disableUpdater bool
|
||||||
|
mutex sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
var localhostIPNets = []*net.IPNet{
|
var localhostIPNets = []*net.IPNet{
|
||||||
@@ -43,6 +44,12 @@ var tailscaleIPNets = []*net.IPNet{
|
|||||||
func NewGeoLiteService() *GeoLiteService {
|
func NewGeoLiteService() *GeoLiteService {
|
||||||
service := &GeoLiteService{}
|
service := &GeoLiteService{}
|
||||||
|
|
||||||
|
if common.EnvConfig.MaxMindLicenseKey == "" && common.EnvConfig.GeoLiteDBUrl == common.MaxMindGeoLiteCityUrl {
|
||||||
|
// Warn the user, and disable the updater.
|
||||||
|
log.Println("MAXMIND_LICENSE_KEY environment variable is empty. The GeoLite2 City database won't be updated.")
|
||||||
|
service.disableUpdater = true
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if err := service.updateDatabase(); err != nil {
|
if err := service.updateDatabase(); err != nil {
|
||||||
log.Printf("Failed to update GeoLite2 City database: %v\n", err)
|
log.Printf("Failed to update GeoLite2 City database: %v\n", err)
|
||||||
@@ -104,18 +111,19 @@ func (s *GeoLiteService) GetLocationByIP(ipAddress string) (country, city string
|
|||||||
|
|
||||||
// UpdateDatabase checks the age of the database and updates it if it's older than 14 days.
|
// UpdateDatabase checks the age of the database and updates it if it's older than 14 days.
|
||||||
func (s *GeoLiteService) updateDatabase() error {
|
func (s *GeoLiteService) updateDatabase() error {
|
||||||
|
if s.disableUpdater {
|
||||||
|
// Avoid updating the GeoLite2 City database.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if s.isDatabaseUpToDate() {
|
if s.isDatabaseUpToDate() {
|
||||||
log.Println("GeoLite2 City database is up-to-date.")
|
log.Println("GeoLite2 City database is up-to-date.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Updating GeoLite2 City database...")
|
log.Println("Updating GeoLite2 City database...")
|
||||||
|
downloadUrl := fmt.Sprintf(common.EnvConfig.GeoLiteDBUrl, common.EnvConfig.MaxMindLicenseKey)
|
||||||
|
|
||||||
// Download and extract the database
|
|
||||||
downloadUrl := fmt.Sprintf(
|
|
||||||
"https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=%s&suffix=tar.gz",
|
|
||||||
common.EnvConfig.MaxMindLicenseKey,
|
|
||||||
)
|
|
||||||
// Download the database tar.gz file
|
// Download the database tar.gz file
|
||||||
resp, err := http.Get(downloadUrl)
|
resp, err := http.Get(downloadUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -8,10 +8,6 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/common"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/model"
|
|
||||||
"log"
|
"log"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
@@ -19,6 +15,10 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -27,8 +27,8 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type JwtService struct {
|
type JwtService struct {
|
||||||
publicKey *rsa.PublicKey
|
PublicKey *rsa.PublicKey
|
||||||
privateKey *rsa.PrivateKey
|
PrivateKey *rsa.PrivateKey
|
||||||
appConfigService *AppConfigService
|
appConfigService *AppConfigService
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ func (s *JwtService) loadOrGenerateKeys() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("can't read jwt private key: " + err.Error())
|
return errors.New("can't read jwt private key: " + err.Error())
|
||||||
}
|
}
|
||||||
s.privateKey, err = jwt.ParseRSAPrivateKeyFromPEM(privateKeyBytes)
|
s.PrivateKey, err = jwt.ParseRSAPrivateKeyFromPEM(privateKeyBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("can't parse jwt private key: " + err.Error())
|
return errors.New("can't parse jwt private key: " + err.Error())
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,7 @@ func (s *JwtService) loadOrGenerateKeys() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("can't read jwt public key: " + err.Error())
|
return errors.New("can't read jwt public key: " + err.Error())
|
||||||
}
|
}
|
||||||
s.publicKey, err = jwt.ParseRSAPublicKeyFromPEM(publicKeyBytes)
|
s.PublicKey, err = jwt.ParseRSAPublicKeyFromPEM(publicKeyBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("can't parse jwt public key: " + err.Error())
|
return errors.New("can't parse jwt public key: " + err.Error())
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,7 @@ func (s *JwtService) GenerateAccessToken(user model.User) (string, error) {
|
|||||||
IsAdmin: user.IsAdmin,
|
IsAdmin: user.IsAdmin,
|
||||||
}
|
}
|
||||||
|
|
||||||
kid, err := s.generateKeyID(s.publicKey)
|
kid, err := s.generateKeyID(s.PublicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.New("failed to generate key ID: " + err.Error())
|
return "", errors.New("failed to generate key ID: " + err.Error())
|
||||||
}
|
}
|
||||||
@@ -108,12 +108,12 @@ func (s *JwtService) GenerateAccessToken(user model.User) (string, error) {
|
|||||||
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claim)
|
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claim)
|
||||||
token.Header["kid"] = kid
|
token.Header["kid"] = kid
|
||||||
|
|
||||||
return token.SignedString(s.privateKey)
|
return token.SignedString(s.PrivateKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *JwtService) VerifyAccessToken(tokenString string) (*AccessTokenJWTClaims, error) {
|
func (s *JwtService) VerifyAccessToken(tokenString string) (*AccessTokenJWTClaims, error) {
|
||||||
token, err := jwt.ParseWithClaims(tokenString, &AccessTokenJWTClaims{}, func(token *jwt.Token) (interface{}, error) {
|
token, err := jwt.ParseWithClaims(tokenString, &AccessTokenJWTClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||||
return s.publicKey, nil
|
return s.PublicKey, nil
|
||||||
})
|
})
|
||||||
if err != nil || !token.Valid {
|
if err != nil || !token.Valid {
|
||||||
return nil, errors.New("couldn't handle this token")
|
return nil, errors.New("couldn't handle this token")
|
||||||
@@ -146,7 +146,7 @@ func (s *JwtService) GenerateIDToken(userClaims map[string]interface{}, clientID
|
|||||||
claims["nonce"] = nonce
|
claims["nonce"] = nonce
|
||||||
}
|
}
|
||||||
|
|
||||||
kid, err := s.generateKeyID(s.publicKey)
|
kid, err := s.generateKeyID(s.PublicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.New("failed to generate key ID: " + err.Error())
|
return "", errors.New("failed to generate key ID: " + err.Error())
|
||||||
}
|
}
|
||||||
@@ -154,7 +154,7 @@ func (s *JwtService) GenerateIDToken(userClaims map[string]interface{}, clientID
|
|||||||
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
|
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
|
||||||
token.Header["kid"] = kid
|
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) {
|
||||||
@@ -166,7 +166,7 @@ func (s *JwtService) GenerateOauthAccessToken(user model.User, clientID string)
|
|||||||
Issuer: common.EnvConfig.AppURL,
|
Issuer: common.EnvConfig.AppURL,
|
||||||
}
|
}
|
||||||
|
|
||||||
kid, err := s.generateKeyID(s.publicKey)
|
kid, err := s.generateKeyID(s.PublicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.New("failed to generate key ID: " + err.Error())
|
return "", errors.New("failed to generate key ID: " + err.Error())
|
||||||
}
|
}
|
||||||
@@ -174,12 +174,12 @@ func (s *JwtService) GenerateOauthAccessToken(user model.User, clientID string)
|
|||||||
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claim)
|
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claim)
|
||||||
token.Header["kid"] = kid
|
token.Header["kid"] = kid
|
||||||
|
|
||||||
return token.SignedString(s.privateKey)
|
return token.SignedString(s.PrivateKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *JwtService) VerifyOauthAccessToken(tokenString string) (*jwt.RegisteredClaims, error) {
|
func (s *JwtService) VerifyOauthAccessToken(tokenString string) (*jwt.RegisteredClaims, error) {
|
||||||
token, err := jwt.ParseWithClaims(tokenString, &jwt.RegisteredClaims{}, func(token *jwt.Token) (interface{}, error) {
|
token, err := jwt.ParseWithClaims(tokenString, &jwt.RegisteredClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||||
return s.publicKey, nil
|
return s.PublicKey, nil
|
||||||
})
|
})
|
||||||
if err != nil || !token.Valid {
|
if err != nil || !token.Valid {
|
||||||
return nil, errors.New("couldn't handle this token")
|
return nil, errors.New("couldn't handle this token")
|
||||||
@@ -193,13 +193,30 @@ func (s *JwtService) VerifyOauthAccessToken(tokenString string) (*jwt.Registered
|
|||||||
return claims, nil
|
return claims, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *JwtService) VerifyIdToken(tokenString string) (*jwt.RegisteredClaims, error) {
|
||||||
|
token, err := jwt.ParseWithClaims(tokenString, &jwt.RegisteredClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
return s.PublicKey, nil
|
||||||
|
}, jwt.WithIssuer(common.EnvConfig.AppURL))
|
||||||
|
|
||||||
|
if err != nil && !errors.Is(err, jwt.ErrTokenExpired) {
|
||||||
|
return nil, errors.New("couldn't handle this token")
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, isValid := token.Claims.(*jwt.RegisteredClaims)
|
||||||
|
if !isValid {
|
||||||
|
return nil, errors.New("can't parse claims")
|
||||||
|
}
|
||||||
|
|
||||||
|
return claims, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetJWK returns the JSON Web Key (JWK) for the public key.
|
// GetJWK returns the JSON Web Key (JWK) for the public key.
|
||||||
func (s *JwtService) GetJWK() (JWK, error) {
|
func (s *JwtService) GetJWK() (JWK, error) {
|
||||||
if s.publicKey == nil {
|
if s.PublicKey == nil {
|
||||||
return JWK{}, errors.New("public key is not initialized")
|
return JWK{}, errors.New("public key is not initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
kid, err := s.generateKeyID(s.publicKey)
|
kid, err := s.generateKeyID(s.PublicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return JWK{}, err
|
return JWK{}, err
|
||||||
}
|
}
|
||||||
@@ -209,8 +226,8 @@ func (s *JwtService) GetJWK() (JWK, error) {
|
|||||||
Kty: "RSA",
|
Kty: "RSA",
|
||||||
Use: "sig",
|
Use: "sig",
|
||||||
Alg: "RS256",
|
Alg: "RS256",
|
||||||
N: base64.RawURLEncoding.EncodeToString(s.publicKey.N.Bytes()),
|
N: base64.RawURLEncoding.EncodeToString(s.PublicKey.N.Bytes()),
|
||||||
E: base64.RawURLEncoding.EncodeToString(big.NewInt(int64(s.publicKey.E)).Bytes()),
|
E: base64.RawURLEncoding.EncodeToString(big.NewInt(int64(s.PublicKey.E)).Bytes()),
|
||||||
}
|
}
|
||||||
|
|
||||||
return jwk, nil
|
return jwk, nil
|
||||||
@@ -245,14 +262,14 @@ func (s *JwtService) generateKeys() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("failed to generate private key: " + err.Error())
|
return errors.New("failed to generate private key: " + err.Error())
|
||||||
}
|
}
|
||||||
s.privateKey = privateKey
|
s.PrivateKey = privateKey
|
||||||
|
|
||||||
if err := s.savePEMKey(privateKeyPath, x509.MarshalPKCS1PrivateKey(privateKey), "RSA PRIVATE KEY"); err != nil {
|
if err := s.savePEMKey(privateKeyPath, x509.MarshalPKCS1PrivateKey(privateKey), "RSA PRIVATE KEY"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
publicKey := &privateKey.PublicKey
|
publicKey := &privateKey.PublicKey
|
||||||
s.publicKey = publicKey
|
s.PublicKey = publicKey
|
||||||
|
|
||||||
if err := s.savePEMKey(publicKeyPath, x509.MarshalPKCS1PublicKey(publicKey), "RSA PUBLIC KEY"); err != nil {
|
if err := s.savePEMKey(publicKeyPath, x509.MarshalPKCS1PublicKey(publicKey), "RSA PUBLIC KEY"); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -280,32 +297,3 @@ func (s *JwtService) savePEMKey(path string, keyBytes []byte, keyType string) er
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadKeys loads RSA keys from the given paths.
|
|
||||||
func (s *JwtService) loadKeys() error {
|
|
||||||
if _, err := os.Stat(privateKeyPath); os.IsNotExist(err) {
|
|
||||||
if err := s.generateKeys(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
privateKeyBytes, err := os.ReadFile(privateKeyPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("can't read jwt private key: %w", err)
|
|
||||||
}
|
|
||||||
s.privateKey, err = jwt.ParseRSAPrivateKeyFromPEM(privateKeyBytes)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("can't parse jwt private key: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
publicKeyBytes, err := os.ReadFile(publicKeyPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("can't read jwt public key: %w", err)
|
|
||||||
}
|
|
||||||
s.publicKey, err = jwt.ParseRSAPublicKeyFromPEM(publicKeyBytes)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("can't parse jwt public key: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-ldap/ldap/v3"
|
"github.com/go-ldap/ldap/v3"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/dto"
|
"github.com/pocket-id/pocket-id/backend/internal/dto"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/model"
|
"github.com/pocket-id/pocket-id/backend/internal/model"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -70,12 +75,13 @@ func (s *LdapService) SyncGroups() error {
|
|||||||
baseDN := s.appConfigService.DbConfig.LdapBase.Value
|
baseDN := s.appConfigService.DbConfig.LdapBase.Value
|
||||||
nameAttribute := s.appConfigService.DbConfig.LdapAttributeGroupName.Value
|
nameAttribute := s.appConfigService.DbConfig.LdapAttributeGroupName.Value
|
||||||
uniqueIdentifierAttribute := s.appConfigService.DbConfig.LdapAttributeGroupUniqueIdentifier.Value
|
uniqueIdentifierAttribute := s.appConfigService.DbConfig.LdapAttributeGroupUniqueIdentifier.Value
|
||||||
filter := "(objectClass=groupOfUniqueNames)"
|
groupMemberOfAttribute := s.appConfigService.DbConfig.LdapAttributeGroupMember.Value
|
||||||
|
filter := s.appConfigService.DbConfig.LdapUserGroupSearchFilter.Value
|
||||||
|
|
||||||
searchAttrs := []string{
|
searchAttrs := []string{
|
||||||
nameAttribute,
|
nameAttribute,
|
||||||
uniqueIdentifierAttribute,
|
uniqueIdentifierAttribute,
|
||||||
"member",
|
groupMemberOfAttribute,
|
||||||
}
|
}
|
||||||
|
|
||||||
searchReq := ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, searchAttrs, []ldap.Control{})
|
searchReq := ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, searchAttrs, []ldap.Control{})
|
||||||
@@ -99,14 +105,15 @@ func (s *LdapService) SyncGroups() error {
|
|||||||
s.db.Where("ldap_id = ?", ldapId).First(&databaseGroup)
|
s.db.Where("ldap_id = ?", ldapId).First(&databaseGroup)
|
||||||
|
|
||||||
// Get group members and add to the correct Group
|
// Get group members and add to the correct Group
|
||||||
groupMembers := value.GetAttributeValues("member")
|
groupMembers := value.GetAttributeValues(groupMemberOfAttribute)
|
||||||
for _, member := range groupMembers {
|
for _, member := range groupMembers {
|
||||||
// Normal output of this would be CN=username,ou=people,dc=example,dc=com
|
// Normal output of this would be CN=username,ou=people,dc=example,dc=com
|
||||||
// Splitting at the "=" and "," then just grabbing the username for that string
|
// Splitting at the "=" and "," then just grabbing the username for that string
|
||||||
singleMember := strings.Split(strings.Split(member, "=")[1], ",")[0]
|
singleMember := strings.Split(strings.Split(member, "=")[1], ",")[0]
|
||||||
|
|
||||||
var databaseUser model.User
|
var databaseUser model.User
|
||||||
s.db.Where("username = ?", singleMember).First(&databaseUser)
|
s.db.Where("username = ?", singleMember).Where("ldap_id IS NOT NULL").First(&databaseUser)
|
||||||
|
|
||||||
membersUserId = append(membersUserId, databaseUser.ID)
|
membersUserId = append(membersUserId, databaseUser.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,9 +182,9 @@ func (s *LdapService) SyncUsers() error {
|
|||||||
emailAttribute := s.appConfigService.DbConfig.LdapAttributeUserEmail.Value
|
emailAttribute := s.appConfigService.DbConfig.LdapAttributeUserEmail.Value
|
||||||
firstNameAttribute := s.appConfigService.DbConfig.LdapAttributeUserFirstName.Value
|
firstNameAttribute := s.appConfigService.DbConfig.LdapAttributeUserFirstName.Value
|
||||||
lastNameAttribute := s.appConfigService.DbConfig.LdapAttributeUserLastName.Value
|
lastNameAttribute := s.appConfigService.DbConfig.LdapAttributeUserLastName.Value
|
||||||
|
profilePictureAttribute := s.appConfigService.DbConfig.LdapAttributeUserProfilePicture.Value
|
||||||
adminGroupAttribute := s.appConfigService.DbConfig.LdapAttributeAdminGroup.Value
|
adminGroupAttribute := s.appConfigService.DbConfig.LdapAttributeAdminGroup.Value
|
||||||
|
filter := s.appConfigService.DbConfig.LdapUserSearchFilter.Value
|
||||||
filter := "(objectClass=person)"
|
|
||||||
|
|
||||||
searchAttrs := []string{
|
searchAttrs := []string{
|
||||||
"memberOf",
|
"memberOf",
|
||||||
@@ -188,6 +195,7 @@ func (s *LdapService) SyncUsers() error {
|
|||||||
emailAttribute,
|
emailAttribute,
|
||||||
firstNameAttribute,
|
firstNameAttribute,
|
||||||
lastNameAttribute,
|
lastNameAttribute,
|
||||||
|
profilePictureAttribute,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filters must start and finish with ()!
|
// Filters must start and finish with ()!
|
||||||
@@ -236,9 +244,14 @@ func (s *LdapService) SyncUsers() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error syncing user %s: %s", newUser.Username, err)
|
log.Printf("Error syncing user %s: %s", newUser.Username, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save profile picture
|
||||||
|
if pictureString := value.GetAttributeValue(profilePictureAttribute); pictureString != "" {
|
||||||
|
if err := s.SaveProfilePicture(databaseUser.ID, pictureString); err != nil {
|
||||||
|
log.Printf("Error saving profile picture for user %s: %s", newUser.Username, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all LDAP users from the database
|
// Get all LDAP users from the database
|
||||||
@@ -259,3 +272,33 @@ func (s *LdapService) SyncUsers() error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *LdapService) SaveProfilePicture(userId string, pictureString string) error {
|
||||||
|
var reader io.Reader
|
||||||
|
|
||||||
|
if _, err := url.ParseRequestURI(pictureString); err == nil {
|
||||||
|
// If the photo is a URL, download it
|
||||||
|
response, err := http.Get(pictureString)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to download profile picture: %w", err)
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
reader = response.Body
|
||||||
|
|
||||||
|
} else if decodedPhoto, err := base64.StdEncoding.DecodeString(pictureString); err == nil {
|
||||||
|
// If the photo is a base64 encoded string, decode it
|
||||||
|
reader = bytes.NewReader(decodedPhoto)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// If the photo is a string, we assume that it's a binary string
|
||||||
|
reader = bytes.NewReader([]byte(pictureString))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the profile picture
|
||||||
|
if err := s.userService.UpdateProfilePicture(userId, reader); err != nil {
|
||||||
|
return fmt.Errorf("failed to update profile picture: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,20 +3,22 @@ package service
|
|||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/common"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/dto"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/model"
|
|
||||||
datatype "github.com/stonith404/pocket-id/backend/internal/model/types"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/utils"
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/dto"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/model"
|
||||||
|
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/utils"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type OidcService struct {
|
type OidcService struct {
|
||||||
@@ -38,71 +40,111 @@ func NewOidcService(db *gorm.DB, jwtService *JwtService, appConfigService *AppCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *OidcService) Authorize(input dto.AuthorizeOidcClientRequestDto, userID, ipAddress, userAgent string) (string, string, error) {
|
func (s *OidcService) Authorize(input dto.AuthorizeOidcClientRequestDto, userID, ipAddress, userAgent string) (string, string, error) {
|
||||||
var userAuthorizedOIDCClient model.UserAuthorizedOidcClient
|
|
||||||
s.db.Preload("Client").First(&userAuthorizedOIDCClient, "client_id = ? AND user_id = ?", input.ClientID, userID)
|
|
||||||
|
|
||||||
if userAuthorizedOIDCClient.Client.IsPublic && input.CodeChallenge == "" {
|
|
||||||
return "", "", &common.OidcMissingCodeChallengeError{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if userAuthorizedOIDCClient.Scope != input.Scope {
|
|
||||||
return "", "", &common.OidcMissingAuthorizationError{}
|
|
||||||
}
|
|
||||||
|
|
||||||
callbackURL, err := s.getCallbackURL(userAuthorizedOIDCClient.Client, input.CallbackURL)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
code, err := s.createAuthorizationCode(input.ClientID, userID, input.Scope, input.Nonce, input.CodeChallenge, input.CodeChallengeMethod)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.auditLogService.Create(model.AuditLogEventClientAuthorization, ipAddress, userAgent, userID, model.AuditLogData{"clientName": userAuthorizedOIDCClient.Client.Name})
|
|
||||||
|
|
||||||
return code, callbackURL, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *OidcService) AuthorizeNewClient(input dto.AuthorizeOidcClientRequestDto, userID, ipAddress, userAgent string) (string, string, error) {
|
|
||||||
var client model.OidcClient
|
var client model.OidcClient
|
||||||
if err := s.db.First(&client, "id = ?", input.ClientID).Error; err != nil {
|
if err := s.db.Preload("AllowedUserGroups").First(&client, "id = ?", input.ClientID).Error; err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the client is not public, the code challenge must be provided
|
||||||
if client.IsPublic && input.CodeChallenge == "" {
|
if client.IsPublic && input.CodeChallenge == "" {
|
||||||
return "", "", &common.OidcMissingCodeChallengeError{}
|
return "", "", &common.OidcMissingCodeChallengeError{}
|
||||||
}
|
}
|
||||||
|
|
||||||
callbackURL, err := s.getCallbackURL(client, input.CallbackURL)
|
// Get the callback URL of the client. Return an error if the provided callback URL is not allowed
|
||||||
|
callbackURL, err := s.getCallbackURL(client.CallbackURLs, input.CallbackURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
userAuthorizedClient := model.UserAuthorizedOidcClient{
|
// Check if the user group is allowed to authorize the client
|
||||||
UserID: userID,
|
var user model.User
|
||||||
ClientID: input.ClientID,
|
if err := s.db.Preload("UserGroups").First(&user, "id = ?", userID).Error; err != nil {
|
||||||
Scope: input.Scope,
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.db.Create(&userAuthorizedClient).Error; err != nil {
|
if !s.IsUserGroupAllowedToAuthorize(user, client) {
|
||||||
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
return "", "", &common.OidcAccessDeniedError{}
|
||||||
err = s.db.Model(&userAuthorizedClient).Update("scope", input.Scope).Error
|
}
|
||||||
} else {
|
|
||||||
return "", "", err
|
// Check if the user has already authorized the client with the given scope
|
||||||
|
hasAuthorizedClient, err := s.HasAuthorizedClient(input.ClientID, userID, input.Scope)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the user has not authorized the client, create a new authorization in the database
|
||||||
|
if !hasAuthorizedClient {
|
||||||
|
userAuthorizedClient := model.UserAuthorizedOidcClient{
|
||||||
|
UserID: userID,
|
||||||
|
ClientID: input.ClientID,
|
||||||
|
Scope: input.Scope,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.db.Create(&userAuthorizedClient).Error; err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
||||||
|
// The client has already been authorized but with a different scope so we need to update the scope
|
||||||
|
if err := s.db.Model(&userAuthorizedClient).Update("scope", input.Scope).Error; err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create the authorization code
|
||||||
code, err := s.createAuthorizationCode(input.ClientID, userID, input.Scope, input.Nonce, input.CodeChallenge, input.CodeChallengeMethod)
|
code, err := s.createAuthorizationCode(input.ClientID, userID, input.Scope, input.Nonce, input.CodeChallenge, input.CodeChallengeMethod)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.auditLogService.Create(model.AuditLogEventNewClientAuthorization, ipAddress, userAgent, userID, model.AuditLogData{"clientName": client.Name})
|
// Log the authorization event
|
||||||
|
if hasAuthorizedClient {
|
||||||
|
s.auditLogService.Create(model.AuditLogEventClientAuthorization, ipAddress, userAgent, userID, model.AuditLogData{"clientName": client.Name})
|
||||||
|
} else {
|
||||||
|
s.auditLogService.Create(model.AuditLogEventNewClientAuthorization, ipAddress, userAgent, userID, model.AuditLogData{"clientName": client.Name})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
return code, callbackURL, nil
|
return code, callbackURL, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasAuthorizedClient checks if the user has already authorized the client with the given scope
|
||||||
|
func (s *OidcService) HasAuthorizedClient(clientID, userID, scope string) (bool, error) {
|
||||||
|
var userAuthorizedOidcClient model.UserAuthorizedOidcClient
|
||||||
|
if err := s.db.First(&userAuthorizedOidcClient, "client_id = ? AND user_id = ?", clientID, userID).Error; err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if userAuthorizedOidcClient.Scope != scope {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsUserGroupAllowedToAuthorize checks if the user group of the user is allowed to authorize the client
|
||||||
|
func (s *OidcService) IsUserGroupAllowedToAuthorize(user model.User, client model.OidcClient) bool {
|
||||||
|
if len(client.AllowedUserGroups) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
isAllowedToAuthorize := false
|
||||||
|
for _, userGroup := range client.AllowedUserGroups {
|
||||||
|
for _, userGroupUser := range user.UserGroups {
|
||||||
|
if userGroup.ID == userGroupUser.ID {
|
||||||
|
isAllowedToAuthorize = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isAllowedToAuthorize
|
||||||
|
}
|
||||||
|
|
||||||
func (s *OidcService) CreateTokens(code, grantType, clientID, clientSecret, codeVerifier string) (string, string, error) {
|
func (s *OidcService) CreateTokens(code, grantType, clientID, clientSecret, codeVerifier string) (string, string, error) {
|
||||||
if grantType != "authorization_code" {
|
if grantType != "authorization_code" {
|
||||||
return "", "", &common.OidcGrantTypeNotSupportedError{}
|
return "", "", &common.OidcGrantTypeNotSupportedError{}
|
||||||
@@ -161,7 +203,7 @@ func (s *OidcService) CreateTokens(code, grantType, clientID, clientSecret, code
|
|||||||
|
|
||||||
func (s *OidcService) GetClient(clientID string) (model.OidcClient, error) {
|
func (s *OidcService) GetClient(clientID string) (model.OidcClient, error) {
|
||||||
var client model.OidcClient
|
var client model.OidcClient
|
||||||
if err := s.db.Preload("CreatedBy").First(&client, "id = ?", clientID).Error; err != nil {
|
if err := s.db.Preload("CreatedBy").Preload("AllowedUserGroups").First(&client, "id = ?", clientID).Error; err != nil {
|
||||||
return model.OidcClient{}, err
|
return model.OidcClient{}, err
|
||||||
}
|
}
|
||||||
return client, nil
|
return client, nil
|
||||||
@@ -186,11 +228,12 @@ func (s *OidcService) ListClients(searchTerm string, sortedPaginationRequest uti
|
|||||||
|
|
||||||
func (s *OidcService) CreateClient(input dto.OidcClientCreateDto, userID string) (model.OidcClient, error) {
|
func (s *OidcService) CreateClient(input dto.OidcClientCreateDto, userID string) (model.OidcClient, error) {
|
||||||
client := model.OidcClient{
|
client := model.OidcClient{
|
||||||
Name: input.Name,
|
Name: input.Name,
|
||||||
CallbackURLs: input.CallbackURLs,
|
CallbackURLs: input.CallbackURLs,
|
||||||
CreatedByID: userID,
|
LogoutCallbackURLs: input.LogoutCallbackURLs,
|
||||||
IsPublic: input.IsPublic,
|
CreatedByID: userID,
|
||||||
PkceEnabled: input.IsPublic || input.PkceEnabled,
|
IsPublic: input.IsPublic,
|
||||||
|
PkceEnabled: input.IsPublic || input.PkceEnabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.db.Create(&client).Error; err != nil {
|
if err := s.db.Create(&client).Error; err != nil {
|
||||||
@@ -208,6 +251,7 @@ func (s *OidcService) UpdateClient(clientID string, input dto.OidcClientCreateDt
|
|||||||
|
|
||||||
client.Name = input.Name
|
client.Name = input.Name
|
||||||
client.CallbackURLs = input.CallbackURLs
|
client.CallbackURLs = input.CallbackURLs
|
||||||
|
client.LogoutCallbackURLs = input.LogoutCallbackURLs
|
||||||
client.IsPublic = input.IsPublic
|
client.IsPublic = input.IsPublic
|
||||||
client.PkceEnabled = input.IsPublic || input.PkceEnabled
|
client.PkceEnabled = input.IsPublic || input.PkceEnabled
|
||||||
|
|
||||||
@@ -357,6 +401,7 @@ func (s *OidcService) GetUserClaimsForClient(userID string, clientID string) (ma
|
|||||||
"family_name": user.LastName,
|
"family_name": user.LastName,
|
||||||
"name": user.FullName(),
|
"name": user.FullName(),
|
||||||
"preferred_username": user.Username,
|
"preferred_username": user.Username,
|
||||||
|
"picture": fmt.Sprintf("%s/api/users/%s/profile-picture.png", common.EnvConfig.AppURL, user.ID),
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(scope, "profile") {
|
if strings.Contains(scope, "profile") {
|
||||||
@@ -372,7 +417,16 @@ func (s *OidcService) GetUserClaimsForClient(userID string, clientID string) (ma
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, customClaim := range customClaims {
|
for _, customClaim := range customClaims {
|
||||||
claims[customClaim.Key] = customClaim.Value
|
// The value of the custom claim can be a JSON object or a string
|
||||||
|
var jsonValue interface{}
|
||||||
|
json.Unmarshal([]byte(customClaim.Value), &jsonValue)
|
||||||
|
if jsonValue != nil {
|
||||||
|
// It's JSON so we store it as an object
|
||||||
|
claims[customClaim.Key] = jsonValue
|
||||||
|
} else {
|
||||||
|
// Marshalling failed, so we store it as a string
|
||||||
|
claims[customClaim.Key] = customClaim.Value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if strings.Contains(scope, "email") {
|
if strings.Contains(scope, "email") {
|
||||||
@@ -382,6 +436,73 @@ func (s *OidcService) GetUserClaimsForClient(userID string, clientID string) (ma
|
|||||||
return claims, nil
|
return claims, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *OidcService) UpdateAllowedUserGroups(id string, input dto.OidcUpdateAllowedUserGroupsDto) (client model.OidcClient, err error) {
|
||||||
|
client, err = s.GetClient(id)
|
||||||
|
if err != nil {
|
||||||
|
return model.OidcClient{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the user groups based on UserGroupIDs in input
|
||||||
|
var groups []model.UserGroup
|
||||||
|
if len(input.UserGroupIDs) > 0 {
|
||||||
|
if err := s.db.Where("id IN (?)", input.UserGroupIDs).Find(&groups).Error; err != nil {
|
||||||
|
return model.OidcClient{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace the current user groups with the new set of user groups
|
||||||
|
if err := s.db.Model(&client).Association("AllowedUserGroups").Replace(groups); err != nil {
|
||||||
|
return model.OidcClient{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the updated client
|
||||||
|
if err := s.db.Save(&client).Error; err != nil {
|
||||||
|
return model.OidcClient{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateEndSession returns the logout callback URL for the client if all the validations pass
|
||||||
|
func (s *OidcService) ValidateEndSession(input dto.OidcLogoutDto, userID string) (string, error) {
|
||||||
|
// If no ID token hint is provided, return an error
|
||||||
|
if input.IdTokenHint == "" {
|
||||||
|
return "", &common.TokenInvalidError{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the ID token hint is provided, verify the ID token
|
||||||
|
claims, err := s.jwtService.VerifyIdToken(input.IdTokenHint)
|
||||||
|
if err != nil {
|
||||||
|
return "", &common.TokenInvalidError{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the client ID is provided check if the client ID in the ID token matches the client ID in the request
|
||||||
|
if input.ClientId != "" && claims.Audience[0] != input.ClientId {
|
||||||
|
return "", &common.OidcClientIdNotMatchingError{}
|
||||||
|
}
|
||||||
|
|
||||||
|
clientId := claims.Audience[0]
|
||||||
|
|
||||||
|
// Check if the user has authorized the client before
|
||||||
|
var userAuthorizedOIDCClient model.UserAuthorizedOidcClient
|
||||||
|
if err := s.db.Preload("Client").First(&userAuthorizedOIDCClient, "client_id = ? AND user_id = ?", clientId, userID).Error; err != nil {
|
||||||
|
return "", &common.OidcMissingAuthorizationError{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the client has no logout callback URLs, return an error
|
||||||
|
if len(userAuthorizedOIDCClient.Client.LogoutCallbackURLs) == 0 {
|
||||||
|
return "", &common.OidcNoCallbackURLError{}
|
||||||
|
}
|
||||||
|
|
||||||
|
callbackURL, err := s.getCallbackURL(userAuthorizedOIDCClient.Client.LogoutCallbackURLs, input.PostLogoutRedirectUri)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return callbackURL, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func (s *OidcService) createAuthorizationCode(clientID string, userID string, scope string, nonce string, codeChallenge string, codeChallengeMethod string) (string, error) {
|
func (s *OidcService) createAuthorizationCode(clientID string, userID string, scope string, nonce string, codeChallenge string, codeChallengeMethod string) (string, error) {
|
||||||
randomString, err := utils.GenerateRandomAlphanumericString(32)
|
randomString, err := utils.GenerateRandomAlphanumericString(32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -428,12 +549,12 @@ func (s *OidcService) validateCodeVerifier(codeVerifier, codeChallenge string, c
|
|||||||
return encodedVerifierHash == codeChallenge
|
return encodedVerifierHash == codeChallenge
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *OidcService) getCallbackURL(client model.OidcClient, inputCallbackURL string) (callbackURL string, err error) {
|
func (s *OidcService) getCallbackURL(urls []string, inputCallbackURL string) (callbackURL string, err error) {
|
||||||
if inputCallbackURL == "" {
|
if inputCallbackURL == "" {
|
||||||
return client.CallbackURLs[0], nil
|
return urls[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, callbackPattern := range client.CallbackURLs {
|
for _, callbackPattern := range urls {
|
||||||
regexPattern := strings.ReplaceAll(regexp.QuoteMeta(callbackPattern), `\*`, ".*") + "$"
|
regexPattern := strings.ReplaceAll(regexp.QuoteMeta(callbackPattern), `\*`, ".*") + "$"
|
||||||
matched, err := regexp.MatchString(regexPattern, inputCallbackURL)
|
matched, err := regexp.MatchString(regexPattern, inputCallbackURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -4,29 +4,32 @@ import (
|
|||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/fxamacker/cbor/v2"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/model/types"
|
|
||||||
"github.com/stonith404/pocket-id/backend/resources"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/fxamacker/cbor/v2"
|
||||||
|
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/resources"
|
||||||
|
|
||||||
"github.com/go-webauthn/webauthn/protocol"
|
"github.com/go-webauthn/webauthn/protocol"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/common"
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/model"
|
"github.com/pocket-id/pocket-id/backend/internal/model"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/utils"
|
"github.com/pocket-id/pocket-id/backend/internal/utils"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TestService struct {
|
type TestService struct {
|
||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
|
jwtService *JwtService
|
||||||
appConfigService *AppConfigService
|
appConfigService *AppConfigService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTestService(db *gorm.DB, appConfigService *AppConfigService) *TestService {
|
func NewTestService(db *gorm.DB, appConfigService *AppConfigService, jwtService *JwtService) *TestService {
|
||||||
return &TestService{db: db, appConfigService: appConfigService}
|
return &TestService{db: db, appConfigService: appConfigService, jwtService: jwtService}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TestService) SeedDatabase() error {
|
func (s *TestService) SeedDatabase() error {
|
||||||
@@ -111,11 +114,12 @@ func (s *TestService) SeedDatabase() error {
|
|||||||
Base: model.Base{
|
Base: model.Base{
|
||||||
ID: "3654a746-35d4-4321-ac61-0bdcff2b4055",
|
ID: "3654a746-35d4-4321-ac61-0bdcff2b4055",
|
||||||
},
|
},
|
||||||
Name: "Nextcloud",
|
Name: "Nextcloud",
|
||||||
Secret: "$2a$10$9dypwot8nGuCjT6wQWWpJOckZfRprhe2EkwpKizxS/fpVHrOLEJHC", // w2mUeZISmEvIDMEDvpY0PnxQIpj1m3zY
|
Secret: "$2a$10$9dypwot8nGuCjT6wQWWpJOckZfRprhe2EkwpKizxS/fpVHrOLEJHC", // w2mUeZISmEvIDMEDvpY0PnxQIpj1m3zY
|
||||||
CallbackURLs: model.CallbackURLs{"http://nextcloud/auth/callback"},
|
CallbackURLs: model.UrlList{"http://nextcloud/auth/callback"},
|
||||||
ImageType: utils.StringPointer("png"),
|
LogoutCallbackURLs: model.UrlList{"http://nextcloud/auth/logout/callback"},
|
||||||
CreatedByID: users[0].ID,
|
ImageType: utils.StringPointer("png"),
|
||||||
|
CreatedByID: users[0].ID,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Base: model.Base{
|
Base: model.Base{
|
||||||
@@ -123,8 +127,11 @@ func (s *TestService) SeedDatabase() error {
|
|||||||
},
|
},
|
||||||
Name: "Immich",
|
Name: "Immich",
|
||||||
Secret: "$2a$10$Ak.FP8riD1ssy2AGGbG.gOpnp/rBpymd74j0nxNMtW0GG1Lb4gzxe", // PYjrE9u4v9GVqXKi52eur0eb2Ci4kc0x
|
Secret: "$2a$10$Ak.FP8riD1ssy2AGGbG.gOpnp/rBpymd74j0nxNMtW0GG1Lb4gzxe", // PYjrE9u4v9GVqXKi52eur0eb2Ci4kc0x
|
||||||
CallbackURLs: model.CallbackURLs{"http://immich/auth/callback"},
|
CallbackURLs: model.UrlList{"http://immich/auth/callback"},
|
||||||
CreatedByID: users[0].ID,
|
CreatedByID: users[1].ID,
|
||||||
|
AllowedUserGroups: []model.UserGroup{
|
||||||
|
userGroups[1],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, client := range oidcClients {
|
for _, client := range oidcClients {
|
||||||
@@ -163,27 +170,31 @@ func (s *TestService) SeedDatabase() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
publicKey1, err := s.getCborPublicKey("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEwcOo5KV169KR67QEHrcYkeXE3CCxv2BgwnSq4VYTQxyLtdmKxegexa8JdwFKhKXa2BMI9xaN15BoL6wSCRFJhg==")
|
// To generate a new key pair, run the following command:
|
||||||
publicKey2, err := s.getCborPublicKey("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESq/wR8QbBu3dKnpaw/v0mDxFFDwnJ/L5XHSg2tAmq5x1BpSMmIr3+DxCbybVvGRmWGh8kKhy7SMnK91M6rFHTA==")
|
// openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 | \
|
||||||
|
// openssl pkcs8 -topk8 -nocrypt | tee >(openssl pkey -pubout)
|
||||||
|
|
||||||
|
publicKeyPasskey1, err := s.getCborPublicKey("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEwcOo5KV169KR67QEHrcYkeXE3CCxv2BgwnSq4VYTQxyLtdmKxegexa8JdwFKhKXa2BMI9xaN15BoL6wSCRFJhg==")
|
||||||
|
publicKeyPasskey2, err := s.getCborPublicKey("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEj4qA0PrZzg8Co1C27nyUbzrp8Ewjr7eOlGI2LfrzmbL5nPhZRAdJ3hEaqrHMSnJBhfMqtQGKwDYpaLIQFAKLhw==")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
webauthnCredentials := []model.WebauthnCredential{
|
webauthnCredentials := []model.WebauthnCredential{
|
||||||
{
|
{
|
||||||
Name: "Passkey 1",
|
Name: "Passkey 1",
|
||||||
CredentialID: []byte("test-credential-1"),
|
CredentialID: []byte("test-credential-tim"),
|
||||||
PublicKey: publicKey1,
|
PublicKey: publicKeyPasskey1,
|
||||||
AttestationType: "none",
|
AttestationType: "none",
|
||||||
Transport: model.AuthenticatorTransportList{protocol.Internal},
|
Transport: model.AuthenticatorTransportList{protocol.Internal},
|
||||||
UserID: users[0].ID,
|
UserID: users[0].ID,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "Passkey 2",
|
Name: "Passkey 2",
|
||||||
CredentialID: []byte("test-credential-2"),
|
CredentialID: []byte("test-credential-craig"),
|
||||||
PublicKey: publicKey2,
|
PublicKey: publicKeyPasskey2,
|
||||||
AttestationType: "none",
|
AttestationType: "none",
|
||||||
Transport: model.AuthenticatorTransportList{protocol.Internal},
|
Transport: model.AuthenticatorTransportList{protocol.Internal},
|
||||||
UserID: users[0].ID,
|
UserID: users[1].ID,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, credential := range webauthnCredentials {
|
for _, credential := range webauthnCredentials {
|
||||||
@@ -280,6 +291,43 @@ func (s *TestService) ResetAppConfig() error {
|
|||||||
return s.appConfigService.LoadDbConfigFromDb()
|
return s.appConfigService.LoadDbConfigFromDb()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *TestService) SetJWTKeys() {
|
||||||
|
privateKeyString := `-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpQIBAAKCAQEAyaeEL0VKoPBXIAaWXsUgmu05lAvEIIdJn0FX9lHh4JE5UY9B
|
||||||
|
83C5sCNdhs9iSWzpeP11EVjWp8i3Yv2CF7c7u50BXnVBGtxpZpFC+585UXacoJ0c
|
||||||
|
hUmarL9GRFJcM1nPHBTFu68aRrn1rIKNHUkNaaxFo0NFGl/4EDDTO8HwawTjwkPo
|
||||||
|
QlRzeByhlvGPVvwgB3Fn93B8QJ/cZhXKxJvjjrC/8Pk76heC/ntEMru71Ix77BoC
|
||||||
|
3j2TuyiN7m9RNBW8BU5q6lKoIdvIeZfTFLzi37iufyfvMrJTixp9zhNB1NxlLCeO
|
||||||
|
Zl2MXegtiGqd2H3cbAyqoOiv9ihUWTfXj7SxJwIDAQABAoIBAQCa8wNZJ08+9y6b
|
||||||
|
RzSIQcTaBuq1XY0oyYvCuX0ToruDyVNX3lJ48udb9vDIw9XsQans9CTeXXsjldGE
|
||||||
|
WPN7sapOcUg6ArMyJqc+zuO/YQu0EwYrTE48BOC7WIZvvTFnq9y+4R9HJjd0nTOv
|
||||||
|
iOlR1W5fAqbH2srgh1mfZ0UIp+9K6ymoinPXVGEXUAuuoMuTEZW/tnA2HT9WEllT
|
||||||
|
2FyMbmXrFzutAQqk9GRmnQh2OQZLxnQWyShVqJEhYBtm6JUUH1YJbyTVzMLgdBM8
|
||||||
|
ukgjTVtRDHaW51ubRSVdGBVT2m1RRtTsYAiZCpM5bwt88aSUS9yDOUiVH+irDg/3
|
||||||
|
IHEuL7IxAoGBAP2MpXPXtOwinajUQ9hKLDAtpq4axGvY+aGP5dNEMsuPo5ggOfUP
|
||||||
|
b4sqr73kaNFO3EbxQOQVoFjehhi4dQxt1/kAala9HZ5N7s26G2+eUWFF8jy7gWSN
|
||||||
|
qusNqGrG4g8D3WOyqZFb/x/m6SE0Jcg7zvIYbnAOq1Fexeik0Fc/DNzLAoGBAMua
|
||||||
|
d4XIfu4ydtU5AIaf1ZNXywgLg+LWxK8ELNqH/Y2vLAeIiTrOVp+hw9z+zHPD5cnu
|
||||||
|
6mix783PCOYNLTylrwtAz3fxSz14lsDFQM3ntzVF/6BniTTkKddctcPyqnTvamah
|
||||||
|
0hD2dzXBS/0mTBYIIMYTNbs0Yj87FTdJZw/+qa2VAoGBAKbzQkp54W6PCIMPabD0
|
||||||
|
fg4nMRZ5F5bv4seIKcunn068QPs9VQxQ4qCfNeLykDYqGA86cgD9YHzD4UZLxv6t
|
||||||
|
IUWbCWod0m/XXwPlpIUlmO5VEUD+MiAUzFNDxf6xAE7ku5UXImJNUjseX6l2Xd5v
|
||||||
|
yz9L6QQuFI5aujQKugiIwp5rAoGATtUVGCCkPNgfOLmkYXu7dxxUCV5kB01+xAEK
|
||||||
|
2OY0n0pG8vfDophH4/D/ZC7nvJ8J9uDhs/3JStexq1lIvaWtG99RNTChIEDzpdn6
|
||||||
|
GH9yaVcb/eB4uJjrNm64FhF8PGCCwxA+xMCZMaARKwhMB2/IOMkxUbWboL3gnhJ2
|
||||||
|
rDO/QO0CgYEA2Grt6uXHm61ji3xSdkBWNtUnj19vS1+7rFJp5SoYztVQVThf/W52
|
||||||
|
BAiXKBdYZDRVoItC/VS2NvAOjeJjhYO/xQ/q3hK7MdtuXfEPpLnyXKkmWo3lrJ26
|
||||||
|
wbeF6l05LexCkI7ShsOuSt+dsyaTJTszuKDIA6YOfWvfo3aVZmlWRaI=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
`
|
||||||
|
|
||||||
|
block, _ := pem.Decode([]byte(privateKeyString))
|
||||||
|
privateKey, _ := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||||
|
|
||||||
|
s.jwtService.PrivateKey = privateKey
|
||||||
|
s.jwtService.PublicKey = &privateKey.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
// getCborPublicKey decodes a Base64 encoded public key and returns the CBOR encoded COSE key
|
// getCborPublicKey decodes a Base64 encoded public key and returns the CBOR encoded COSE key
|
||||||
func (s *TestService) getCborPublicKey(base64PublicKey string) ([]byte, error) {
|
func (s *TestService) getCborPublicKey(base64PublicKey string) ([]byte, error) {
|
||||||
decodedKey, err := base64.StdEncoding.DecodeString(base64PublicKey)
|
decodedKey, err := base64.StdEncoding.DecodeString(base64PublicKey)
|
||||||
|
|||||||
@@ -2,19 +2,21 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/common"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/dto"
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/model"
|
"github.com/pocket-id/pocket-id/backend/internal/dto"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/utils"
|
"github.com/pocket-id/pocket-id/backend/internal/model"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/utils"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserGroupService struct {
|
type UserGroupService struct {
|
||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
|
appConfigService *AppConfigService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUserGroupService(db *gorm.DB) *UserGroupService {
|
func NewUserGroupService(db *gorm.DB, appConfigService *AppConfigService) *UserGroupService {
|
||||||
return &UserGroupService{db: db}
|
return &UserGroupService{db: db, appConfigService: appConfigService}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *UserGroupService) List(name string, sortedPaginationRequest utils.SortedPaginationRequest) (groups []model.UserGroup, response utils.PaginationResponse, err error) {
|
func (s *UserGroupService) List(name string, sortedPaginationRequest utils.SortedPaginationRequest) (groups []model.UserGroup, response utils.PaginationResponse, err error) {
|
||||||
@@ -51,7 +53,8 @@ func (s *UserGroupService) Delete(id string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if group.LdapID != nil {
|
// Disallow deleting the group if it is an LDAP group and LDAP is enabled
|
||||||
|
if group.LdapID != nil && s.appConfigService.DbConfig.LdapEnabled.Value == "true" {
|
||||||
return &common.LdapUserGroupUpdateError{}
|
return &common.LdapUserGroupUpdateError{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,13 +86,13 @@ func (s *UserGroupService) Update(id string, input dto.UserGroupCreateDto, allow
|
|||||||
return model.UserGroup{}, err
|
return model.UserGroup{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if group.LdapID != nil && !allowLdapUpdate {
|
// Disallow updating the group if it is an LDAP group and LDAP is enabled
|
||||||
|
if !allowLdapUpdate && group.LdapID != nil && s.appConfigService.DbConfig.LdapEnabled.Value == "true" {
|
||||||
return model.UserGroup{}, &common.LdapUserGroupUpdateError{}
|
return model.UserGroup{}, &common.LdapUserGroupUpdateError{}
|
||||||
}
|
}
|
||||||
|
|
||||||
group.Name = input.Name
|
group.Name = input.Name
|
||||||
group.FriendlyName = input.FriendlyName
|
group.FriendlyName = input.FriendlyName
|
||||||
group.LdapID = &input.LdapID
|
|
||||||
|
|
||||||
if err := s.db.Preload("Users").Save(&group).Error; err != nil {
|
if err := s.db.Preload("Users").Save(&group).Error; err != nil {
|
||||||
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
||||||
|
|||||||
@@ -3,28 +3,34 @@ package service
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/common"
|
"github.com/google/uuid"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/dto"
|
"github.com/pocket-id/pocket-id/backend/internal/utils/image"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/model"
|
"io"
|
||||||
"github.com/stonith404/pocket-id/backend/internal/model/types"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/utils"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/utils/email"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"log"
|
"log"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/dto"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/model"
|
||||||
|
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/utils"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/utils/email"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserService struct {
|
type UserService struct {
|
||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
jwtService *JwtService
|
jwtService *JwtService
|
||||||
auditLogService *AuditLogService
|
auditLogService *AuditLogService
|
||||||
emailService *EmailService
|
emailService *EmailService
|
||||||
|
appConfigService *AppConfigService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUserService(db *gorm.DB, jwtService *JwtService, auditLogService *AuditLogService, emailService *EmailService) *UserService {
|
func NewUserService(db *gorm.DB, jwtService *JwtService, auditLogService *AuditLogService, emailService *EmailService, appConfigService *AppConfigService) *UserService {
|
||||||
return &UserService{db: db, jwtService: jwtService, auditLogService: auditLogService, emailService: emailService}
|
return &UserService{db: db, jwtService: jwtService, auditLogService: auditLogService, emailService: emailService, appConfigService: appConfigService}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *UserService) ListUsers(searchTerm string, sortedPaginationRequest utils.SortedPaginationRequest) ([]model.User, utils.PaginationResponse, error) {
|
func (s *UserService) ListUsers(searchTerm string, sortedPaginationRequest utils.SortedPaginationRequest) ([]model.User, utils.PaginationResponse, error) {
|
||||||
@@ -46,13 +52,79 @@ func (s *UserService) GetUser(userID string) (model.User, error) {
|
|||||||
return user, err
|
return user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *UserService) GetProfilePicture(userID string) (io.Reader, int64, error) {
|
||||||
|
// Validate the user ID to prevent directory traversal
|
||||||
|
if err := uuid.Validate(userID); err != nil {
|
||||||
|
return nil, 0, &common.InvalidUUIDError{}
|
||||||
|
}
|
||||||
|
|
||||||
|
profilePicturePath := fmt.Sprintf("%s/profile-pictures/%s.png", common.EnvConfig.UploadPath, userID)
|
||||||
|
file, err := os.Open(profilePicturePath)
|
||||||
|
if err == nil {
|
||||||
|
// Get the file size
|
||||||
|
fileInfo, err := file.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
return file, fileInfo.Size(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the file does not exist, return the default profile picture
|
||||||
|
user, err := s.GetUser(userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultPicture, err := profilepicture.CreateDefaultProfilePicture(user.FirstName, user.LastName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultPicture, int64(defaultPicture.Len()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UserService) UpdateProfilePicture(userID string, file io.Reader) error {
|
||||||
|
// Validate the user ID to prevent directory traversal
|
||||||
|
if err := uuid.Validate(userID); err != nil {
|
||||||
|
return &common.InvalidUUIDError{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the image to a smaller square image
|
||||||
|
profilePicture, err := profilepicture.CreateProfilePicture(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the directory exists
|
||||||
|
profilePictureDir := fmt.Sprintf("%s/profile-pictures", common.EnvConfig.UploadPath)
|
||||||
|
if err := os.MkdirAll(profilePictureDir, os.ModePerm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the profile picture file
|
||||||
|
createdProfilePicture, err := os.Create(fmt.Sprintf("%s/%s.png", profilePictureDir, userID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer createdProfilePicture.Close()
|
||||||
|
|
||||||
|
// Copy the image to the file
|
||||||
|
_, err = io.Copy(createdProfilePicture, profilePicture)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *UserService) DeleteUser(userID string) error {
|
func (s *UserService) DeleteUser(userID string) error {
|
||||||
var user model.User
|
var user model.User
|
||||||
if err := s.db.Where("id = ?", userID).First(&user).Error; err != nil {
|
if err := s.db.Where("id = ?", userID).First(&user).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.LdapID != nil {
|
// Disallow deleting the user if it is an LDAP user and LDAP is enabled
|
||||||
|
if user.LdapID != nil && s.appConfigService.DbConfig.LdapEnabled.Value == "true" {
|
||||||
return &common.LdapUserUpdateError{}
|
return &common.LdapUserUpdateError{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +158,8 @@ func (s *UserService) UpdateUser(userID string, updatedUser dto.UserCreateDto, u
|
|||||||
return model.User{}, err
|
return model.User{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.LdapID != nil && !allowLdapUpdate {
|
// Disallow updating the user if it is an LDAP group and LDAP is enabled
|
||||||
|
if !allowLdapUpdate && user.LdapID != nil && s.appConfigService.DbConfig.LdapEnabled.Value == "true" {
|
||||||
return model.User{}, &common.LdapUserUpdateError{}
|
return model.User{}, &common.LdapUserUpdateError{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/go-webauthn/webauthn/protocol"
|
|
||||||
"github.com/go-webauthn/webauthn/webauthn"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/common"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/model"
|
|
||||||
datatype "github.com/stonith404/pocket-id/backend/internal/model/types"
|
|
||||||
"github.com/stonith404/pocket-id/backend/internal/utils"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-webauthn/webauthn/protocol"
|
||||||
|
"github.com/go-webauthn/webauthn/webauthn"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/model"
|
||||||
|
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/utils"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WebAuthnService struct {
|
type WebAuthnService struct {
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package cookie
|
package cookie
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/stonith404/pocket-id/backend/internal/common"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
var AccessTokenCookieName = "__Host-access_token"
|
var AccessTokenCookieName = "__Host-access_token"
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ package email
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/stonith404/pocket-id/backend/resources"
|
|
||||||
htemplate "html/template"
|
htemplate "html/template"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"path"
|
"path"
|
||||||
ttemplate "text/template"
|
ttemplate "text/template"
|
||||||
|
|
||||||
|
"github.com/pocket-id/pocket-id/backend/resources"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Template[V any] struct {
|
type Template[V any] struct {
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/stonith404/pocket-id/backend/resources"
|
|
||||||
"io"
|
"io"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pocket-id/pocket-id/backend/resources"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetFileExtension(filename string) string {
|
func GetFileExtension(filename string) string {
|
||||||
|
|||||||
96
backend/internal/utils/image/profile_picture.go
Normal file
96
backend/internal/utils/image/profile_picture.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
package profilepicture
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"github.com/disintegration/imaging"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/resources"
|
||||||
|
"golang.org/x/image/font"
|
||||||
|
"golang.org/x/image/font/opentype"
|
||||||
|
"golang.org/x/image/math/fixed"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const profilePictureSize = 300
|
||||||
|
|
||||||
|
// CreateProfilePicture resizes the profile picture to a square
|
||||||
|
func CreateProfilePicture(file io.Reader) (*bytes.Buffer, error) {
|
||||||
|
img, err := imaging.Decode(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode image: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
img = imaging.Fill(img, profilePictureSize, profilePictureSize, imaging.Center, imaging.Lanczos)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = imaging.Encode(&buf, img, imaging.PNG)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to encode image: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDefaultProfilePicture creates a profile picture with the initials
|
||||||
|
func CreateDefaultProfilePicture(firstName, lastName string) (*bytes.Buffer, error) {
|
||||||
|
// Get the initials
|
||||||
|
initials := ""
|
||||||
|
if len(firstName) > 0 {
|
||||||
|
initials += string(firstName[0])
|
||||||
|
}
|
||||||
|
if len(lastName) > 0 {
|
||||||
|
initials += string(lastName[0])
|
||||||
|
}
|
||||||
|
initials = strings.ToUpper(initials)
|
||||||
|
|
||||||
|
// Create a blank image with a white background
|
||||||
|
img := imaging.New(profilePictureSize, profilePictureSize, color.RGBA{R: 255, G: 255, B: 255, A: 255})
|
||||||
|
|
||||||
|
// Load the font
|
||||||
|
fontBytes, err := resources.FS.ReadFile("fonts/PlayfairDisplay-Bold.ttf")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read font file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the font
|
||||||
|
fontFace, err := opentype.Parse(fontBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse font: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a font.Face with a specific size
|
||||||
|
fontSize := 160.0
|
||||||
|
face, err := opentype.NewFace(fontFace, &opentype.FaceOptions{
|
||||||
|
Size: fontSize,
|
||||||
|
DPI: 72,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create font face: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a drawer for the image
|
||||||
|
drawer := &font.Drawer{
|
||||||
|
Dst: img,
|
||||||
|
Src: image.NewUniform(color.RGBA{R: 0, G: 0, B: 0, A: 255}), // Black text color
|
||||||
|
Face: face,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Center the initials
|
||||||
|
x := (profilePictureSize - font.MeasureString(face, initials).Ceil()) / 2
|
||||||
|
y := (profilePictureSize-face.Metrics().Height.Ceil())/2 + face.Metrics().Ascent.Ceil() - 10
|
||||||
|
drawer.Dot = fixed.P(x, y)
|
||||||
|
|
||||||
|
// Draw the initials
|
||||||
|
drawer.DrawString(initials)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = imaging.Encode(&buf, img, imaging.PNG)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to encode image: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &buf, nil
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -62,3 +64,12 @@ func CamelCaseToSnakeCase(s string) string {
|
|||||||
}
|
}
|
||||||
return string(result)
|
return string(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CamelCaseToScreamingSnakeCase(s string) string {
|
||||||
|
// Insert underscores before uppercase letters (except the first one)
|
||||||
|
re := regexp.MustCompile(`([a-z0-9])([A-Z])`)
|
||||||
|
snake := re.ReplaceAllString(s, `${1}_${2}`)
|
||||||
|
|
||||||
|
// Convert to uppercase
|
||||||
|
return strings.ToUpper(snake)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,95 +1,92 @@
|
|||||||
{{ define "style" }}
|
{{ define "style" }}
|
||||||
<style>
|
<style>
|
||||||
body {
|
/* Reset styles for email clients */
|
||||||
font-family: Arial, sans-serif;
|
body, table, td, p, a {
|
||||||
background-color: #f0f0f0;
|
margin: 0;
|
||||||
color: #333;
|
padding: 0;
|
||||||
margin: 0;
|
border: 0;
|
||||||
padding: 0;
|
font-size: 100%;
|
||||||
}
|
font-family: Arial, sans-serif;
|
||||||
.container {
|
line-height: 1.5;
|
||||||
background-color: #fff;
|
}
|
||||||
color: #333;
|
body {
|
||||||
padding: 32px;
|
background-color: #f0f0f0;
|
||||||
border-radius: 10px;
|
color: #333;
|
||||||
max-width: 600px;
|
}
|
||||||
margin: 40px auto;
|
.container {
|
||||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
width: 100%;
|
||||||
}
|
max-width: 600px;
|
||||||
.header {
|
margin: 40px auto;
|
||||||
display: flex;
|
background-color: #fff;
|
||||||
justify-content: space-between;
|
border-radius: 10px;
|
||||||
align-items: center;
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
margin-bottom: 24px;
|
padding: 32px;
|
||||||
}
|
}
|
||||||
.header .logo {
|
.header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
margin-bottom: 24px;
|
||||||
gap: 8px;
|
}
|
||||||
}
|
.header .logo img {
|
||||||
.header .logo img {
|
width: 32px;
|
||||||
width: 32px;
|
height: 32px;
|
||||||
height: 32px;
|
vertical-align: middle;
|
||||||
object-fit: cover;
|
}
|
||||||
}
|
.header h1 {
|
||||||
.header h1 {
|
font-size: 1.5rem;
|
||||||
font-size: 1.5rem;
|
font-weight: bold;
|
||||||
font-weight: bold;
|
display: inline-block;
|
||||||
}
|
vertical-align: middle;
|
||||||
.warning {
|
margin-left: 8px;
|
||||||
background-color: #ffd966;
|
}
|
||||||
color: #7f6000;
|
.warning {
|
||||||
padding: 4px 12px;
|
background-color: #ffd966;
|
||||||
border-radius: 50px;
|
color: #7f6000;
|
||||||
font-size: 0.875rem;
|
padding: 4px 12px;
|
||||||
}
|
border-radius: 50px;
|
||||||
.content {
|
font-size: 0.875rem;
|
||||||
background-color: #fafafa;
|
margin: auto 0 auto auto;
|
||||||
color: #333;
|
}
|
||||||
padding: 24px;
|
.content {
|
||||||
border-radius: 10px;
|
background-color: #fafafa;
|
||||||
}
|
padding: 24px;
|
||||||
.content h2 {
|
border-radius: 10px;
|
||||||
font-size: 1.25rem;
|
}
|
||||||
font-weight: bold;
|
.content h2 {
|
||||||
margin-bottom: 16px;
|
font-size: 1.25rem;
|
||||||
}
|
font-weight: bold;
|
||||||
.grid {
|
margin-bottom: 16px;
|
||||||
display: grid;
|
}
|
||||||
grid-template-columns: 1fr 1fr;
|
.grid {
|
||||||
gap: 16px;
|
width: 100%;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
.grid div {
|
.grid td {
|
||||||
display: flex;
|
width: 50%;
|
||||||
flex-direction: column;
|
padding-bottom: 8px;
|
||||||
}
|
vertical-align: top;
|
||||||
.grid p {
|
}
|
||||||
margin: 0;
|
.label {
|
||||||
}
|
color: #888;
|
||||||
.label {
|
font-size: 0.875rem;
|
||||||
color: #888;
|
}
|
||||||
font-size: 0.875rem;
|
.message {
|
||||||
margin-bottom: 4px;
|
font-size: 1rem;
|
||||||
}
|
line-height: 1.5;
|
||||||
.message {
|
margin-top: 16px;
|
||||||
font-size: 1rem;
|
}
|
||||||
line-height: 1.5;
|
.button {
|
||||||
}
|
background-color: #000000;
|
||||||
.button {
|
color: #ffffff;
|
||||||
border-radius: 0.375rem;
|
padding: 0.7rem 1.5rem;
|
||||||
font-size: 1rem;
|
text-decoration: none;
|
||||||
font-weight: 500;
|
border-radius: 4px;
|
||||||
background-color: #000000;
|
font-size: 1rem;
|
||||||
color: #ffffff;
|
font-weight: 500;
|
||||||
padding: 0.7rem 1.5rem;
|
display: inline-block;
|
||||||
outline: none;
|
margin-top: 24px;
|
||||||
border: none;
|
}
|
||||||
text-decoration: none;
|
.button-container {
|
||||||
}
|
text-align: center;
|
||||||
.button-container {
|
}
|
||||||
text-align: center;
|
|
||||||
margin-top: 24px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|||||||
@@ -1,36 +1,40 @@
|
|||||||
{{ define "base" }}
|
{{ define "base" }}
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="logo">
|
<div class="logo">
|
||||||
<img src="{{ .LogoURL }}" alt="{{ .AppName }}"/>
|
<img src="{{ .LogoURL }}" alt="{{ .AppName }}"/>
|
||||||
<h1>{{ .AppName }}</h1>
|
<h1>{{ .AppName }}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="warning">Warning</div>
|
<div class="warning">Warning</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<h2>New Sign-In Detected</h2>
|
<h2>New Sign-In Detected</h2>
|
||||||
<div class="grid">
|
<table class="grid">
|
||||||
{{ if and .Data.City .Data.Country }}
|
<tr>
|
||||||
<div>
|
{{ if and .Data.City .Data.Country }}
|
||||||
<p class="label">Approximate Location</p>
|
<td>
|
||||||
<p>{{ .Data.City }}, {{ .Data.Country }}</p>
|
<p class="label">Approximate Location</p>
|
||||||
</div>
|
<p>{{ .Data.City }}, {{ .Data.Country }}</p>
|
||||||
{{ end }}
|
</td>
|
||||||
<div>
|
{{ end }}
|
||||||
<p class="label">IP Address</p>
|
<td>
|
||||||
<p>{{ .Data.IPAddress }}</p>
|
<p class="label">IP Address</p>
|
||||||
</div>
|
<p>{{ .Data.IPAddress }}</p>
|
||||||
<div>
|
</td>
|
||||||
<p class="label">Device</p>
|
</tr>
|
||||||
<p>{{ .Data.Device }}</p>
|
<tr>
|
||||||
</div>
|
<td>
|
||||||
<div>
|
<p class="label">Device</p>
|
||||||
<p class="label">Sign-In Time</p>
|
<p>{{ .Data.Device }}</p>
|
||||||
<p>{{ .Data.DateTime.Format "2006-01-02 15:04:05 UTC" }}</p>
|
</td>
|
||||||
</div>
|
<td>
|
||||||
</div>
|
<p class="label">Sign-In Time</p>
|
||||||
<p class="message">
|
<p>{{ .Data.DateTime.Format "2006-01-02 15:04:05 UTC" }}</p>
|
||||||
This sign-in was detected from a new device or location. If you recognize this activity, you can
|
</td>
|
||||||
safely ignore this message. If not, please review your account and security settings.
|
</tr>
|
||||||
</p>
|
</table>
|
||||||
</div>
|
<p class="message">
|
||||||
|
This sign-in was detected from a new device or location. If you recognize this activity, you can
|
||||||
|
safely ignore this message. If not, please review your account and security settings.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
{{ end -}}
|
{{ end -}}
|
||||||
@@ -4,5 +4,5 @@ import "embed"
|
|||||||
|
|
||||||
// Embedded file systems for the project
|
// Embedded file systems for the project
|
||||||
|
|
||||||
//go:embed email-templates images migrations
|
//go:embed email-templates images migrations fonts
|
||||||
var FS embed.FS
|
var FS embed.FS
|
||||||
|
|||||||
BIN
backend/resources/fonts/PlayfairDisplay-Bold.ttf
Normal file
BIN
backend/resources/fonts/PlayfairDisplay-Bold.ttf
Normal file
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
DROP TABLE oidc_clients_allowed_user_groups;
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
CREATE TABLE oidc_clients_allowed_user_groups
|
||||||
|
(
|
||||||
|
user_group_id UUID NOT NULL REFERENCES user_groups ON DELETE CASCADE,
|
||||||
|
oidc_client_id UUID NOT NULL REFERENCES oidc_clients ON DELETE CASCADE,
|
||||||
|
PRIMARY KEY (oidc_client_id, user_group_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
UPDATE user_groups SET ldap_id = '' WHERE ldap_id IS NULL;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
UPDATE user_groups SET ldap_id = null WHERE ldap_id = '';
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE oidc_clients DROP COLUMN logout_callback_urls;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE oidc_clients ADD COLUMN logout_callback_urls JSONB;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
DROP TABLE oidc_clients_allowed_user_groups;
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
CREATE TABLE oidc_clients_allowed_user_groups
|
||||||
|
(
|
||||||
|
user_group_id TEXT NOT NULL,
|
||||||
|
oidc_client_id TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (oidc_client_id, user_group_id),
|
||||||
|
FOREIGN KEY (oidc_client_id) REFERENCES oidc_clients (id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (user_group_id) REFERENCES user_groups (id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
UPDATE user_groups SET ldap_id = '' WHERE ldap_id IS NULL;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
UPDATE user_groups SET ldap_id = null WHERE ldap_id = '';
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE oidc_clients DROP COLUMN logout_callback_urls;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE oidc_clients ADD COLUMN logout_callback_urls BLOB;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
pocket-id:
|
pocket-id:
|
||||||
image: stonith404/pocket-id # or ghcr.io/stonith404/pocket-id
|
image: ghcr.io/pocket-id/pocket-id
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
env_file: .env
|
env_file: .env
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
---
|
|
||||||
id: cloudflare-zero-trust
|
|
||||||
---
|
|
||||||
# Cloudflare Zero Trust
|
|
||||||
|
|
||||||
**Note: Cloudflare will need to be able to reach your Pocket ID instance and vice versa for this to work correctly**
|
|
||||||
|
|
||||||
## Pocket ID Setup
|
|
||||||
|
|
||||||
1. In Pocket-ID create a new OIDC Client, name it i.e. `Cloudflare Zero Trust`.
|
|
||||||
2. Set a logo for this OIDC Client if you would like too.
|
|
||||||
3. Set the callback URL to: `https://<your-team-name>.cloudflareaccess.com/cdn-cgi/access/callback`.
|
|
||||||
4. Copy the Client ID, Client Secret, Authorization URL, Token URL, and Certificate URL for the next steps.
|
|
||||||
|
|
||||||
## Cloudflare Zero Trust Setup
|
|
||||||
|
|
||||||
1. Login to Cloudflare Zero Trust [Dashboard](https://one.dash.cloudflare.com/).
|
|
||||||
2. Navigate to Settings > Authentication > Login Methods.
|
|
||||||
3. Click `Add New` under login methods.
|
|
||||||
4. Create a name for the new login method.
|
|
||||||
5. Paste in the `Client ID` from Pocket ID into the `App ID` field.
|
|
||||||
6. Paste the `Client Secret` from Pocket ID into the `Client Secret` field.
|
|
||||||
7. Paste the `Authorization URL` from Pocket ID into the `Auth URL` field.
|
|
||||||
8. Paste the `Token URL` from Pocket ID into the `Token URL` field.
|
|
||||||
9. Paste the `Certificate URL` from Pocket ID into the `Certificate URL` field.
|
|
||||||
10. Save the new login method and test to make sure it works with cloudflare.
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
---
|
|
||||||
id: grist
|
|
||||||
---
|
|
||||||
|
|
||||||
# Grist
|
|
||||||
|
|
||||||
## Pocket ID Setup
|
|
||||||
1. In Pocket-ID create a new OIDC Client, name it i.e. `Grist`
|
|
||||||
2. Set the callback url to: `https://<Grist Host>/oauth2/callback`
|
|
||||||
3. In Grist (Docker/Docker Compose/etc), set these environment variables:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
GRIST_OIDC_IDP_ISSUER="https://<Pocket ID Host>/.well-known/openid-configuration"
|
|
||||||
GRIST_OIDC_IDP_CLIENT_ID="<Client ID from the OIDC Client created in Pocket ID>"
|
|
||||||
GRIST_OIDC_IDP_CLIENT_SECRET="<Client Secret from the OIDC Client created in Pocket ID>"
|
|
||||||
GRIST_OIDC_SP_HOST="https://<Grist Host>"
|
|
||||||
GRIST_OIDC_IDP_SCOPES="openid email profile" # Default
|
|
||||||
GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT=true # Default=false, needs to be true for Pocket Id b/c end_session_endpoint is not implemented
|
|
||||||
GRIST_OIDC_IDP_END_SESSION_ENDPOINT="https://<Pocket ID Host>/api/webauthn/logout" # Only set this if GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT=false and you need to define a custom endpoint
|
|
||||||
```
|
|
||||||
4. Also ensure that the `GRIST_DEFAULT_EMAIL` env variable is set to the same email address as your user profile within Pocket ID
|
|
||||||
5. Start/Restart Grist
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
---
|
|
||||||
id: hoarder
|
|
||||||
---
|
|
||||||
|
|
||||||
# Hoarder
|
|
||||||
|
|
||||||
1. In Pocket-ID create a new OIDC Client, name it i.e. `Hoarder`
|
|
||||||
2. Set the callback url to: `https://<your-hoarder-subdomain>.<your-domain>/api/auth/callback/custom`
|
|
||||||
3. Open your `.env` file from your Hoarder compose and add these lines:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
OAUTH_WELLKNOWN_URL = https://<your-pocket-id-subdomain>.<your-domain>/.well-known/openid-configuration
|
|
||||||
OAUTH_CLIENT_SECRET = <client secret from the created OIDC client>
|
|
||||||
OAUTH_CLIENT_ID = <client id from the created OIDC client>
|
|
||||||
OAUTH_PROVIDER_NAME = Pocket-Id
|
|
||||||
NEXTAUTH_URL = https:///<your-hoarder-subdomain>.<your-domain>
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Optional: If you like to disable password authentication and link your existing hoarder account with your pocket-id identity
|
|
||||||
|
|
||||||
```ini
|
|
||||||
DISABLE_PASSWORD_AUTH = true
|
|
||||||
OAUTH_ALLOW_DANGEROUS_EMAIL_ACCOUNT_LINKING = true
|
|
||||||
```
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 73 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 81 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 47 KiB |
@@ -1,63 +0,0 @@
|
|||||||
---
|
|
||||||
id: jellyfin
|
|
||||||
---
|
|
||||||
|
|
||||||
# Jellyfin
|
|
||||||
|
|
||||||
> Due to the current limitations of the Jellyfin SSO plugin, this integration will only work in a browser. When tested, the Jellyfin app did not work and displayed an error, even when custom menu buttons were created.
|
|
||||||
|
|
||||||
> To view the original references and a full list of capabilities, please visit the [Jellyfin SSO OpenID Section](https://github.com/9p4/jellyfin-plugin-sso?tab=readme-ov-file#openid).
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
- [Jellyfin Server](https://jellyfin.org/downloads/server)
|
|
||||||
- [Jellyfin SSO Plugin](https://github.com/9p4/jellyfin-plugin-sso)
|
|
||||||
- HTTPS connection to your Jellyfin server
|
|
||||||
|
|
||||||
## OIDC - Pocket ID Setup
|
|
||||||
|
|
||||||
To start, we need to create a new SSO resource in our Jellyfin application.
|
|
||||||
|
|
||||||
> Replace the `JELLYFINDOMAIN` and `PROVIDER` elements in the URL.
|
|
||||||
|
|
||||||
1. Log into the admin panel, and go to OIDC Clients -> Add OIDC Client.
|
|
||||||
2. **Name**: Jellyfin (or any name you prefer)
|
|
||||||
3. **Callback URL**: `https://JELLYFINDOMAIN.com/sso/OID/redirect/PROVIDER`
|
|
||||||
4. For this example, we’ll be using the provider named "test_resource."
|
|
||||||
5. Click **Save**. Keep the page open, as we will need the OID client ID and OID secret.
|
|
||||||
|
|
||||||
## OIDC Client - Jellyfin SSO Resource
|
|
||||||
|
|
||||||
1. Visit the plugin page (<i>Administration Dashboard -> My Plugins -> SSO-Auth</i>).
|
|
||||||
2. Enter the <i>OID Provider Name (we used "test_resource" as our name in the callback URL), Open ID, OID Secret, and mark it as enabled.</i>
|
|
||||||
3. The following steps are optional based on your needs. In this guide, we’ll be managing only regular users, not admins.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
> To manage user access through groups, follow steps **4, 5, and 6**. Otherwise, leave it blank and skip to step 7.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
4. Under <i>Roles</i>, type the name of the group you want to use. **Note:** This must be the group name, not the label. Double-check in Pocket ID, as an incorrect name will lock users out.
|
|
||||||
5. Skip every field until you reach the **Role Claim** field, and type `groups`.
|
|
||||||
> This step is crucial if you want to manage users through groups.
|
|
||||||
6. Repeat the above step under **Request Additional Scopes**. This will pull the group scope during the sign-in process; otherwise, the previous steps won’t work.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
7. Skip the remaining fields until you reach **Scheme Override**. Enter `https` here. If omitted, it will attempt to use HTTP first, which will break as WebAuthn requires an HTTPS connection.
|
|
||||||
8. Click **Save** and restart Jellyfin.
|
|
||||||
|
|
||||||
## Optional Step - Custom Home Button
|
|
||||||
|
|
||||||
Follow the [guide to create a login button on the login page](https://github.com/9p4/jellyfin-plugin-sso?tab=readme-ov-file#creating-a-login-button-on-the-main-page) to add a custom button on your sign-in page. This step is optional, as you could also provide the sign-in URL via a bookmark or other means.
|
|
||||||
|
|
||||||
## Signing into Your Jellyfin Instance
|
|
||||||
|
|
||||||
Done! You have successfully set up SSO for your Jellyfin instance using Pocket ID.
|
|
||||||
|
|
||||||
> **Note:** Sometimes there may be a brief delay when using the custom menu option. This is related to the Jellyfin plugin and not Pocket ID.
|
|
||||||
|
|
||||||
If your users already have accounts, as long as their Pocket ID username matches their Jellyfin ID, they will be logged in automatically. Otherwise, a new user will be created with access to all of your folders. Of course, you can modify this in your configuration as desired.
|
|
||||||
|
|
||||||
This setup will only work if sign-in is performed using the `https://jellyfin.example.com/sso/OID/start/PROVIDER` URL. This URL initiates the SSO plugin and applies all the configurations we completed above.
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
---
|
|
||||||
id: netbox
|
|
||||||
---
|
|
||||||
|
|
||||||
# Netbox
|
|
||||||
|
|
||||||
**This guide does not currently show how to map groups in netbox from OIDC claims**
|
|
||||||
|
|
||||||
The following example variables are used, and should be replaced with your actual URLS.
|
|
||||||
|
|
||||||
- netbox.example.com (The url of your netbox instance.)
|
|
||||||
- id.example.com (The url of your Pocket ID instance.)
|
|
||||||
|
|
||||||
## Pocket ID Setup
|
|
||||||
|
|
||||||
1. In Pocket-ID create a new OIDC Client, name it i.e. `Netbox`.
|
|
||||||
2. Set a logo for this OIDC Client if you would like too.
|
|
||||||
3. Set the callback URL to: `https://netbox.example.com/oauth/complete/oidc/`.
|
|
||||||
4. Copy the `Client ID`, and the `Client Secret` for use in the next steps.
|
|
||||||
|
|
||||||
## Netbox Setup
|
|
||||||
|
|
||||||
This guide assumes you are using the git based install of netbox.
|
|
||||||
|
|
||||||
1. On your netbox server navigate to `/opt/netbox/netbox/netbox`
|
|
||||||
2. Add the following to your `configuration.py` file:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Remote authentication support
|
|
||||||
REMOTE_AUTH_ENABLED = True
|
|
||||||
REMOTE_AUTH_BACKEND = 'social_core.backends.open_id_connect.OpenIdConnectAuth'
|
|
||||||
REMOTE_AUTH_HEADER = 'HTTP_REMOTE_USER'
|
|
||||||
REMOTE_AUTH_USER_FIRST_NAME = 'HTTP_REMOTE_USER_FIRST_NAME'
|
|
||||||
REMOTE_AUTH_USER_LAST_NAME = 'HTTP_REMOTE_USER_LAST_NAME'
|
|
||||||
REMOTE_AUTH_USER_EMAIL = 'HTTP_REMOTE_USER_EMAIL'
|
|
||||||
REMOTE_AUTH_AUTO_CREATE_USER = True
|
|
||||||
REMOTE_AUTH_DEFAULT_GROUPS = []
|
|
||||||
REMOTE_AUTH_DEFAULT_PERMISSIONS = {}
|
|
||||||
|
|
||||||
SOCIAL_AUTH_OIDC_ENDPOINT = 'https://id.example.com'
|
|
||||||
SOCIAL_AUTH_OIDC_KEY = '<client id from the first part of this guide>'
|
|
||||||
SOCIAL_AUTH_OIDC_SECRET = '<client id from the first part of this guide>'
|
|
||||||
LOGOUT_REDIRECT_URL = 'https://netbox.example.com'
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Save the file and restart netbox: `sudo systemctl start netbox netbox-rq`
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
---
|
|
||||||
id: open-webui
|
|
||||||
---
|
|
||||||
|
|
||||||
# Open WebUI
|
|
||||||
|
|
||||||
1. In Pocket-ID, create a new OIDC Client, name it i.e. `Open WebUI`.
|
|
||||||
2. Set the callback URL to: `https://openwebui.domain/oauth/oidc/callback`
|
|
||||||
3. Add the following to your docker `.env` file for Open WebUI:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
ENABLE_OAUTH_SIGNUP=true
|
|
||||||
OAUTH_CLIENT_ID=<client id from pocket ID>
|
|
||||||
OAUTH_CLIENT_SECRET=<client secret from pocket ID>
|
|
||||||
OAUTH_PROVIDER_NAME=Pocket ID
|
|
||||||
OPENID_PROVIDER_URL=https://<your pocket id url>/.well-known/openid-configuration
|
|
||||||
```
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
---
|
|
||||||
id: pgadmin
|
|
||||||
---
|
|
||||||
|
|
||||||
# pgAdmin
|
|
||||||
|
|
||||||
The following example variables are used, and should be replaced with your actual URLS.
|
|
||||||
|
|
||||||
- pgadmin.example.com (The url of your pgAdmin instance.)
|
|
||||||
- id.example.com (The url of your Pocket ID instance.)
|
|
||||||
|
|
||||||
## Pocket ID Setup
|
|
||||||
|
|
||||||
1. In Pocket-ID create a new OIDC Client, name it i.e. `pgAdmin`.
|
|
||||||
2. Set a logo for this OIDC Client if you would like too.
|
|
||||||
3. Set the callback URL to: `https://pgadmin.example.com/oauth2/authorize`.
|
|
||||||
4. Copy the `Client ID`, `Client Secret`, `Authorization URL`, `Userinfo URL`, `Token URL`, and `OIDC Discovery URL` for use in the next steps.
|
|
||||||
|
|
||||||
# pgAdmin Setup
|
|
||||||
|
|
||||||
1. Add the following to the `config_local.py` file for pgAdmin:
|
|
||||||
|
|
||||||
**Make sure to replace https://id.example.com with your actual Pocket ID URL**
|
|
||||||
|
|
||||||
```python
|
|
||||||
AUTHENTICATION_SOURCES = ['oauth2', 'internal'] # This keeps internal authentication enabled as well as oauth2
|
|
||||||
OAUTH2_AUTO_CREATE_USER = True
|
|
||||||
OAUTH2_CONFIG = [{
|
|
||||||
'OAUTH2_NAME' : 'pocketid',
|
|
||||||
'OAUTH2_DISPLAY_NAME' : 'Pocket ID',
|
|
||||||
'OAUTH2_CLIENT_ID' : '<client id from the earlier step>',
|
|
||||||
'OAUTH2_CLIENT_SECRET' : '<client secret from the earlier step>',
|
|
||||||
'OAUTH2_TOKEN_URL' : 'https://id.example.com/api/oidc/token',
|
|
||||||
'OAUTH2_AUTHORIZATION_URL' : 'https://id.example/authorize',
|
|
||||||
'OAUTH2_API_BASE_URL' : 'https://id.example.com',
|
|
||||||
'OAUTH2_USERINFO_ENDPOINT' : 'https://id.example.com/api/oidc/userinfo',
|
|
||||||
'OAUTH2_SERVER_METADATA_URL' : 'https://id.example.com/.well-known/openid-configuration',
|
|
||||||
'OAUTH2_SCOPE' : 'openid email profile',
|
|
||||||
'OAUTH2_ICON' : 'fa-openid',
|
|
||||||
'OAUTH2_BUTTON_COLOR' : '#fd4b2d' # Can select any color you would like here.
|
|
||||||
}]
|
|
||||||
```
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
---
|
|
||||||
id: portainer
|
|
||||||
---
|
|
||||||
|
|
||||||
# Portainer
|
|
||||||
|
|
||||||
**This requires Portainers Business Edition**
|
|
||||||
|
|
||||||
The following example variables are used, and should be replaced with your actual URLS.
|
|
||||||
|
|
||||||
- portainer.example.com (The url of your Portainer instance.)
|
|
||||||
- id.example.com (The url of your Pocket ID instance.)
|
|
||||||
|
|
||||||
## Pocket ID Setup
|
|
||||||
|
|
||||||
1. In Pocket-ID create a new OIDC Client, name it i.e. `Portainer`.
|
|
||||||
2. Set a logo for this OIDC Client if you would like too.
|
|
||||||
3. Set the callback URL to: `https://portainer.example.com/`.
|
|
||||||
4. Copy the `Client ID`, `Client Secret`, `Authorization URL`, `Userinfo URL`, and `Token URL` for use in the next steps.
|
|
||||||
|
|
||||||
# Portainer Setup
|
|
||||||
|
|
||||||
- While initally setting up OAuth in Portainer, its recommended to keep the `Hide internal authentication prompt` set to `Off` incase you need a fallback login
|
|
||||||
- This guide does **NOT** cover how to setup group claims in Portainer.
|
|
||||||
|
|
||||||
1. Open the Portainer web interface and navigate to: `Settings > Authentication`
|
|
||||||
2. Select `Custom OAuth Provider`
|
|
||||||
3. Paste the `Client ID` from Pocket ID into the `Client ID` field in Portainer.
|
|
||||||
4. Paste the `Client Secret` from Pocket ID into the `Client Secret` field in Portainer.
|
|
||||||
5. Paste the `Authorization URL` from Pocket ID into the `Authorization URL` field in Portainer.
|
|
||||||
6. Paste the `Token URL` from Pocket ID into the `Access token URL` field in Portainer.
|
|
||||||
7. Paste the `Userinfo URL` from Pocket ID into the `Resource URL` field in Portainer.
|
|
||||||
8. Set the `Redirect URL` to `https://portainer.example.com`
|
|
||||||
9. Set the `Logout URL` to `https://portainer.example.com`
|
|
||||||
10. Set the `User identifier` field to `preferred_username`. (This will use the users username vs the email)
|
|
||||||
11. Set the `Scopes` field to: `email openid profile`
|
|
||||||
12. Set `Auth Style` to `Auto detect`
|
|
||||||
13. Save the settings and test the new OAuth Login.
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
---
|
|
||||||
id: proxmox
|
|
||||||
---
|
|
||||||
|
|
||||||
# Proxmox
|
|
||||||
|
|
||||||
The following example variables are used, and should be replaced with your actual URLS.
|
|
||||||
|
|
||||||
- proxmox.example.com (The url of your proxmox instance.)
|
|
||||||
- id.example.com (The url of your Pocket ID instance.)
|
|
||||||
|
|
||||||
## Pocket ID Setup
|
|
||||||
|
|
||||||
1. In Pocket-ID create a new OIDC Client, name it i.e. `Proxmox`.
|
|
||||||
2. Set a logo for this OIDC Client if you would like too.
|
|
||||||
3. Set the callback URL to: `https://proxmox.example.com`.
|
|
||||||
4. Copy the `Client ID`, and the `Client Secret` for use in the next steps.
|
|
||||||
|
|
||||||
## Proxmox Setup
|
|
||||||
|
|
||||||
1. Open the Proxmox Console and navigate to: `Datacenter - Realms`
|
|
||||||
2. Add a new `Open ID Connect Server` Realm
|
|
||||||
3. Enter `https://id.example.com` for the `Issuer URL`
|
|
||||||
4. Enter a name for the realm of your choice ie. `PocketID`
|
|
||||||
5. Paste the `Client ID` from Pocket ID into the `Client ID` field in Proxmox.
|
|
||||||
6. Paste the `Client Secret` from Pocket ID into the `Client Key` field in Proxmox.
|
|
||||||
7. You can check the `Default` box if you want this to be the deafult realm proxmox uses when signing in.
|
|
||||||
8. Check the `Autocreate Users` checkbox. (This will automaitcally create users in Proxmox if they dont exsist.)
|
|
||||||
9. Select `username` for the `Username Claim` dropdown (This is personal preference and controls how the username is shown, ie: `username = username@PocketID` or `email = username@example@PocketID`).
|
|
||||||
10. Leave the rest as defaults and click `OK` to save the new realm.
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
---
|
|
||||||
id: semaphore-ui
|
|
||||||
---
|
|
||||||
|
|
||||||
# Semaphore UI
|
|
||||||
|
|
||||||
1. In Pocket-ID create a new OIDC Client, name it i.e. `Semaphore UI`.
|
|
||||||
2. Set the callback URL to: `https://<your-semaphore-ui-url>/api/auth/oidc/pocketid/redirect/`.
|
|
||||||
3. Add the following to your `config.json` file for Semaphore UI:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"oidc_providers": {
|
|
||||||
"pocketid": {
|
|
||||||
"display_name": "Sign in with PocketID",
|
|
||||||
"provider_url": "https://<your-pocket-id-url>",
|
|
||||||
"client_id": "<client-id-from-pocket-id>",
|
|
||||||
"client_secret": "<client-secret-from-pocket-id>",
|
|
||||||
"redirect_url": "https://<your-semaphore-ui-url>/api/auth/oidc/pocketid/redirect/",
|
|
||||||
"scopes": [
|
|
||||||
"openid",
|
|
||||||
"profile",
|
|
||||||
"email"
|
|
||||||
],
|
|
||||||
"username_claim": "email",
|
|
||||||
"name_claim": "given_name"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
---
|
|
||||||
id: vikunja
|
|
||||||
---
|
|
||||||
|
|
||||||
# Vikunja
|
|
||||||
|
|
||||||
1. In Pocket-ID create a new OIDC Client, name it i.e. `Vikunja`
|
|
||||||
2. Set the callback url to: `https://<your-vikunja-subdomain>.<your-domain>/auth/openid/pocketid`
|
|
||||||
3. In `Vikunja` ensure to map a config file to your container, see [here](https://vikunja.io/docs/config-options/#using-a-config-file-with-docker-compose)
|
|
||||||
4. Add or set the following content to the `config.yml` file:
|
|
||||||
|
|
||||||
```yml
|
|
||||||
auth:
|
|
||||||
openid:
|
|
||||||
enabled: true
|
|
||||||
redirecturl: https://<your-vikunja-subdomain>.<your-domain>/auth/openid/pocketid
|
|
||||||
providers:
|
|
||||||
- name: Pocket-Id
|
|
||||||
authurl: https://<your-pocket-id-subdomain>.<your-domain>
|
|
||||||
clientid: <client id from the created OIDC client>
|
|
||||||
clientsecret: <client secret from the created OIDC client>
|
|
||||||
```
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user