-
Notifications
You must be signed in to change notification settings - Fork 0
/
auth.go
147 lines (129 loc) · 4.13 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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package main
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"log"
"net/http"
"net/url"
"os"
"strings"
)
// sessionData represents session data stored in the database
type sessionData struct {
CASUser string `json:"cas_user"`
Authenticated bool `json:"is_authenticated"`
ECMember bool `json:"ec_member"`
WTGMember bool `json:"wtg_member"`
}
// Key for context, set in authetnicate middleware. Go docs say,
// "Users of WithValue should define their own types for keys."
type contextKey string
// context keys
const casUserKey = contextKey("casUser")
const adminKey = contextKey("admin")
const authenticatedKey = contextKey("authenticated")
// Take cookie, extract session ID, decode additional info from database,
// and attach to context. Assumes that cookie has been validated already.
func contextFromCookie(ctx context.Context, cookie *http.Cookie) (context.Context, error) {
// extract stuff from cookie
messageSplit := strings.Split(cookie.Value, ".")
sessionID := []byte(messageSplit[0][4:])
db, err := getDB()
if err != nil {
return ctx, err
}
defer db.Close()
row := db.QueryRow("SELECT data FROM sessions WHERE session_id = ?", sessionID)
var jsonData []byte
err = row.Scan(&jsonData)
if err != nil {
return ctx, err
}
sd := sessionData{}
err = json.Unmarshal(jsonData, &sd)
if err != nil {
return ctx, err
}
admin := sd.ECMember || sd.WTGMember
ctx = context.WithValue(ctx, casUserKey, strings.ToLower(sd.CASUser))
ctx = context.WithValue(ctx, adminKey, admin)
ctx = context.WithValue(ctx, authenticatedKey, sd.Authenticated)
return ctx, nil
}
func unauthenticatedContext(ctx context.Context) context.Context {
ctx = context.WithValue(ctx, casUserKey, "")
ctx = context.WithValue(ctx, adminKey, false)
ctx = context.WithValue(ctx, authenticatedKey, false)
return ctx
}
// context accessors
func adminFromContext(ctx context.Context) bool {
admin, ok := ctx.Value(adminKey).(bool)
if !ok {
return false
}
return admin
}
func casUserFromContext(ctx context.Context) string {
casUser, ok := ctx.Value(casUserKey).(string)
if !ok {
return ""
}
return casUser
}
// verifyCookie returns whether or not a cookie signed with https://github.com/tj/node-cookie-signature
// has been modified, tampered with, or otherwise mangled.
func verifyCookie(cookie *http.Cookie) (bool, error) {
// extract stuff from cookie
messageSplit := strings.Split(cookie.Value, ".")
message := []byte(messageSplit[0][4:])
messageMAC := messageSplit[1]
// create HMAC to see if it matches the one in the cookie
mac := hmac.New(sha256.New, []byte(os.Getenv("SESSION_SECRET")))
mac.Write([]byte(message))
expectedMAC := mac.Sum(nil)
expectedMACEncoded := base64.RawStdEncoding.EncodeToString(expectedMAC)
messageMACUnescaped, err := url.QueryUnescape(messageMAC)
if err != nil {
return false, err
}
return messageMACUnescaped == expectedMACEncoded, nil
}
// authenticate decodes a session cookie from https://github.com/expressjs/session,
// extracts the session info from the database, and stores it on the request context.
func authenticate(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
// This is a function so that we return the most recent version of r,
// which is assigned to multiple times in this handler.
next.ServeHTTP(w, r)
}()
// save original context, and replace request context with unauthenticated context
origCtx := r.Context()
r = r.WithContext(unauthenticatedContext(origCtx))
// this is what Express sessions names cookies by default
cookie, err := r.Cookie("connect.sid")
if err == http.ErrNoCookie {
return
}
valid, err := verifyCookie(cookie)
if err != nil {
log.Printf("unable to verify cookie: %s", err.Error())
return
}
if !valid {
log.Printf("cookie invalid")
return
}
// extract cookie info and attach to request context, ignoring unauthenticated context
ctx, err := contextFromCookie(origCtx, cookie)
if err != nil {
log.Printf("unable to attach session info to context: %s", err.Error())
return
}
r = r.WithContext(ctx)
})
}