Documentation
¶
Overview ¶
Package resource provides the data layer for human-uploaded reference material (samples, playbooks, templates, references). Resources are scoped to global, persona, or user visibility and stored as blobs in S3 with metadata in PostgreSQL.
Index ¶
- Constants
- Variables
- func BuildS3Key(scope Scope, scopeID, resourceID, filename string) string
- func BuildURI(scheme string, scope Scope, scopeID, category, filename string) string
- func CanModifyResource(c Claims, r *Resource) bool
- func CanReadResource(c Claims, r *Resource) bool
- func CanWriteScope(c Claims, scope Scope, scopeID string) bool
- func GenerateID() (string, error)
- func IsNotFound(err error) bool
- func PersonaAdminRoles(roles []string) []string
- func SanitizeFilename(name string) (string, error)
- func ValidateCategory(cat string) error
- func ValidateDescription(desc string) error
- func ValidateDisplayName(name string) error
- func ValidateMIMEType(mt string) error
- func ValidateScope(scope Scope, scopeID string) error
- func ValidateTags(tags []string) error
- type Claims
- type ClaimsExtractor
- type Deps
- type Filter
- type Handler
- type ParsedURI
- type Resource
- type S3Client
- type Scope
- type ScopeFilter
- type Store
- type Update
Constants ¶
const ( // DefaultListLimit is used when no limit is specified in a list query. DefaultListLimit = 100 // MaxListLimit caps a client-supplied page size so a single list request // cannot pull an unbounded window. MaxListLimit = 200 )
const ( MaxUploadBytes = 100 << 20 // 100 MB MaxDescriptionLen = 2000 MaxDisplayNameLen = 200 MaxTagsPerResource = 20 MaxTagLen = 50 MaxCategoryLen = 31 )
Validation limits.
const DefaultURIScheme = "mcp"
DefaultURIScheme is used when no scheme is configured.
const MaxMultipartMemory = 10 << 20
MaxMultipartMemory is the max memory for multipart form parsing (10 MB).
Variables ¶
var DeniedExtensions = map[string]bool{ ".exe": true, ".sh": true, ".bat": true, ".cmd": true, ".ps1": true, ".msi": true, ".com": true, ".scr": true, }
DeniedExtensions lists file extensions that are blocked for upload.
var DeniedMIMETypes = map[string]bool{ "application/x-executable": true, "application/x-msdos-program": true, "application/x-msdownload": true, "application/x-sh": true, "application/x-shellscript": true, "application/x-bat": true, "application/x-msi": true, }
DeniedMIMETypes lists MIME types that are blocked for upload.
var ErrForbidden = errors.New("forbidden")
ErrForbidden signals that authentication succeeded at the credential level but the request is refused for a policy reason the client can recover from without re-authenticating — specifically a CSRF-token failure on a cookie-authenticated mutation. A ClaimsExtractor returns it so the handler responds 403 (not 401), matching the admin/portal surfaces and preventing the SPA from force-logging-out the user on a recoverable CSRF error.
Functions ¶
func BuildS3Key ¶
BuildS3Key constructs the S3 object key for a resource blob.
func CanModifyResource ¶
CanModifyResource checks whether the caller can update or delete a resource. The caller must be the original uploader OR have write permission for the scope.
func CanReadResource ¶
CanReadResource checks whether the caller can read a specific resource.
func CanWriteScope ¶
CanWriteScope checks whether the caller has write permission for the given scope.
func GenerateID ¶
GenerateID returns a cryptographically random 32-character hex string.
func IsNotFound ¶ added in v1.111.0
IsNotFound reports whether an error from a Store read means the resource does not exist, as opposed to the read having failed.
The Postgres store surfaces a missing row as a wrapped sql.ErrNoRows rather than as (nil, nil), so a caller that must distinguish "deleted" from "the database is down" cannot do it by nil-checking the result. Getting that distinction wrong is not cosmetic: a prompt attachment whose resource was deleted has to degrade to a flagged broken link, while a failed read has to fail closed.
func PersonaAdminRoles ¶ added in v1.111.0
PersonaAdminRoles extracts the persona names a role set grants admin authority over, tolerating any role prefix.
func SanitizeFilename ¶
SanitizeFilename normalizes a filename for storage: lowercase, no spaces, no path separators or shell metacharacters, preserves extension.
func ValidateCategory ¶
ValidateCategory checks that a category matches the required pattern.
func ValidateDescription ¶
ValidateDescription checks description length and content.
func ValidateDisplayName ¶
ValidateDisplayName checks display name length and content.
func ValidateMIMEType ¶
ValidateMIMEType checks that the MIME type is not on the deny list. The base type is extracted (e.g. "text/html" from "text/html; charset=utf-8") before checking the deny list.
func ValidateScope ¶
ValidateScope checks scope and scope_id consistency.
func ValidateTags ¶
ValidateTags checks tag count, length, and format.
Types ¶
type Claims ¶
type Claims struct {
Sub string // Keycloak subject (user ID)
Email string // user email
Personas []string // persona names the user belongs to
Roles []string // raw roles from auth (may have prefix, e.g., "dp_admin")
IsAdmin bool // resolved by the caller from persona config
AdminOfPersonas []string // persona names this user can admin (resolved by caller from role patterns)
}
Claims represents the identity information needed for resource permission checks.
func BuildClaims ¶ added in v1.111.0
BuildClaims assembles permission claims from an authenticated caller's identity. It owns the roles-to-persona-admin mapping so every surface that must apply the resource read rule — the resources middleware, prompt attachment serving (#1013), the attachment REST handler — derives claims identically instead of each reimplementing it.
persona is the caller's single resolved persona; pass "" when none is resolved. It is separate from roles because persona membership is resolved from roles by the platform's persona registry, which this package does not see.
type ClaimsExtractor ¶
ClaimsExtractor extracts resource Claims from an HTTP request. Provided by the platform auth middleware.
type Deps ¶
type Deps struct {
Store Store
S3Client S3Client
S3Bucket string
URIScheme string // defaults to "mcp" if empty
OnCreate func(*Resource) // called after successful create to register with MCP
OnDelete func(string) // called after successful delete with URI to unregister
}
Deps holds the dependencies for the resource HTTP handler.
type Filter ¶
type Filter struct {
Scopes []ScopeFilter // visibility scopes (derived from claims)
Category string // optional category filter
Tag string // optional tag filter
Query string // optional text search in display_name/description
Limit int
Offset int
}
Filter specifies criteria for listing resources.
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler provides HTTP endpoints for resource CRUD.
func NewHandler ¶
func NewHandler(deps Deps, extractFn ClaimsExtractor, authMiddle func(http.Handler) http.Handler) *Handler
NewHandler creates a resource handler with auth middleware.
type Resource ¶
type Resource struct {
ID string `json:"id" example:"res_01HK7R9F"`
Scope Scope `json:"scope" example:"persona"`
ScopeID string `json:"scope_id,omitempty" example:"data-engineer"` // persona name or user sub; empty for global
Category string `json:"category" example:"runbooks"`
Filename string `json:"filename" example:"etl-runbook.md"`
DisplayName string `json:"display_name" example:"ETL Runbook"`
Description string `json:"description" example:"Step-by-step procedures for ETL pipeline operations"`
MIMEType string `json:"mime_type" example:"text/markdown"`
SizeBytes int64 `json:"size_bytes" example:"34000"`
S3Key string `json:"s3_key" example:"resources/res_01HK7R9F/etl-runbook.md"`
URI string `json:"uri" example:"mcp://persona/data-engineer/runbooks/etl-runbook.md"`
Tags []string `json:"tags"`
UploaderSub string `json:"uploader_sub" example:"550e8400-e29b-41d4-a716-446655440000"`
UploaderEmail string `json:"uploader_email" example:"marcus.johnson@example.com"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
Resource represents a human-uploaded reference material entry.
type S3Client ¶
type S3Client interface {
PutObject(ctx context.Context, bucket, key string, data []byte, contentType string) error
GetObject(ctx context.Context, bucket, key string) (body []byte, contentType string, err error)
DeleteObject(ctx context.Context, bucket, key string) error
}
S3Client abstracts blob storage operations for resources.
type ScopeFilter ¶
ScopeFilter identifies a single scope+id pair for visibility filtering.
func VisibleScopes ¶
func VisibleScopes(c Claims) []ScopeFilter
VisibleScopes returns the set of (scope, scope_id) tuples the caller is allowed to see. Always derived from claims, never from request input.
type Store ¶
type Store interface {
Insert(ctx context.Context, r Resource) error
Get(ctx context.Context, id string) (*Resource, error)
GetByURI(ctx context.Context, uri string) (*Resource, error)
List(ctx context.Context, filter Filter) ([]Resource, int, error)
Update(ctx context.Context, id string, u Update) error
Delete(ctx context.Context, id string) error
}
Store persists and queries resource metadata.
func NewPostgresStore ¶
NewPostgresStore creates a resource store backed by PostgreSQL.