Skip to content

Commit 02369ea

Browse files
authored
Merge branch 'pocket-id:main' into main
2 parents 022f01a + 03f9be0 commit 02369ea

67 files changed

Lines changed: 740 additions & 298 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.0.2
1+
2.1.0

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1+
## v2.1.0
2+
3+
### Bug Fixes
4+
5+
- invalid cookie name for email login code device token ([d6a7b50](https://github.com/pocket-id/pocket-id/commit/d6a7b503ff4571b1291a55a569add3374f5e2d5b) by @stonith404)
6+
7+
### Features
8+
9+
- add issuer url to oidc client details list ([#1197](https://github.com/pocket-id/pocket-id/pull/1197) by @kmendell)
10+
- process nonce within device authorization flow ([#1185](https://github.com/pocket-id/pocket-id/pull/1185) by @justincmoy)
11+
12+
### Other
13+
14+
- run SCIM jobs in context of gocron instead of custom implementation ([4881130](https://github.com/pocket-id/pocket-id/commit/4881130eadcef0642f8a87650b7c36fda453b51b) by @stonith404)
15+
16+
**Full Changelog**: https://github.com/pocket-id/pocket-id/compare/v2.0.2...v2.1.0
17+
118
## v2.0.2
219

320
### Bug Fixes

backend/internal/bootstrap/bootstrap.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,13 @@ func Bootstrap(ctx context.Context) error {
4848
return fmt.Errorf("failed to initialize application images: %w", err)
4949
}
5050

51+
scheduler, err := job.NewScheduler()
52+
if err != nil {
53+
return fmt.Errorf("failed to create job scheduler: %w", err)
54+
}
55+
5156
// Create all services
52-
svc, err := initServices(ctx, db, httpClient, imageExtensions, fileStorage)
57+
svc, err := initServices(ctx, db, httpClient, imageExtensions, fileStorage, scheduler)
5358
if err != nil {
5459
return fmt.Errorf("failed to initialize services: %w", err)
5560
}
@@ -74,11 +79,7 @@ func Bootstrap(ctx context.Context) error {
7479
}
7580
shutdownFns = append(shutdownFns, shutdownFn)
7681

77-
// Init the job scheduler
78-
scheduler, err := job.NewScheduler()
79-
if err != nil {
80-
return fmt.Errorf("failed to create job scheduler: %w", err)
81-
}
82+
// Register scheduled jobs
8283
err = registerScheduledJobs(ctx, db, svc, httpClient, scheduler)
8384
if err != nil {
8485
return fmt.Errorf("failed to register scheduled jobs: %w", err)

backend/internal/bootstrap/scheduler_bootstrap.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ func registerScheduledJobs(ctx context.Context, db *gorm.DB, svc *services, http
3535
if err != nil {
3636
return fmt.Errorf("failed to register analytics job in scheduler: %w", err)
3737
}
38+
err = scheduler.RegisterScimJobs(ctx, svc.scimService)
39+
if err != nil {
40+
return fmt.Errorf("failed to register SCIM scheduler job: %w", err)
41+
}
3842

3943
return nil
4044
}

backend/internal/bootstrap/services_bootstrap.go

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,35 @@ import (
55
"fmt"
66
"net/http"
77

8+
"github.com/pocket-id/pocket-id/backend/internal/job"
89
"gorm.io/gorm"
910

1011
"github.com/pocket-id/pocket-id/backend/internal/service"
1112
"github.com/pocket-id/pocket-id/backend/internal/storage"
1213
)
1314

1415
type services struct {
15-
appConfigService *service.AppConfigService
16-
appImagesService *service.AppImagesService
17-
emailService *service.EmailService
18-
geoLiteService *service.GeoLiteService
19-
auditLogService *service.AuditLogService
20-
jwtService *service.JwtService
21-
webauthnService *service.WebAuthnService
22-
scimService *service.ScimService
23-
scimSchedulerService *service.ScimSchedulerService
24-
userService *service.UserService
25-
customClaimService *service.CustomClaimService
26-
oidcService *service.OidcService
27-
userGroupService *service.UserGroupService
28-
ldapService *service.LdapService
29-
apiKeyService *service.ApiKeyService
30-
versionService *service.VersionService
31-
fileStorage storage.FileStorage
32-
appLockService *service.AppLockService
16+
appConfigService *service.AppConfigService
17+
appImagesService *service.AppImagesService
18+
emailService *service.EmailService
19+
geoLiteService *service.GeoLiteService
20+
auditLogService *service.AuditLogService
21+
jwtService *service.JwtService
22+
webauthnService *service.WebAuthnService
23+
scimService *service.ScimService
24+
userService *service.UserService
25+
customClaimService *service.CustomClaimService
26+
oidcService *service.OidcService
27+
userGroupService *service.UserGroupService
28+
ldapService *service.LdapService
29+
apiKeyService *service.ApiKeyService
30+
versionService *service.VersionService
31+
fileStorage storage.FileStorage
32+
appLockService *service.AppLockService
3333
}
3434

3535
// Initializes all services
36-
func initServices(ctx context.Context, db *gorm.DB, httpClient *http.Client, imageExtensions map[string]string, fileStorage storage.FileStorage) (svc *services, err error) {
36+
func initServices(ctx context.Context, db *gorm.DB, httpClient *http.Client, imageExtensions map[string]string, fileStorage storage.FileStorage, scheduler *job.Scheduler) (svc *services, err error) {
3737
svc = &services{}
3838

3939
svc.appConfigService, err = service.NewAppConfigService(ctx, db)
@@ -52,7 +52,7 @@ func initServices(ctx context.Context, db *gorm.DB, httpClient *http.Client, ima
5252

5353
svc.geoLiteService = service.NewGeoLiteService(httpClient)
5454
svc.auditLogService = service.NewAuditLogService(db, svc.appConfigService, svc.emailService, svc.geoLiteService)
55-
svc.jwtService, err = service.NewJwtService(db, svc.appConfigService)
55+
svc.jwtService, err = service.NewJwtService(ctx, db, svc.appConfigService)
5656
if err != nil {
5757
return nil, fmt.Errorf("failed to create JWT service: %w", err)
5858
}
@@ -63,20 +63,17 @@ func initServices(ctx context.Context, db *gorm.DB, httpClient *http.Client, ima
6363
return nil, fmt.Errorf("failed to create WebAuthn service: %w", err)
6464
}
6565

66-
svc.oidcService, err = service.NewOidcService(ctx, db, svc.jwtService, svc.appConfigService, svc.auditLogService, svc.customClaimService, svc.webauthnService, httpClient, fileStorage)
66+
svc.scimService = service.NewScimService(db, scheduler, httpClient)
67+
68+
svc.oidcService, err = service.NewOidcService(ctx, db, svc.jwtService, svc.appConfigService, svc.auditLogService, svc.customClaimService, svc.webauthnService, svc.scimService, httpClient, fileStorage)
6769
if err != nil {
6870
return nil, fmt.Errorf("failed to create OIDC service: %w", err)
6971
}
7072

71-
svc.userGroupService = service.NewUserGroupService(db, svc.appConfigService)
72-
svc.userService = service.NewUserService(db, svc.jwtService, svc.auditLogService, svc.emailService, svc.appConfigService, svc.customClaimService, svc.appImagesService, fileStorage)
73+
svc.userGroupService = service.NewUserGroupService(db, svc.appConfigService, svc.scimService)
74+
svc.userService = service.NewUserService(db, svc.jwtService, svc.auditLogService, svc.emailService, svc.appConfigService, svc.customClaimService, svc.appImagesService, svc.scimService, fileStorage)
7375
svc.ldapService = service.NewLdapService(db, httpClient, svc.appConfigService, svc.userService, svc.userGroupService, fileStorage)
7476
svc.apiKeyService = service.NewApiKeyService(db, svc.emailService)
75-
svc.scimService = service.NewScimService(db, httpClient)
76-
svc.scimSchedulerService, err = service.NewScimSchedulerService(ctx, svc.scimService)
77-
if err != nil {
78-
return nil, fmt.Errorf("failed to create SCIM scheduler service: %w", err)
79-
}
8077

8178
svc.versionService = service.NewVersionService(httpClient)
8279

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
package cmds
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"os"
8+
9+
"github.com/pocket-id/pocket-id/backend/internal/model"
10+
"github.com/spf13/cobra"
11+
"gorm.io/gorm"
12+
13+
"github.com/pocket-id/pocket-id/backend/internal/bootstrap"
14+
"github.com/pocket-id/pocket-id/backend/internal/common"
15+
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
16+
"github.com/pocket-id/pocket-id/backend/internal/service"
17+
"github.com/pocket-id/pocket-id/backend/internal/utils"
18+
jwkutils "github.com/pocket-id/pocket-id/backend/internal/utils/jwk"
19+
)
20+
21+
type encryptionKeyRotateFlags struct {
22+
NewKey string
23+
Yes bool
24+
}
25+
26+
func init() {
27+
var flags encryptionKeyRotateFlags
28+
29+
encryptionKeyRotateCmd := &cobra.Command{
30+
Use: "encryption-key-rotate",
31+
Short: "Re-encrypts data using a new encryption key",
32+
RunE: func(cmd *cobra.Command, args []string) error {
33+
db, err := bootstrap.NewDatabase()
34+
if err != nil {
35+
return err
36+
}
37+
38+
return encryptionKeyRotate(cmd.Context(), flags, db, &common.EnvConfig)
39+
},
40+
}
41+
42+
encryptionKeyRotateCmd.Flags().StringVar(&flags.NewKey, "new-key", "", "New encryption key to re-encrypt data with")
43+
encryptionKeyRotateCmd.Flags().BoolVarP(&flags.Yes, "yes", "y", false, "Do not prompt for confirmation")
44+
45+
rootCmd.AddCommand(encryptionKeyRotateCmd)
46+
}
47+
48+
func encryptionKeyRotate(ctx context.Context, flags encryptionKeyRotateFlags, db *gorm.DB, envConfig *common.EnvConfigSchema) error {
49+
oldKey := envConfig.EncryptionKey
50+
newKey := []byte(flags.NewKey)
51+
if len(newKey) == 0 {
52+
return errors.New("new encryption key is required (--new-key)")
53+
}
54+
if len(newKey) < 16 {
55+
return errors.New("new encryption key must be at least 16 bytes long")
56+
}
57+
58+
if !flags.Yes {
59+
fmt.Println("WARNING: Rotating the encryption key will re-encrypt secrets in the database. Pocket-ID must be restarted with the new ENCRYPTION_KEY after rotation is complete.")
60+
ok, err := utils.PromptForConfirmation("Continue")
61+
if err != nil {
62+
return err
63+
}
64+
if !ok {
65+
fmt.Println("Aborted")
66+
os.Exit(1)
67+
}
68+
}
69+
70+
appConfigService, err := service.NewAppConfigService(ctx, db)
71+
if err != nil {
72+
return fmt.Errorf("failed to create app config service: %w", err)
73+
}
74+
instanceID := appConfigService.GetDbConfig().InstanceID.Value
75+
76+
// Derive the encryption keys used for the JWK encryption
77+
oldKek, err := jwkutils.LoadKeyEncryptionKey(&common.EnvConfigSchema{EncryptionKey: oldKey}, instanceID)
78+
if err != nil {
79+
return fmt.Errorf("failed to derive old key encryption key: %w", err)
80+
}
81+
newKek, err := jwkutils.LoadKeyEncryptionKey(&common.EnvConfigSchema{EncryptionKey: newKey}, instanceID)
82+
if err != nil {
83+
return fmt.Errorf("failed to derive new key encryption key: %w", err)
84+
}
85+
86+
// Derive the encryption keys used for EncryptedString fields
87+
oldEncKey, err := datatype.DeriveEncryptedStringKey(oldKey)
88+
if err != nil {
89+
return fmt.Errorf("failed to derive old encrypted string key: %w", err)
90+
}
91+
newEncKey, err := datatype.DeriveEncryptedStringKey(newKey)
92+
if err != nil {
93+
return fmt.Errorf("failed to derive new encrypted string key: %w", err)
94+
}
95+
96+
err = db.Transaction(func(tx *gorm.DB) error {
97+
err = rotateSigningKeyEncryption(ctx, tx, oldKek, newKek)
98+
if err != nil {
99+
return err
100+
}
101+
102+
err = rotateScimTokens(tx, oldEncKey, newEncKey)
103+
if err != nil {
104+
return err
105+
}
106+
107+
return nil
108+
})
109+
if err != nil {
110+
return err
111+
}
112+
113+
fmt.Println("Encryption key rotation completed successfully.")
114+
fmt.Println("Restart pocket-id with the new ENCRYPTION_KEY to use the rotated data.")
115+
116+
return nil
117+
}
118+
119+
func rotateSigningKeyEncryption(ctx context.Context, db *gorm.DB, oldKek []byte, newKek []byte) error {
120+
oldProvider := &jwkutils.KeyProviderDatabase{}
121+
err := oldProvider.Init(jwkutils.KeyProviderOpts{
122+
DB: db,
123+
Kek: oldKek,
124+
})
125+
if err != nil {
126+
return fmt.Errorf("failed to init key provider with old encryption key: %w", err)
127+
}
128+
129+
key, err := oldProvider.LoadKey(ctx)
130+
if err != nil {
131+
return fmt.Errorf("failed to load signing key using old encryption key: %w", err)
132+
}
133+
if key == nil {
134+
return nil
135+
}
136+
137+
newProvider := &jwkutils.KeyProviderDatabase{}
138+
err = newProvider.Init(jwkutils.KeyProviderOpts{
139+
DB: db,
140+
Kek: newKek,
141+
})
142+
if err != nil {
143+
return fmt.Errorf("failed to init key provider with new encryption key: %w", err)
144+
}
145+
146+
if err := newProvider.SaveKey(ctx, key); err != nil {
147+
return fmt.Errorf("failed to store signing key with new encryption key: %w", err)
148+
}
149+
150+
return nil
151+
}
152+
153+
type scimTokenRow struct {
154+
ID string
155+
Token string
156+
}
157+
158+
func rotateScimTokens(db *gorm.DB, oldEncKey []byte, newEncKey []byte) error {
159+
var rows []scimTokenRow
160+
err := db.Model(&model.ScimServiceProvider{}).Select("id, token").Scan(&rows).Error
161+
if err != nil {
162+
return fmt.Errorf("failed to list SCIM service providers: %w", err)
163+
}
164+
165+
for _, row := range rows {
166+
if row.Token == "" {
167+
continue
168+
}
169+
170+
decBytes, err := datatype.DecryptEncryptedStringWithKey(oldEncKey, row.Token)
171+
if err != nil {
172+
return fmt.Errorf("failed to decrypt SCIM token for provider %s: %w", row.ID, err)
173+
}
174+
175+
encValue, err := datatype.EncryptEncryptedStringWithKey(newEncKey, decBytes)
176+
if err != nil {
177+
return fmt.Errorf("failed to encrypt SCIM token for provider %s: %w", row.ID, err)
178+
}
179+
180+
err = db.Model(&model.ScimServiceProvider{}).Where("id = ?", row.ID).Update("token", encValue).Error
181+
if err != nil {
182+
return fmt.Errorf("failed to update SCIM token for provider %s: %w", row.ID, err)
183+
}
184+
}
185+
186+
return nil
187+
}

0 commit comments

Comments
 (0)