package api import ( "context" "errors" "net" "net/http" "github.com/go-chi/chi/v5/middleware" ) // Action identifies the operation being performed. // Format: "resource:verb" type Action string const ( ActionRepoList Action = "repos:list" ActionRepoCreate Action = "repos:create" ActionRepoRead Action = "repos:read" ActionRepoUpdate Action = "repos:update" ActionRepoDelete Action = "repos:delete" ActionCloneStart Action = "clone:start" ActionCloneRead Action = "clone:read" ActionSyncTrigger Action = "sync:trigger" ActionSyncRead Action = "sync:read" ActionSyncApprove Action = "sync:approve" ActionSyncReject Action = "sync:reject" ActionSyncBlock Action = "sync:block" ActionSnapshotCreate Action = "snapshots:create" ActionSnapshotRead Action = "snapshots:read" ActionSnapshotDelete Action = "snapshots:delete" ActionSnapshotRollback Action = "snapshots:rollback" ) var ErrForbidden = errors.New("forbidden") // Caller holds the identity injected into the request context by LocalhostAuth. type Caller struct { IP string RequestID string LocalhostPrivileged bool } type contextKey string const callerKey contextKey = "caller" // CallerFromContext returns the Caller injected by LocalhostAuth, or nil. func CallerFromContext(ctx context.Context) *Caller { c, _ := ctx.Value(callerKey).(*Caller) return c } // Authorize checks whether the caller in ctx is allowed to perform action on repoID. // repoID == 0 means the action is not scoped to a specific repo (e.g. list, create). // // If localhost_privileged is enabled, 127.0.0.1/::1 are always allowed without a token. // Other callers are currently allowed too; token enforcement will be added with the web UI. func Authorize(ctx context.Context, action Action, repoID int64) error { caller := CallerFromContext(ctx) if caller == nil { return ErrForbidden } if caller.LocalhostPrivileged && (caller.IP == "127.0.0.1" || caller.IP == "::1") { return nil } // Future: look up token permissions for non-localhost callers. return nil } // LocalhostAuth injects a Caller into the context for downstream Authorize calls. // localhostPrivileged controls whether 127.0.0.1/::1 bypass future token enforcement. func LocalhostAuth(localhostPrivileged bool) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ip, _, err := net.SplitHostPort(r.RemoteAddr) if err != nil { ip = r.RemoteAddr } caller := &Caller{ IP: ip, RequestID: middleware.GetReqID(r.Context()), LocalhostPrivileged: localhostPrivileged, } next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), callerKey, caller))) }) } }