GOWDK's current hook model is small and net/http-first.
Current Contracts
| Extension point | Type | Scope |
|---|---|---|
| Generated app handler | http.Handler | Wrap with normal Go middleware in app startup. |
| Guards | addons/ssr.GuardRegistry, runtime/auth.Provider | Generated request-time routes with @guard. |
| Rate limiting | *ratelimit.Limiter | Generated action, API, fragment, SSR, and split-backend proxy routes when the addon is enabled. |
| Handler context | context.Context | User handlers read request metadata through runtime/app helpers. |
Generated apps expose Handler() (http.Handler, error) and
ServeMux() (*http.ServeMux, error). App-owned startup code can wrap the
handler with ordinary middleware:
handler, err := gowdkapp.Handler()
if err != nil {
panic(err)
}
wrapped := myMiddleware(handler)
http.ListenAndServe(":8080", wrapped)
Guards
@guard is optional, but a page is not public by default: a page that declares
no @guard warns (missing_page_guard) and its route is denied (403) at request
time until access is stated. Use @guard public to serve the page on purpose.
public is a compile-time marker, must be the only guard on that page, and does
not require runtime backing code.
Routes with non-public @guard IDs require backing code in the generated app
package. A guarded generated app will not compile until the required hook
exists. Non-public page guards also require request-time page rendering for the
page GET route; build-time SPA pages emit static HTML and cannot enforce
frontend access.
func GOWDKGuardRegistry() ssr.GuardRegistry {
return ssr.GuardRegistry{
"auth.required": func(ctx ssr.LoadContext) error {
return nil
},
}
}
Native RBAC guards reuse @guard IDs:
@guard role:admin, permission:patients.read
Generated app packages with native RBAC guard IDs require:
func GOWDKAuthProvider() auth.Provider
Define the application-owned principal source from generated app startup code:
import (
"net/http"
gowdkauth "github.com/cssbruno/gowdk/runtime/auth"
)
func GOWDKAuthProvider() gowdkauth.Provider {
return gowdkauth.ProviderFunc(func(request *http.Request) (*gowdkauth.Principal, error) {
return &gowdkauth.Principal{
ID: "user-1",
Roles: []string{"admin"},
Permissions: []string{"patients.read"},
}, nil
})
}
RBAC guard behavior:
role:<name>requires the principal to have that role.permission:<name>requires the principal to have that permission.- Multiple guard IDs are enforced in declaration order, so multiple RBAC guards are an AND check.
- A missing
GOWDKAuthProviderfunction fails at Go compile time. A nil principal, provider error, or missing role/permission fails closed with HTTP 403. - GOWDK does not manage users, passwords, OAuth, sessions, tenants, or storage.
The auth provider adapts application-owned identity into
auth.Principal. - Native RBAC guards are a defense-in-depth redundancy layer for generated route/page access. They must never replace backend authorization around protected resources, data access, or service methods.
Guard behavior (see guards.md for the full access contract):
- Missing
@guardis amissing_page_guardwarning and the route is denied (403) at request time — except on a page that also declaresact/api/fragmentendpoints, where it is a build error. @guard publicmarks intentional public access and cannot be combined with protected guard IDs.- Non-public page guards on build-time SPA/action page routes fail validation;
add
load {}orgo ssr {}with the SSR addon when the page itself is protected. - Guards run in declaration order.
- Missing custom guard backing code fails at Go compile time.
- Guard errors fail closed with HTTP 403.
- Guards run before action decoding, API handler calls, fragment hooks, SSR
load {}, and user business logic. - Guards return
nilorerrortoday. Redirect/custom response guard results are planned.
Rate Limiting
When ratelimit.Addon() is enabled, generated apps expose:
gowdkapp.RegisterRateLimiter(limiter)
Generated request-time routes call the registered limiter before guards and user handler logic. If no limiter is registered, requests continue.
Ordering
Current generated request-time order:
- Attach route or endpoint context metadata.
- Install panic boundary for supported generated lanes.
- Run rate limiter when enabled and registered.
- Run guards when declared.
- Run CSRF validation for generated action POSTs when enabled.
- Decode generated action input when applicable.
- Call user Go handler, fragment hook, or SSR load/render path.
- Write the returned
runtime/response.Responseor generated HTML.
Non-Goals
- No generated route rewriting hook.
- No generated response transform hook.
- No generated fetch/navigation interception hook.
- No custom GOWDK context type; user code receives
context.Context. - No framework-specific middleware in generated core. Echo, Gin, and Fiber
adapters wrap the same
http.Handler.