Feat v0.9.1 server side subpath routing (#74)
All checks were successful
CI/CD / detect-changes (push) Successful in 4s
CI/CD / test-runners (push) Has been skipped
CI/CD / e2e-smoke (push) Has been skipped
CI/CD / test-frontend (push) Successful in 5s
CI/CD / test-go-pg (push) Successful in 2m55s
CI/CD / test-sqlite (push) Successful in 2m58s
CI/CD / build-and-deploy (push) Successful in 1m12s
All checks were successful
CI/CD / detect-changes (push) Successful in 4s
CI/CD / test-runners (push) Has been skipped
CI/CD / e2e-smoke (push) Has been skipped
CI/CD / test-frontend (push) Successful in 5s
CI/CD / test-go-pg (push) Successful in 2m55s
CI/CD / test-sqlite (push) Successful in 2m58s
CI/CD / build-and-deploy (push) Successful in 1m12s
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit was merged in pull request #74.
This commit is contained in:
@@ -470,6 +470,16 @@ func (e *Engine) RenderExtensionSurface() gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
// ── Early auth short-circuit ─────────────────────────────
|
||||
// For all-authenticated packages, redirect before surface matching.
|
||||
if rawSurfaces, ok := sr.Manifest["surfaces"].([]any); ok && len(rawSurfaces) > 0 {
|
||||
profile := aggregateAccess(rawSurfaces)
|
||||
if profile == "authenticated" && c.GetString("user_id") == "" {
|
||||
c.Redirect(http.StatusTemporaryRedirect, e.cfg.BasePath+"/login")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ── Multi-surface resolution ─────────────────────────────
|
||||
var surfacePath string
|
||||
var surfaceParams map[string]string
|
||||
@@ -611,6 +621,35 @@ func splitPath(p string) []string {
|
||||
return parts
|
||||
}
|
||||
|
||||
// aggregateAccess scans surfaces to determine the package's access profile.
|
||||
// Returns "public" (all public), "authenticated" (all require auth), or "mixed".
|
||||
func aggregateAccess(surfaces []any) string {
|
||||
hasPublic := false
|
||||
hasAuth := false
|
||||
for _, raw := range surfaces {
|
||||
s, ok := raw.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
access, _ := s["access"].(string)
|
||||
if access == "" {
|
||||
access = "authenticated" // default
|
||||
}
|
||||
if access == "public" {
|
||||
hasPublic = true
|
||||
} else {
|
||||
hasAuth = true
|
||||
}
|
||||
if hasPublic && hasAuth {
|
||||
return "mixed"
|
||||
}
|
||||
}
|
||||
if hasPublic {
|
||||
return "public"
|
||||
}
|
||||
return "authenticated"
|
||||
}
|
||||
|
||||
// evaluateAccess checks whether the current request meets an access requirement.
|
||||
func evaluateAccess(c *gin.Context, access string) bool {
|
||||
switch {
|
||||
@@ -747,12 +786,14 @@ func (e *Engine) RegisterExtensionRoutes(
|
||||
sGroup := base.Group("/s/:slug")
|
||||
sGroup.Use(optAuth) // populate user context if token present; don't require it
|
||||
|
||||
// Root path: /s/:slug (no sub-path) — surface only
|
||||
sGroup.GET("", surfaceHandler)
|
||||
|
||||
// Sub-paths: /s/:slug/* — dispatch between API and surface
|
||||
sGroup.Any("/*path", func(c *gin.Context) {
|
||||
// Dispatcher handles both root and sub-path requests.
|
||||
// API requests (/s/:slug/api/*) go to extAPIHandler with JWT auth.
|
||||
// Everything else goes to the surface handler with per-surface access checks.
|
||||
dispatch := func(c *gin.Context) {
|
||||
subPath := c.Param("path")
|
||||
if subPath == "" {
|
||||
subPath = "/"
|
||||
}
|
||||
|
||||
// API requests: /s/:slug/api/*
|
||||
if strings.HasPrefix(subPath, "/api/") || subPath == "/api" {
|
||||
@@ -783,7 +824,17 @@ func (e *Engine) RegisterExtensionRoutes(
|
||||
}
|
||||
|
||||
surfaceHandler(c)
|
||||
}
|
||||
|
||||
// Root path: /s/:slug (Gin requires a separate registration since
|
||||
// /*path doesn't match the bare group path)
|
||||
sGroup.GET("", func(c *gin.Context) {
|
||||
c.Params = append(c.Params, gin.Param{Key: "path", Value: "/"})
|
||||
dispatch(c)
|
||||
})
|
||||
|
||||
// Sub-paths: /s/:slug/*path — API and surface dispatch
|
||||
sGroup.Any("/*path", dispatch)
|
||||
}
|
||||
|
||||
// surfaceHandler returns the appropriate Gin handler for a surface.
|
||||
|
||||
Reference in New Issue
Block a user