Skip to content

Commit 0165044

Browse files
committed
feat: integrate QR code functionality using qrcode library
- Added QRCodeDisplay component for rendering QR codes. - Updated MFASettings component to use QRCodeDisplay for MFA setup. - Modified TunnelStatus component to display QR codes using QRCodeDisplay. - Refactored ChannelsView to utilize QRCodeDisplay for WeChat ILink setup. - Removed unused QR code properties from API responses and components. - Updated tests for MFASettings, TunnelStatus, and ChannelsView to accommodate QR code changes. - Added type definitions for qrcode library.
1 parent fff0655 commit 0165044

51 files changed

Lines changed: 4272 additions & 965 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

server/go.mod

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,10 @@ require (
3636
github.com/nlpodyssey/cybertron v0.2.1
3737
github.com/orca-zhang/ecache v1.1.3
3838
github.com/orca-zhang/ecache2 v0.0.1
39-
github.com/pquerna/otp v1.5.0
4039
github.com/prometheus/client_golang v1.23.2
4140
github.com/robfig/cron/v3 v3.0.1
4241
github.com/rs/zerolog v1.34.0
4342
github.com/shirou/gopsutil/v3 v3.24.5
44-
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
4543
github.com/sourcegraph/conc v0.3.0
4644
github.com/spf13/cobra v1.10.2
4745
github.com/stretchr/testify v1.11.1
@@ -69,7 +67,6 @@ require (
6967
github.com/BurntSushi/toml v1.6.0 // indirect
7068
github.com/apparentlymart/go-cidr v1.1.0 // indirect
7169
github.com/beorn7/perks v1.0.1 // indirect
72-
github.com/boombuler/barcode v1.0.1 // indirect
7370
github.com/cespare/xxhash/v2 v2.3.0 // indirect
7471
github.com/coredns/caddy v1.1.1 // indirect
7572
github.com/coredns/coredns v1.11.3 // indirect

server/go.sum

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@ github.com/asg017/sqlite-vec-go-bindings v0.1.6 h1:Nx0jAzyS38XpkKznJ9xQjFXz2X9tI
1414
github.com/asg017/sqlite-vec-go-bindings v0.1.6/go.mod h1:A8+cTt/nKFsYCQF6OgzSNpKZrzNo5gQsXBTfsXHXY0Q=
1515
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
1616
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
17-
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
18-
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
19-
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
2017
github.com/bwmarrin/discordgo v0.29.0 h1:FmWeXFaKUwrcL3Cx65c20bTRW+vOb6k8AnaP+EgjDno=
2118
github.com/bwmarrin/discordgo v0.29.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
2219
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -299,8 +296,6 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
299296
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
300297
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
301298
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
302-
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
303-
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
304299
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
305300
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
306301
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
@@ -326,8 +321,6 @@ github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFt
326321
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
327322
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
328323
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
329-
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
330-
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
331324
github.com/smarty/assertions v1.16.0 h1:EvHNkdRA4QHMrn75NZSoUQ/mAUXAYWfatfB01yTCzfY=
332325
github.com/smarty/assertions v1.16.0/go.mod h1:duaaFdCS0K9dnoM50iyek/eYINOZ64gbh1Xlf6LG7AI=
333326
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=

server/internal/api/remote_access.go

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ func (h *RemoteAccessHandler) GetRemoteAccessStatus(c echo.Context) error {
192192
})
193193
}
194194

195-
// GetQRCode returns a QR code for the current tunnel URL.
195+
// GetQRCode returns the raw QR payload for the current tunnel URL.
196196
func (h *RemoteAccessHandler) GetQRCode(c echo.Context) error {
197197
// Check if tunnel is running
198198
if !h.tunnelManager.IsRunning() {
@@ -211,19 +211,10 @@ func (h *RemoteAccessHandler) GetQRCode(c echo.Context) error {
211211
})
212212
}
213213

214-
// Generate QR code
215-
qrData, err := ngrok.GenerateQRCode(url, 200)
216-
if err != nil {
217-
return c.JSON(http.StatusInternalServerError, map[string]interface{}{
218-
"success": false,
219-
"error": err.Error(),
220-
})
221-
}
222-
223214
return c.JSON(http.StatusOK, map[string]interface{}{
224215
"success": true,
225216
"url": url,
226-
"qrcode": qrData,
217+
"qr_url": url,
227218
})
228219
}
229220

server/internal/api/remote_access_sdk.go

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ func (h *SDKRemoteAccessHandler) GetRemoteAccessStatus(c echo.Context) error {
123123
})
124124
}
125125

126-
// GetQRCode generates a QR code for the tunnel URL with embedded auth token.
126+
// GetQRCode returns the raw QR payload for the tunnel URL with embedded auth token.
127127
func (h *SDKRemoteAccessHandler) GetQRCode(c echo.Context) error {
128128
tunnelURL := h.tunnelManager.GetURL()
129129
if tunnelURL == "" {
@@ -133,29 +133,12 @@ func (h *SDKRemoteAccessHandler) GetQRCode(c echo.Context) error {
133133
})
134134
}
135135

136-
// Build QR URL: tunnel + /chat + token (if JWT service available)
137-
qrURL := tunnelURL + "/chat"
138-
if h.jwtService != nil {
139-
if userClaims := auth.GetUserFromContext(c); userClaims != nil {
140-
token, err := h.jwtService.GenerateAccessToken(userClaims)
141-
if err == nil {
142-
qrURL += "?access_token=" + token
143-
}
144-
}
145-
}
146-
147-
qrcode, err := ngrok.GenerateQRCode(qrURL, 200)
148-
if err != nil {
149-
return c.JSON(http.StatusInternalServerError, map[string]interface{}{
150-
"success": false,
151-
"error": err.Error(),
152-
})
153-
}
136+
qrURL := buildTunnelQRURL(tunnelURL, h.jwtService, auth.GetUserFromContext(c))
154137

155138
return c.JSON(http.StatusOK, map[string]interface{}{
156139
"success": true,
157140
"url": tunnelURL,
158-
"qrcode": qrcode,
141+
"qr_url": qrURL,
159142
})
160143
}
161144

server/internal/api/tunnel_handler.go

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,7 @@ func (h *TunnelHandler) GetTunnelStatus(c echo.Context) error {
416416
})
417417
}
418418

419-
// GetQRCode generates a QR code for the tunnel URL.
419+
// GetQRCode returns the raw QR payload for the tunnel URL.
420420
func (h *TunnelHandler) GetQRCode(c echo.Context) error {
421421
h.mu.RLock()
422422
active := h.active
@@ -437,29 +437,12 @@ func (h *TunnelHandler) GetQRCode(c echo.Context) error {
437437
})
438438
}
439439

440-
// Build QR URL: tunnel + /chat + token (if JWT service available)
441-
qrURL := url + "/chat"
442-
if h.jwtService != nil {
443-
if userClaims := auth.GetUserFromContext(c); userClaims != nil {
444-
token, err := h.jwtService.GenerateAccessToken(userClaims)
445-
if err == nil {
446-
qrURL += "?access_token=" + token
447-
}
448-
}
449-
}
450-
451-
qrcode, err := ngrok.GenerateQRCode(qrURL, 200)
452-
if err != nil {
453-
return c.JSON(http.StatusInternalServerError, map[string]interface{}{
454-
"success": false,
455-
"error": err.Error(),
456-
})
457-
}
440+
qrURL := buildTunnelQRURL(url, h.jwtService, auth.GetUserFromContext(c))
458441

459442
return c.JSON(http.StatusOK, map[string]interface{}{
460443
"success": true,
461444
"url": url,
462-
"qrcode": qrcode,
445+
"qr_url": qrURL,
463446
})
464447
}
465448

server/internal/api/tunnel_handler_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,46 @@ func TestTunnelHandler_GetQRCode_NoActiveTunnel(t *testing.T) {
368368
}
369369
}
370370

371+
func TestTunnelHandler_GetQRCode_ReturnsRawQRURLWithoutImageData(t *testing.T) {
372+
h := NewTunnelHandler(nil, 80)
373+
manager := &stubTunnelManager{
374+
provider: tunnel.ProviderAuto,
375+
running: true,
376+
url: "https://blue.example.com",
377+
}
378+
h.active = manager
379+
380+
e := echo.New()
381+
h.RegisterRoutes(e)
382+
383+
req := httptest.NewRequest(http.MethodGet, "/api/v1/tunnel/qrcode", nil)
384+
rec := httptest.NewRecorder()
385+
386+
e.ServeHTTP(rec, req)
387+
388+
if rec.Code != http.StatusOK {
389+
t.Fatalf("Status code = %d, want %d body=%s", rec.Code, http.StatusOK, rec.Body.String())
390+
}
391+
392+
var resp map[string]interface{}
393+
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
394+
t.Fatalf("Failed to unmarshal response: %v", err)
395+
}
396+
397+
if resp["success"] != true {
398+
t.Fatal("Expected success to be true")
399+
}
400+
if got := resp["url"]; got != "https://blue.example.com" {
401+
t.Fatalf("url = %v, want %q", got, "https://blue.example.com")
402+
}
403+
if got := resp["qr_url"]; got != "https://blue.example.com/chat" {
404+
t.Fatalf("qr_url = %v, want %q", got, "https://blue.example.com/chat")
405+
}
406+
if _, ok := resp["qrcode"]; ok {
407+
t.Fatal("response should not include backend QR image data")
408+
}
409+
}
410+
371411
func TestTunnelHandler_BackwardsCompatibility(t *testing.T) {
372412
_, e := setupTunnelHandler(t)
373413

server/internal/api/tunnel_qr.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package api
2+
3+
import (
4+
"strings"
5+
6+
"github.com/IceWhaleTech/ZimaOS-Blue/server/internal/auth"
7+
)
8+
9+
func buildTunnelQRURL(baseURL string, jwtService *auth.JWTService, userClaims *auth.UserClaims) string {
10+
qrURL := strings.TrimSpace(baseURL)
11+
if qrURL == "" {
12+
return ""
13+
}
14+
15+
qrURL = strings.TrimRight(qrURL, "/") + "/chat"
16+
if jwtService != nil && userClaims != nil {
17+
token, err := jwtService.GenerateAccessToken(userClaims)
18+
if err == nil && strings.TrimSpace(token) != "" {
19+
qrURL += "?access_token=" + token
20+
}
21+
}
22+
23+
return qrURL
24+
}

server/internal/mfa/handler.go

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import (
66
"sync"
77
"time"
88

9+
"github.com/IceWhaleTech/ZimaOS-Blue/server/internal/timeutil"
910
"github.com/go-webauthn/webauthn/protocol"
1011
"github.com/google/uuid"
1112
"github.com/labstack/echo/v4"
12-
"github.com/IceWhaleTech/ZimaOS-Blue/server/internal/timeutil"
1313
)
1414

1515
// Handler handles HTTP requests for MFA operations.
@@ -88,16 +88,10 @@ func (h *Handler) RegisterRoutes(g *echo.Group) {
8888
webauthn.DELETE("/credentials/:id", h.WebAuthnDeleteCredential)
8989
}
9090

91-
// SetupRequest represents a request to start MFA setup.
92-
type SetupRequest struct {
93-
IncludeQRCode bool `json:"include_qr_code"`
94-
}
95-
9691
// SetupResponseDTO represents the response for MFA setup.
9792
type SetupResponseDTO struct {
9893
Secret string `json:"secret"`
9994
URI string `json:"uri"`
100-
QRCode string `json:"qr_code,omitempty"`
10195
}
10296

10397
// Setup handles MFA setup initiation.
@@ -112,12 +106,7 @@ func (h *Handler) Setup(c echo.Context) error {
112106
username = userID.String()
113107
}
114108

115-
var req SetupRequest
116-
if err := c.Bind(&req); err != nil {
117-
req.IncludeQRCode = true // Default to including QR code
118-
}
119-
120-
setup, err := h.totp.Setup(username, req.IncludeQRCode)
109+
setup, err := h.totp.Setup(username)
121110
if err != nil {
122111
return echo.NewHTTPError(http.StatusInternalServerError, "failed to generate MFA setup")
123112
}
@@ -130,7 +119,6 @@ func (h *Handler) Setup(c echo.Context) error {
130119
return c.JSON(http.StatusOK, &SetupResponseDTO{
131120
Secret: setup.Secret,
132121
URI: setup.URI,
133-
QRCode: setup.QRCode,
134122
})
135123
}
136124

@@ -212,9 +200,9 @@ func (h *Handler) Disable(c echo.Context) error {
212200

213201
// StatusResponse represents the MFA status response.
214202
type StatusResponse struct {
215-
Enabled bool `json:"enabled"`
216-
RecoveryCount int `json:"recovery_codes_remaining"`
217-
SetupRequired bool `json:"setup_required,omitempty"`
203+
Enabled bool `json:"enabled"`
204+
RecoveryCount int `json:"recovery_codes_remaining"`
205+
SetupRequired bool `json:"setup_required,omitempty"`
218206
}
219207

220208
// Status handles getting MFA status.

server/internal/mfa/handler_test.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,7 @@ func TestNewHandler(t *testing.T) {
3232
func TestHandler_Setup(t *testing.T) {
3333
handler, e := setupTestHandler()
3434

35-
reqBody := `{"include_qr_code":true}`
36-
req := httptest.NewRequest(http.MethodPost, "/auth/mfa/setup", strings.NewReader(reqBody))
37-
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
35+
req := httptest.NewRequest(http.MethodPost, "/auth/mfa/setup", nil)
3836
rec := httptest.NewRecorder()
3937
c := e.NewContext(req, rec)
4038
c.Set("user_id", uuid.New())
@@ -60,9 +58,6 @@ func TestHandler_Setup(t *testing.T) {
6058
if resp.URI == "" {
6159
t.Error("Setup() URI is empty")
6260
}
63-
if resp.QRCode == "" {
64-
t.Error("Setup() QR code is empty when requested")
65-
}
6661
}
6762

6863
func TestHandler_Setup_Unauthorized(t *testing.T) {

0 commit comments

Comments
 (0)