2023-08-13 03:04:16 +01:00
|
|
|
package api
|
|
|
|
|
|
|
|
import (
|
2023-09-11 17:45:37 +01:00
|
|
|
"bytes"
|
2023-08-13 03:04:16 +01:00
|
|
|
"encoding/json"
|
2023-09-11 17:23:44 +01:00
|
|
|
"github.com/gorilla/websocket"
|
2023-08-13 03:04:16 +01:00
|
|
|
"github.com/julienschmidt/httprouter"
|
2023-09-11 11:55:52 +01:00
|
|
|
"log"
|
2023-08-13 03:04:16 +01:00
|
|
|
"net/http"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
2023-09-11 17:23:44 +01:00
|
|
|
var upgrader = websocket.Upgrader{}
|
|
|
|
|
|
|
|
func SetupApiServer(listen string, auth *AuthChecker, send Smtp, recv Imap) *http.Server {
|
2023-08-13 03:04:16 +01:00
|
|
|
r := httprouter.New()
|
|
|
|
|
2023-08-21 00:26:22 +01:00
|
|
|
// === ACCOUNT ===
|
2023-09-11 17:23:44 +01:00
|
|
|
r.GET("/identities", auth.Middleware(func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, b AuthClaims) {
|
2023-08-21 00:26:22 +01:00
|
|
|
// TODO(melon): find users aliases and other account data
|
|
|
|
}))
|
|
|
|
|
|
|
|
// === SMTP ===
|
2023-09-11 17:23:44 +01:00
|
|
|
r.POST("/smtp", auth.Middleware(MessageSender(send)))
|
2023-09-11 14:55:12 +01:00
|
|
|
|
2023-09-11 17:35:29 +01:00
|
|
|
r.GET("/imap", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
|
2023-09-11 17:23:44 +01:00
|
|
|
// upgrade to websocket conn and defer close
|
|
|
|
c, err := upgrader.Upgrade(rw, req, nil)
|
2023-09-11 14:55:12 +01:00
|
|
|
if err != nil {
|
2023-09-11 17:23:44 +01:00
|
|
|
log.Println("[Imap] Failed to upgrade to websocket:", err)
|
2023-08-21 00:26:22 +01:00
|
|
|
return
|
|
|
|
}
|
2023-09-11 17:23:44 +01:00
|
|
|
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()
|
2023-08-21 00:26:22 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2023-09-11 17:23:44 +01:00
|
|
|
if mt != websocket.TextMessage {
|
|
|
|
return
|
2023-09-11 11:55:52 +01:00
|
|
|
}
|
2023-09-11 17:23:44 +01:00
|
|
|
if len(msg) >= 2000 {
|
2023-08-21 00:26:22 +01:00
|
|
|
return
|
|
|
|
}
|
2023-09-11 17:23:44 +01:00
|
|
|
|
2023-09-11 17:45:37 +01:00
|
|
|
// 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 {
|
2023-09-11 17:49:44 +01:00
|
|
|
_ = c.WriteJSON(map[string]string{"error": "Authentication missing"})
|
2023-09-11 17:45:37 +01:00
|
|
|
return
|
|
|
|
}
|
2023-09-11 17:23:44 +01:00
|
|
|
|
2023-09-11 17:45:37 +01:00
|
|
|
// get a "possible" auth token value
|
|
|
|
// exit on empty token value
|
|
|
|
if tokenMsg.Token == "" {
|
2023-09-11 17:49:44 +01:00
|
|
|
_ = c.WriteJSON(map[string]string{"error": "Authentication missing"})
|
2023-08-21 00:26:22 +01:00
|
|
|
return
|
|
|
|
}
|
2023-09-11 17:23:44 +01:00
|
|
|
|
|
|
|
// check the token
|
2023-09-11 17:45:37 +01:00
|
|
|
authUser, err := auth.Check(tokenMsg.Token)
|
2023-08-21 00:26:22 +01:00
|
|
|
if err != nil {
|
2023-09-11 17:49:44 +01:00
|
|
|
_ = c.WriteJSON(map[string]string{"error": "Authentication invalid"})
|
2023-08-21 00:26:22 +01:00
|
|
|
return
|
|
|
|
}
|
2023-09-11 17:23:44 +01:00
|
|
|
|
2023-09-11 17:46:53 +01:00
|
|
|
// open imap client
|
2023-09-11 17:23:44 +01:00
|
|
|
client, err := recv.MakeClient(authUser.Subject)
|
2023-08-21 00:26:22 +01:00
|
|
|
if err != nil {
|
2023-09-11 17:45:37 +01:00
|
|
|
_ = c.WriteJSON(map[string]string{"error": "Making client failed"})
|
2023-08-21 00:26:22 +01:00
|
|
|
return
|
|
|
|
}
|
2023-09-11 17:23:44 +01:00
|
|
|
|
2023-09-11 17:46:53 +01:00
|
|
|
// auth was ok
|
|
|
|
err = c.WriteJSON(map[string]string{"auth": "ok"})
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-09-11 17:23:44 +01:00
|
|
|
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 []string `json:"args"`
|
|
|
|
}
|
|
|
|
err := c.ReadJSON(&m)
|
|
|
|
if err != nil {
|
2023-09-11 17:49:44 +01:00
|
|
|
_ = c.WriteJSON(map[string]string{"error": "Invalid input"})
|
2023-09-11 17:23:44 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// handle action
|
|
|
|
j, err := client.HandleWS(m.Action, m.Args)
|
|
|
|
if err != nil {
|
2023-09-11 17:49:44 +01:00
|
|
|
_ = c.WriteJSON(map[string]string{"error": "Action failed"})
|
2023-09-11 17:23:44 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// write outgoing message
|
|
|
|
err = c.WriteJSON(j)
|
|
|
|
if err != nil {
|
2023-09-11 17:49:44 +01:00
|
|
|
_ = c.WriteJSON(map[string]string{"error": "Invalid output"})
|
2023-09-11 17:23:44 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
2023-08-13 03:04:16 +01:00
|
|
|
|
|
|
|
return &http.Server{
|
2023-08-21 00:26:22 +01:00
|
|
|
Addr: listen,
|
2023-08-13 03:04:16 +01:00
|
|
|
Handler: r,
|
|
|
|
ReadTimeout: time.Minute,
|
|
|
|
ReadHeaderTimeout: time.Minute,
|
|
|
|
WriteTimeout: time.Minute,
|
|
|
|
IdleTimeout: time.Minute,
|
|
|
|
MaxHeaderBytes: 2500,
|
|
|
|
}
|
|
|
|
}
|
2023-08-21 00:26:22 +01:00
|
|
|
|
|
|
|
// 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,
|
|
|
|
})
|
|
|
|
}
|