chore: rework endpoints for push

This commit is contained in:
Krzysztof Dryś 2024-05-06 11:09:52 +02:00
parent a8317c1af6
commit 0eb932017b
8 changed files with 151 additions and 51 deletions

View File

@ -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
@ -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
}

View File

@ -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()
@ -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
@ -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 {

View File

@ -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"`

View File

@ -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"
@ -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,

View File

@ -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"

View File

@ -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)

View File

@ -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)
@ -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
}
@ -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 == "" {

View File

@ -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"
@ -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"`
@ -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"`
}
@ -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,
@ -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 {
@ -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
}