diff --git a/handler/auth/totp/totp.go b/handler/auth/totp/totp.go index e095fb5..cce1782 100644 --- a/handler/auth/totp/totp.go +++ b/handler/auth/totp/totp.go @@ -2,7 +2,6 @@ package totpHandler import ( "errors" - "fmt" "github.com/fossyy/filekeeper/session" "github.com/fossyy/filekeeper/types" "github.com/fossyy/filekeeper/utils" @@ -19,7 +18,10 @@ func GET(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) return } - component := totpView.Main("Filekeeper - 2FA Page") + component := totpView.Main("Filekeeper - 2FA Page", types.Message{ + Code: 1, + Message: "", + }) err := component.Render(r.Context(), w) if err != nil { w.WriteHeader(http.StatusInternalServerError) @@ -73,7 +75,16 @@ func POST(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, cookie.Value, http.StatusSeeOther) return } else { - fmt.Fprint(w, "wrong") + component := totpView.Main("Filekeeper - 2FA Page", types.Message{ + Code: 0, + Message: "Incorrect code. Please try again with the latest code from your authentication app.", + }) + err := component.Render(r.Context(), w) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + } } diff --git a/handler/user/session/terminate/terminate.go b/handler/user/session/terminate/terminate.go new file mode 100644 index 0000000..95caf0a --- /dev/null +++ b/handler/user/session/terminate/terminate.go @@ -0,0 +1,23 @@ +package userSessionTerminateHandler + +import ( + "github.com/fossyy/filekeeper/session" + userView "github.com/fossyy/filekeeper/view/user" + "net/http" +) + +func DELETE(w http.ResponseWriter, r *http.Request) { + id := r.PathValue("id") + + _, mySession, _ := session.GetSession(r) + otherSession, _ := session.Get(id) + otherSession.Delete() + session.RemoveSessionInfo(mySession.Email, otherSession.ID) + + component := userView.SessionTable(session.GetSessions(mySession.Email)) + err := component.Render(r.Context(), w) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } +} diff --git a/handler/user/totp/setup.go b/handler/user/totp/setup.go index 065d8c6..6c60740 100644 --- a/handler/user/totp/setup.go +++ b/handler/user/totp/setup.go @@ -42,7 +42,10 @@ func GET(w http.ResponseWriter, r *http.Request) { return } - component := userTotpSetupView.Main("Filekeeper - 2FA Setup Page", base64Str, secret, userSession) + component := userTotpSetupView.Main("Filekeeper - 2FA Setup Page", base64Str, secret, userSession, types.Message{ + Code: 3, + Message: "", + }) if err := component.Render(r.Context(), w); err != nil { w.WriteHeader(http.StatusInternalServerError) return @@ -59,25 +62,34 @@ func POST(w http.ResponseWriter, r *http.Request) { secret := r.Form.Get("secret") totp := gotp.NewDefaultTOTP(secret) userSession := r.Context().Value("user").(types.User) + uri := totp.ProvisioningUri(userSession.Email, "filekeeper") + + base64Str, err := generateQRCode(uri) + if err != nil { + fmt.Printf("%v\n", err) + w.WriteHeader(http.StatusInternalServerError) + return + } if totp.Verify(code, time.Now().Unix()) { if err := db.DB.InitializeTotp(userSession.Email, secret); err != nil { w.WriteHeader(http.StatusInternalServerError) return } cache.DeleteUser(userSession.Email) - fmt.Fprint(w, "Authentication successful! Access granted.") - return - } else { - uri := totp.ProvisioningUri(userSession.Email, "filekeeper") - - base64Str, err := generateQRCode(uri) - if err != nil { - fmt.Printf("%v\n", err) + component := userTotpSetupView.Main("Filekeeper - 2FA Setup Page", base64Str, secret, userSession, types.Message{ + Code: 1, + Message: "Your TOTP setup is complete! Your account is now more secure.", + }) + if err := component.Render(r.Context(), w); err != nil { w.WriteHeader(http.StatusInternalServerError) return } - - component := userTotpSetupView.Main("Filekeeper - 2FA Setup Page", base64Str, secret, userSession) + return + } else { + component := userTotpSetupView.Main("Filekeeper - 2FA Setup Page", base64Str, secret, userSession, types.Message{ + Code: 0, + Message: "The code you entered is incorrect. Please double-check the code and try again.", + }) if err := component.Render(r.Context(), w); err != nil { w.WriteHeader(http.StatusInternalServerError) return diff --git a/routes/routes.go b/routes/routes.go index 8948521..6ff4877 100644 --- a/routes/routes.go +++ b/routes/routes.go @@ -17,6 +17,7 @@ import ( uploadHandler "github.com/fossyy/filekeeper/handler/upload" "github.com/fossyy/filekeeper/handler/upload/initialisation" userHandler "github.com/fossyy/filekeeper/handler/user" + userSessionTerminateHandler "github.com/fossyy/filekeeper/handler/user/session/terminate" userHandlerTotpSetup "github.com/fossyy/filekeeper/handler/user/totp" "github.com/fossyy/filekeeper/middleware" "net/http" @@ -25,194 +26,114 @@ import ( func SetupRoutes() *http.ServeMux { handler := http.NewServeMux() - handler.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - switch r.RequestURI { - case "/": - switch r.Method { - case http.MethodGet: - indexHandler.GET(w, r) - default: - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - } - default: - w.WriteHeader(http.StatusNotFound) - } + handler.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) { + indexHandler.GET(w, r) }) - authRouter := http.NewServeMux() - handler.Handle("/auth/", http.StripPrefix("/auth", authRouter)) - authRouter.HandleFunc("/google", func(w http.ResponseWriter, r *http.Request) { - switch r.Method { - case http.MethodGet: - middleware.Guest(googleOauthHandler.GET, w, r) - default: - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - } + handler.HandleFunc("GET /auth/google", func(w http.ResponseWriter, r *http.Request) { + middleware.Guest(googleOauthHandler.GET, w, r) }) - authRouter.HandleFunc("/totp", func(w http.ResponseWriter, r *http.Request) { - switch r.Method { - case http.MethodGet: - middleware.Guest(totpHandler.GET, w, r) - case http.MethodPost: - middleware.Guest(totpHandler.POST, w, r) - default: - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - } + handler.HandleFunc("GET /auth/totp", func(w http.ResponseWriter, r *http.Request) { + middleware.Guest(totpHandler.GET, w, r) }) - authRouter.HandleFunc("/google/callback", func(w http.ResponseWriter, r *http.Request) { - switch r.Method { - case http.MethodGet: - middleware.Guest(googleOauthCallbackHandler.GET, w, r) - default: - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - } + handler.HandleFunc("POST /auth/totp", func(w http.ResponseWriter, r *http.Request) { + middleware.Guest(totpHandler.POST, w, r) }) - authRouter.HandleFunc("/google/setup/{code}", func(w http.ResponseWriter, r *http.Request) { - switch r.Method { - case http.MethodGet: - middleware.Guest(googleOauthSetupHandler.GET, w, r) - case http.MethodPost: - middleware.Guest(googleOauthSetupHandler.POST, w, r) - default: - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - } + handler.HandleFunc("GET /auth/google/callback", func(w http.ResponseWriter, r *http.Request) { + middleware.Guest(googleOauthCallbackHandler.GET, w, r) }) - handler.HandleFunc("/signin", func(w http.ResponseWriter, r *http.Request) { - switch r.Method { - case http.MethodGet: - middleware.Guest(signinHandler.GET, w, r) - case http.MethodPost: - middleware.Guest(signinHandler.POST, w, r) - } + handler.HandleFunc("GET /auth/google/setup/{code}", func(w http.ResponseWriter, r *http.Request) { + middleware.Guest(googleOauthSetupHandler.GET, w, r) + }) + handler.HandleFunc("POST /auth/google/setup/{code}", func(w http.ResponseWriter, r *http.Request) { + middleware.Guest(googleOauthSetupHandler.POST, w, r) }) - signupRouter := http.NewServeMux() - handler.Handle("/signup/", http.StripPrefix("/signup", signupRouter)) - - signupRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - switch r.Method { - case http.MethodGet: - middleware.Guest(signupHandler.GET, w, r) - case http.MethodPost: - middleware.Guest(signupHandler.POST, w, r) - default: - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - } + handler.HandleFunc("GET /signin", func(w http.ResponseWriter, r *http.Request) { + middleware.Guest(signinHandler.GET, w, r) }) - signupRouter.HandleFunc("/verify/{code}", func(w http.ResponseWriter, r *http.Request) { + handler.HandleFunc("POST /signin", func(w http.ResponseWriter, r *http.Request) { + middleware.Guest(signinHandler.POST, w, r) + }) + + handler.HandleFunc("GET /signup", func(w http.ResponseWriter, r *http.Request) { + middleware.Guest(signupHandler.GET, w, r) + }) + + handler.HandleFunc("POST /signup", func(w http.ResponseWriter, r *http.Request) { + middleware.Guest(signupHandler.POST, w, r) + }) + + handler.HandleFunc("GET /signup/verify/{code}", func(w http.ResponseWriter, r *http.Request) { middleware.Guest(signupVerifyHandler.GET, w, r) }) - forgotPasswordRouter := http.NewServeMux() - handler.Handle("/forgot-password/", http.StripPrefix("/forgot-password", forgotPasswordRouter)) - forgotPasswordRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - switch r.Method { - case http.MethodGet: - middleware.Guest(forgotPasswordHandler.GET, w, r) - case http.MethodPost: - middleware.Guest(forgotPasswordHandler.POST, w, r) - default: - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - } + handler.HandleFunc("GET /forgot-password", func(w http.ResponseWriter, r *http.Request) { + middleware.Guest(forgotPasswordHandler.GET, w, r) }) - forgotPasswordRouter.HandleFunc("/verify/{code}", func(w http.ResponseWriter, r *http.Request) { - switch r.Method { - case http.MethodGet: - middleware.Guest(forgotPasswordVerifyHandler.GET, w, r) - case http.MethodPost: - middleware.Guest(forgotPasswordVerifyHandler.POST, w, r) - default: - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - } + handler.HandleFunc("POST /forgot-password", func(w http.ResponseWriter, r *http.Request) { + middleware.Guest(forgotPasswordHandler.POST, w, r) }) - handler.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) { - switch r.Method { - case http.MethodGet: - middleware.Auth(userHandler.GET, w, r) - default: - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - } + handler.HandleFunc("GET /forgot-password/verify/{code}", func(w http.ResponseWriter, r *http.Request) { + middleware.Guest(forgotPasswordVerifyHandler.GET, w, r) }) - handler.HandleFunc("/user/totp/setup", func(w http.ResponseWriter, r *http.Request) { - switch r.Method { - case http.MethodGet: - middleware.Auth(userHandlerTotpSetup.GET, w, r) - case http.MethodPost: - middleware.Auth(userHandlerTotpSetup.POST, w, r) - default: - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - } + handler.HandleFunc("POST /forgot-password/verify/{code}", func(w http.ResponseWriter, r *http.Request) { + middleware.Guest(forgotPasswordVerifyHandler.POST, w, r) }) - // Upload router - uploadRouter := http.NewServeMux() - handler.Handle("/upload/", http.StripPrefix("/upload", uploadRouter)) - - uploadRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - switch r.Method { - case http.MethodGet: - middleware.Auth(uploadHandler.GET, w, r) - default: - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - } + handler.HandleFunc("GET /user", func(w http.ResponseWriter, r *http.Request) { + middleware.Auth(userHandler.GET, w, r) }) - uploadRouter.HandleFunc("/{id}", func(w http.ResponseWriter, r *http.Request) { - switch r.Method { - case http.MethodPost: - middleware.Auth(uploadHandler.POST, w, r) - default: - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - } + handler.HandleFunc("DELETE /user/session/terminate/{id}", func(w http.ResponseWriter, r *http.Request) { + middleware.Auth(userSessionTerminateHandler.DELETE, w, r) }) - uploadRouter.HandleFunc("/init", func(w http.ResponseWriter, r *http.Request) { - switch r.Method { - case http.MethodPost: - middleware.Auth(initialisation.POST, w, r) - default: - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - } + handler.HandleFunc("GET /user/totp/setup", func(w http.ResponseWriter, r *http.Request) { + middleware.Auth(userHandlerTotpSetup.GET, w, r) }) - // Download router - downloadRouter := http.NewServeMux() - handler.Handle("/download/", http.StripPrefix("/download", downloadRouter)) - downloadRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - switch r.Method { - case http.MethodGet: - middleware.Auth(downloadHandler.GET, w, r) - default: - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - } + handler.HandleFunc("POST /user/totp/setup", func(w http.ResponseWriter, r *http.Request) { + middleware.Auth(userHandlerTotpSetup.POST, w, r) }) - downloadRouter.HandleFunc("/{id}", func(w http.ResponseWriter, r *http.Request) { - switch r.Method { - case http.MethodGet: - downloadFileHandler.GET(w, r) - default: - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - } + handler.HandleFunc("GET /upload", func(w http.ResponseWriter, r *http.Request) { + middleware.Auth(uploadHandler.GET, w, r) }) - handler.HandleFunc("/logout", func(w http.ResponseWriter, r *http.Request) { + handler.HandleFunc("POST /upload/{id}", func(w http.ResponseWriter, r *http.Request) { + middleware.Auth(uploadHandler.POST, w, r) + }) + + handler.HandleFunc("POST /upload/init", func(w http.ResponseWriter, r *http.Request) { + middleware.Auth(initialisation.POST, w, r) + }) + + handler.HandleFunc("GET /download", func(w http.ResponseWriter, r *http.Request) { + middleware.Auth(downloadHandler.GET, w, r) + }) + + handler.HandleFunc("GET /download/{id}", func(w http.ResponseWriter, r *http.Request) { + downloadFileHandler.GET(w, r) + }) + + handler.HandleFunc("GET /logout", func(w http.ResponseWriter, r *http.Request) { middleware.Auth(logoutHandler.GET, w, r) }) - handler.HandleFunc("/robots.txt", func(w http.ResponseWriter, r *http.Request) { + handler.HandleFunc("GET /robots.txt", func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "public/robots.txt") }) - handler.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) { + handler.HandleFunc("GET /favicon.ico", func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "public/favicon.ico") }) diff --git a/view/totp/totp.templ b/view/totp/totp.templ index 3694d1c..a645c11 100644 --- a/view/totp/totp.templ +++ b/view/totp/totp.templ @@ -2,17 +2,30 @@ package totpView import ( "github.com/fossyy/filekeeper/view/layout" + "github.com/fossyy/filekeeper/types" ) -templ content(title string) { +templ content(title string, msg types.Message) { @layout.Base(title){
+ switch msg.Code { + case 0: + + }

Verify Your Identity

- Enter the 6-digit code sent to your registered device to complete the login process. + Please enter the 6-digit code generated by your authentication app to complete the login process.

@@ -36,7 +49,7 @@ templ content(title string) {
} @@ -262,6 +262,33 @@ templ content(title string, user types.User, ListSession []*session.SessionInfo) } } +templ SessionTable(ListSession []*session.SessionInfo){ + + for _, ses := range ListSession { + + {ses.IP} + + {ses.Browser + ses.Version} + + {ses.OS + ses.OSVersion} + + {ses.AccessAt} + + + + + + } + +} + templ Main(title string, user types.User, ListSession []*session.SessionInfo) { @content(title, user, ListSession) } \ No newline at end of file