Skip to content

Commit

Permalink
chore: rework endpoints for push
Browse files Browse the repository at this point in the history
  • Loading branch information
krzysztofdrys committed May 6, 2024
1 parent a8317c1 commit 0eb9320
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 51 deletions.
40 changes: 29 additions & 11 deletions e2e-tests/pass/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,17 +101,6 @@ func confirmSyncMobileRequest(connectionToken string) (string, error) {
return resp.ProxyToken, nil
}

func getMobileToken(fcm string) (string, error) {
var resp struct {
MobileSyncConfirmToken string `json:"mobile_sync_confirm_token"`
}
if err := request("GET", fmt.Sprintf("/mobile/sync/%s/token", fcm), "", nil, &resp); err != nil {
return "", fmt.Errorf("failed to get mobile token: %w", err)
}

return resp.MobileSyncConfirmToken, nil
}

func request(method, path, auth string, req, resp interface{}) error {
url := getApiURL() + path
var body io.Reader
Expand Down Expand Up @@ -151,3 +140,32 @@ func request(method, path, auth string, req, resp interface{}) error {

return nil
}

type RequestSyncResponse struct {
BrowserExtensionWaitToken string `json:"browser_extension_wait_token"`
MobileConfirmToken string `json:"mobile_confirm_token"`
}

func browserExtensionRequestSync(token string) (RequestSyncResponse, error) {
var resp RequestSyncResponse

if err := request("POST", "/browser_extension/sync/request", token, nil, &resp); err != nil {
return resp, fmt.Errorf("failed to configure browser: %w", err)
}

return resp, nil
}

func browserExtensionPushToMobile(token string) error {
req := struct {
Body string `json:"push_body"`
}{
Body: "sent from browser extension",
}
resp := struct{}{}
if err := request("POST", "/browser_extension/sync/push", token, req, &resp); err != nil {
return fmt.Errorf("failed to configure browser: %w", err)
}

return nil
}
17 changes: 11 additions & 6 deletions e2e-tests/pass/sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ func TestSyncHappyFlow(t *testing.T) {

browserExtensionDone := make(chan struct{})
mobileParingDone := make(chan struct{})
confirmMobileChannel := make(chan string)

fcm := uuid.NewString()
deviceID := getDeviceID()
Expand All @@ -26,7 +27,15 @@ func TestSyncHappyFlow(t *testing.T) {
return
}

proxyToken, err := browserExtensionWaitForSyncConfirm(syncToken)
requestSyncResp, err := browserExtensionRequestSync(syncToken)
if err != nil {
t.Errorf("Error when Browser Extension requested sync confirm: %v", err)
return
}

confirmMobileChannel <- requestSyncResp.MobileConfirmToken

proxyToken, err := browserExtensionWaitForSyncConfirm(requestSyncResp.BrowserExtensionWaitToken)
if err != nil {
t.Errorf("Error when Browser Extension waited for sync confirm: %v", err)
return
Expand All @@ -51,11 +60,7 @@ func TestSyncHappyFlow(t *testing.T) {
return
}

confirmToken, err := getMobileToken(fcm)
if err != nil {
t.Errorf("Failed to fetch mobile token: %v", err)
return
}
confirmToken := <-confirmMobileChannel

proxyToken, err := confirmSyncMobile(confirmToken)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion e2e-tests/pass/ws.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func getWsURL() string {
}

func browserExtensionWaitForSyncConfirm(token string) (string, error) {
url := getWsURL() + "/browser_extension/sync/request"
url := getWsURL() + "/browser_extension/sync/wait"

var resp struct {
BrowserExtensionSyncToken string `json:"browser_extension_proxy_token"`
Expand Down
11 changes: 4 additions & 7 deletions internal/pass/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (

"github.com/twofas/2fas-server/config"
httphelpers "github.com/twofas/2fas-server/internal/common/http"
"github.com/twofas/2fas-server/internal/common/logging"
"github.com/twofas/2fas-server/internal/common/recovery"
"github.com/twofas/2fas-server/internal/pass/connection"
"github.com/twofas/2fas-server/internal/pass/pairing"
Expand Down Expand Up @@ -100,16 +99,14 @@ func NewServer(cfg config.PassConfig) *Server {
router.POST("/mobile/pairing/confirm", pairing.MobileConfirmHandler(pairingApp))
router.GET("/mobile/pairing/proxy", pairing.MobileProxyWSHandler(pairingApp, proxyPairingApp))

router.GET("/browser_extension/sync/request", sync.ExtensionRequestSync(syncApp))
router.POST("/browser_extension/sync/request", sync.ExtensionRequestSync(syncApp))
router.POST("/browser_extension/sync/push", sync.ExtensionRequestPush(syncApp))
router.GET("/browser_extension/sync/wait", sync.ExtensionRequestWait(syncApp))

router.GET("/browser_extension/sync/proxy", sync.ExtensionProxyWSHandler(syncApp, proxySyncApp))
router.POST("/mobile/sync/confirm", sync.MobileConfirmHandler(syncApp))
router.GET("/mobile/sync/proxy", sync.MobileProxyWSHandler(syncApp, proxySyncApp))

if cfg.FakeMobilePush {
logging.Info("Enabled '/mobile/sync/:fcm/token' endpoint. This should happen in test env only!")
router.GET("/mobile/sync/:fcm/token", sync.MobileGenerateSyncToken(syncApp))
}

return &Server{
router: router,
addr: cfg.Addr,
Expand Down
1 change: 1 addition & 0 deletions internal/pass/sign/lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const (
ConnectionTypeBrowserExtensionWait ConnectionType = "be/wait"
ConnectionTypeBrowserExtensionProxy ConnectionType = "be/proxy"
ConnectionTypeBrowserExtensionSyncRequest ConnectionType = "be/sync/request"
ConnectionTypeBrowserExtensionSyncWait ConnectionType = "be/sync/wait"
ConnectionTypeBrowserExtensionSync ConnectionType = "be/sync/proxy"
ConnectionTypeMobileProxy ConnectionType = "mobile/proxy"
ConnectionTypeMobileConfirm ConnectionType = "mobile/confirm"
Expand Down
9 changes: 9 additions & 0 deletions internal/pass/sync/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ func (s *Syncing) VerifyExtRequestSyncToken(ctx context.Context, proxyToken stri
return fcmToken, nil
}

// VerifyExtWaitForSyncToken verifies wait for sync request token and returns fcm_token.
func (s *Syncing) VerifyExtWaitForSyncToken(ctx context.Context, proxyToken string) (string, error) {
fcmToken, err := s.signSvc.CanI(proxyToken, sign.ConnectionTypeBrowserExtensionSyncWait)
if err != nil {
return "", fmt.Errorf("failed to check token signature: %w", err)
}
return fcmToken, nil
}

// VerifyExtSyncToken verifies sync token and returns fcm_token.
func (s *Syncing) VerifyExtSyncToken(ctx context.Context, proxyToken string) (string, error) {
fcmToken, err := s.signSvc.CanI(proxyToken, sign.ConnectionTypeBrowserExtensionSync)
Expand Down
77 changes: 61 additions & 16 deletions internal/pass/sync/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

func ExtensionRequestSync(syncingApp *Syncing) gin.HandlerFunc {
return func(gCtx *gin.Context) {
token, err := connection.TokenFromWSProtocol(gCtx.Request)
token, err := tokenFromRequest(gCtx)
if err != nil {
logging.Errorf("Failed to get token from request: %v", err)
gCtx.Status(http.StatusForbidden)
Expand All @@ -26,8 +26,67 @@ func ExtensionRequestSync(syncingApp *Syncing) gin.HandlerFunc {
return
}

resp, err := syncingApp.RequestSync(gCtx, fcmToken)
if err != nil {
logging.Errorf("Failed to request sync: %v", err)
gCtx.Status(http.StatusInternalServerError)
return
}
gCtx.JSON(http.StatusOK, resp)
}
}

type PushToMobileRequest struct {
Body string `json:"push_body"`
}

func ExtensionRequestPush(syncingApp *Syncing) gin.HandlerFunc {
return func(gCtx *gin.Context) {
log := logging.FromContext(gCtx.Request.Context())

token, err := tokenFromRequest(gCtx)
if err != nil {
log.Errorf("Failed to get token from request: %v", err)
gCtx.Status(http.StatusForbidden)
return
}
fcmToken, err := syncingApp.VerifyExtWaitForSyncToken(gCtx, token)
if err != nil {
log.Errorf("Failed to verify proxy token: %v", err)
gCtx.String(http.StatusUnauthorized, "Invalid auth token")
return
}
var req PushToMobileRequest
if err := gCtx.BindJSON(&req); err != nil {
gCtx.String(http.StatusBadRequest, err.Error())
return
}

log.Infof("Send push to mobile %q: %q", fcmToken, req.Body)

gCtx.Status(http.StatusOK)
}
}

func ExtensionRequestWait(syncingApp *Syncing) gin.HandlerFunc {
return func(gCtx *gin.Context) {
log := logging.FromContext(gCtx.Request.Context())

token, err := connection.TokenFromWSProtocol(gCtx.Request)
if err != nil {
log.Errorf("Failed to get token from request: %v", err)
gCtx.Status(http.StatusForbidden)
return
}
fcmToken, err := syncingApp.VerifyExtWaitForSyncToken(gCtx, token)
if err != nil {
log.Errorf("Failed to verify proxy token: %v", err)
gCtx.String(http.StatusUnauthorized, "Invalid auth token")
return
}

if err := syncingApp.ServeSyncingRequestWS(gCtx.Writer, gCtx.Request, fcmToken); err != nil {
logging.Errorf("Failed to verify proxy token: %v", err)
log.Errorf("Failed to verify proxy token: %v", err)
gCtx.Status(http.StatusInternalServerError)
return
}
Expand Down Expand Up @@ -114,20 +173,6 @@ func MobileProxyWSHandler(syncingApp *Syncing, proxy *connection.ProxyServer) gi
}
}

func MobileGenerateSyncToken(syncingApp *Syncing) gin.HandlerFunc {
return func(gCtx *gin.Context) {
fcm := gCtx.Param("fcm")
if fcm == "" {
gCtx.Status(http.StatusBadRequest)
return
}
if err := syncingApp.sendMobileToken(fcm, gCtx.Writer); err != nil {
logging.Errorf("Failed to send mobile token: %v", err)
gCtx.Status(http.StatusInternalServerError)
}
}
}

func tokenFromRequest(gCtx *gin.Context) (string, error) {
tokenHeader := gCtx.GetHeader("Authorization")
if tokenHeader == "" {
Expand Down
45 changes: 35 additions & 10 deletions internal/pass/sync/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/http"
"time"

"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"

"github.com/twofas/2fas-server/internal/common/logging"
Expand Down Expand Up @@ -37,10 +38,6 @@ const (
syncTokenValidityDuration = 3 * time.Minute
)

type ConfigureBrowserExtensionRequest struct {
ExtensionID string `json:"extension_id"`
}

type ConfigureBrowserExtensionResponse struct {
BrowserExtensionPairingToken string `json:"browser_extension_pairing_token"`
ConnectionToken string `json:"connection_token"`
Expand All @@ -51,11 +48,6 @@ type ExtensionWaitForConnectionInput struct {
HttpReq *http.Request
}

type RequestSyncResponse struct {
BrowserExtensionProxyToken string `json:"browser_extension_proxy_token"`
Status string `json:"status"`
}

type MobileSyncPayload struct {
MobileSyncToken string `json:"mobile_sync_token"`
}
Expand Down Expand Up @@ -112,6 +104,11 @@ func (s *Syncing) requestSync(ctx context.Context, fcmToken string) {
s.store.RequestSync(fcmToken)
}

type WaitForSyncResponse struct {
BrowserExtensionProxyToken string `json:"browser_extension_proxy_token"`
Status string `json:"status"`
}

func (s *Syncing) sendTokenAndCloseConn(fcmToken string, conn *websocket.Conn) error {
extProxyToken, err := s.signSvc.SignAndEncode(sign.Message{
ConnectionID: fcmToken,
Expand All @@ -122,7 +119,7 @@ func (s *Syncing) sendTokenAndCloseConn(fcmToken string, conn *websocket.Conn) e
return fmt.Errorf("failed to generate ext proxy token: %v", err)
}

if err := conn.WriteJSON(RequestSyncResponse{
if err := conn.WriteJSON(WaitForSyncResponse{
BrowserExtensionProxyToken: extProxyToken,
Status: "ok",
}); err != nil {
Expand Down Expand Up @@ -178,3 +175,31 @@ func (s *Syncing) confirmSync(ctx context.Context, fcmToken string) (ConfirmSync

return ConfirmSyncResponse{ProxyToken: mobileProxyToken}, nil
}

type RequestSyncResponse struct {
BrowserExtensionWaitToken string `json:"browser_extension_wait_token"`
MobileConfirmToken string `json:"mobile_confirm_token"`
}

func (s *Syncing) RequestSync(ctx *gin.Context, token string) (RequestSyncResponse, error) {
mobileConfirmToken, err := s.signSvc.SignAndEncode(sign.Message{
ConnectionID: token,
ExpiresAt: time.Now().Add(syncTokenValidityDuration),
ConnectionType: sign.ConnectionTypeMobileSyncConfirm,
})
if err != nil {
return RequestSyncResponse{}, fmt.Errorf("failed to generate mobile confirm token: %v", err)
}
browserExtensionWaitToken, err := s.signSvc.SignAndEncode(sign.Message{
ConnectionID: token,
ExpiresAt: time.Now().Add(syncTokenValidityDuration),
ConnectionType: sign.ConnectionTypeBrowserExtensionSyncWait,
})
if err != nil {
return RequestSyncResponse{}, fmt.Errorf("failed to generate browser extension wait token: %v", err)
}
return RequestSyncResponse{
BrowserExtensionWaitToken: browserExtensionWaitToken,
MobileConfirmToken: mobileConfirmToken,
}, nil
}

0 comments on commit 0eb9320

Please sign in to comment.