yankit

package module
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Nov 4, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

README

yankit

Shared HTTP toolkit for the Yan services (YanAuth, YanLearn). Echo v5, Go 1.25+. Dependencies: Echo v5, lestrrat-go/jwx/v3 (JWT/JWKS), standard library.

  • Error rendering — uniform {"code","error"} JSON bodies with stable snake_case add-only codes; HTTP status carries the semantics. Unknown errors collapse to 500 internal_error and never leak details to the client.
  • Auth chain — consumer-side middlewares: JWTAuth (local JWKS signature verification, no network call), RequireScope (preview tokens refused on management routes), RequirePerm (namespaced permission codes, 403 carries the missing code). Composable independently, or via NewAuth(Options{Keys, Namespace}).
  • Startup scaffold — New(logger) returns an Echo instance wired with the shared error handler and one structured slog line per request; Healthz(ping) mounts a liveness probe.
log := slog.New(slog.NewJSONHandler(os.Stdout, nil))
e := yankit.New(log)
e.GET("/healthz", yankit.Healthz(dbPing))

// 启动时拉取 YanAuth /.well-known/jwks.json 并缓存(来源注入,验签零网络调用)
set, _ := yankit.ParseJWKS(jwksBody)
auth := yankit.NewAuth(yankit.Options{
    Keys:      yankit.KeySetFunc(func() (jwk.Set, error) { return set, nil }),
    Namespace: "learn",
})
api := e.Group("/api/v1", auth.JWT(), auth.Scope(yankit.ScopeNormal))
api.GET("/courses/:id", showCourse, auth.Perm("course:read"))

Minimal runnable example: go run ./examples/minimal.

Error-code contract (add-only, never rename a published code; see YanLearn ADR 0002): codes must not leak information — share one code across failure reasons that must stay indistinguishable, and keep the precise reason in the audit log.

yankit must stay tiny: only code both services genuinely share (ADR 0001).

Divergences from YanAuth's current rendering (reconcile on adoption)

yankit is ported from YanAuth's internal/httpapi but fixes four behaviors. YanAuth picks these up when it switches to yankit (implementation phase 0):

  • HEAD requests get no body on every error path, not just echo.HTTPError.
  • Echo v5 router sentinels (404/405 are unexported HTTPStatusCoder implementors, not *echo.HTTPError) render with their real status; YanAuth today would collapse them to 500.
  • 405 maps to method_not_allowed instead of the "error" fallback.
  • 5xx messages are scrubbed to the HTTP status text — handler-supplied detail goes to the log, never the client.
  • Auth middlewares converge instead of panicking: used standalone (without JWTAuth in front), RequireScope/RequirePerm answer 403 for claims-less requests, where YanAuth's current code panics on the type assertion.

License

Apache-2.0 — see LICENSE.

Documentation

Overview

Package yankit is the shared HTTP toolkit for the Yan services (YanAuth, YanLearn): uniform {"code","error"} JSON error rendering and an Echo v5 startup scaffold (slog request logging, health-check helper).

It must stay tiny: only code both services genuinely share (ADR 0001).

Index

Constants

View Source
const (
	ScopeNormal  = "normal"  // 常规管理/学习会话
	ScopePreview = "preview" // 学生视角预览(拒绝管理类接口)
)

Token scopes and the expected issuer, shared with YanAuth.

View Source
const (
	CodeInvalidRequest      = "invalid_request"
	CodeUnauthorized        = "unauthorized"
	CodeForbidden           = "forbidden"
	CodeNotFound            = "not_found"
	CodeMethodNotAllowed    = "method_not_allowed"
	CodeConflict            = "conflict"
	CodeRateLimited         = "rate_limited"
	CodeDatabaseUnavailable = "database_unavailable"
	CodeInternalError       = "internal_error"

	// 鉴权链错误码,与 YanAuth 现行契约一致(add-only)。
	CodeInvalidToken      = "invalid_token"
	CodeScopeNotAllowed   = "scope_not_allowed"
	CodeMissingPermission = "missing_permission"
)

Stable machine-readable error codes (snake_case, add-only contract: never rename a published code). Rendered alongside the human-readable message:

{"code": "not_found", "error": "Not Found"}

SECURITY: codes must not leak information. Where failure reasons must stay indistinguishable to callers, share one code and keep the precise reason in the audit log (e.g. "invalid_credentials").

These constants cover what the framework itself renders; each service adds its own domain codes under its namespace.

Variables

This section is empty.

Functions

func DefaultCodeForStatus

func DefaultCodeForStatus(status int) string

DefaultCodeForStatus gives every response a code even where no specific business code applies.

func ErrorHandler

func ErrorHandler(log *slog.Logger, opts ...func(*errorHandlerConfig)) echo.HTTPErrorHandler

ErrorHandler returns uniform JSON error responses: every error body is {"code": <stable snake_case code>, "error": <human message>}. Details of any 5xx go to the log, never the client; unknown errors collapse to 500 internal_error.

func Healthz

func Healthz(ping func(ctx context.Context) error) echo.HandlerFunc

Healthz reports liveness, optionally probing a dependency (typically the database: pass its Ping). A nil ping skips the probe. A failing probe renders 503 database_unavailable through the shared error handler.

func JWTAuth

func JWTAuth(keys KeySetSource) echo.MiddlewareFunc

JWTAuth verifies the Bearer JWT against keys and stores the claims in the request context (read them with ClaimsOf). 401 invalid_token otherwise.

func New

func New(log *slog.Logger) *echo.Echo

New returns an Echo instance wired with yankit's error handler and slog request logging: the startup scaffold shared by the Yan services.

func ParseJWKS

func ParseJWKS(b []byte) (jwk.Set, error)

ParseJWKS parses a JWKS document (e.g. fetched at startup from YanAuth's /.well-known/jwks.json) into a key set.

func RenderError

func RenderError(c *echo.Context, status int, code, msg string) error

RenderError writes the uniform JSON error body. An empty message falls back to the HTTP status text; HEAD requests get no body.

func RequestLog

func RequestLog(log *slog.Logger) echo.MiddlewareFunc

RequestLog writes one structured log line per request.

func RequirePerm

func RequirePerm(code string) echo.MiddlewareFunc

RequirePerm rejects tokens whose perms do not include the fully-qualified permission code (e.g. "learn:course:create"). 403 missing_permission, with the missing code in the error message, otherwise.

func RequireScope

func RequireScope(scope string) echo.MiddlewareFunc

RequireScope rejects tokens whose scope does not match — e.g. preview tokens are refused by management routes requiring ScopeNormal. 403 scope_not_allowed otherwise.

func WithMessages

func WithMessages(r MessageResolver) func(*errorHandlerConfig)

WithMessages registers a render-time message resolver (service-side i18n error catalogs, ADR 0011). Variadic: the zero-argument ErrorHandler keeps its original behavior.

Types

type APIError

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

APIError carries a stable snake_case code plus an HTTP status and a human-readable message. Handlers return it; ErrorHandler renders it.

func NewAPIError

func NewAPIError(status int, code, msg string) *APIError

NewAPIError builds an *APIError.

func (*APIError) Error

func (e *APIError) Error() string

func (*APIError) StatusCode

func (e *APIError) StatusCode() int

StatusCode implements echo.HTTPStatusCoder so middleware using echo.ResolveResponseStatus (e.g. RequestLog) sees the real status.

type Auth

type Auth struct {
	Options
}

Auth bundles the three middlewares with shared options, for services that keep keys and a permission namespace in one place.

func NewAuth

func NewAuth(o Options) *Auth

NewAuth builds an Auth group.

func (*Auth) JWT

func (a *Auth) JWT() echo.MiddlewareFunc

JWT returns the JWT verification middleware.

func (*Auth) Perm

func (a *Auth) Perm(code string) echo.MiddlewareFunc

Perm returns the permission middleware for a namespaced code: with Namespace "learn", Perm("course:create") checks "learn:course:create".

func (*Auth) Scope

func (a *Auth) Scope(scope string) echo.MiddlewareFunc

Scope returns the scope-check middleware.

type Claims

type Claims struct {
	Subject string   // user id(preview 令牌为影子账号 id)
	Scope   string   // normal | preview
	Act     string   // 发起预览的教师 id(仅 preview)
	Perms   []string // 带服务命名空间的权限码
}

Claims are the YanAuth token claims a consumer service needs.

func ClaimsOf

func ClaimsOf(c *echo.Context) *Claims

ClaimsOf returns the verified claims stored by JWTAuth, or nil when the request was not authenticated.

func ParseToken

func ParseToken(raw string, set jwk.Set) (*Claims, error)

ParseToken verifies a YanAuth JWT against a key set and extracts the consumer-facing claims. Signature, expiry and not-before are all enforced.

type KeySetFunc

type KeySetFunc func() (jwk.Set, error)

KeySetFunc adapts a function to KeySetSource.

func (KeySetFunc) KeySet

func (f KeySetFunc) KeySet() (jwk.Set, error)

type KeySetSource

type KeySetSource interface {
	KeySet() (jwk.Set, error)
}

KeySetSource supplies the verification key set. It is called per request, so implementations decide their own caching/refresh strategy — verification itself stays local (no network call inside yankit).

type MessageResolver

type MessageResolver func(c *echo.Context, code string) (string, bool)

MessageResolver localizes an error body at render time: given the request context and the stable error code, return the human message in the caller's locale. false (= not found) keeps the message baked into the error at construction. Consulted for 4xx responses only — 5xx bodies are scrubbed to the HTTP status text regardless.

type Options

type Options struct {
	Keys      KeySetSource
	Namespace string // 可选;Perm 会以此为前缀
}

Options configure an Auth group: the verification key source and the service's permission namespace (e.g. "learn" for codes like "learn:course:create").

Directories

Path Synopsis
examples
minimal command
Command minimal demonstrates the yankit scaffold: an Echo v5 server with slog JSON request logging, the shared health-check handler and uniform {"code","error"} error rendering.
Command minimal demonstrates the yankit scaffold: an Echo v5 server with slog JSON request logging, the shared health-check handler and uniform {"code","error"} error rendering.

Jump to

Keyboard shortcuts

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