diff --git a/handler/auth/google/callback/callback.go b/handler/auth/google/callback/callback.go index 962cbea..8b62297 100644 --- a/handler/auth/google/callback/callback.go +++ b/handler/auth/google/callback/callback.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/fossyy/filekeeper/cache" "github.com/fossyy/filekeeper/db" + googleOauthSetupHandler "github.com/fossyy/filekeeper/handler/auth/google/setup" signinHandler "github.com/fossyy/filekeeper/handler/signin" "github.com/fossyy/filekeeper/logger" "github.com/fossyy/filekeeper/session" @@ -70,7 +71,7 @@ func init() { for _, data := range CsrfTokens { data.mu.Lock() - if currentTime.Sub(data.CreateTime) > time.Minute-10 { + if currentTime.Sub(data.CreateTime) > time.Minute*10 { delete(CsrfTokens, data.Token) cacheClean++ } @@ -133,7 +134,13 @@ func GET(w http.ResponseWriter, r *http.Request) { } if !db.DB.IsUserRegistered(oauthUser.Email, "ll") { - http.Redirect(w, r, "/signup", http.StatusSeeOther) + code := utils.GenerateRandomString(64) + googleOauthSetupHandler.SetupUser[code] = &googleOauthSetupHandler.UnregisteredUser{ + Code: code, + Email: oauthUser.Email, + CreateTime: time.Now(), + } + http.Redirect(w, r, fmt.Sprintf("/auth/google/setup/%s", code), http.StatusSeeOther) return } diff --git a/handler/auth/google/setup/setup.go b/handler/auth/google/setup/setup.go new file mode 100644 index 0000000..5ba6b3e --- /dev/null +++ b/handler/auth/google/setup/setup.go @@ -0,0 +1,159 @@ +package googleOauthSetupHandler + +import ( + "fmt" + "github.com/fossyy/filekeeper/db" + signinHandler "github.com/fossyy/filekeeper/handler/signin" + "github.com/fossyy/filekeeper/logger" + "github.com/fossyy/filekeeper/session" + "github.com/fossyy/filekeeper/types" + "github.com/fossyy/filekeeper/types/models" + "github.com/fossyy/filekeeper/utils" + authView "github.com/fossyy/filekeeper/view/auth" + signupView "github.com/fossyy/filekeeper/view/signup" + "github.com/google/uuid" + "net/http" + "sync" + "time" +) + +type UnregisteredUser struct { + Code string + Email string + CreateTime time.Time + mu sync.Mutex +} + +var log *logger.AggregatedLogger +var SetupUser map[string]*UnregisteredUser + +func init() { + log = logger.Logger() + SetupUser = make(map[string]*UnregisteredUser) + + ticker := time.NewTicker(time.Minute) + go func() { + for { + <-ticker.C + currentTime := time.Now() + cacheClean := 0 + cleanID := utils.GenerateRandomString(10) + log.Info(fmt.Sprintf("Cache cleanup [GoogleSetup] [%s] initiated at %02d:%02d:%02d", cleanID, currentTime.Hour(), currentTime.Minute(), currentTime.Second())) + + for _, data := range SetupUser { + data.mu.Lock() + if currentTime.Sub(data.CreateTime) > time.Minute*10 { + delete(SetupUser, data.Code) + cacheClean++ + } + + data.mu.Unlock() + } + + log.Info(fmt.Sprintf("Cache cleanup [GoogleSetup] [%s] completed: %d entries removed. Finished at %s", cleanID, cacheClean, time.Since(currentTime))) + } + }() +} + +func GET(w http.ResponseWriter, r *http.Request) { + code := r.PathValue("code") + if _, ok := SetupUser[code]; !ok { + http.Redirect(w, r, "/signup", http.StatusSeeOther) + return + } + component := authView.GoogleSetup("Setup page", types.Message{ + Code: 3, + Message: "", + }) + err := component.Render(r.Context(), w) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + log.Error(err.Error()) + return + } +} + +func POST(w http.ResponseWriter, r *http.Request) { + code := r.PathValue("code") + unregisteredUser, ok := SetupUser[code] + if !ok { + http.Error(w, "Unauthorized Action", http.StatusUnauthorized) + return + } + err := r.ParseForm() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + log.Error(err.Error()) + return + } + username := r.Form.Get("username") + password := r.Form.Get("password") + + isValid := utils.ValidatePassword(password) + if !isValid { + component := authView.GoogleSetup("Setup page", types.Message{ + Code: 0, + Message: "Password is invalid", + }) + err := component.Render(r.Context(), w) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + log.Error(err.Error()) + return + } + return + } + + hashedPassword, err := utils.HashPassword(password) + userID := uuid.New() + newUser := models.User{ + UserID: userID, + Username: username, + Email: unregisteredUser.Email, + Password: hashedPassword, + } + + err = db.DB.CreateUser(&newUser) + if err != nil { + component := signupView.Main("Sign up Page", types.Message{ + Code: 0, + Message: "Email or Username has been registered", + }) + err := component.Render(r.Context(), w) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + log.Error(err.Error()) + return + } + return + } + + delete(SetupUser, code) + + storeSession := session.GlobalSessionStore.Create() + storeSession.Values["user"] = types.User{ + UserID: userID, + Email: unregisteredUser.Email, + Username: username, + Authenticated: true, + } + + userAgent := r.Header.Get("User-Agent") + browserInfo, osInfo := signinHandler.ParseUserAgent(userAgent) + + sessionInfo := session.SessionInfo{ + SessionID: storeSession.ID, + Browser: browserInfo["browser"], + Version: browserInfo["version"], + OS: osInfo["os"], + OSVersion: osInfo["version"], + IP: utils.ClientIP(r), + Location: "Indonesia", + } + + storeSession.Save(w) + session.AddSessionInfo(unregisteredUser.Email, &sessionInfo) + + http.Redirect(w, r, "/user", http.StatusSeeOther) + return +} diff --git a/routes/routes.go b/routes/routes.go index 3b23ac7..e151bbd 100644 --- a/routes/routes.go +++ b/routes/routes.go @@ -3,6 +3,7 @@ package routes import ( googleOauthHandler "github.com/fossyy/filekeeper/handler/auth/google" googleOauthCallbackHandler "github.com/fossyy/filekeeper/handler/auth/google/callback" + googleOauthSetupHandler "github.com/fossyy/filekeeper/handler/auth/google/setup" downloadHandler "github.com/fossyy/filekeeper/handler/download" downloadFileHandler "github.com/fossyy/filekeeper/handler/download/file" forgotPasswordHandler "github.com/fossyy/filekeeper/handler/forgotPassword" @@ -58,15 +59,16 @@ func SetupRoutes() *http.ServeMux { } }) - //TODO make a setup route for unregistered google oauth user - //authRouter.HandleFunc("/google/setup", func(w http.ResponseWriter, r *http.Request) { - // switch r.Method { - // case http.MethodGet: - // middleware.Guest(googleOauthSetupHandler.GET, w, r) - // default: - // http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - // } - //}) + 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("/signin", func(w http.ResponseWriter, r *http.Request) { switch r.Method { diff --git a/view/auth/auth.templ b/view/auth/auth.templ new file mode 100644 index 0000000..0c203f9 --- /dev/null +++ b/view/auth/auth.templ @@ -0,0 +1,90 @@ +package authView + +import ( + "github.com/fossyy/filekeeper/types" + "github.com/fossyy/filekeeper/view/layout" +) + +templ form(err types.Message, title string) { + @layout.Base(title){ +
Enter your email below to login to your account
+ switch err.Code { + case 0: +