package server import ( "encoding/json" "fmt" "net/http" "net/url" "time" "github.com/keys-pub/keys" "github.com/keys-pub/keys-ext/http/api" "github.com/labstack/echo/v4" "github.com/pkg/errors" ) type invite struct { Sender keys.ID `json:"s"` Recipient keys.ID `json:"r"` } func (s *Server) postInviteCode(c echo.Context) error { s.logger.Infof("Server %s %s", c.Request().Method, c.Request().URL.String()) ctx := c.Request().Context() auth, err := s.auth(c, newAuth("Authorization", "kid", nil)) if err != nil { return s.ErrForbidden(c, err) } recipient := c.Param("rid") if recipient == "" { return s.ErrBadRequest(c, errors.Errorf("no recipient id")) } rid, err := keys.ParseID(recipient) if err != nil { return s.ErrBadRequest(c, err) } inv := invite{ Sender: auth.KID, Recipient: rid, } ib, err := json.Marshal(inv) if err != nil { return s.ErrInternalServer(c, err) } var code string for i := 0; i < 3; i++ { randWords := keys.RandWords(3) existing, err := s.rds.Get(ctx, code) if err != nil { return s.ErrInternalServer(c, err) } if existing != "" { s.logger.Errorf("invite code conflict") continue } code = randWords break } if code == "" { return s.ErrInternalServer(c, errors.Errorf("invite code conflict")) } codeKey := fmt.Sprintf("code %s", code) if err := s.rds.Set(ctx, codeKey, string(ib)); err != nil { return s.ErrInternalServer(c, err) } // TODO: Configurable expiry? if err := s.rds.Expire(ctx, codeKey, time.Hour); err != nil { return s.ErrInternalServer(c, err) } s.logger.Debugf("Created code: %s", code) resp := api.InviteCodeCreateResponse{ Code: code, } return JSON(c, http.StatusOK, resp) } func (s *Server) getInviteCode(c echo.Context) error { s.logger.Infof("Server %s %s", c.Request().Method, c.Request().URL.String()) ctx := c.Request().Context() auth, err := s.auth(c, newAuth("Authorization", "", nil)) if err != nil { return s.ErrForbidden(c, err) } code, err := url.QueryUnescape(c.Param("code")) if err != nil { return s.ErrBadRequest(c, err) } key := fmt.Sprintf("code %s", code) s.logger.Debugf("Get code: %s", key) out, err := s.rds.Get(ctx, key) if err != nil { return s.ErrInternalServer(c, err) } if out == "" { return s.ErrNotFound(c, errors.Errorf("code not found")) } var inv invite if err := json.Unmarshal([]byte(out), &inv); err != nil { return s.ErrInternalServer(c, err) } // Only allow the sender or recipient to view the invite. // This can happen if client has many keys and is brute forcing to find // which one to use. if inv.Recipient != auth.KID && inv.Sender != auth.KID { s.logger.Debugf("Recipient mistmatch: %s != %s", inv.Recipient, auth.KID) return s.ErrNotFound(c, errors.Errorf("code not found")) } // TODO: Remove on access or when it's used? // if err := s.rds.Delete(ctx, key); err != nil { // return s.ErrInternalServer(c, err) // } resp := api.InviteCodeResponse{ Sender: inv.Sender, Recipient: inv.Recipient, } return JSON(c, http.StatusOK, resp) }