feat: send push to mobile (#49)

feat: send push to mobile
This commit is contained in:
Krzysztof Dryś 2024-05-27 16:56:05 +02:00 committed by GitHub
parent bc22800bf4
commit 7bf9bfb906
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 134 additions and 33 deletions

View File

@ -156,16 +156,19 @@ func browserExtensionRequestSync(token string) (RequestSyncResponse, error) {
return resp, nil
}
func browserExtensionPushToMobile(token string) error {
func browserExtensionPush(token, body string) (string, error) {
var resp struct {
Response string `json:"response"`
}
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)
Body: body,
}
return nil
if err := request("POST", "/browser_extension/sync/push", token, req, &resp); err != nil {
return "", fmt.Errorf("failed to send push notification: %w", err)
}
return resp.Response, nil
}

View File

@ -33,6 +33,13 @@ func TestSyncHappyFlow(t *testing.T) {
return
}
pushResp, err := browserExtensionPush(requestSyncResp.BrowserExtensionWaitToken, "body")
if err != nil {
t.Errorf("Error when Browser Extension tried to send push notification: %v", err)
return
}
t.Logf("Push response: %v", pushResp)
confirmMobileChannel <- requestSyncResp.MobileConfirmToken
proxyToken, err := browserExtensionWaitForSyncConfirm(requestSyncResp.BrowserExtensionWaitToken)

View File

@ -9,7 +9,7 @@ import (
)
func main() {
addrFlag := flag.String("addr", ":80;:8081;:8082", "list of addresses to check sep by ;")
addrFlag := flag.String("addr", ":80;:8081;:8082;:8088", "list of addresses to check sep by ;")
flag.Parse()
addresses := strings.Split(*addrFlag, ";")

View File

@ -0,0 +1,72 @@
package fcm
import (
"context"
"encoding/json"
"fmt"
"time"
firebase "firebase.google.com/go/v4"
"firebase.google.com/go/v4/messaging"
"google.golang.org/api/option"
"github.com/twofas/2fas-server/internal/common/logging"
)
// Response is returned from firebsase for push request.
type Response string
type Client interface {
Send(ctx context.Context, message *messaging.Message) (Response, error)
}
type client struct {
FcmMessaging *messaging.Client
}
func NewClient(ctx context.Context, credentials string) (*client, error) {
opt := option.WithCredentialsJSON([]byte(credentials))
app, err := firebase.NewApp(ctx, nil, opt)
if err != nil {
return nil, fmt.Errorf("failed to create firebase app: %w", err)
}
fcmClient, err := app.Messaging(ctx)
if err != nil {
return nil, fmt.Errorf("failed to create fcm client: %w", err)
}
return &client{FcmMessaging: fcmClient}, nil
}
func (c *client) Send(ctx context.Context, message *messaging.Message) (Response, error) {
contextWithTimeout, cancel := context.WithTimeout(ctx, 20*time.Second)
defer cancel()
logging.FromContext(ctx).Info()
response, err := c.FcmMessaging.Send(contextWithTimeout, message)
if err != nil {
return "", fmt.Errorf("failed to send push message: %w", err)
}
return Response(response), nil
}
type FakePushClient struct {
}
func NewFakePushClient() *FakePushClient {
return &FakePushClient{}
}
func (p *FakePushClient) Send(ctx context.Context, message *messaging.Message) (Response, error) {
data, err := json.Marshal(message)
if err != nil {
return "", fmt.Errorf("failed to marshal message: %w", err)
}
logging.WithFields(logging.Fields{
"notification": string(data),
}).Debug("Sending fake push notifications")
return "ok", nil
}

View File

@ -4,18 +4,16 @@ import (
"context"
"log"
firebase "firebase.google.com/go/v4"
"firebase.google.com/go/v4/messaging"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/kms"
"github.com/gin-gonic/gin"
"google.golang.org/api/option"
"github.com/twofas/2fas-server/config"
httphelpers "github.com/twofas/2fas-server/internal/common/http"
"github.com/twofas/2fas-server/internal/common/recovery"
"github.com/twofas/2fas-server/internal/pass/connection"
"github.com/twofas/2fas-server/internal/pass/fcm"
"github.com/twofas/2fas-server/internal/pass/pairing"
"github.com/twofas/2fas-server/internal/pass/sign"
"github.com/twofas/2fas-server/internal/pass/sync"
@ -55,25 +53,20 @@ func NewServer(cfg config.PassConfig) *Server {
}
ctx := context.Background()
var fcmClient *messaging.Client
var fcmClient fcm.Client
if cfg.FirebaseServiceAccount != "" {
opt := option.WithCredentialsJSON([]byte(cfg.FirebaseServiceAccount))
app, err := firebase.NewApp(ctx, nil, opt)
if err != nil {
log.Fatalf("Error initializing FCM App: %v", err)
}
fcmClient, err = app.Messaging(ctx)
fcmClient, err = fcm.NewClient(ctx, cfg.FirebaseServiceAccount)
if err != nil {
log.Fatalf("Error initializing Messaging Client: %v", err)
}
} else {
fcmClient = fcm.NewFakePushClient()
}
// TODO: use client in later phase.
_ = fcmClient
pairingApp := pairing.NewApp(signSvc, cfg.PairingRequestTokenValidityDuration)
proxyPairingApp := connection.NewProxyServer("device_id")
syncApp := sync.NewApp(signSvc, cfg.FakeMobilePush)
syncApp := sync.NewApp(signSvc, fcmClient)
proxySyncApp := connection.NewProxyServer("fcm_token")
router := gin.New()

View File

@ -64,9 +64,16 @@ func ExtensionRequestPush(syncingApp *Syncing) gin.HandlerFunc {
return
}
log.Infof("Send push to mobile %q: %q", fcmToken, req.Body)
resp, err := syncingApp.SendPush(gCtx, fcmToken, req.Body)
if err != nil {
log.Errorf("Failed to send push message: %v", err)
gCtx.Status(http.StatusInternalServerError)
return
}
gCtx.Status(http.StatusOK)
gCtx.JSON(http.StatusOK, map[string]string{
"response": string(resp),
})
}
}

View File

@ -39,7 +39,7 @@ func (s *MemoryStore) ConfirmSync(fcmToken string) bool {
return true
}
func (s *MemoryStore) IsSyncCofirmed(fcmToken string) bool {
func (s *MemoryStore) IsSyncConfirmed(fcmToken string) bool {
v, ok := s.getItem(fcmToken)
if !ok {
return false

View File

@ -7,29 +7,33 @@ import (
"net/http"
"time"
"firebase.google.com/go/v4/messaging"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/twofas/2fas-server/internal/common/logging"
"github.com/twofas/2fas-server/internal/pass/connection"
"github.com/twofas/2fas-server/internal/pass/fcm"
"github.com/twofas/2fas-server/internal/pass/sign"
)
type Syncing struct {
store store
signSvc *sign.Service
store store
signSvc *sign.Service
fcmClient fcm.Client
}
type store interface {
RequestSync(fmtToken string)
ConfirmSync(fmtToken string) bool
IsSyncCofirmed(fmtToken string) bool
RequestSync(fcmToken string)
ConfirmSync(fcmToken string) bool
IsSyncConfirmed(fcmToken string) bool
}
func NewApp(signService *sign.Service, fakeMobilePush bool) *Syncing {
func NewApp(signService *sign.Service, fcmClient fcm.Client) *Syncing {
return &Syncing{
store: NewMemoryStore(),
signSvc: signService,
store: NewMemoryStore(),
signSvc: signService,
fcmClient: fcmClient,
}
}
@ -96,7 +100,7 @@ func (s *Syncing) ServeSyncingRequestWS(w http.ResponseWriter, r *http.Request,
}
func (s *Syncing) isSyncConfirmed(ctx context.Context, fcmToken string) bool {
return s.store.IsSyncCofirmed(fcmToken)
return s.store.IsSyncConfirmed(fcmToken)
}
func (s *Syncing) requestSync(ctx context.Context, fcmToken string) {
@ -178,3 +182,18 @@ func (s *Syncing) RequestSync(ctx *gin.Context, token string) (RequestSyncRespon
MobileConfirmToken: mobileConfirmToken,
}, nil
}
func (s *Syncing) SendPush(ctx *gin.Context, token string, body string) (fcm.Response, error) {
msg := &messaging.Message{
Token: token,
Android: &messaging.AndroidConfig{
Notification: &messaging.AndroidNotification{
Title: "2pass push request",
},
Data: map[string]string{
"data": body,
},
},
}
return s.fcmClient.Send(ctx, msg)
}