File: slack_token.go

package info (click to toggle)
golang-github-nicholas-fedor-shoutrrr 0.12.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 5,680 kB
  • sloc: sh: 74; makefile: 58
file content (154 lines) | stat: -rw-r--r-- 4,341 bytes parent folder | download
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package slack

import (
	"fmt"
	"net/url"
	"regexp"
	"strconv"
	"strings"

	"github.com/nicholas-fedor/shoutrrr/pkg/types"
)

const webhookBase = "https://hooks.slack.com/services/"

// Token type identifiers.
const (
	HookTokenIdentifier = "hook"
	UserTokenIdentifier = "xoxp"
	BotTokenIdentifier  = "xoxb"
)

// Token length and offset constants.
const (
	MinTokenLength       = 3  // Minimum length for a valid token string
	TypeIdentifierLength = 4  // Length of the type identifier (e.g., "xoxb", "hook")
	TypeIdentifierOffset = 5  // Offset to skip type identifier and separator (e.g., "xoxb:")
	Part1Length          = 9  // Expected length of part 1 in token
	Part2Length          = 9  // Expected length of part 2 in token
	Part3Length          = 24 // Expected length of part 3 in token
)

// Token match group indices.
const (
	tokenMatchFull  = iota // Full match
	tokenMatchType         // Type identifier (e.g., "xoxb", "hook")
	tokenMatchPart1        // First part of the token
	tokenMatchSep1         // First separator
	tokenMatchPart2        // Second part of the token
	tokenMatchSep2         // Second separator
	tokenMatchPart3        // Third part of the token
	tokenMatchCount        // Total number of match groups
)

var tokenPattern = regexp.MustCompile(
	`(?:(?P<type>xox.|hook)[-:]|:?)(?P<p1>[A-Z0-9]{` + strconv.Itoa(
		Part1Length,
	) + `,})(?P<s1>[-/,])(?P<p2>[A-Z0-9]{` + strconv.Itoa(
		Part2Length,
	) + `,})(?P<s2>[-/,])(?P<p3>[A-Za-z0-9]{` + strconv.Itoa(
		Part3Length,
	) + `,})`,
)

var _ types.ConfigProp = &Token{}

// Token is a Slack API token or a Slack webhook token.
type Token struct {
	raw string
}

// SetFromProp sets the token from a property value, implementing the types.ConfigProp interface.
func (token *Token) SetFromProp(propValue string) error {
	if len(propValue) < MinTokenLength {
		return ErrInvalidToken
	}

	match := tokenPattern.FindStringSubmatch(propValue)
	if match == nil || len(match) != tokenMatchCount {
		return ErrInvalidToken
	}

	typeIdentifier := match[tokenMatchType]
	if typeIdentifier == "" {
		typeIdentifier = HookTokenIdentifier
	}

	token.raw = fmt.Sprintf("%s:%s-%s-%s",
		typeIdentifier, match[tokenMatchPart1], match[tokenMatchPart2], match[tokenMatchPart3])

	if match[tokenMatchSep1] != match[tokenMatchSep2] {
		return ErrMismatchedTokenSeparators
	}

	return nil
}

// GetPropValue returns the token as a property value, implementing the types.ConfigProp interface.
func (token *Token) GetPropValue() (string, error) {
	if token == nil {
		return "", nil
	}

	return token.raw, nil
}

// TypeIdentifier returns the type identifier of the token.
func (token *Token) TypeIdentifier() string {
	return token.raw[:TypeIdentifierLength]
}

// ParseToken parses and normalizes a token string.
func ParseToken(str string) (*Token, error) {
	token := &Token{}
	if err := token.SetFromProp(str); err != nil {
		return nil, err
	}

	return token, nil
}

// String returns the token in normalized format with dashes (-) as separator.
func (token *Token) String() string {
	return token.raw
}

// UserInfo returns a url.Userinfo struct populated from the token.
func (token *Token) UserInfo() *url.Userinfo {
	return url.UserPassword(token.raw[:TypeIdentifierLength], token.raw[TypeIdentifierOffset:])
}

// IsAPIToken returns whether the identifier is set to anything else but the webhook identifier (`hook`).
func (token *Token) IsAPIToken() bool {
	return token.TypeIdentifier() != HookTokenIdentifier
}

// WebhookURL returns the corresponding Webhook URL for the token.
func (token *Token) WebhookURL() string {
	stringBuilder := strings.Builder{}
	stringBuilder.WriteString(webhookBase)
	stringBuilder.Grow(len(token.raw) - TypeIdentifierOffset)

	for i := TypeIdentifierOffset; i < len(token.raw); i++ {
		c := token.raw[i]
		if c == '-' {
			c = '/'
		}

		stringBuilder.WriteByte(c)
	}

	return stringBuilder.String()
}

// Authorization returns the corresponding `Authorization` HTTP header value for the token.
func (token *Token) Authorization() string {
	stringBuilder := strings.Builder{}
	stringBuilder.WriteString("Bearer ")
	stringBuilder.Grow(len(token.raw))
	stringBuilder.WriteString(token.raw[:TypeIdentifierLength])
	stringBuilder.WriteRune('-')
	stringBuilder.WriteString(token.raw[TypeIdentifierOffset:])

	return stringBuilder.String()
}