87 lines
2.8 KiB
Go
87 lines
2.8 KiB
Go
package meta
|
|
|
|
import (
|
|
"context"
|
|
|
|
"nadir/internal/auth"
|
|
"nadir/internal/module"
|
|
"nadir/internal/rbac"
|
|
|
|
"github.com/danielgtaylor/huma/v2"
|
|
)
|
|
|
|
// WhoamiInput carries the session cookie. The endpoint is not behind the RBAC
|
|
// middleware (it requires no specific permission), so it validates the session
|
|
// itself.
|
|
type WhoamiInput struct {
|
|
SessionID string `cookie:"nadir_session_id"`
|
|
Auth string `header:"Authorization"`
|
|
}
|
|
|
|
// WhoamiBody reports who the caller is and, per module, which permissions they
|
|
// actually hold. Combined with /api/_modules (the full module/permission grid),
|
|
// this gives the frontend everything it needs to render the permission matrix.
|
|
type WhoamiBody struct {
|
|
Username string `json:"username" example:"urania" doc:"Authenticated username"`
|
|
Permissions map[string][]string `json:"permissions" doc:"Module ID -> permissions the caller holds. Modules where they hold none are omitted."`
|
|
}
|
|
|
|
type WhoamiOutput struct{ Body WhoamiBody }
|
|
|
|
// RegisterWhoami adds the current-user endpoint. It resolves the caller's
|
|
// concrete grants by asking the RBAC store about each module's permissions,
|
|
// so "*" wildcards in roles are expanded for free.
|
|
func RegisterWhoami(api huma.API, sessions *auth.SessionStore, tokens *auth.TokenAuth, roles *rbac.RBAC, mods []module.Module) {
|
|
huma.Register(api, huma.Operation{
|
|
OperationID: "whoami",
|
|
Method: "GET",
|
|
Path: "/api/whoami",
|
|
Summary: "Get the current user and their permissions",
|
|
Description: "Returns the authenticated username and, per module, the " +
|
|
"permissions the caller holds (wildcards resolved). Pair with " +
|
|
"/api/_modules to render the full permission matrix.",
|
|
Tags: []string{"Meta"},
|
|
Errors: []int{401, 429},
|
|
}, func(ctx context.Context, in *WhoamiInput) (*WhoamiOutput, error) {
|
|
var username string
|
|
|
|
if raw, isBearer := auth.BearerToken(in.Auth); isBearer {
|
|
if tokens == nil {
|
|
return nil, huma.Error401Unauthorized("unauthorized")
|
|
}
|
|
name, ok, throttled := tokens.Verify(auth.ClientIP(ctx), raw)
|
|
if throttled {
|
|
return nil, huma.Error429TooManyRequests("too many failed token attempts; wait a minute")
|
|
}
|
|
if !ok {
|
|
return nil, huma.Error401Unauthorized("unauthorized")
|
|
}
|
|
username = name
|
|
} else {
|
|
sess, ok := sessions.GetByToken(in.SessionID)
|
|
if !ok {
|
|
return nil, huma.Error401Unauthorized("unauthorized")
|
|
}
|
|
username = sess.Username
|
|
}
|
|
|
|
held := make(map[string][]string)
|
|
for _, m := range mods {
|
|
var perms []string
|
|
for _, p := range m.Permissions() {
|
|
if roles.Can(username, m.ID(), p) {
|
|
perms = append(perms, string(p))
|
|
}
|
|
}
|
|
if len(perms) > 0 {
|
|
held[m.ID()] = perms
|
|
}
|
|
}
|
|
|
|
out := &WhoamiOutput{}
|
|
out.Body = WhoamiBody{Username: username, Permissions: held}
|
|
return out, nil
|
|
})
|
|
}
|
|
|