diff --git a/handler/user/totp/setup.go b/handler/user/totp/setup.go index 4b73e86..2af54ec 100644 --- a/handler/user/totp/setup.go +++ b/handler/user/totp/setup.go @@ -4,82 +4,80 @@ import ( "bytes" "encoding/base64" "fmt" - "github.com/fossyy/filekeeper/db" - "github.com/fossyy/filekeeper/types" - "github.com/fossyy/filekeeper/utils" userTotpSetupView "github.com/fossyy/filekeeper/view/user/totp" - "github.com/skip2/go-qrcode" - "github.com/xlzd/gotp" "image/png" "net/http" "time" + + "github.com/fossyy/filekeeper/db" + "github.com/fossyy/filekeeper/types" + "github.com/skip2/go-qrcode" + "github.com/xlzd/gotp" ) +func generateQRCode(uri string) (string, error) { + qr, err := qrcode.New(uri, qrcode.Medium) + if err != nil { + return "", fmt.Errorf("failed to generate QR code: %w", err) + } + + var buffer bytes.Buffer + if err := png.Encode(&buffer, qr.Image(256)); err != nil { + return "", fmt.Errorf("failed to encode QR code to PNG: %w", err) + } + + return base64.StdEncoding.EncodeToString(buffer.Bytes()), nil +} + func GET(w http.ResponseWriter, r *http.Request) { secret := gotp.RandomSecret(16) userSession := r.Context().Value("user").(types.User) totp := gotp.NewDefaultTOTP(secret) - uri := totp.ProvisioningUri(userSession.Email, utils.Getenv("DOMAIN")) - qr, err := qrcode.New(uri, qrcode.Medium) + uri := totp.ProvisioningUri(userSession.Email, "filekeeper") + base64Str, err := generateQRCode(uri) if err != nil { - fmt.Printf("Failed to generate QR code: %v", err) + fmt.Printf("%v\n", err) w.WriteHeader(http.StatusInternalServerError) return } - var buffer bytes.Buffer - err = png.Encode(&buffer, qr.Image(256)) - if err != nil { - fmt.Printf("Failed to encode QR code to PNG: %v", err) - w.WriteHeader(http.StatusInternalServerError) - return - } - base64Str := base64.StdEncoding.EncodeToString(buffer.Bytes()) - component := userTotpSetupView.Main("Totp setup page", base64Str, secret) - err = component.Render(r.Context(), w) - if err != nil { + component := userTotpSetupView.Main("Filekeeper - 2FA Setup Page", base64Str, secret, userSession) + if err := component.Render(r.Context(), w); err != nil { w.WriteHeader(http.StatusInternalServerError) return } } func POST(w http.ResponseWriter, r *http.Request) { - r.ParseForm() + if err := r.ParseForm(); err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + code := r.Form.Get("totp") secret := r.Form.Get("secret") totp := gotp.NewDefaultTOTP(secret) userSession := r.Context().Value("user").(types.User) - fmt.Println(userSession) if totp.Verify(code, time.Now().Unix()) { - err := db.DB.InitializeTotp(userSession.Email, secret) - if err != nil { + if err := db.DB.InitializeTotp(userSession.Email, secret); err != nil { w.WriteHeader(http.StatusInternalServerError) return } - fmt.Fprintf(w, "Authentication successful! Access granted.") - return + fmt.Fprint(w, "Authentication successful! Access granted.") } else { - uri := totp.ProvisioningUri(userSession.Email, utils.Getenv("DOMAIN")) - qr, err := qrcode.New(uri, qrcode.Medium) + uri := totp.ProvisioningUri(userSession.Email, "filekeeper") + + base64Str, err := generateQRCode(uri) if err != nil { - fmt.Printf("Failed to generate QR code: %v", err) + fmt.Printf("%v\n", err) w.WriteHeader(http.StatusInternalServerError) return } - var buffer bytes.Buffer - err = png.Encode(&buffer, qr.Image(256)) - if err != nil { - fmt.Printf("Failed to encode QR code to PNG: %v", err) + + component := userTotpSetupView.Main("Filekeeper - 2FA Setup Page", base64Str, secret, userSession) + if err := component.Render(r.Context(), w); err != nil { w.WriteHeader(http.StatusInternalServerError) return } - base64Str := base64.StdEncoding.EncodeToString(buffer.Bytes()) - component := userTotpSetupView.Main("Totp setup page", base64Str, secret) - err = component.Render(r.Context(), w) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } - return } } diff --git a/view/user/totp/setup.templ b/view/user/totp/setup.templ index aea14a3..1aedcad 100644 --- a/view/user/totp/setup.templ +++ b/view/user/totp/setup.templ @@ -2,17 +2,51 @@ package userTotpSetupView import ( "github.com/fossyy/filekeeper/view/layout" + "github.com/fossyy/filekeeper/types" ) -templ content(title string, qrcode string, code string) { +templ content(title string, qrcode string, code string, user types.User) { @layout.Base(title){ + @layout.Navbar(user)
-
-
-

Set up Two-Factor Authentication

+
+
+
+ + + + + + +

Set up Two-Factor Authentication

+

Secure your account with time-based one-time passwords (TOTP).

+
+

Here's how to set up the Google Authenticator app:

+
    +
  1. Download the Google Authenticator app on your mobile device.
  2. +
  3. Open the app and tap "Begin Setup".
  4. +
  5. Select "Scan a barcode" and point your camera at the QR code below.
  6. +
  7. The app will automatically add your account and display a 6-digit code.
  8. +
  9. Enter this code on the website to complete the setup.
  10. +
+
-
+
-

{code}

+
+
+

Backup Code:

+
12345-67890
+

TOTP Secret:

+
+ {code} +
@@ -32,12 +73,12 @@ templ content(title string, qrcode string, code string) { for="totp"> Totp Code - +
-
@@ -51,6 +92,6 @@ templ content(title string, qrcode string, code string) { } } -templ Main(title string, qrcode string, code string) { - @content(title, qrcode, code) +templ Main(title string, qrcode string, code string, user types.User) { + @content(title, qrcode, code, user) } \ No newline at end of file diff --git a/view/user/user.templ b/view/user/user.templ index 1bec6e0..f6f2084 100644 --- a/view/user/user.templ +++ b/view/user/user.templ @@ -75,7 +75,7 @@ templ content(title string, user types.User, ListSession []*session.SessionInfo) class="hover:bg-gray-200 inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 px-4 py-2" type="button" id="radix-:rq:" aria-haspopup="menu" aria-expanded="false" data-state="closed" - href="/user/totp/setup"> + href="/user/totp/setup" hx-get="/user/totp/setup" hx-swap="outerHTML" hx-push-url="true" hx-target="#content"> Setup