-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- [+] 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
0 parents
commit dfb1355
Showing
12 changed files
with
1,515 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} | ||
} | ||
}) | ||
} |
Oops, something went wrong.