apitoken

package
v0.44.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 25, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package apitoken implements bearer-token authentication for non-interactive callers: AI agents, the work.example.com FilexClient, and the embedded MCP server.

A token is a 64-char hex string handed out once at create time (POST /api/admin/ai-tokens). Only its sha256 hash is stored in the api_tokens table. Every token is bound to a user, and a request authenticated by this driver carries BOTH the user and the token through auth.Middleware (see AuthenticateToken), so the gates downstream — RequireAdmin among them — judge it by what the token grants, not by the account's role.

Credentials are read from either header:

X-Filex-Token: <token>
Authorization: Bearer <token>

The matched *model.APIToken is attached to the request context so downstream handlers can enforce per-token scopes (see auth.TokenFrom).

Index

Constants

View Source
const (
	ScopeRead   = "read"
	ScopeWrite  = "write"
	ScopeDelete = "delete"
	ScopeMCP    = "mcp"
	ScopeAdmin  = "admin"
)

Issuable token scopes. A token grants exactly the scopes in its list — an empty list grants NOTHING (see ParseIssued in issue.go for why, and for the one rule every door that mints a token applies). RequireScope gates each verb against these; the issuers reject anything not in the set so an operator can't mint a token carrying a typo'd scope that silently grants nothing.

read   — list / info / download / search (REST) — read-only file ops
write  — upload / mkdir / move (REST)            — file mutations
delete — delete (REST)                            — soft-delete files
mcp    — the streamable-HTTP MCP server at /api/ai/mcp
admin  — the full admin surface at /api/ai/admin/* and the admin_*
         MCP tools (users, storages, settings, replica, queue, …)
View Source
const HeaderName = "X-Filex-Token"

HeaderName is the dedicated header AI/MCP callers send.

View Source
const ScopeRootPrefix = "root:"

ScopeRootPrefix marks a path-confinement scope: `root:<adapter>://<rel>`. Unlike the verb scopes it carries a value and is enforced by package confine as a hard path ceiling — across both /api/files and the AI surface. A token may combine verb scopes with at most one root scope.

Variables

View Source
var ErrScopesRequired = errors.New("at least one scope is required (read, write, delete, mcp or admin)")

ErrScopesRequired is an issuance request that names no verb scope.

ValidScopes is the canonical, ordered set of issuable VERB scopes. The `root:` confinement scope is validated separately (see IsValidScope).

Functions

func ExtractToken

func ExtractToken(r *http.Request) string

ExtractToken pulls the raw token from the X-Filex-Token header first, then falls back to `Authorization: Bearer <token>`.

func HashToken

func HashToken(raw string) string

HashToken returns the sha256 hex digest stored in api_tokens.token_hash.

func IsValidScope

func IsValidScope(s string) bool

IsValidScope reports whether s is a known issuable scope — a verb scope or a well-formed `root:<adapter>://<rel>` confinement scope.

⚠ "Well-formed" is decided by the confinement parser itself, not by a non-empty check. A root scope it cannot read (`root:projects`, no storage named) used to be accepted here and then ignored by every surface that enforces confinement, so the operator believed they had issued a folder credential and had in fact issued an unconfined one.

func JoinScopes

func JoinScopes(verbs, roots []string) string

JoinScopes is the stored form of a parsed list: the verbs, then the roots.

func ParseIssued

func ParseIssued(raw string) (verbs, roots []string, err error)

ParseIssued validates a requested scope list for a NEW token: every entry is a known verb or a well-formed `root:` confinement, duplicates collapse, and at least one VERB is present — a root on its own grants no verb, so it is refused like an empty list rather than minted as a token that can do nothing. It returns the verbs (in the order ValidScopes lists them) and the roots (in the order given).

Types

type Driver

type Driver struct {
	// contains filtered or unexported fields
}

Driver is the API-token auth driver.

func New

func New(store db.Store) *Driver

New constructs a driver bound to a store. Init must still be called.

func (*Driver) Authenticate

func (d *Driver) Authenticate(r *http.Request) (*model.User, error)

Authenticate resolves a bearer/X-Filex-Token credential to its bound user. Returns auth.ErrUnauthorized (so the middleware falls through to the next driver) when no credential is present or it doesn't validate.

⚠ The user alone is not the whole answer for a token: see AuthenticateToken, which is what auth.Middleware actually calls.

func (*Driver) AuthenticateToken

func (d *Driver) AuthenticateToken(r *http.Request) (*model.User, *model.APIToken, error)

AuthenticateToken implements auth.TokenAuthenticator: it returns the bound user AND the matched token, so the chain attaches the token to the request context and every scope and confinement check downstream can see it.

⚠ Returning only the user was the defect: a request authenticated here then looked, to every gate after it, like the account's own session, and was judged by the account's role instead of by the token's scopes.

func (*Driver) Capabilities

func (d *Driver) Capabilities() auth.Capabilities

Capabilities — token auth is headless; no interactive sign-in surface.

func (*Driver) Init

func (d *Driver) Init(_ context.Context, _ map[string]any) error

Init validates the driver has a store.

func (*Driver) Name

func (d *Driver) Name() string

Name implements auth.Driver.

type UnknownScopeError

type UnknownScopeError struct{ Scope string }

UnknownScopeError names a scope this server does not issue.

func (*UnknownScopeError) Error

func (e *UnknownScopeError) Error() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL