From 7b3bdff0879b05779b0b15950259124cfe88995c Mon Sep 17 00:00:00 2001 From: "guoguo.yu" <guoguo.yu@etu.hesge.ch> Date: Fri, 6 Oct 2023 09:48:18 +0200 Subject: [PATCH] added distinction between admin and user, and password retrieval via mail --- go.mod | 4 +- go.sum | 4 ++ main.go | 122 +++++++++++++++++++++++++++++++++++++++++-- models/page_templ.go | 3 ++ models/user.go | 10 ++++ view/forgot.html | 21 ++++++++ view/index.html | 2 + view/profile.html | 3 ++ view/signup.html | 6 +++ 9 files changed, 170 insertions(+), 5 deletions(-) create mode 100644 view/forgot.html diff --git a/go.mod b/go.mod index 9d2a3b0..1557296 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module auth +module tp-secApp go 1.20 @@ -9,6 +9,7 @@ require ( ) require ( + github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df // indirect github.com/golang/snappy v0.0.1 // indirect github.com/gorilla/securecookie v1.1.1 // indirect github.com/klauspost/compress v1.13.6 // indirect @@ -20,4 +21,5 @@ require ( golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect golang.org/x/text v0.7.0 // indirect + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect ) diff --git a/go.sum b/go.sum index 3f2e128..ce9989c 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df h1:Bao6dhmbTA1KFVxmJ6nBoMuOJit2yjEgLJpIMYpop0E= +github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df/go.mod h1:GJr+FCSXshIwgHBtLglIg9M2l2kQSi6QjVAngtzI08Y= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= @@ -58,3 +60,5 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= diff --git a/main.go b/main.go index 750e853..3b7437d 100644 --- a/main.go +++ b/main.go @@ -1,28 +1,79 @@ package main import ( - "auth/models" + "tp-secApp/models" "context" "fmt" "html/template" "log" "net/http" "os" - "github.com/joho/godotenv" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" + "strconv" + "time" + "github.com/go-gomail/gomail" ) var usersCollection *mongo.Collection +// Session represents a user session. +type Session struct { + ID string + CreationTime time.Time +} + +// sessions is a map to store active sessions. +var sessions map[string]Session + +func cleanupSessions(w http.ResponseWriter, r *http.Request) { + expirationDuration := 10 * time.Second + + for sessionID, session := range sessions { + if time.Since(session.CreationTime) > expirationDuration { + // Session has expired, remove it + delete(sessions, sessionID) + // call the timeoutHandler + timeoutfor(w, r) + + fmt.Printf("Session with ID %s has expired.\n", sessionID) + } + } +} + +func timeoutfor(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Sorry, your request timed out.") + http.Redirect(w, r, "/failed-signin", http.StatusTemporaryRedirect) +} + +func sendPasswordResetEmail(email, token string) error { + m := gomail.NewMessage() + //sendgrid_email := os.Getenv("SMTP_FROM_ADDRESS") + m.SetHeader("From", "notifications@mstd.dansmonorage.blue") + m.SetHeader("To", email) + m.SetHeader("Subject", "Password Reset") + m.SetBody("text/html", fmt.Sprintf(":((", token)) + + sendgrid := os.Getenv("SENDGRID_API_KEY") + d := gomail.NewDialer("smtp.sendgrid.net", 587, "apikey", sendgrid) + // Send the email + return d.DialAndSend(m) +} + func main() { + // Initialize the sessions map + sessions = make(map[string]Session) + // Load the .env file godotenv.Load() URI := os.Getenv("MONGO_URI") client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(URI)) if err != nil { - panic(err) + panic(err) //panic is a built-in function that stops the ordinary flow + // of control and begins panicking. + // When the function F calls panic, execution of F stops, + // any deferred functions in F are executed normally, and then F returns to its caller. } fmt.Println("Connected successfully") usersCollection = client.Database("authentication").Collection("users") @@ -32,19 +83,46 @@ func main() { http.HandleFunc("/signup", signUpHandler) http.HandleFunc("/failed-signin", failedSignin) http.HandleFunc("/profile", successSignin) + http.HandleFunc("/reset-password", handlePasswordReset) log.Fatal(http.ListenAndServe(":5500", nil)) } +func handlePasswordReset(w http.ResponseWriter, r *http.Request) { + email := r.FormValue("email") + token := r.FormValue("token") + + err := sendPasswordResetEmail(email, token) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + fmt.Fprintln(w, "Password reset email sent.") +} + func signUpHandler(w http.ResponseWriter, r *http.Request) { fullName := r.FormValue("fullName") email := r.FormValue("email") password := r.FormValue("password") username := r.FormValue("username") + // TODO: Add age to the database + ageStr := r.FormValue("age") + age, err := strconv.Atoi(ageStr) + bio := r.FormValue("bio") + admin := r.FormValue("admin") + + if err != nil { + panic(err) + } + //age := r.FormValue("age") user := models.User{ FullName: fullName, Email: email, Password: password, Username: username, + Age: age, + Bio: bio, + Role: admin, } var search models.User if usersCollection.FindOne(context.TODO(), bson.M{"username": username}).Decode(&search); search.Username == username { @@ -55,7 +133,7 @@ func signUpHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Email already taken") return } - _, err := usersCollection.InsertOne(context.TODO(), user) + _, err = usersCollection.InsertOne(context.TODO(), user) if err != nil { fmt.Fprintln(w, err) return @@ -81,6 +159,30 @@ func successSignin(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusInternalServerError) return } + // Load the session + sessionID := r.Header.Get("Session-ID") + session, ok := sessions[sessionID] + if !ok { + // Create a new session + session = Session{ + ID: time.Now().String(), + CreationTime: time.Now(), + } + sessions[session.ID] = session + } + + // Create a timer for inactivity logout + timeoutDuration := 2 * time.Second // Adjust the timeout duration as needed + inactivityTimer := time.NewTimer(timeoutDuration) + + go func() { + // Wait for the inactivity timer to expire + <-inactivityTimer.C + + // Log the user out due to inactivity + http.Redirect(w, r, "/signin", http.StatusTemporaryRedirect) + }() + } func signInHandler(w http.ResponseWriter, r *http.Request) { @@ -96,6 +198,18 @@ func signInHandler(w http.ResponseWriter, r *http.Request) { Username: user.Username, Fullname: user.FullName, Email: user.Email, + Age: user.Age, + Bio: user.Bio, + Role: user.Role, + } + // Create a new session + r.Header.Add("Session-ID", time.Now().String()) + session := Session{ + ID: time.Now().String(), + CreationTime: time.Now(), } + sessions[session.ID] = session + + http.Redirect(w, r, "/profile", http.StatusTemporaryRedirect) } diff --git a/models/page_templ.go b/models/page_templ.go index e6f92ff..3adedff 100644 --- a/models/page_templ.go +++ b/models/page_templ.go @@ -4,4 +4,7 @@ type PageTemplate struct { Username string Email string Fullname string + Age int + Bio string + Role string } diff --git a/models/user.go b/models/user.go index e2da74f..031cfc4 100644 --- a/models/user.go +++ b/models/user.go @@ -2,10 +2,20 @@ package models import "go.mongodb.org/mongo-driver/bson/primitive" + +// TODO: Instead of using string for role, use enum. +const ( + Admin = "Admin" + Normal = "Normal" +) + type User struct { ID primitive.ObjectID `bson:"_id,omitempty"` FullName string `bson:"fullname"` Email string `bson:"email"` Username string `bson:"username,omitempty,required"` Password string `bson:"password,omitempty,required"` + Age int + Bio string + Role string } diff --git a/view/forgot.html b/view/forgot.html new file mode 100644 index 0000000..c9da767 --- /dev/null +++ b/view/forgot.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Password Reset</title> +</head> + +<body> + <h1>Password Reset</h1> + <p>If you've forgotten your password, click the link below to reset it:</p> + + <!-- Password Reset Link --> + <form action="/reset-password"> + <label for="email">Email:</label> + <input type="email" id="email" name="email" required> + <a href="/reset-password?token=your-reset-token">Reset Password</a> + </form> + +</body> + +</html> \ No newline at end of file diff --git a/view/index.html b/view/index.html index 08a8e77..d1f8390 100644 --- a/view/index.html +++ b/view/index.html @@ -16,6 +16,8 @@ <input type="text" id="username" name="username" required> <label for="password">Password:</label> <input type="password" id="password" name="password" required> + <h2>Forgot Password</h2> + <a href="forgot.html">click here</a> <button type="submit" name="submit" id="submit">Login</button> </form> <p>Don't have an account? <a href="signup.html">Sign up here</a>.</p> diff --git a/view/profile.html b/view/profile.html index 7ba2623..6c4a145 100644 --- a/view/profile.html +++ b/view/profile.html @@ -84,6 +84,9 @@ <h2 id="fullName">{{.Fullname}}</h2> <p id="email">{{.Email}}</p> <p id="username">{{.Username}}</p> + <p id="age">{{.Age}}</p> + <p id="bio">{{.Bio}}</p> + <p id="admin"></p> </div> <p><a href="index.html">Logout</a></p> </div> diff --git a/view/signup.html b/view/signup.html index 34a0294..b12f441 100644 --- a/view/signup.html +++ b/view/signup.html @@ -20,6 +20,12 @@ <input type="email" id="email" name="email" required> <label for="password">Password:</label> <input type="password" id="password" name="password" required> + <label for="age">Age:</label> + <input type="age" id="age" name="age" required> + <label for="bio">Bio:</label> + <input type="bio" id="bio" name="bio" required> + <label for="admin" id="admin" name="admin" required>Admin:</label> + <input type="checkbox" id="admin" name="admin" value="admin"> <button type="submit">Sign Up</button> </form> <p>Already have an account? <a href="index.html">Login here</a>.</p> -- GitLab