package server
import (
"encoding/json"
"io/ioutil"
"github.com/keys-pub/keys"
"github.com/keys-pub/keys/dstore"
"github.com/keys-pub/keys/dstore/events"
"github.com/keys-pub/keys/encoding"
"github.com/keys-pub/keys/http"
"github.com/keys-pub/keys/tsutil"
"github.com/keys-pub/keys/users"
"github.com/labstack/echo/v4"
"github.com/pkg/errors"
)
type Server struct {
fi Fire
rds Redis
clock tsutil.Clock
logger Logger
client http.Client
URL string
users *users.Users
sigchains *keys.Sigchains
tasks Tasks
internalAuth string
admins []keys.ID
internalKey *[32]byte
}
type Fire interface {
dstore.Documents
events.Events
}
func New(fi Fire, rds Redis, client http.Client, clock tsutil.Clock, logger Logger) *Server {
sigchains := keys.NewSigchains(fi)
usrs := users.New(fi, sigchains, users.Client(client), users.Clock(clock))
return &Server{
fi: fi,
rds: rds,
client: client,
clock: tsutil.NewClock(),
tasks: newUnsetTasks(),
sigchains: sigchains,
users: usrs,
logger: logger,
}
}
func (s *Server) SetInternalAuth(internalAuth string) {
s.internalAuth = internalAuth
}
func (s *Server) SetInternalKey(internalKey string) error {
if internalKey == "" {
return errors.Errorf("empty secret key")
}
sk, err := encoding.Decode(internalKey, encoding.Hex)
if err != nil {
return err
}
s.internalKey = keys.Bytes32(sk)
return nil
}
func (s *Server) SetAdmins(admins []keys.ID) {
s.admins = admins
}
func (s *Server) SetTasks(tasks Tasks) {
s.tasks = tasks
}
func NewHandler(s *Server) http.Handler {
return newHandler(s)
}
func newHandler(s *Server) *echo.Echo {
e := echo.New()
e.HTTPErrorHandler = s.ErrorHandler
s.AddRoutes(e)
return e
}
func (s *Server) AddRoutes(e *echo.Echo) {
e.GET("/sigchain/:kid/:seq", s.getSigchainStatement)
e.PUT("/sigchain/:kid/:seq", s.putSigchainStatement)
e.GET("/sigchain/:kid", s.getSigchain)
e.POST("/check", s.check)
e.GET("/user/search", s.getUserSearch)
e.GET("/user/:user", s.getUser)
e.POST("/task/check/:kid", s.taskCheck)
e.POST("/cron/check", s.cronCheck)
e.POST("/vault/:kid", s.postVault)
e.GET("/vault/:kid", s.listVault)
e.DELETE("/vault/:kid", s.deleteVault)
e.HEAD("/vault/:kid", s.headVault)
e.PUT("/disco/:kid/:rid/:type", s.putDisco)
e.GET("/disco/:kid/:rid/:type", s.getDisco)
e.DELETE("/disco/:kid/:rid", s.deleteDisco)
e.POST("/invite/code/:kid/:rid", s.postInviteCode)
e.GET("/invite/code/:code", s.getInviteCode)
e.GET("/share/:kid", s.getShare)
e.PUT("/share/:kid", s.putShare)
e.GET("/:kid", s.getSigchainAliased)
e.GET("/:kid/:seq", s.getSigchainStatementAliased)
e.PUT("/:kid/:seq", s.putSigchainStatementAliased)
e.PUT("/channel/:cid", s.putChannel) e.GET("/channel/:cid", s.getChannel) e.POST("/channel/:cid/msgs", s.postMessage) e.GET("/channel/:cid/msgs", s.getMessages) e.POST("/channels/status", s.postChannelsStatus)
e.POST("/batch", s.postBatch)
e.POST("/dm/:sender/:recipient", s.postDirectMessage) e.GET("/dm/:recipient", s.getDirectMessages) e.GET("/dm/token/:recipient", s.getDirectToken)
e.PUT("/follow/:sender/:recipient", s.putFollow) e.GET("/follows/:recipient", s.getFollows) e.GET("/follow/:sender/:recipient", s.getFollow) e.DELETE("/follow/:sender/:recipient", s.deleteFollow)
e.GET("/twitter/:kid/:name/:id", s.checkTwitter)
e.POST("/admin/check/:kid", s.adminCheck)
}
func (s *Server) SetClock(clock tsutil.Clock) {
s.clock = clock
}
func JSON(c echo.Context, status int, i interface{}) error {
var b []byte
switch v := i.(type) {
case []byte:
b = v
default:
mb, err := json.Marshal(i)
if err != nil {
panic(err)
}
b = mb
}
return c.Blob(status, echo.MIMEApplicationJSONCharsetUTF8, b)
}
func (s *Server) checkInternalAuth(c echo.Context) error {
if s.internalAuth == "" {
return s.ErrForbidden(c, errors.Errorf("no auth token set on server"))
}
auth := c.Request().Header.Get("Authorization")
if auth == "" {
return s.ErrForbidden(c, errors.Errorf("no auth token specified"))
}
if auth != s.internalAuth {
return s.ErrForbidden(c, errors.Errorf("invalid auth token"))
}
return nil
}
func readBody(c echo.Context, required bool, maxLength int) ([]byte, int, error) {
br := c.Request().Body
if br == nil {
if !required {
return []byte{}, 0, nil
}
return nil, http.StatusBadRequest, errors.Errorf("missing body")
}
b, err := ioutil.ReadAll(br)
if err != nil {
return nil, http.StatusInternalServerError, err
}
if len(b) > maxLength {
return nil, http.StatusRequestEntityTooLarge, errors.Errorf("request too large")
}
if len(b) == 0 && required {
return nil, http.StatusBadRequest, errors.Errorf("no body data")
}
return b, 0, nil
}