Skip to content

Commit 21e0307

Browse files
committed
merge: resolve conflicts with dev (ProjectAuth Redis cache, async asset refs)
Merge dev into fix/encryption-review-issues, resolving conflicts: - auth.go: combine KEK helper functions (HEAD) with Redis-cached project lookups (dev) - session.go: keep AssetRefWriter buffered approach over goroutine approach - container.go/main.go: keep AssetRefWriter DI registration and shutdown - router_admin.go: update ProjectAuth call to include Redis parameter
2 parents d9a0e2b + fa46480 commit 21e0307

6 files changed

Lines changed: 59 additions & 8 deletions

File tree

.github/workflows/e2e-test.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,9 @@ jobs:
7070
run: |
7171
cd src/server
7272
docker compose -f docker-compose.test.yml logs
73+
74+
- name: Teardown
75+
if: always()
76+
run: |
77+
cd src/server
78+
docker compose -f docker-compose.test.yml down --timeout 10

src/server/api/go/cmd/server/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ func main() {
9898
engine := router.NewRouter(router.RouterDeps{
9999
Config: cfg,
100100
DB: db,
101+
Redis: rdb,
101102
Log: log,
102103
SessionHandler: sessionHandler,
103104
DiskHandler: diskHandler,

src/server/api/go/configs/config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ log:
1515

1616
database:
1717
dsn: "host=${DATABASE_HOST} user=${DATABASE_USER} password=${DATABASE_PASSWORD} dbname=${DATABASE_NAME} port=${DATABASE_EXPORT_PORT} sslmode=disable TimeZone=UTC"
18-
maxOpen: 50
19-
maxIdle: 25
18+
maxOpen: 100
19+
maxIdle: 50
2020
maxIdleTimeSec: 300
2121
autoMigrate: true
2222
enableTLS: ${DATABASE_ENABLE_TLS}

src/server/api/go/internal/middleware/auth.go

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
package middleware
22

33
import (
4+
"context"
45
"encoding/base64"
6+
"encoding/json"
57
"errors"
68
"net/http"
79
"strings"
10+
"time"
811

912
"github.com/gin-gonic/gin"
13+
"github.com/redis/go-redis/v9"
1014
"go.opentelemetry.io/otel"
1115
"go.opentelemetry.io/otel/attribute"
1216
"go.opentelemetry.io/otel/trace"
@@ -61,10 +65,16 @@ func GetUserKEKBase64IfEncrypted(c *gin.Context) string {
6165
return base64.StdEncoding.EncodeToString(kek)
6266
}
6367

68+
const (
69+
projectAuthCachePrefix = "project:auth:"
70+
projectAuthCacheTTL = 5 * time.Minute
71+
)
72+
6473
// ProjectAuth returns a middleware that authenticates requests using project bearer tokens.
6574
// Token format: sk-ac-{auth_secret}.{encrypted_master_key}
6675
// Derives a KEK and stores it in context for downstream encryption operations.
67-
func ProjectAuth(cfg *config.Config, db *gorm.DB) gin.HandlerFunc {
76+
// It caches project lookups in Redis to avoid hitting the database on every request.
77+
func ProjectAuth(cfg *config.Config, db *gorm.DB, rdb *redis.Client) gin.HandlerFunc {
6878
return func(c *gin.Context) {
6979
// Create auth span without propagating context to avoid nested span hierarchy
7080
authCtx, authSpan := otel.Tracer("middleware").Start(
@@ -93,8 +103,8 @@ func ProjectAuth(cfg *config.Config, db *gorm.DB) gin.HandlerFunc {
93103
// HMAC lookup uses auth_secret (both formats)
94104
lookup := tokens.HMAC256Hex(cfg.Root.SecretPepper, parsed.AuthSecret)
95105

96-
var project model.Project
97-
if err := db.WithContext(authCtx).Where(&model.Project{SecretKeyHMAC: lookup}).First(&project).Error; err != nil {
106+
project, err := lookupProject(authCtx, db, rdb, lookup)
107+
if err != nil {
98108
if errors.Is(err, gorm.ErrRecordNotFound) {
99109
authSpan.SetAttributes(attribute.Bool("authenticated", false))
100110
authSpan.End()
@@ -135,7 +145,7 @@ func ProjectAuth(cfg *config.Config, db *gorm.DB) gin.HandlerFunc {
135145
)
136146
authSpan.End()
137147

138-
c.Set("project", &project)
148+
c.Set("project", project)
139149
SetWideEventField(c, "project_id", project.ID.String())
140150

141151
// Derive KEK: unwrap master_key from token if present.
@@ -157,3 +167,35 @@ func ProjectAuth(cfg *config.Config, db *gorm.DB) gin.HandlerFunc {
157167
c.Next()
158168
}
159169
}
170+
171+
// lookupProject tries Redis cache first, falls back to DB on miss or Redis error.
172+
func lookupProject(ctx context.Context, db *gorm.DB, rdb *redis.Client, hmac string) (*model.Project, error) {
173+
cacheKey := projectAuthCachePrefix + hmac
174+
175+
// Try Redis first
176+
if rdb != nil {
177+
data, err := rdb.Get(ctx, cacheKey).Bytes()
178+
if err == nil {
179+
var project model.Project
180+
if json.Unmarshal(data, &project) == nil {
181+
return &project, nil
182+
}
183+
}
184+
// On redis.Nil or any other error, fall through to DB
185+
}
186+
187+
// DB lookup
188+
var project model.Project
189+
if err := db.WithContext(ctx).Where(&model.Project{SecretKeyHMAC: hmac}).First(&project).Error; err != nil {
190+
return nil, err
191+
}
192+
193+
// Write-back to Redis (best-effort, don't block on failure)
194+
if rdb != nil {
195+
if data, err := json.Marshal(&project); err == nil {
196+
_ = rdb.Set(ctx, cacheKey, data, projectAuthCacheTTL).Err()
197+
}
198+
}
199+
200+
return &project, nil
201+
}

src/server/api/go/internal/router/router.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"net/http"
55

66
"github.com/gin-gonic/gin"
7+
"github.com/redis/go-redis/v9"
78
"go.uber.org/zap"
89
"gorm.io/gorm"
910

@@ -19,6 +20,7 @@ import (
1920
type RouterDeps struct {
2021
Config *config.Config
2122
DB *gorm.DB
23+
Redis *redis.Client
2224
Log *zap.Logger
2325
SessionHandler *handler.SessionHandler
2426
DiskHandler *handler.DiskHandler
@@ -66,7 +68,7 @@ func NewRouter(d RouterDeps) *gin.Engine {
6668
{
6769
projectAuth := d.ProjectAuthOverride
6870
if projectAuth == nil {
69-
projectAuth = middleware.ProjectAuth(d.Config, d.DB)
71+
projectAuth = middleware.ProjectAuth(d.Config, d.DB, d.Redis)
7072
}
7173
v1.Use(projectAuth)
7274

src/server/api/go/internal/router/router_admin.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ func NewAdminRouter(d AdminRouterDeps) *gin.Engine {
3939
// Admin project encryption routes - protected by ProjectAuth (Bearer API key)
4040
adminProject := r.Group("/admin/v1")
4141
{
42-
adminProject.Use(middleware.ProjectAuth(d.Config, d.DB))
42+
adminProject.Use(middleware.ProjectAuth(d.Config, d.DB, d.Redis))
4343

4444
adminProject.POST("/project/encrypt", d.AdminHandler.EncryptProject)
4545
adminProject.POST("/project/decrypt", d.AdminHandler.DecryptProject)

0 commit comments

Comments
 (0)