1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package captokens

// TokenDef defines a token type with its required and optional scope fields.
type TokenDef struct {
	// RequiredScope lists fields that must be present in the token scope
	RequiredScope []string
	// OptionalScope lists fields that may be present in the token scope
	OptionalScope []string
	// Description explains what this token type authorizes
	Description string
}

// TokenDefinitions is the registry of all known token types.
// Token IDs follow the pattern: "service:operation"
var TokenDefinitions = map[string]TokenDef{
	"maildir:list-mailboxes": {
		RequiredScope: []string{"root_dir"},
		OptionalScope: []string{},
		Description:   "List all mailboxes under root_dir",
	},
	"maildir:open-mailbox": {
		RequiredScope: []string{"mailbox_dir"},
		OptionalScope: []string{},
		Description:   "Access to list and search messages in mailbox_dir",
	},
	"maildir:read-metadata": {
		RequiredScope: []string{"mailbox_dir"},
		OptionalScope: []string{},
		Description:   "Read message summaries (from/to/subject/date) in mailbox_dir",
	},
	"maildir:read-message": {
		RequiredScope: []string{"mailbox_dir"},
		OptionalScope: []string{"message_id"}, // can restrict to specific message
		Description:   "Read full message content in mailbox_dir",
	},
	"maildir:search-mailbox": {
		RequiredScope: []string{"mailbox_dir"},
		OptionalScope: []string{},
		Description:   "Search messages in mailbox_dir",
	},
}

// ValidateTokenType checks if a token ID exists in the registry.
func ValidateTokenType(tokenID string) (TokenDef, bool) {
	def, exists := TokenDefinitions[tokenID]
	return def, exists
}

// ValidateScope checks if a scope map contains all required fields for a token type.
// Returns error if required fields are missing.
func ValidateScope(tokenID string, scope map[string]any) error {
	def, exists := TokenDefinitions[tokenID]
	if !exists {
		return &UnknownTokenTypeError{TokenID: tokenID}
	}

	// Check all required fields are present
	for _, field := range def.RequiredScope {
		if _, ok := scope[field]; !ok {
			return &MissingScopeFieldError{
				TokenID: tokenID,
				Field:   field,
			}
		}
	}

	return nil
}

// UnknownTokenTypeError is returned when a token ID is not in the registry.
type UnknownTokenTypeError struct {
	TokenID string
}

func (e *UnknownTokenTypeError) Error() string {
	return "unknown token type: " + e.TokenID
}

// MissingScopeFieldError is returned when required scope fields are missing.
type MissingScopeFieldError struct {
	TokenID string
	Field   string
}

func (e *MissingScopeFieldError) Error() string {
	return "token " + e.TokenID + " requires scope field: " + e.Field
}