utils.config.go

  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,
	}
}