Documentation
¶
Index ¶
- Constants
- Variables
- func AdminRolesFromConfigFile(path string) []string
- func AdminRolesFromEnv() []string
- func AllowsRequest(perms StudioPermissions, method, path string) bool
- func DevBypass() bool
- func KnownStudioRoles() []string
- func MembersAPI(c Config) http.Handler
- func ProxyHandler(inner http.Handler, c Config) http.Handler
- func ReadAdminConfigFile(path string) ([]byte, error)
- func RequireAdmin(c Config, next http.Handler) http.Handler
- func SchemaHandler(c Config) http.HandlerFunc
- func SessionHandler(c Config) http.HandlerFunc
- func VerifyHandler(c Config) http.HandlerFunc
- type Config
- type Result
- type StudioPermissions
- type StudioRoleLookup
Constants ¶
const ( ModeSelf = "self" ModeElevated = "elevated" )
Who a Studio data request acts as.
Studio used to forward every request with the service role key, which bypasses RLS entirely: being admitted to the panel was the same thing as having unrestricted access to the database. Identity and capability are separate, so the acting identity is now separate too.
- ModeSelf forwards the caller's own token. PostgREST assumes their role and their own RLS policies apply, so Studio shows them exactly what their application would.
- ModeElevated injects the service role key. It is the escape hatch for administration that RLS would otherwise block, gated on the `elevatedSql` capability and recorded.
A caller may always ask to drop to ModeSelf. Only a role holding `elevatedSql` may act elevated, and asking for it without that capability is refused rather than silently downgraded — a request that would return a misleadingly empty result is worse than an error.
const ActingModeHeader = "X-Supatype-Acting-Mode"
ActingModeHeader lets the client state which identity it wants, and carries the resolved mode back on the response so the UI can show it. Studio's elevated banner reads this rather than assuming.
const RoleAdmin = "admin"
RoleAdmin is the only role that can grant or revoke Studio access.
Variables ¶
var DefaultAdminRoles = []string{"admin", "supatype_admin"}
DefaultAdminRoles are allowed Studio admin roles when config/env omit overrides.
Functions ¶
func AdminRolesFromConfigFile ¶
AdminRolesFromConfigFile merges adminRoles from admin-config.json when present.
func AdminRolesFromEnv ¶
func AdminRolesFromEnv() []string
AdminRolesFromEnv reads STUDIO_ADMIN_ROLES (comma-separated) or returns defaults.
func AllowsRequest ¶ added in v1.4.0
func AllowsRequest(perms StudioPermissions, method, path string) bool
AllowsRequest reports whether a capability set covers one proxied request.
Mirrors the control plane's `resolvePermission` mapping so a role means the same thing in both hosts. Until Studio stops proxying through the service role, this is the only thing standing between an `editor` membership and a schema change.
func DevBypass ¶
func DevBypass() bool
DevBypass is true when local open Studio is explicitly enabled (supatype dev docker only).
func KnownStudioRoles ¶ added in v1.4.0
func KnownStudioRoles() []string
KnownStudioRoles lists the roles a membership row may hold, most privileged first, so the API response and CLI help are stably ordered.
func MembersAPI ¶ added in v1.4.0
MembersAPI serves the Studio membership assignment API:
GET /admin/studio-roles — the role catalogue and its matrix
GET /admin/studio-members — current grants
PATCH /admin/studio-members/{userId} — set a role {"role": "editor"}
DELETE /admin/studio-members/{userId} — revoke
Writes `_supatype.studio_members` and nothing else — never `app_metadata`, which belongs to the developer's own application roles.
Every request re-resolves the caller's capability from the database. A session payload or cookie would let a demoted admin keep administering until it expired, and capability is exactly the thing that must not be cacheable.
func ProxyHandler ¶
ProxyHandler forwards privileged API calls after admin JWT verification. inner must be the main service mux (without /studio routes).
func ReadAdminConfigFile ¶
ReadAdminConfigFile reads admin-config.json from a relative path under the working directory.
func RequireAdmin ¶
RequireAdmin wraps a handler with studio admin JWT checks (skipped when DevBypass).
func SchemaHandler ¶ added in v1.4.0
func SchemaHandler(c Config) http.HandlerFunc
SchemaHandler serves GET /studio/schema.
func SessionHandler ¶ added in v1.4.0
func SessionHandler(c Config) http.HandlerFunc
SessionHandler serves GET /studio/session.
Deliberately has no `userId` parameter: it answers for the caller the token names and nobody else. A parameter would be an invitation to ask about another user, and the answer would then have to be trusted by whoever asked.
func VerifyHandler ¶
func VerifyHandler(c Config) http.HandlerFunc
VerifyHandler serves GET /studio/auth/verify.
Types ¶
type Config ¶
type Config struct {
JWTSecret string
ServiceRoleKey string
// AnonKey is sent as `apikey` when a request acts as the caller rather than
// elevated, so gateways that require a key still see one that carries no
// privilege of its own.
AnonKey string
AdminRoles []string
Mode string
// StudioRole resolves a verified user id to their Studio role from
// `_supatype.studio_members`. Studio capability deliberately does not come
// from a JWT claim: `app_metadata` is the developer's namespace for their own
// app roles, so reading it here means assigning an app role can hand out admin
// UI access by accident. Nil keeps the legacy claim-based path, so a
// deployment that has not been migrated still works.
StudioRole StudioRoleLookup
}
Config holds studio auth handler dependencies.
func ConfigFromServer ¶
func ConfigFromServer(cfg *serverconf.ServerConfig) Config
ConfigFromServer builds handler config from ServerConfig and admin-config path.
type Result ¶
type Result struct {
Allowed bool
Message string
Role string
Sub string
// Permissions is set when capability came from a membership role. Nil on the
// legacy claim path, where the claim only ever meant "is an admin".
Permissions *StudioPermissions
}
Result is the outcome of an admin access check.
func ResolveAccess ¶ added in v1.4.0
ResolveAccess decides Studio access for a request.
The token establishes *identity* (signature, expiry, subject). Capability comes from `_supatype.studio_members` when a lookup is configured, so a user with no membership row is refused even if their claims say "admin", and a member is admitted without needing any claim at all.
func VerifyBearerToken ¶
VerifyBearerToken validates a user JWT and checks studio admin role membership. Role resolution: app_metadata.role first, then top-level role when it is an admin role.
type StudioPermissions ¶ added in v1.4.0
type StudioPermissions struct {
// Read and Write cover content rows — the data plane.
Read bool `json:"read"`
Write bool `json:"write"`
// SchemaView covers the schema and migration history views.
SchemaView bool `json:"schemaView"`
// SQLEditor runs queries as the acting user's own role, so RLS applies to
// them. This is what makes the editor safe to hand to a contractor: they can
// explore and debug without being able to dump the database.
SQLEditor bool `json:"sqlEditor"`
// ElevatedSQL covers DDL and anything that bypasses RLS.
ElevatedSQL bool `json:"elevatedSql"`
// ManageMembers covers Studio membership and project API keys.
ManageMembers bool `json:"manageMembers"`
}
StudioPermissions is the capability set Studio renders from.
func PermissionsForRole ¶ added in v1.4.0
func PermissionsForRole(role string) (StudioPermissions, bool)
PermissionsForRole returns the capability set for a Studio role, and whether the role is one this deployment understands.
type StudioRoleLookup ¶ added in v1.4.0
StudioRoleLookup returns the Studio role recorded for a user id. The second result is false when the user has no membership row.