Skip to content

Commit

Permalink
Initial Commit
Browse files Browse the repository at this point in the history
- [+] feat: add 2FA middleware with TOTP authentication and customizable configuration
- [+] feat: implement cookie-based authentication for 2FA validation
- [+] feat: add support for various storage providers and customizable storage configuration
- [+] feat: provide customizable redirect URL for 2FA validation and ability to skip 2FA for specific paths
- [+] feat: allow customizable JSON marshaling and unmarshaling functions
- [+] feat: add ability to skip middleware based on custom logic
- [+] feat: generate QR code images for 2FA secret keys with customizable barcode path template and image
- [+] feat: support flexible token lookup from various sources (header, query, form, param, cookie)
- [+] feat: manage context keys for storing 2FA information based on account names
- [+] test: add unit tests for the 2FA middleware and its components
- [+] docs: create README with project overview, features, and usage instructions
- [+] chore: add BSD 3-Clause license
- [+] chore: initialize Go module and add required dependencies
- [+] test: add benchmarks for comparing performance with different JSON libraries
  • Loading branch information
H0llyW00dzZ committed May 22, 2024
0 parents commit dfb1355
Show file tree
Hide file tree
Showing 12 changed files with 1,515 additions and 0 deletions.
556 changes: 556 additions & 0 deletions 2fa_test.go

Large diffs are not rendered by default.

28 changes: 28 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
BSD 3-Clause License

Copyright (c) 2024, H0llyW00dzZ

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
57 changes: 57 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Fiber 2FA Middleware

This is a custom 2FA (Two-Factor Authentication) middleware for the Fiber web framework. It provides a secure and easy-to-use solution for implementing 2FA in Fiber applications. The middleware supports TOTP (Time-based One-Time Password) authentication and offers customizable configuration options.

> [!NOTE]
> This 2FA middleware is still a work in progress and may not be stable for use in production environments. Use it with caution and thoroughly test it before deploying to production.
## Features

The middleware provides the following features:

### TOTP Authentication
- Generation and verification of TOTP tokens
- Customizable token length and time step size

### Flexible Storage
- Support for various storage providers (e.g., in-memory, database)
- Customizable storage configuration

### Cookie-based Authentication
- Secure cookie-based authentication for 2FA validation
- Customizable cookie settings (name, expiration, domain, etc.)

### Customizable Redirect
- Configurable redirect URL for 2FA validation
- Ability to skip 2FA for specific paths

### JSON Marshaling and Unmarshaling
- Customizable JSON marshaling and unmarshaling functions
- Support for custom JSON encoding/decoding

### Advanced Configuration
- Customizable context key for storing 2FA information
- Ability to skip middleware based on custom logic

### QR Code Generation
- Automatic generation of QR code images for 2FA secret keys
- Customizable barcode path template
- Support for custom barcode images

### Customizable Token Lookup
- Flexible token lookup from various sources (header, query, form, param, cookie)
- Configurable token lookup string format

### Context Key Management
- Customizable context key for storing 2FA information in the request context
- Ability to retrieve and manage context keys based on account names

More features and validation capabilities will be added in the future to enhance the middleware's functionality and cater to a wider range of validation scenarios.

## Contributing

Contributions are welcome! If you encounter any issues or have suggestions for improvements, please open an issue or submit a pull request.

## License

This project is licensed under the [BSD License](LICENSE).
204 changes: 204 additions & 0 deletions benchmark_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
// Copyright (c) 2024 H0llyW00dz All rights reserved.
//
// License: BSD 3-Clause License

package twofa_test

import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
"time"

twofa "github.com/H0llyW00dzZ/fiber2fa"
"github.com/bytedance/sonic"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/storage/memory/v2"
"github.com/xlzd/gotp"
)

func BenchmarSonicJSONkMiddleware_Handle(b *testing.B) {
// Set up the storage with an in-memory store for simplicity
store := memory.New()
secret := gotp.RandomSecret(16)

// Create a default Info struct and store it for Simulate State
info := twofa.Info{
ContextKey: "gopherBenchmarkOTP1",
Secret: secret,
CookieValue: "",
ExpirationTime: time.Time{},
}
infoJSON, _ := sonic.Marshal(info)
_ = store.Set("gopherBenchmarkOTP1", infoJSON, 0) // Ignoring error for brevity

// Define a middleware instance with default configuration
middleware := twofa.New(twofa.Config{
Secret: secret,
Storage: store,
ContextKey: "gopherBenchmarkOTP1",
RedirectURL: "/2fa",
CookieMaxAge: 86400,
CookieName: "twofa_cookie",
TokenLookup: "header:Authorization",
JSONMarshal: sonic.Marshal,
JSONUnmarshal: sonic.Unmarshal,
})

// Create a new Fiber app and register the middleware
app := fiber.New()
app.Use(func(c *fiber.Ctx) error {
c.Locals("gopherBenchmarkOTP1", "gopherBenchmarkOTP1")
return c.Next()
})
app.Use(middleware)

// Define routes that will be used for testing
app.Get("/", func(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusOK)
})
app.Post("/", func(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusOK)
})

// Generate a valid 2FA token
totp := gotp.NewDefaultTOTP(secret)

// Define the token lookup scenarios
scenarios := []struct {
name string
requestURL string
requestMethod string
requestBody io.Reader
requestHeaders map[string]string
requestCookies []*http.Cookie
expectedStatus int
}{
{
name: "Header",
requestURL: "https://hack/",
requestMethod: "GET",
requestHeaders: map[string]string{
"Authorization": fmt.Sprintf("Bearer %s", totp.Now()),
},
expectedStatus: fiber.StatusOK,
},
}

// Run the benchmark scenarios in parallel
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
for _, scenario := range scenarios {
// Create a new HTTP request for each scenario
req := httptest.NewRequest(scenario.requestMethod, scenario.requestURL, scenario.requestBody)
for key, value := range scenario.requestHeaders {
req.Header.Set(key, value)
}
for _, cookie := range scenario.requestCookies {
req.AddCookie(cookie)
}

// Perform the request
resp, err := app.Test(req)
if err != nil {
b.Fatalf("Failed to perform request: %v", err)
}
resp.Body.Close()
}
}
})
}

func BenchmarStdJSONkMiddleware_Handle(b *testing.B) {
// Set up the storage with an in-memory store for simplicity
store := memory.New()
secret := gotp.RandomSecret(16)

// Create a default Info struct and store it for Simulate State
info := twofa.Info{
ContextKey: "gopherBenchmarkOTP2",
Secret: secret,
CookieValue: "",
ExpirationTime: time.Time{},
}
infoJSON, _ := sonic.Marshal(info)
_ = store.Set("gopherBenchmarkOTP2", infoJSON, 0) // Ignoring error for brevity

// Define a middleware instance with default configuration
middleware := twofa.New(twofa.Config{
Secret: secret,
Storage: store,
ContextKey: "gopherBenchmarkOTP2",
RedirectURL: "/2fa",
CookieMaxAge: 86400,
CookieName: "twofa_cookie",
TokenLookup: "header:Authorization",
})

// Create a new Fiber app and register the middleware
app := fiber.New()
app.Use(func(c *fiber.Ctx) error {
c.Locals("gopherBenchmarkOTP2", "gopherBenchmarkOTP2")
return c.Next()
})
app.Use(middleware)

// Define routes that will be used for testing
app.Get("/", func(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusOK)
})
app.Post("/", func(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusOK)
})

// Generate a valid 2FA token
totp := gotp.NewDefaultTOTP(secret)

// Define the token lookup scenarios
scenarios := []struct {
name string
requestURL string
requestMethod string
requestBody io.Reader
requestHeaders map[string]string
requestCookies []*http.Cookie
expectedStatus int
}{
{
name: "Header",
requestURL: "https://hack/",
requestMethod: "GET",
requestHeaders: map[string]string{
"Authorization": fmt.Sprintf("Bearer %s", totp.Now()),
},
expectedStatus: fiber.StatusOK,
},
}

// Run the benchmark scenarios in parallel
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
for _, scenario := range scenarios {
// Create a new HTTP request for each scenario
req := httptest.NewRequest(scenario.requestMethod, scenario.requestURL, scenario.requestBody)
for key, value := range scenario.requestHeaders {
req.Header.Set(key, value)
}
for _, cookie := range scenario.requestCookies {
req.AddCookie(cookie)
}

// Perform the request
resp, err := app.Test(req)
if err != nil {
b.Fatalf("Failed to perform request: %v", err)
}
resp.Body.Close()
}
}
})
}
Loading

0 comments on commit dfb1355

Please sign in to comment.