-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.go
More file actions
242 lines (204 loc) · 7.04 KB
/
Copy pathserver.go
File metadata and controls
242 lines (204 loc) · 7.04 KB
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
package main
import (
"fmt"
"html/template"
"log"
"net/http"
"path"
"strconv"
"github.com/gorilla/mux"
"github.com/gorilla/sessions"
)
// The cookie store
var store *sessions.CookieStore
func initCookieStore() {
// Sessions
store = sessions.NewCookieStore([]byte(Config.SessionKey))
}
const (
DefaultSession = "lnb-session"
DefaultSessionVersion = 1
)
type handlerFuncType func(w http.ResponseWriter, r *http.Request)
// Takes the track database and
// The router initializissociated with the host as only argument
func initHostRouter(db *ITrackDatabase, hc *fbHostConfig, r *mux.Router) {
log.Print("initializing router for host " + db.Hostname)
// Api set-up and routes
api := mux.NewRouter()
r.PathPrefix("/api").Handler(http.StripPrefix("/api", mw(api, setContentTypeMiddleware)))
// Unauthenticated API routes
api.HandleFunc("/login", login(db.Hostname)).
Methods("POST")
api.HandleFunc("/logout", logout).
Methods("POST")
api.HandleFunc("/tracks/", getTracks(db)).
Methods("GET")
api.HandleFunc("/users/", getUsers(db.Hostname)).
Methods("GET")
api.Handle("/config", http.HandlerFunc(getConfig(db.Hostname))).
Methods("GET")
// First time install route, only works when there are no users in the DB yet
api.HandleFunc("/install", install).
Methods("POST")
// Authenticated routes
// So what's happening here is kind of confusing:
// all of these routes require the authMiddleware middleware,
// So we use the mw() func to put it in front of our actual handler
// the mw function returns a http.Handler, which is why we use api.Handle().
// The directly used funcs (e.g. logout) are of type func(http.ResponseWriter, http.Request)
// the executed funcs (e.g. addTrack(db)) are ones that require the track database for the current host,
// so they are wrapped in another func that returns a func of the above type.
// http.HandlerFunc is a convenience method to turn the above func type into a http.Handler
api.Handle("/tracks/", mw(http.HandlerFunc(addTrack(db)), authMiddleware)).
Methods("POST")
api.Handle("/tracks/{trackID}", mw(http.HandlerFunc(updateTrack(db)), authMiddleware)).
Methods("POST")
api.Handle("/tracks/{trackID}/publish", mw(http.HandlerFunc(publishTrack(db, hc)), authMiddleware)).
Methods("POST")
api.Handle("/tracks/{trackID}/claim", mw(http.HandlerFunc(claimTrack(db)), authMiddleware)).
Methods("POST")
api.Handle("/tracks/{trackID}", mw(http.HandlerFunc(deleteTrack(db)), authMiddleware)).
Methods("DELETE")
api.Handle("/users/", mw(http.HandlerFunc(addUser(db.Hostname)), adminMiddleware)).
Methods("POST")
api.Handle("/users/{userID}", mw(http.HandlerFunc(updateUser), authMiddleware)).
Methods("POST")
api.Handle("/config", mw(http.HandlerFunc(setConfig(db.Hostname)), adminMiddleware)).
Methods("POST")
api.Handle("/fb/getRedirectURL", mw(http.HandlerFunc(fbGetRedirectionURL(hc)), adminMiddleware)).
Methods("GET")
api.Handle("/fb/access", mw(http.HandlerFunc(fbGetAccess(hc)), adminMiddleware)).
Methods("GET")
api.Handle("/fb/setUserAccessToken", mw(http.HandlerFunc(fbSetUserAccessToken(hc)), adminMiddleware)).
Methods("POST")
api.Handle("/fb/setPage", mw(http.HandlerFunc(fbSetPage(hc)), adminMiddleware)).
Methods("POST")
api.Handle("/fb/pages", mw(http.HandlerFunc(fbGetPages(hc)), adminMiddleware)).
Methods("GET")
api.Handle("/fb", mw(http.HandlerFunc(fbClear(hc)), adminMiddleware)).
Methods("DELETE")
/* Static files */
themeStaticDir := path.Join(Config.ThemeDir, db.Hostname)
log.Printf("serving static on /static/ from %s", themeStaticDir)
r.PathPrefix("/static/").Handler(http.FileServer(http.Dir(themeStaticDir)))
/* The 404 handler just returns the templated index.html */
indexFileLocation := path.Join(themeStaticDir, "index.html")
r.NotFoundHandler = http.HandlerFunc(indexFallback(db, hc, indexFileLocation))
}
/* Utility
----------------------------------*/
func mw(h http.Handler, adapters ...func(http.Handler) http.Handler) http.Handler {
for _, adapter := range adapters {
h = adapter(h)
}
return h
}
func throwError(w http.ResponseWriter, message string, code int) {
w.WriteHeader(code)
fmt.Fprintf(w, `{"error":%q}`, message)
}
/* Middleware
----------------------------------*/
// Content-Type json
func setContentTypeMiddleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
h.ServeHTTP(w, r)
})
}
// Authentication
func authMiddleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
session, err := store.Get(r, DefaultSession)
if err != nil {
throwError(w, err.Error(), http.StatusInternalServerError)
return
}
userIDI := session.Values["userID"]
userID, ok := userIDI.(string)
if !ok || len(userID) == 0 {
throwError(w, "Nuh-uh", http.StatusForbidden)
} else {
h.ServeHTTP(w, r)
}
})
}
func adminMiddleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
session, err := store.Get(r, DefaultSession)
if err != nil {
throwError(w, err.Error(), http.StatusInternalServerError)
return
}
userIDI := session.Values["userID"]
userID, ok := userIDI.(string)
if !ok || len(userID) == 0 {
throwError(w, "Nuh-uh", http.StatusForbidden)
return
}
userRoleI := session.Values["userRole"]
userRole, ok := userRoleI.(string)
if !ok || len(userRole) == 0 || userRole != "admin" {
throwError(w, "Nuh-uh", http.StatusForbidden)
return
}
h.ServeHTTP(w, r)
})
}
/* Index.html fallback
----------------------------------*/
func indexFallback(db *ITrackDatabase, hc *fbHostConfig, file string) handlerFuncType {
return func(w http.ResponseWriter, r *http.Request) {
t, err := template.ParseFiles(file)
if err != nil {
log.Print(err)
throwError(w, "Couldn't parse index file", http.StatusInternalServerError)
return
}
protocol := "http"
if Config.env == "production" {
protocol = "https"
}
data := struct {
Title string
Description string
URL string
Host string
}{
"",
"",
protocol + "://" + hc.host + "/",
hc.host,
}
var seoConfig SeoConfig
for _, vConfig := range Config.Sites {
if vConfig.Hostname == db.Hostname {
seoConfig = vConfig.SeoConfig
break
}
}
/* SEO / Opengraph variables */
path := r.URL.Path[1:]
if len(path) == 0 {
latestTrack := db.getLatestTrack()
data.Title = buildMeta(seoConfig.Title.Episode, latestTrack)
data.Description = buildMeta(seoConfig.Description.Episode, latestTrack)
} else {
episode, err := strconv.Atoi(path)
if err != nil {
data.Title = seoConfig.Title.Default
data.Description = seoConfig.Description.Default
} else {
track := db.getTrackByEpisode(episode)
if track != nil {
data.Title = buildMeta(seoConfig.Title.Episode, *track)
data.Description = buildMeta(seoConfig.Description.Episode, *track)
} else {
http.Redirect(w, r, "https://"+r.Host+"/", http.StatusTemporaryRedirect)
}
}
}
t.Execute(w, data)
}
}