mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-03-02 08:50:20 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a879bfa418 | ||
|
|
164ce6a3d7 | ||
|
|
ef1aeb7152 | ||
|
|
47c39f6d38 | ||
|
|
2884021055 | ||
|
|
def39b8703 | ||
|
|
d071641890 | ||
|
|
397544c0f3 | ||
|
|
1fb99e5d52 | ||
|
|
7b403552ba |
21
.github/ISSUE_TEMPLATE/bug.yml
vendored
21
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -34,4 +34,23 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Before submitting, please check if the issues hasn't been raised before.
|
||||
### Additional Information
|
||||
- type: textarea
|
||||
id: extra-information
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "Version and Environment"
|
||||
description: "Please specify the version of Pocket ID, along with any environment-specific configurations, such your reverse proxy, that might be relevant."
|
||||
placeholder: "e.g., v0.24.1"
|
||||
- type: textarea
|
||||
id: log-files
|
||||
validations:
|
||||
required: false
|
||||
attributes:
|
||||
label: "Log Output"
|
||||
description: "Output of log files when the issue occured to help us diagnose the issue."
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
**Before submitting, please check if the issue hasn't been raised before.**
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
## [](https://github.com/stonith404/pocket-id/compare/v0.27.0...v) (2025-01-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add `__HOST` prefix to cookies ([#175](https://github.com/stonith404/pocket-id/issues/175)) ([164ce6a](https://github.com/stonith404/pocket-id/commit/164ce6a3d7fa8ae5275c94302952cf318e3b3113))
|
||||
* send hostname derived from `PUBLIC_APP_URL` with SMTP EHLO command ([397544c](https://github.com/stonith404/pocket-id/commit/397544c0f3f2b49f1f34ae53e6b9daf194d1ae28))
|
||||
* use OS hostname for SMTP EHLO message ([47c39f6](https://github.com/stonith404/pocket-id/commit/47c39f6d382c496cb964262adcf76cc8dbb96da3))
|
||||
|
||||
## [](https://github.com/stonith404/pocket-id/compare/v0.26.0...v) (2025-01-22)
|
||||
|
||||
|
||||
|
||||
@@ -55,14 +55,14 @@ The frontend is built with [SvelteKit](https://kit.svelte.dev) and written in Ty
|
||||
3. Install the dependencies with `npm install`
|
||||
4. Start the frontend with `npm run dev`
|
||||
|
||||
You're all set!
|
||||
|
||||
### Reverse Proxy
|
||||
We use [Caddy](https://caddyserver.com) as a reverse proxy. You can use any other reverse proxy if you want but you have to configure it yourself.
|
||||
|
||||
#### Setup
|
||||
Run `caddy run --config reverse-proxy/Caddyfile` in the root folder.
|
||||
|
||||
You're all set!
|
||||
|
||||
### Testing
|
||||
|
||||
We are using [Playwright](https://playwright.dev) for end-to-end testing.
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
APP_ENV=production
|
||||
PUBLIC_APP_URL=http://localhost
|
||||
# /!\ If PUBLIC_APP_URL is not a localhost address, it must be HTTPS
|
||||
DB_PROVIDER=sqlite
|
||||
# MAXMIND_LICENSE_KEY=fixme # needed for IP geolocation in the audit log
|
||||
SQLITE_DB_PATH=data/pocket-id.db
|
||||
POSTGRES_CONNECTION_STRING=postgresql://postgres:postgres@localhost:5432/pocket-id
|
||||
UPLOAD_PATH=data/uploads
|
||||
|
||||
@@ -6,9 +6,10 @@ import (
|
||||
)
|
||||
|
||||
func Bootstrap() {
|
||||
initApplicationImages()
|
||||
|
||||
db := newDatabase()
|
||||
appConfigService := service.NewAppConfigService(db)
|
||||
|
||||
initApplicationImages()
|
||||
initRouter(db, appConfigService)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/stonith404/pocket-id/backend/internal/utils/cookie"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -184,7 +186,10 @@ func (uc *UserController) exchangeOneTimeAccessTokenHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
utils.AddAccessTokenCookie(c, uc.appConfigService.DbConfig.SessionDuration.Value, token)
|
||||
sessionDurationInMinutesParsed, _ := strconv.Atoi(uc.appConfigService.DbConfig.SessionDuration.Value)
|
||||
maxAge := sessionDurationInMinutesParsed * 60
|
||||
cookie.AddAccessTokenCookie(c, maxAge, token)
|
||||
|
||||
c.JSON(http.StatusOK, userDto)
|
||||
}
|
||||
|
||||
@@ -201,7 +206,10 @@ func (uc *UserController) getSetupAccessTokenHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
utils.AddAccessTokenCookie(c, uc.appConfigService.DbConfig.SessionDuration.Value, token)
|
||||
sessionDurationInMinutesParsed, _ := strconv.Atoi(uc.appConfigService.DbConfig.SessionDuration.Value)
|
||||
maxAge := sessionDurationInMinutesParsed * 60
|
||||
cookie.AddAccessTokenCookie(c, maxAge, token)
|
||||
|
||||
c.JSON(http.StatusOK, userDto)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,9 @@ import (
|
||||
"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"
|
||||
"github.com/stonith404/pocket-id/backend/internal/utils/cookie"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -42,12 +43,12 @@ func (wc *WebauthnController) beginRegistrationHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.SetCookie("session_id", options.SessionID, int(options.Timeout.Seconds()), "/", "", true, true)
|
||||
cookie.AddSessionIdCookie(c, int(options.Timeout.Seconds()), options.SessionID)
|
||||
c.JSON(http.StatusOK, options.Response)
|
||||
}
|
||||
|
||||
func (wc *WebauthnController) verifyRegistrationHandler(c *gin.Context) {
|
||||
sessionID, err := c.Cookie("session_id")
|
||||
sessionID, err := c.Cookie(cookie.SessionIdCookieName)
|
||||
if err != nil {
|
||||
c.Error(&common.MissingSessionIdError{})
|
||||
return
|
||||
@@ -76,12 +77,12 @@ func (wc *WebauthnController) beginLoginHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.SetCookie("session_id", options.SessionID, int(options.Timeout.Seconds()), "/", "", true, true)
|
||||
cookie.AddSessionIdCookie(c, int(options.Timeout.Seconds()), options.SessionID)
|
||||
c.JSON(http.StatusOK, options.Response)
|
||||
}
|
||||
|
||||
func (wc *WebauthnController) verifyLoginHandler(c *gin.Context) {
|
||||
sessionID, err := c.Cookie("session_id")
|
||||
sessionID, err := c.Cookie(cookie.SessionIdCookieName)
|
||||
if err != nil {
|
||||
c.Error(&common.MissingSessionIdError{})
|
||||
return
|
||||
@@ -105,7 +106,10 @@ func (wc *WebauthnController) verifyLoginHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
utils.AddAccessTokenCookie(c, wc.appConfigService.DbConfig.SessionDuration.Value, token)
|
||||
sessionDurationInMinutesParsed, _ := strconv.Atoi(wc.appConfigService.DbConfig.SessionDuration.Value)
|
||||
maxAge := sessionDurationInMinutesParsed * 60
|
||||
cookie.AddAccessTokenCookie(c, maxAge, token)
|
||||
|
||||
c.JSON(http.StatusOK, userDto)
|
||||
}
|
||||
|
||||
@@ -165,6 +169,6 @@ func (wc *WebauthnController) updateCredentialHandler(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (wc *WebauthnController) logoutHandler(c *gin.Context) {
|
||||
utils.AddAccessTokenCookie(c, "0", "")
|
||||
cookie.AddAccessTokenCookie(c, 0, "")
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ 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"
|
||||
)
|
||||
|
||||
@@ -19,7 +20,7 @@ func NewJwtAuthMiddleware(jwtService *service.JwtService, ignoreUnauthenticated
|
||||
func (m *JwtAuthMiddleware) Add(adminOnly bool) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// Extract the token from the cookie or the Authorization header
|
||||
token, err := c.Cookie("access_token")
|
||||
token, err := c.Cookie(cookie.AccessTokenCookieName)
|
||||
if err != nil {
|
||||
authorizationHeaderSplitted := strings.Split(c.GetHeader("Authorization"), " ")
|
||||
if len(authorizationHeaderSplitted) == 2 {
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"net"
|
||||
"net/smtp"
|
||||
"net/textproto"
|
||||
"os"
|
||||
ttemplate "text/template"
|
||||
"time"
|
||||
)
|
||||
@@ -99,7 +100,7 @@ func SendEmail[V any](srv *EmailService, toEmail email.Address, template email.T
|
||||
smtpAddress := srv.appConfigService.DbConfig.SmtpHost.Value + ":" + port
|
||||
var client *smtp.Client
|
||||
if srv.appConfigService.DbConfig.SmtpTls.Value == "false" {
|
||||
client, err = smtp.Dial(smtpAddress)
|
||||
client, err = srv.connectToSmtpServer(smtpAddress)
|
||||
} else if port == "465" {
|
||||
client, err = srv.connectToSmtpServerUsingImplicitTLS(
|
||||
smtpAddress,
|
||||
@@ -115,9 +116,16 @@ func SendEmail[V any](srv *EmailService, toEmail email.Address, template email.T
|
||||
if err != nil {
|
||||
return 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)
|
||||
}
|
||||
}
|
||||
|
||||
smtpUser := srv.appConfigService.DbConfig.SmtpUser.Value
|
||||
smtpPassword := srv.appConfigService.DbConfig.SmtpPassword.Value
|
||||
|
||||
@@ -141,6 +149,15 @@ func SendEmail[V any](srv *EmailService, toEmail email.Address, template email.T
|
||||
return nil
|
||||
}
|
||||
|
||||
func (srv *EmailService) connectToSmtpServer(serverAddr string) (*smtp.Client, error) {
|
||||
conn, err := netDialer.Dial("tcp", serverAddr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to SMTP server: %w", err)
|
||||
}
|
||||
client, err := smtp.NewClient(conn, srv.appConfigService.DbConfig.SmtpHost.Value)
|
||||
return client, err
|
||||
}
|
||||
|
||||
func (srv *EmailService) connectToSmtpServerUsingImplicitTLS(serverAddr string, tlsConfig *tls.Config) (*smtp.Client, error) {
|
||||
tlsDialer := &tls.Dialer{
|
||||
NetDialer: netDialer,
|
||||
|
||||
13
backend/internal/utils/cookie/add_cookie.go
Normal file
13
backend/internal/utils/cookie/add_cookie.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package cookie
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func AddAccessTokenCookie(c *gin.Context, maxAgeInSeconds int, token string) {
|
||||
c.SetCookie(AccessTokenCookieName, token, maxAgeInSeconds, "/", "", true, true)
|
||||
}
|
||||
|
||||
func AddSessionIdCookie(c *gin.Context, maxAgeInSeconds int, sessionID string) {
|
||||
c.SetCookie(SessionIdCookieName, sessionID, maxAgeInSeconds, "/", "", true, true)
|
||||
}
|
||||
16
backend/internal/utils/cookie/cookie_names.go
Normal file
16
backend/internal/utils/cookie/cookie_names.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package cookie
|
||||
|
||||
import (
|
||||
"github.com/stonith404/pocket-id/backend/internal/common"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var AccessTokenCookieName = "__Host-access_token"
|
||||
var SessionIdCookieName = "__Host-session"
|
||||
|
||||
func init() {
|
||||
if strings.HasPrefix(common.EnvConfig.AppURL, "http://") {
|
||||
AccessTokenCookieName = "access_token"
|
||||
SessionIdCookieName = "session"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func AddAccessTokenCookie(c *gin.Context, sessionDurationInMinutes string, token string) {
|
||||
sessionDurationInMinutesParsed, _ := strconv.Atoi(sessionDurationInMinutes)
|
||||
maxAge := sessionDurationInMinutesParsed * 60
|
||||
c.SetCookie("access_token", token, maxAge, "/", "", true, true)
|
||||
}
|
||||
22
docs/docs/client-examples/grist.md
Normal file
22
docs/docs/client-examples/grist.md
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
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
|
||||
46
docs/docs/client-examples/netbox.md
Normal file
46
docs/docs/client-examples/netbox.md
Normal file
@@ -0,0 +1,46 @@
|
||||
---
|
||||
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`
|
||||
42
docs/docs/client-examples/pgadmin.md
Normal file
42
docs/docs/client-examples/pgadmin.md
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
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.
|
||||
}]
|
||||
```
|
||||
38
docs/docs/client-examples/portainer.md
Normal file
38
docs/docs/client-examples/portainer.md
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
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.
|
||||
30
docs/docs/client-examples/proxmox.md
Normal file
30
docs/docs/client-examples/proxmox.md
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
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,77 +0,0 @@
|
||||
---
|
||||
id: contribute
|
||||
---
|
||||
|
||||
# Contributing
|
||||
|
||||
I am happy that you want to contribute to Pocket ID and help to make it better! All contributions are welcome, including issues, suggestions, pull requests and more.
|
||||
|
||||
## Getting started
|
||||
|
||||
You've found a bug, have suggestion or something else, just create an issue on GitHub and we can get in touch.
|
||||
|
||||
## Submit a Pull Request
|
||||
|
||||
Before you submit the pull request for review please ensure that
|
||||
|
||||
- The pull request naming follows the [Conventional Commits specification](https://www.conventionalcommits.org):
|
||||
|
||||
`<type>[optional scope]: <description>`
|
||||
|
||||
example:
|
||||
|
||||
```
|
||||
feat(share): add password protection
|
||||
```
|
||||
|
||||
Where `TYPE` can be:
|
||||
|
||||
- **feat** - is a new feature
|
||||
- **doc** - documentation only changes
|
||||
- **fix** - a bug fix
|
||||
- **refactor** - code change that neither fixes a bug nor adds a feature
|
||||
|
||||
- Your pull request has a detailed description
|
||||
- You run `npm run format` to format the code
|
||||
|
||||
## Setup project
|
||||
|
||||
Pocket ID consists of a frontend, backend and a reverse proxy.
|
||||
|
||||
### Backend
|
||||
|
||||
The backend is built with [Gin](https://gin-gonic.com) and written in Go.
|
||||
|
||||
#### Setup
|
||||
|
||||
1. Open the `backend` folder
|
||||
2. Copy the `.env.example` file to `.env` and change the `APP_ENV` to `development`
|
||||
3. Start the backend with `go run cmd/main.go`
|
||||
|
||||
### Frontend
|
||||
|
||||
The frontend is built with [SvelteKit](https://kit.svelte.dev) and written in TypeScript.
|
||||
|
||||
#### Setup
|
||||
|
||||
1. Open the `frontend` folder
|
||||
2. Copy the `.env.example` file to `.env`
|
||||
3. Install the dependencies with `npm install`
|
||||
4. Start the frontend with `npm run dev`
|
||||
|
||||
You're all set!
|
||||
|
||||
### Reverse Proxy
|
||||
We use [Caddy](https://caddyserver.com) as a reverse proxy. You can use any other reverse proxy if you want but you have to configure it yourself.
|
||||
|
||||
#### Setup
|
||||
Run `caddy run --config reverse-proxy/Caddyfile` in the root folder.
|
||||
|
||||
### Testing
|
||||
|
||||
We are using [Playwright](https://playwright.dev) for end-to-end testing.
|
||||
|
||||
The tests can be run like this:
|
||||
1. Start the backend normally
|
||||
2. Start the frontend in production mode with `npm run build && node build/index.js`
|
||||
3. Run the tests with `npm run test`
|
||||
@@ -59,12 +59,17 @@ const sidebars: SidebarsConfig = {
|
||||
slug: "client-examples",
|
||||
},
|
||||
items: [
|
||||
"client-examples/cloudflare-zero-trust",
|
||||
"client-examples/grist",
|
||||
"client-examples/hoarder",
|
||||
"client-examples/jellyfin",
|
||||
"client-examples/vikunja",
|
||||
"client-examples/netbox",
|
||||
"client-examples/open-webui",
|
||||
"client-examples/pgadmin",
|
||||
"client-examples/portainer",
|
||||
"client-examples/proxmox",
|
||||
"client-examples/semaphore-ui",
|
||||
"client-examples/cloudflare-zero-trust",
|
||||
"client-examples/vikunja",
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -82,8 +87,9 @@ const sidebars: SidebarsConfig = {
|
||||
label: "Helping Out",
|
||||
items: [
|
||||
{
|
||||
type: "doc",
|
||||
id: "help-out/contribute",
|
||||
type: "link",
|
||||
label: "Contributing",
|
||||
href: "https://github.com/stonith404/pocket-id/blob/main/CONTRIBUTING.md",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
PUBLIC_APP_URL=http://localhost
|
||||
# /!\ If PUBLIC_APP_URL is not a localhost address, it must be HTTPS
|
||||
INTERNAL_BACKEND_URL=http://localhost:8080
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pocket-id-frontend",
|
||||
"version": "0.27.0",
|
||||
"version": "0.27.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev --port 3000",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { env } from '$env/dynamic/private';
|
||||
import { ACCESS_TOKEN_COOKIE_NAME } from '$lib/constants';
|
||||
import type { Handle, HandleServerError } from '@sveltejs/kit';
|
||||
import { AxiosError } from 'axios';
|
||||
import jwt from 'jsonwebtoken';
|
||||
@@ -9,7 +10,7 @@ import jwt from 'jsonwebtoken';
|
||||
process.env.INTERNAL_BACKEND_URL = env.INTERNAL_BACKEND_URL ?? 'http://localhost:8080';
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
const accessToken = event.cookies.get('access_token');
|
||||
const accessToken = event.cookies.get(ACCESS_TOKEN_COOKIE_NAME);
|
||||
|
||||
let isSignedIn: boolean = false;
|
||||
let isAdmin: boolean = false;
|
||||
|
||||
2
frontend/src/lib/constants.ts
Normal file
2
frontend/src/lib/constants.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export const HTTPS_ENABLED = process.env.PUBLIC_APP_URL?.startsWith('https://') ?? false;
|
||||
export const ACCESS_TOKEN_COOKIE_NAME = HTTPS_ENABLED ? '__Host-access_token' : 'access_token';
|
||||
@@ -1,10 +1,11 @@
|
||||
import { ACCESS_TOKEN_COOKIE_NAME } from '$lib/constants';
|
||||
import AppConfigService from '$lib/services/app-config-service';
|
||||
import UserService from '$lib/services/user-service';
|
||||
import type { LayoutServerLoad } from './$types';
|
||||
|
||||
export const load: LayoutServerLoad = async ({ cookies }) => {
|
||||
const userService = new UserService(cookies.get('access_token'));
|
||||
const appConfigService = new AppConfigService(cookies.get('access_token'));
|
||||
const userService = new UserService(cookies.get(ACCESS_TOKEN_COOKIE_NAME));
|
||||
const appConfigService = new AppConfigService(cookies.get(ACCESS_TOKEN_COOKIE_NAME));
|
||||
|
||||
const user = await userService
|
||||
.getCurrent()
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { ACCESS_TOKEN_COOKIE_NAME } from '$lib/constants';
|
||||
import OidcService from '$lib/services/oidc-service';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ url, cookies }) => {
|
||||
const clientId = url.searchParams.get('client_id');
|
||||
const oidcService = new OidcService(cookies.get('access_token'));
|
||||
const oidcService = new OidcService(cookies.get(ACCESS_TOKEN_COOKIE_NAME));
|
||||
|
||||
const client = await oidcService.getClient(clientId!);
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { ACCESS_TOKEN_COOKIE_NAME } from '$lib/constants';
|
||||
import UserService from '$lib/services/user-service';
|
||||
import WebAuthnService from '$lib/services/webauthn-service';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies }) => {
|
||||
const webauthnService = new WebAuthnService(cookies.get('access_token'));
|
||||
const userService = new UserService(cookies.get('access_token'));
|
||||
const webauthnService = new WebAuthnService(cookies.get(ACCESS_TOKEN_COOKIE_NAME));
|
||||
const userService = new UserService(cookies.get(ACCESS_TOKEN_COOKIE_NAME));
|
||||
const account = await userService.getCurrent();
|
||||
const passkeys = await webauthnService.listCredentials();
|
||||
return {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { ACCESS_TOKEN_COOKIE_NAME } from '$lib/constants';
|
||||
import AppConfigService from '$lib/services/app-config-service';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies }) => {
|
||||
const appConfigService = new AppConfigService(cookies.get('access_token'));
|
||||
const appConfigService = new AppConfigService(cookies.get(ACCESS_TOKEN_COOKIE_NAME));
|
||||
const appConfig = await appConfigService.list(true);
|
||||
return { appConfig };
|
||||
};
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { ACCESS_TOKEN_COOKIE_NAME } from '$lib/constants';
|
||||
import OIDCService from '$lib/services/oidc-service';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies }) => {
|
||||
const oidcService = new OIDCService(cookies.get('access_token'));
|
||||
const oidcService = new OIDCService(cookies.get(ACCESS_TOKEN_COOKIE_NAME));
|
||||
const clients = await oidcService.listClients();
|
||||
return clients;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { ACCESS_TOKEN_COOKIE_NAME } from '$lib/constants';
|
||||
import OidcService from '$lib/services/oidc-service';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ params, cookies }) => {
|
||||
const oidcService = new OidcService(cookies.get('access_token'));
|
||||
const oidcService = new OidcService(cookies.get(ACCESS_TOKEN_COOKIE_NAME));
|
||||
return await oidcService.getClient(params.id);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { ACCESS_TOKEN_COOKIE_NAME } from '$lib/constants';
|
||||
import UserGroupService from '$lib/services/user-group-service';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies }) => {
|
||||
const userGroupService = new UserGroupService(cookies.get('access_token'));
|
||||
const userGroupService = new UserGroupService(cookies.get(ACCESS_TOKEN_COOKIE_NAME));
|
||||
const userGroups = await userGroupService.list();
|
||||
return userGroups;
|
||||
};
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { ACCESS_TOKEN_COOKIE_NAME } from '$lib/constants';
|
||||
import UserGroupService from '$lib/services/user-group-service';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ params, cookies }) => {
|
||||
const userGroupService = new UserGroupService(cookies.get('access_token'));
|
||||
const userGroupService = new UserGroupService(cookies.get(ACCESS_TOKEN_COOKIE_NAME));
|
||||
const userGroup = await userGroupService.get(params.id);
|
||||
|
||||
return { userGroup };
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { ACCESS_TOKEN_COOKIE_NAME } from '$lib/constants';
|
||||
import UserService from '$lib/services/user-service';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies }) => {
|
||||
const userService = new UserService(cookies.get('access_token'));
|
||||
const userService = new UserService(cookies.get(ACCESS_TOKEN_COOKIE_NAME));
|
||||
const users = await userService.list();
|
||||
return users;
|
||||
};
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { ACCESS_TOKEN_COOKIE_NAME } from '$lib/constants';
|
||||
import UserService from '$lib/services/user-service';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ params, cookies }) => {
|
||||
const userService = new UserService(cookies.get('access_token'));
|
||||
const userService = new UserService(cookies.get(ACCESS_TOKEN_COOKIE_NAME));
|
||||
const user = await userService.get(params.id);
|
||||
return user;
|
||||
};
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { ACCESS_TOKEN_COOKIE_NAME } from '$lib/constants';
|
||||
import AuditLogService from '$lib/services/audit-log-service';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies }) => {
|
||||
const auditLogService = new AuditLogService(cookies.get('access_token'));
|
||||
const auditLogService = new AuditLogService(cookies.get(ACCESS_TOKEN_COOKIE_NAME));
|
||||
const auditLogs = await auditLogService.list({
|
||||
sort: {
|
||||
column: 'createdAt',
|
||||
|
||||
@@ -2,9 +2,4 @@
|
||||
reverse_proxy /api/* http://localhost:{$BACKEND_PORT:8080}
|
||||
reverse_proxy /.well-known/* http://localhost:{$BACKEND_PORT:8080}
|
||||
reverse_proxy /* http://localhost:{$PORT:3000}
|
||||
|
||||
log {
|
||||
output file /var/log/caddy/access.log
|
||||
level WARN
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,4 @@
|
||||
reverse_proxy /* http://localhost:{$PORT:3000} {
|
||||
trusted_proxies 0.0.0.0/0
|
||||
}
|
||||
|
||||
log {
|
||||
output file /var/log/caddy/access.log
|
||||
level WARN
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
# Check if the script is being run from the root of the project
|
||||
if [ ! -f .version ] || [ ! -f frontend/package.json ] || [ ! -f CHANGELOG.md ]; then
|
||||
echo "Error: This script must be run from the root of the project."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Read the current version from .version
|
||||
VERSION=$(cat .version)
|
||||
|
||||
@@ -29,6 +35,13 @@ else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Confirm release creation
|
||||
read -p "This will create a new $RELEASE_TYPE release with version $NEW_VERSION. Do you want to proceed? (y/n) " CONFIRM
|
||||
if [[ "$CONFIRM" != "y" ]]; then
|
||||
echo "Release process canceled."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Update the .version file with the new version
|
||||
echo $NEW_VERSION >.version
|
||||
git add .version
|
||||
@@ -58,4 +71,30 @@ git tag "v$NEW_VERSION"
|
||||
git push
|
||||
git push --tags
|
||||
|
||||
# Check if GitHub CLI is installed
|
||||
if ! command -v gh &>/dev/null; then
|
||||
echo "GitHub CLI (gh) is not installed. Please install it and authenticate using 'gh auth login'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract the changelog content for the latest release
|
||||
echo "Extracting changelog content for version $NEW_VERSION..."
|
||||
CHANGELOG=$(awk '/^## / {if (NR > 1) exit} NR > 1 {print}' CHANGELOG.md | awk 'NR > 2 || NF {print}')
|
||||
|
||||
if [ -z "$CHANGELOG" ]; then
|
||||
echo "Error: Could not extract changelog for version $NEW_VERSION."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create the release on GitHub
|
||||
echo "Creating GitHub release..."
|
||||
gh release create "v$NEW_VERSION" --title "v$NEW_VERSION" --notes "$CHANGELOG"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "GitHub release created successfully."
|
||||
else
|
||||
echo "Error: Failed to create GitHub release."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Release process complete. New version: $NEW_VERSION"
|
||||
|
||||
Reference in New Issue
Block a user