package main import ( "bytes" "log" "net/http" "net/url" "time" "github.com/emersion/go-ical" "golang.org/x/crypto/argon2" ) func main() { cfg := loadConfig() printConfig(&cfg) handler := handler{ calURL: url.URL(cfg.CalendarURL), ignore: cfg.Ignore, tokens: cfg.UserTokens, } mux := http.ServeMux{} mux.HandleFunc("/", handler.handle) server := http.Server{ Addr: ":" + cfg.Port, Handler: &mux, ReadTimeout: 5 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 30 * time.Second, ReadHeaderTimeout: 2 * time.Second, } log.Println("Listening on", cfg.Port) log.Fatal(server.ListenAndServe()) } type handler struct { ignore ignoreRules tokens map[string]userToken calURL url.URL } func (h handler) makeTokenURL(token string) string { newURL := h.calURL query := newURL.Query() query.Set("token", token) newURL.RawQuery = query.Encode() return newURL.String() } func (h handler) handle(w http.ResponseWriter, r *http.Request) { brightspaceToken := r.URL.Query().Get("brightspace_token") proxyUserID := r.URL.Query().Get("proxy_user_id") proxyToken := r.URL.Query().Get("proxy_token") if !userOK(h.tokens, proxyUserID, []byte(proxyToken)) { http.Error(w, "Bad proxy_user_id or proxy_token", http.StatusUnauthorized) return } resp, err := http.Get(h.makeTokenURL(brightspaceToken)) if err != nil { http.Error(w, "Error sending request to Brightspace", http.StatusInternalServerError) return } cal, err := ical.NewDecoder(resp.Body).Decode() if err != nil { http.Error(w, "Could not decode iCal", http.StatusInternalServerError) return } filter(h.ignore, cal.Component) if err = ical.NewEncoder(w).Encode(cal); err != nil { log.Println("Error writing calendar:", err) } } func filter(ignore ignoreRules, c *ical.Component) { j := 0 for _, child := range c.Children { keep := true if child.Name == ical.CompEvent { if location := child.Props.Get(ical.PropLocation); location != nil { for _, locationRule := range ignore.LocationRegexes { if locationRule.MatchString(location.Value) { keep = false } } } if summary := child.Props.Get(ical.PropSummary); summary != nil { for _, summaryRule := range ignore.SummaryRegexes { if summaryRule.MatchString(summary.Value) { keep = false } } } } if keep { c.Children[j] = child j++ } } c.Children = c.Children[:j] } func userOK(tokens map[string]userToken, id string, token []byte) bool { info, ok := tokens[id] if !ok { return false } return bytes.Compare(hash(token, info.Salt), info.Hash) == 0 } func hash(token []byte, salt []byte) []byte { return argon2.IDKey(token, salt, 1, 64*1024, 4, 32) }