Documentation
¶
Overview ¶
Package studiomembers resolves Studio capability from the project database.
Studio access lives in `_supatype.studio_members`, not in a JWT claim. `app_metadata` is the developer's namespace for their own application roles, so granting an app role must never hand out admin UI access; and `_supatype` is absent from PGRST_DB_SCHEMAS, so nothing reachable through the data plane can read or write the membership table.
Index ¶
- Constants
- Variables
- func Audit(ctx context.Context, actorID, targetID, action, role string)
- func AuditElevated(ctx context.Context, actorID, method, path string)
- func Available() bool
- func Lookup(userID string) (string, bool)
- func Revoke(ctx context.Context, actingUserID, targetUserID string) error
- func SetRole(ctx context.Context, actingUserID, targetUserID, role string) error
- type Member
Constants ¶
const RoleAdmin = "admin"
RoleAdmin is the role that may grant and revoke access. Duplicated from studioauth rather than imported, because studioauth depends on this package's lookup and the cycle would be worse than the constant.
Variables ¶
var ErrLastAdmin = errors.New("this is the last Studio admin — promote someone else first")
ErrLastAdmin is returned when a change would leave the project with no admin.
var ErrUnknownUser = errors.New("no such user in this project")
ErrUnknownUser is returned when the target has no row in auth.users.
Functions ¶
func Audit ¶
Audit records a membership change in `_supatype.studio_audit`.
Deliberately *not* inside SetRole/Revoke's transaction, and deliberately non-fatal. Refusing to revoke a compromised admin's access because the audit table is missing would be the worse failure; the write is logged at error level instead so a broken trail is noisy rather than silent.
`actorID` is empty when the change came from a path with no signed-in actor (the dev bypass, or the CLI against the database directly).
func AuditElevated ¶
AuditElevated records a request that bypassed RLS with the service role.
No target: the record is about what the actor did, not about someone else. The method and path go in `detail` so the trail says *what* was reached, which is the whole point of making elevation visible.
func Available ¶
func Available() bool
Available reports whether membership lookups can be performed at all. Used to decide between the membership path and the legacy claim path, so a deployment with no DSN configured is not locked out of its own Studio.
func Lookup ¶
Lookup returns the Studio role recorded for a verified user id.
The second result is false whenever access cannot be positively established — no membership row, no database, an unreadable table, a malformed id. Every such case is a denial: an admin UI that opens up when its authority is unreachable is worse than one that is briefly unavailable.
func SetRole ¶
SetRole grants or updates Studio access for one of the project's own users.
`actingUserID` is the caller. Two rules protect the project from its own admins: nobody may change their own role — self-demotion is a footgun and self-promotion is the escalation this whole design exists to prevent — and the last admin cannot be demoted, because there would then be nobody able to grant access to anyone.
Types ¶
type Member ¶
type Member struct {
UserID string `json:"userId"`
Email string `json:"email"`
Role string `json:"role"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
// PlatformAccount marks a grant held by a Supatype Cloud account rather than
// one of this project's own users. Those cannot sign in to a self-hosted
// GoTrue, so self-host lists them read-only rather than pretending otherwise.
PlatformAccount bool `json:"platformAccount"`
}
Member is one membership row, joined to the project user it belongs to.