/
auth.go
132 lines (117 loc) · 3.16 KB
/
auth.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
// Package auth determines and asserts client permissions to access and modify
// server resources.
package auth
import (
"crypto/rand"
"encoding/base64"
"fmt"
"net"
"net/http"
"strings"
"sync"
"golang.org/x/crypto/bcrypt"
)
var (
// IsReverseProxied specifies, if the server is deployed behind a reverse
// proxy.
IsReverseProxied bool
// ReverseProxyIP specifies the IP of a non-localhost reverse proxy. Used
// for filtering in XFF IP determination.
ReverseProxyIP string
// board: IP: IsBanned
bans = map[string]map[string]bool{}
bansMu sync.RWMutex
NullPositions = Positions{CurBoard: NotLoggedIn, AnyBoard: NotLoggedIn}
)
// Extract IP of the request, honouring reverse proxies, if any.
func GetIP(r *http.Request) (string, error) {
ip := getIP(r)
if net.ParseIP(ip) == nil {
return "", fmt.Errorf("invalid IP: %s", ip)
}
return ip, nil
}
// Get request IP as net.IP.
func GetByteIP(r *http.Request) (net.IP, error) {
ipStr := getIP(r)
ipData := net.ParseIP(ipStr)
if ipData == nil {
return nil, fmt.Errorf("invalid IP: %s", ipStr)
}
return ipData, nil
}
// Get IP for logging, replace with placeholder on error.
// TODO(Kagami): Migrate boilerplate to use this helper.
func GetLogIP(r *http.Request) (ip string) {
ip, err := GetIP(r)
if err != nil {
ip = "invalid IP"
}
return
}
func getIP(req *http.Request) string {
if IsReverseProxied {
addresses := strings.Split(req.Header.Get("X-Forwarded-For"), ",")
// March from right to left until we get a public address.
// That will be the address right before our reverse proxy.
for i := len(addresses) - 1; i >= 0; i-- {
// Header can contain padding spaces
ip := strings.TrimSpace(addresses[i])
// Filter the reverse proxy IPs
switch {
case ip == ReverseProxyIP:
case !net.ParseIP(ip).IsGlobalUnicast():
default:
return ip
}
}
}
ip, _, err := net.SplitHostPort(req.RemoteAddr)
if err != nil {
return req.RemoteAddr // No port in address
}
return ip
}
// RandomID generates a randomID of base64 characters of desired byte length
func RandomID(length int) (string, error) {
buf := make([]byte, length)
_, err := rand.Read(buf)
return base64.RawStdEncoding.EncodeToString(buf), err
}
// BcryptHash generates a bcrypt hash from the passed string
func BcryptHash(password string, rounds int) ([]byte, error) {
return bcrypt.GenerateFromPassword([]byte(password), rounds)
}
// BcryptCompare compares a bcrypt hash with a user-supplied string
func BcryptCompare(password string, hash []byte) error {
return bcrypt.CompareHashAndPassword(hash, []byte(password))
}
// IsBanned returns if the IP is banned on the target board
func IsBanned(board, ip string) (banned bool) {
bansMu.RLock()
defer bansMu.RUnlock()
global := bans["all"]
ips := bans[board]
if global != nil && global[ip] {
return true
}
if ips != nil && ips[ip] {
return true
}
return false
}
// SetBans replaces the ban cache with the new set
func SetBans(b ...Ban) {
newBans := map[string]map[string]bool{}
for _, b := range b {
board, ok := newBans[b.Board]
if !ok {
board = map[string]bool{}
newBans[b.Board] = board
}
board[b.IP] = true
}
bansMu.Lock()
bans = newBans
bansMu.Unlock()
}