1
0
mirror of https://github.com/H0llyW00dzZ/fiber2fa.git synced 2025-02-06 11:02:32 +00:00
fiber2fa/qrcode.go
H0llyW00dzZ 0cb92d3234
Docs Refactor TODO Comment (#91)
* Docs Refactor TODO Comment

- [+] docs(qrcode.go): clarify QRCodePath function and improve grammar in comments
- [+] refactor(qrcode.go): suggest improving GenerateQRcodePath without relying on filesystem

* Improve TODO Comments

- [+] refactor(qrcode): add note about improving QRCodePath method without relying on filesystem
2024-05-30 19:09:19 +07:00

146 lines
4.8 KiB
Go

// Copyright (c) 2024 H0llyW00dz All rights reserved.
//
// License: BSD 3-Clause License
package twofa
import (
"bytes"
"fmt"
"image"
"image/png"
"net/url"
"strings"
"github.com/gofiber/fiber/v2"
"github.com/skip2/go-qrcode"
)
// GenerateQRcodePath generates the QR code image for the 2FA secret key and stores the QR code data.
//
// TODO: Improve this function by using an otpverifier (internal) package.
// The QRCodePath (this function) should be the location where the user wants to scan the QR code. For example, if the user registers from example.com/2fa/register,
// then this is the place for the QR code image: example.com/2fa/register/scanqrcode/b689a842-065f-4664-xxxx-xxxxxxxxx.png (note: "b689a842-065f-4664-xxxx-xxxxxxxxx" is a UUID).
// After the user completes scanning the QR code at example.com/2fa/register, this path will redirect to another page using c.Next().
// Also Note that It's possible to improve this method using the above approach without relying on or needing a filesystem such as a file manager (e.g., for storing the QR code image), since this is written in Go.
func (m *Middleware) GenerateQRcodePath(c *fiber.Ctx) error {
// Get the context key from c.Locals
contextKey, err := m.getContextKey(c)
if err != nil {
return m.SendUnauthorizedResponse(c, err)
}
// Retrieve the 2FA information from the storage
info, err := m.getInfoFromStorage(contextKey)
if err != nil {
return m.SendInternalErrorResponse(c, err)
}
if info == nil {
return m.SendUnauthorizedResponse(c, fiber.NewError(fiber.StatusUnauthorized, "2FA information not found"))
}
// Check if the user is already registered
if info.IsRegistered() {
// User is already registered, skip generating the QR code
return c.Next()
}
// Generate the QR code image and data
qrCodeImage, qrCodeData, err := m.generateQRCode(c, info)
if err != nil {
return m.SendInternalErrorResponse(c, err)
}
// Set the QR code data in the Info struct
info.SetQRCodeData(qrCodeData)
// Update the Info struct in the storage
err = m.updateInfoInStorage(contextKey)
if err != nil {
return m.SendInternalErrorResponse(c, err)
}
// Set the response headers
c.Set(fiber.HeaderContentType, "image/png")
// Write the QR code image to the response
err = png.Encode(c, qrCodeImage)
if err != nil {
return m.SendInternalErrorResponse(c, err)
}
return nil
}
// generateQRCode generates the QR code image and data based on the provided Info struct.
func (m *Middleware) generateQRCode(c *fiber.Ctx, info *Info) (image.Image, []byte, error) {
// Get the value of the context key
contextValue := info.ContextKey
// Generate the identifier using the configured identifier generator
identifier := m.GenerateIdentifier(c)
// Generate the QR code content
secretKey := info.GetSecret()
// Create a slice to hold the arguments for fmt.Sprintf
args := make([]any, 0) // zero allocation
args = append(args, url.QueryEscape(m.Config.Issuer), url.QueryEscape(contextValue), url.QueryEscape(secretKey), url.QueryEscape(m.Config.Issuer))
// Add additional arguments based on the placeholders in the Content template
numPlaceholders := strings.Count(m.Config.QRCode.Content, "%")
for i := 3; i < numPlaceholders; i++ { // Set the default starting index to 3 to avoid writing tests again for custom content.
// Get the value for the additional argument from the request context or configuration
argValue := c.Query(fmt.Sprintf("arg%d", i))
args = append(args, url.QueryEscape(argValue))
}
// Generate the QR code content using fmt.Sprintf with the variable arguments
qrCodeContent := fmt.Sprintf(m.Config.QRCode.Content, args...)
// Check if a custom QR code image is provided in the configuration
if m.Config.QRCode.Image != nil {
// Encode the custom QR code image to PNG format
var buf bytes.Buffer
err := png.Encode(&buf, m.Config.QRCode.Image)
if err != nil {
return nil, nil, err
}
return m.Config.QRCode.Image, buf.Bytes(), nil
}
// Generate the QR code
var qrCode *qrcode.QRCode
var err error
if m.Config.Encode.VersionNumber != 0 {
// Generate the QR code with the specified version number
qrCode, err = qrcode.NewWithForcedVersion(qrCodeContent, m.Config.Encode.VersionNumber, m.Config.Encode.Level)
} else {
// Generate the QR code with automatic version determination
qrCode, err = qrcode.New(qrCodeContent, m.Config.Encode.Level)
}
if err != nil {
return nil, nil, err
}
// Encode the QR code as PNG
qrCodeImage := qrCode.Image(m.Config.Encode.Size)
// Encode the QR code image to PNG format
var buf bytes.Buffer
err = png.Encode(&buf, qrCodeImage)
if err != nil {
return nil, nil, err
}
// Set the registration status to true
info.SetRegistered(true)
// Set the identifier in the Info struct
info.SetIdentifier(identifier)
return qrCodeImage, buf.Bytes(), nil
}