121 lines
3.4 KiB
Go
121 lines
3.4 KiB
Go
package ficha
|
|
|
|
import (
|
|
"encoding/json"
|
|
"time"
|
|
)
|
|
|
|
// Token is a validated, decrypted token. It exposes the consumer-defined
|
|
// permissions and data, and provides methods for permission checks.
|
|
//
|
|
// Tokens are returned by Issuer.Validate. Consumers do not construct them
|
|
// directly. A Token is read-only — its methods do not mutate state.
|
|
type Token struct {
|
|
id string
|
|
issuedAt time.Time
|
|
expiresAt time.Time
|
|
permissions []string
|
|
data json.RawMessage
|
|
}
|
|
|
|
// newToken builds a Token from a decoded payload. Package-private —
|
|
// only the Issuer constructs Tokens, after successful validation.
|
|
func newToken(p payload) *Token {
|
|
t := &Token{
|
|
id: p.ID,
|
|
issuedAt: time.Unix(p.Iat, 0),
|
|
permissions: p.Permissions,
|
|
data: p.Data,
|
|
}
|
|
if p.Exp != 0 {
|
|
t.expiresAt = time.Unix(p.Exp, 0)
|
|
}
|
|
return t
|
|
}
|
|
|
|
// ID returns the unique token identifier (used by the revocation store).
|
|
func (t *Token) ID() string { return t.id }
|
|
|
|
// IssuedAt returns when the token was issued.
|
|
func (t *Token) IssuedAt() time.Time { return t.issuedAt }
|
|
|
|
// ExpiresAt returns when the token expires. Returns the zero time.Time
|
|
// if the token was issued with NoExpiry; check NeverExpires before use.
|
|
func (t *Token) ExpiresAt() time.Time { return t.expiresAt }
|
|
|
|
// NeverExpires reports whether the token was issued without an expiry.
|
|
func (t *Token) NeverExpires() bool { return t.expiresAt.IsZero() }
|
|
|
|
// Permissions returns a copy of the token's permission strings.
|
|
// The returned slice is safe to retain and modify.
|
|
func (t *Token) Permissions() []string {
|
|
out := make([]string, len(t.permissions))
|
|
copy(out, t.permissions)
|
|
return out
|
|
}
|
|
|
|
// UnmarshalData decodes the consumer-defined data blob into v.
|
|
// Returns nil with v unchanged if the token has no data.
|
|
func (t *Token) UnmarshalData(v any) error {
|
|
if len(t.data) == 0 {
|
|
return nil
|
|
}
|
|
return json.Unmarshal(t.data, v)
|
|
}
|
|
|
|
// Has reports whether the token holds the given permission (exact match).
|
|
func (t *Token) Has(perm string) bool {
|
|
for _, p := range t.permissions {
|
|
if p == perm {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// HasAll reports whether the token holds every given permission.
|
|
// Returns true when called with no arguments (vacuous truth). For a
|
|
// fail-closed variant that returns false on empty input, see RequiresAll.
|
|
func (t *Token) HasAll(perms ...string) bool {
|
|
for _, p := range perms {
|
|
if !t.Has(p) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// RequiresAll reports whether the token holds every given permission,
|
|
// and requires that at least one permission be specified. Returns false
|
|
// on empty input — use this when an empty permission list indicates a
|
|
// programmer error rather than a public/no-auth route.
|
|
//
|
|
// For the vacuous-truth variant (empty input → true), use HasAll.
|
|
func (t *Token) RequiresAll(perms ...string) bool {
|
|
if len(perms) == 0 {
|
|
return false
|
|
}
|
|
return t.HasAll(perms...)
|
|
}
|
|
|
|
// HasAny reports whether the token holds at least one of the given permissions.
|
|
// Returns false when called with no arguments.
|
|
func (t *Token) HasAny(perms ...string) bool {
|
|
for _, p := range perms {
|
|
if t.Has(p) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// HasNone reports whether the token holds none of the given permissions.
|
|
// Returns true when called with no arguments.
|
|
func (t *Token) HasNone(perms ...string) bool {
|
|
for _, p := range perms {
|
|
if t.Has(p) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|