mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-10-10 11:49:05 +02:00
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
103 lines
3.6 KiB
Go
103 lines
3.6 KiB
Go
package authz
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"path"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// Router registers routes together with the scope each one requires
|
|
// Every route under /api must be registered through a Router or PublicRouter, which a test over the complete route table checks with IsDeclared
|
|
type Router struct {
|
|
group *gin.RouterGroup
|
|
auth *Middleware
|
|
optional bool
|
|
}
|
|
|
|
// Group returns a router for routes below the relative path
|
|
func (r *Router) Group(relativePath string) *Router {
|
|
return &Router{group: r.group.Group(relativePath), auth: r.auth, optional: r.optional}
|
|
}
|
|
|
|
// Optional returns a router whose routes let requests without a usable credential through as anonymous
|
|
// Handlers on these routes must check PrincipalFrom before relying on a signed-in user
|
|
func (r *Router) Optional() *Router {
|
|
return &Router{group: r.group, auth: r.auth, optional: true}
|
|
}
|
|
|
|
// Public returns a router for routes that require no authentication at all
|
|
func (r *Router) Public() *PublicRouter {
|
|
return &PublicRouter{group: r.group, auth: r.auth}
|
|
}
|
|
|
|
func (r *Router) GET(relativePath string, scope Scope, handlers ...gin.HandlerFunc) {
|
|
r.Handle(http.MethodGet, relativePath, scope, handlers...)
|
|
}
|
|
|
|
func (r *Router) POST(relativePath string, scope Scope, handlers ...gin.HandlerFunc) {
|
|
r.Handle(http.MethodPost, relativePath, scope, handlers...)
|
|
}
|
|
|
|
func (r *Router) PUT(relativePath string, scope Scope, handlers ...gin.HandlerFunc) {
|
|
r.Handle(http.MethodPut, relativePath, scope, handlers...)
|
|
}
|
|
|
|
func (r *Router) PATCH(relativePath string, scope Scope, handlers ...gin.HandlerFunc) {
|
|
r.Handle(http.MethodPatch, relativePath, scope, handlers...)
|
|
}
|
|
|
|
func (r *Router) DELETE(relativePath string, scope Scope, handlers ...gin.HandlerFunc) {
|
|
r.Handle(http.MethodDelete, relativePath, scope, handlers...)
|
|
}
|
|
|
|
// Handle registers a route that requires the scope, running authorization before every other handler of the route
|
|
func (r *Router) Handle(method, relativePath string, scope Scope, handlers ...gin.HandlerFunc) {
|
|
// An unknown scope can never be granted, so it is a programming error just like a duplicate route in gin
|
|
if !scope.Known() {
|
|
panic(fmt.Sprintf("route %s %s requires unknown scope %q", method, joinPaths(r.group.BasePath(), relativePath), scope))
|
|
}
|
|
|
|
chain := make([]gin.HandlerFunc, 0, len(handlers)+1)
|
|
chain = append(chain, r.auth.require(scope, r.optional))
|
|
chain = append(chain, handlers...)
|
|
r.group.Handle(method, relativePath, chain...)
|
|
r.auth.declare(method, joinPaths(r.group.BasePath(), relativePath))
|
|
}
|
|
|
|
// PublicRouter registers routes that require no authentication
|
|
// Routing them through here keeps public access an explicit decision instead of a missing middleware
|
|
type PublicRouter struct {
|
|
group *gin.RouterGroup
|
|
auth *Middleware
|
|
}
|
|
|
|
func (r *PublicRouter) GET(relativePath string, handlers ...gin.HandlerFunc) {
|
|
r.Handle(http.MethodGet, relativePath, handlers...)
|
|
}
|
|
|
|
func (r *PublicRouter) POST(relativePath string, handlers ...gin.HandlerFunc) {
|
|
r.Handle(http.MethodPost, relativePath, handlers...)
|
|
}
|
|
|
|
// Handle registers a public route
|
|
func (r *PublicRouter) Handle(method, relativePath string, handlers ...gin.HandlerFunc) {
|
|
r.group.Handle(method, relativePath, handlers...)
|
|
r.auth.declare(method, joinPaths(r.group.BasePath(), relativePath))
|
|
}
|
|
|
|
// joinPaths mirrors how gin builds a route's absolute path so declared routes match gin's route table
|
|
func joinPaths(absolutePath, relativePath string) string {
|
|
if relativePath == "" {
|
|
return absolutePath
|
|
}
|
|
|
|
finalPath := path.Join(absolutePath, relativePath)
|
|
if strings.HasSuffix(relativePath, "/") && !strings.HasSuffix(finalPath, "/") {
|
|
return finalPath + "/"
|
|
}
|
|
return finalPath
|
|
}
|