package api import ( "bytes" "encoding/json" "github.com/gorilla/websocket" "github.com/julienschmidt/httprouter" "log" "net/http" "time" ) var upgrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true }, } func SetupApiServer(listen string, auth *AuthChecker, send Smtp, recv Imap) *http.Server { r := httprouter.New() // === ACCOUNT === r.GET("/identities", auth.Middleware(func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, b AuthClaims) { // TODO(melon): find users aliases and other account data })) // === SMTP === r.POST("/smtp", auth.Middleware(MessageSender(send))) r.GET("/imap", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) { // upgrade to websocket conn and defer close c, err := upgrader.Upgrade(rw, req, nil) if err != nil { log.Println("[Imap] Failed to upgrade to websocket:", err) return } defer c.Close() // set a really short deadline to refuse unauthenticated clients deadline := time.Now().Add(5 * time.Second) _ = c.SetReadDeadline(deadline) _ = c.SetWriteDeadline(deadline) // close on all possible errors, assume we are being attacked mt, msg, err := c.ReadMessage() if err != nil { return } if mt != websocket.TextMessage { return } if len(msg) >= 2000 { return } // parse token from message var tokenMsg struct { Token string `json:"token"` } dec := json.NewDecoder(bytes.NewReader(msg)) dec.DisallowUnknownFields() err = dec.Decode(&tokenMsg) if err != nil { _ = c.WriteJSON(map[string]string{"error": "Authentication missing"}) return } // get a "possible" auth token value // exit on empty token value if tokenMsg.Token == "" { _ = c.WriteJSON(map[string]string{"error": "Authentication missing"}) return } // check the token authUser, err := auth.Check(tokenMsg.Token) if err != nil { _ = c.WriteJSON(map[string]string{"error": "Authentication invalid"}) return } mailInboxes := authUser.Claims.Perms.Search("mail:inbox=*") if len(mailInboxes) != 1 { _ = c.WriteJSON(map[string]string{"error": "Authentication should only contain one owned inbox"}) return } // open imap client client, err := recv.MakeClient(mailInboxes[0][len("mail:inbox="):]) if err != nil { log.Println("Making a client failed:", err) _ = c.WriteJSON(map[string]string{"error": "Making a client failed"}) return } // auth was ok err = c.WriteJSON(map[string]string{"auth": "ok"}) if err != nil { return } for { // authenticated users get longer to reply // a simple ping/pong setup bypasses this d := time.Now().Add(5 * time.Minute) _ = c.SetReadDeadline(d) _ = c.SetWriteDeadline(d) // read incoming message var m struct { Action string `json:"action"` Args json.RawMessage `json:"args"` } err := c.ReadJSON(&m) if err != nil { _ = c.WriteJSON(map[string]string{"error": "Invalid input"}) return } // handle action j, err := client.HandleWS(m.Action, m.Args) if err != nil { _ = c.WriteJSON(map[string]string{"error": "Action failed"}) return } // write outgoing message err = c.WriteJSON(j) if err != nil { _ = c.WriteJSON(map[string]string{"error": "Invalid output"}) return } } }) return &http.Server{ Addr: listen, Handler: r, ReadTimeout: time.Minute, ReadHeaderTimeout: time.Minute, WriteTimeout: time.Minute, IdleTimeout: time.Minute, MaxHeaderBytes: 2500, } } // apiError outputs a generic JSON error message func apiError(rw http.ResponseWriter, code int, m string) { rw.WriteHeader(code) _ = json.NewEncoder(rw).Encode(map[string]string{ "error": m, }) }