context.go•6.03 kB
package session
import (
"context"
"errors"
"github.com/Southclaws/fault"
"github.com/Southclaws/fault/fctx"
"github.com/Southclaws/fault/fmsg"
"github.com/Southclaws/fault/ftag"
"github.com/Southclaws/opt"
"github.com/Southclaws/storyden/app/resources/account"
"github.com/Southclaws/storyden/app/resources/account/role"
"github.com/Southclaws/storyden/app/resources/rbac"
)
var (
ErrNoAccountInContext = errors.New("no account in context")
ErrMalformedContextValue = errors.New("malformed context value: internal bug")
)
var contextKey = struct{}{}
type sessionContext struct {
account opt.Optional[account.Account]
roles role.Roles
// NOTE: These are the security schemes defined in the OpenAPI spec under
// `securitySchemes` - they aren't generated by the codegen so be careful.
securityScheme string
// sessionToken stores the session token for later revocation during logout.
// This is only populated for browser sessions, not access keys.
sessionToken opt.Optional[string]
}
func WithAccount(ctx context.Context, u account.Account, roles role.Roles) context.Context {
return context.WithValue(ctx, contextKey, sessionContext{
account: opt.New(u),
roles: roles,
securityScheme: "browser",
sessionToken: opt.NewEmpty[string](),
})
}
func WithAccountAndToken(ctx context.Context, u account.Account, roles role.Roles, token string) context.Context {
return context.WithValue(ctx, contextKey, sessionContext{
account: opt.New(u),
roles: roles,
securityScheme: "browser",
sessionToken: opt.New(token),
})
}
func WithAccessKey(ctx context.Context, u account.Account, roles role.Roles) context.Context {
return context.WithValue(ctx, contextKey, sessionContext{
account: opt.New(u),
roles: roles,
securityScheme: "access_key",
sessionToken: opt.NewEmpty[string](),
})
}
func WithGuest(ctx context.Context, roles role.Roles) context.Context {
return context.WithValue(ctx, contextKey, sessionContext{
account: opt.NewEmpty[account.Account](),
roles: roles,
securityScheme: "browser",
sessionToken: opt.NewEmpty[string](),
})
}
func WithInternal(ctx context.Context) context.Context {
return context.WithValue(ctx, contextKey, sessionContext{
account: opt.NewEmpty[account.Account](),
roles: role.Roles{},
securityScheme: "",
sessionToken: opt.NewEmpty[string](),
})
}
func Authorise(ctx context.Context, fn func() error, perms ...rbac.Permission) error {
value := ctx.Value(contextKey)
if value == nil {
panic("request context does not contain a session context value, this is an internal bug")
}
sc, ok := value.(sessionContext)
if !ok {
return fault.Wrap(ErrMalformedContextValue, fctx.With(ctx), ftag.With(ftag.Unauthenticated))
}
return sc.roles.Permissions().Authorise(ctx, fn, perms...)
}
func GetRoles(ctx context.Context) role.Roles {
value := ctx.Value(contextKey)
if value == nil {
panic("request context does not contain a session context value, this is an internal bug")
}
sc, ok := value.(sessionContext)
if !ok {
panic(ErrMalformedContextValue)
}
return sc.roles
}
// GetAccountID pulls out an account ID associated with the call.
func GetAccountID(ctx context.Context) (account.AccountID, error) {
value := ctx.Value(contextKey)
if value == nil {
panic("request context does not contain a session context value, this is an internal bug")
}
sc, ok := value.(sessionContext)
if !ok {
return account.AccountID{}, fault.Wrap(ErrMalformedContextValue, fctx.With(ctx), ftag.With(ftag.Unauthenticated))
}
acc, ok := sc.account.Get()
if !ok {
return account.AccountID{}, fault.Wrap(ErrNoAccountInContext,
fctx.With(ctx),
ftag.With(ftag.Unauthenticated),
fmsg.With("GetAccountID"),
)
}
return acc.ID, nil
}
// GetAccount pulls out an account associated with the call.
func GetAccount(ctx context.Context) (account.Account, error) {
value := ctx.Value(contextKey)
if value == nil {
panic("request context does not contain a session context value, this is an internal bug")
}
sc, ok := value.(sessionContext)
if !ok {
return account.Account{}, fault.Wrap(ErrMalformedContextValue, fctx.With(ctx), ftag.With(ftag.Unauthenticated))
}
acc, ok := sc.account.Get()
if !ok {
return account.Account{}, fault.Wrap(ErrNoAccountInContext,
fctx.With(ctx),
ftag.With(ftag.Unauthenticated),
fmsg.With("GetAccount"),
)
}
return acc, nil
}
func GetSecurityScheme(ctx context.Context) (string, error) {
value := ctx.Value(contextKey)
if value == nil {
panic("request context does not contain a session context value, this is an internal bug")
}
sc, ok := value.(sessionContext)
if !ok {
return "", fault.Wrap(ErrMalformedContextValue, fctx.With(ctx), ftag.With(ftag.Unauthenticated))
}
return sc.securityScheme, nil
}
func GetOptAccountID(ctx context.Context) opt.Optional[account.AccountID] {
value := ctx.Value(contextKey)
if value == nil {
return opt.NewEmpty[account.AccountID]()
}
sc, ok := value.(sessionContext)
if !ok {
return opt.NewEmpty[account.AccountID]()
}
acc, ok := sc.account.Get()
if !ok {
return opt.NewEmpty[account.AccountID]()
}
return opt.New(acc.ID)
}
func GetOptAccount(ctx context.Context) opt.Optional[account.Account] {
value := ctx.Value(contextKey)
if value == nil {
return opt.NewEmpty[account.Account]()
}
sc, ok := value.(sessionContext)
if !ok {
return opt.NewEmpty[account.Account]()
}
acc, ok := sc.account.Get()
if !ok {
return opt.NewEmpty[account.Account]()
}
return opt.New(acc)
}
// GetSessionToken retrieves the session token from the context if present.
// This is only available for browser sessions, not access keys.
func GetSessionToken(ctx context.Context) opt.Optional[string] {
value := ctx.Value(contextKey)
if value == nil {
return opt.NewEmpty[string]()
}
sc, ok := value.(sessionContext)
if !ok {
return opt.NewEmpty[string]()
}
return sc.sessionToken
}