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