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
|
package main
import (
"fmt"
"html/template"
"net/http"
"os"
"strings"
"sync"
xtemplate "text/template"
"time"
"github.com/microcosm-cc/bluemonday"
)
var Version string
type Config struct {
Host string
Templates *template.Template
XMLTemplates *xtemplate.Template
UGCPolicy *bluemonday.Policy
Users []*User
sessionMapLock sync.Mutex
sessions map[string]*User
// moderation configs
allowHosts map[string]bool
denyHosts map[string]bool
}
// A returns the current active account for the session
func (config *Config) ActiveAccount(session string) *User {
config.sessionMapLock.Lock()
defer config.sessionMapLock.Unlock()
if user, exists := config.sessions[session]; exists {
return user
} else {
// no user exists.
fmt.Printf("[critical] session did not contain a user. This means you need to setup a user account prior to using tap.")
return &User{}
}
}
// ChangeActiveAccount changes the current active account for a session
func (config *Config) ChangeActiveAccount(session string, userID string) {
config.sessionMapLock.Lock()
defer config.sessionMapLock.Unlock()
for _, user := range config.Users {
if user.UserNamePlain == userID {
config.sessions[session] = user
}
}
}
// IsHostAllowed determines if we are permitted to talk/accept messages from
// a host server.
// if an allowlist is created then only hosts on the allowlist will be permitted
// otherwise, all hosts will be permitted except those listed in a denylist
func (config *Config) IsHostAllowed(host string) bool {
config.sessionMapLock.Lock()
defer config.sessionMapLock.Unlock()
if config.allowHosts != nil {
if _, exists := config.allowHosts[host]; exists {
return true
}
return false
} else {
if _, exists := config.denyHosts[host]; exists {
return false
}
return true
}
}
func InitConfig(hostname string) (*Config, error) {
config := &Config{}
templates, err := template.ParseGlob("./templates/*.tpl.html")
if err != nil {
return nil, fmt.Errorf("could not parse template files: %v", err)
}
config.Templates = templates
xtemplates, err := xtemplate.ParseGlob("./templates/*.tpl.xml")
if err != nil {
return nil, fmt.Errorf("could not parse template files: %v", err)
}
config.XMLTemplates = xtemplates
bluemonday.UGCPolicy()
p := bluemonday.NewPolicy()
p.AllowStandardURLs()
p.AllowElements("a", "p", "span", "ul", "li", "strong", "br")
p.AllowAttrs("href").OnElements("a")
p.AddTargetBlankToFullyQualifiedLinks(true)
config.UGCPolicy = p
config.Host = hostname
config.sessions = make(map[string]*User)
config.denyHosts = make(map[string]bool)
// one host per line
if data, err := os.ReadFile("denylist"); err == nil {
for _, host := range strings.Split(string(data), "\n") {
fmt.Printf("[config] adding %s to the deny list\n", host)
config.denyHosts[host] = true
}
}
// note that we only initialize the allow list if the file
// is actually readable...
if data, err := os.ReadFile("allowlist"); err == nil {
fmt.Printf("[config] instantiating the allow lists. Only hosts on the list will be permitted to post messages to the inbox\n")
config.allowHosts = make(map[string]bool)
for _, host := range strings.Split(string(data), "\n") {
fmt.Printf("[config] adding %s to the allow list\n", host)
config.allowHosts[host] = true
}
}
return config, nil
}
type Page struct {
Title string
User *User
FeaturedPost *Post
Posts []*Post
Accounts []*User
IsAdmin bool
ReplyToRef string
ReplyContents template.HTML
ActiveAccount *User
BuildTime time.Time
Generator string
}
func (config *Config) DefaultPage(r *http.Request) Page {
var activeAccount *User
if isAdmin, session := config.IsAdmin(r); isAdmin {
activeAccount = config.ActiveAccount(session)
}
return Page{
BuildTime: time.Now(),
Generator: fmt.Sprintf("tap %s", Version),
ActiveAccount: activeAccount,
}
}
|