* feat: add middleware example demonstrating route groups Self-contained example covering v.Use(), v.Group(), nested groups, Group.Use(), and middleware chaining with role-based access control. * feat: add per-action middleware via WithMiddleware ActionOption Reuses the existing Middleware type so the same auth/logging functions work at both page and action level. Middleware runs after CSRF and rate-limit checks, with full access to session and signals. * feat: add RedirectView helper and refactor session example to use middleware RedirectView lets middleware abort and redirect in one step. The session example now uses an authRequired middleware on a route group instead of an inline check inside the view. * fix: remove dead code, fix double Load and extractParams mismatch - Remove componentRegistry (written, never read) - Remove unused signal methods: Bytes, Int64, Float - Remove unreachable nil check in registerCtx - Simplify injectRouteParams (extractParams already returns fresh map) - Fix double sync.Map.Load in injectSignals - Merge Shutdown/shutdown into single method - Inline currSessionNum - Fix extractParams: mismatched literal segment now returns nil - Minor: new(bytes.Buffer), go c.Sync(), genRandID reads 4 bytes
152 lines
3.9 KiB
Go
152 lines
3.9 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/ryanhamamura/via"
|
|
"github.com/ryanhamamura/via/h"
|
|
)
|
|
|
|
func main() {
|
|
v := via.New()
|
|
v.Config(via.Options{
|
|
ServerAddress: ":8080",
|
|
DocumentTitle: "Middleware Example",
|
|
})
|
|
|
|
// --- Middleware definitions ---
|
|
|
|
// requestLogger logs every page request to stdout.
|
|
requestLogger := func(c *via.Context, next func()) {
|
|
fmt.Printf("[%s] request\n", time.Now().Format("15:04:05"))
|
|
next()
|
|
}
|
|
|
|
// authRequired redirects unauthenticated users to /login.
|
|
authRequired := func(c *via.Context, next func()) {
|
|
if c.Session().GetString("role") == "" {
|
|
c.RedirectView("/login")
|
|
return
|
|
}
|
|
next()
|
|
}
|
|
|
|
// auditLog prints the authenticated username to stdout.
|
|
auditLog := func(c *via.Context, next func()) {
|
|
fmt.Printf("[audit] user=%s\n", c.Session().GetString("username"))
|
|
next()
|
|
}
|
|
|
|
// superAdminOnly rejects non-superadmin users with a forbidden view.
|
|
superAdminOnly := func(c *via.Context, next func()) {
|
|
if c.Session().GetString("role") != "superadmin" {
|
|
c.View(func() h.H {
|
|
return h.Div(
|
|
h.H1(h.Text("Forbidden")),
|
|
h.P(h.Text("Super-admin access required.")),
|
|
h.A(h.Href("/admin/dashboard"), h.Text("Back to dashboard")),
|
|
)
|
|
})
|
|
return
|
|
}
|
|
next()
|
|
}
|
|
|
|
// --- Route registration ---
|
|
|
|
v.Use(requestLogger) // global middleware
|
|
|
|
admin := v.Group("/admin", authRequired) // prefixed group
|
|
admin.Use(auditLog) // Group.Use()
|
|
superAdmin := admin.Group("/super", superAdminOnly) // nested group
|
|
|
|
// Public: redirect root to login
|
|
v.Page("/", func(c *via.Context) {
|
|
c.View(func() h.H {
|
|
c.Redirect("/login")
|
|
return h.Div()
|
|
})
|
|
})
|
|
|
|
// Public: login page with role-selection buttons
|
|
v.Page("/login", func(c *via.Context) {
|
|
loginAdmin := c.Action(func() {
|
|
c.Session().Set("role", "admin")
|
|
c.Session().Set("username", "alice")
|
|
c.Session().RenewToken()
|
|
c.Redirect("/admin/dashboard")
|
|
})
|
|
|
|
loginSuper := c.Action(func() {
|
|
c.Session().Set("role", "superadmin")
|
|
c.Session().Set("username", "bob")
|
|
c.Session().RenewToken()
|
|
c.Redirect("/admin/dashboard")
|
|
})
|
|
|
|
c.View(func() h.H {
|
|
return h.Div(
|
|
h.H1(h.Text("Login")),
|
|
h.P(h.Text("Choose a role:")),
|
|
h.Button(h.Text("Login as Admin"), loginAdmin.OnClick()),
|
|
h.Raw(" "),
|
|
h.Button(h.Text("Login as Super Admin"), loginSuper.OnClick()),
|
|
)
|
|
})
|
|
})
|
|
|
|
// Per-action middleware: only superadmins can invoke this action.
|
|
requireSuperAdmin := func(c *via.Context, next func()) {
|
|
if c.Session().GetString("role") != "superadmin" {
|
|
return
|
|
}
|
|
next()
|
|
}
|
|
|
|
// Admin: dashboard (requires authRequired + auditLog)
|
|
admin.Page("/dashboard", func(c *via.Context) {
|
|
logout := c.Action(func() {
|
|
c.Session().Delete("role")
|
|
c.Session().Delete("username")
|
|
c.Redirect("/login")
|
|
})
|
|
|
|
dangerAction := c.Action(func() {
|
|
fmt.Printf("[danger] executed by %s\n", c.Session().GetString("username"))
|
|
c.Sync()
|
|
}, via.WithMiddleware(requireSuperAdmin))
|
|
|
|
c.View(func() h.H {
|
|
username := c.Session().GetString("username")
|
|
role := c.Session().GetString("role")
|
|
return h.Div(
|
|
h.H1(h.Textf("Dashboard — %s (%s)", username, role)),
|
|
h.Ul(
|
|
h.Li(h.A(h.Href("/admin/super/settings"), h.Text("Super Admin Settings"))),
|
|
),
|
|
h.H2(h.Text("Danger Zone")),
|
|
h.P(h.Text("This action is protected by per-action middleware (superadmin only):")),
|
|
h.Button(h.Text("Delete Everything"), dangerAction.OnClick()),
|
|
h.Br(),
|
|
h.Br(),
|
|
h.Button(h.Text("Logout"), logout.OnClick()),
|
|
)
|
|
})
|
|
})
|
|
|
|
// Super-admin: settings (requires authRequired + auditLog + superAdminOnly)
|
|
superAdmin.Page("/settings", func(c *via.Context) {
|
|
c.View(func() h.H {
|
|
username := c.Session().GetString("username")
|
|
return h.Div(
|
|
h.H1(h.Textf("Super Admin Settings — %s", username)),
|
|
h.P(h.Text("Only super-admins can see this page.")),
|
|
h.A(h.Href("/admin/dashboard"), h.Text("Back to dashboard")),
|
|
)
|
|
})
|
|
})
|
|
|
|
v.Start()
|
|
}
|