main.go (view raw)
1package main
2
3import (
4 "context"
5 "fmt"
6 "html/template"
7 "log"
8 "net/http"
9 "os"
10 "time"
11
12 "github.com/birabittoh/auth-boilerplate/auth"
13 "github.com/birabittoh/myks"
14 "github.com/glebarez/sqlite"
15 "github.com/joho/godotenv"
16 "gorm.io/gorm"
17)
18
19type key int
20
21type User struct {
22 gorm.Model
23 Username string
24 Email string
25 PasswordHash string
26 RememberToken string
27}
28
29var (
30 db *gorm.DB
31 g *auth.Auth
32
33 ks = myks.New[uint](0)
34 durationDay = 24 * time.Hour
35 durationWeek = 7 * durationDay
36 templates = template.Must(template.ParseGlob("templates/*.html"))
37)
38
39const userContextKey key = 0
40
41func main() {
42 err := godotenv.Load()
43 if err != nil {
44 log.Println("Error loading .env file")
45 }
46
47 // Connessione al database SQLite
48 db, err = gorm.Open(sqlite.Open("database.db"), &gorm.Config{})
49 if err != nil {
50 log.Fatal(err)
51 }
52
53 // Creazione della tabella utenti
54 db.AutoMigrate(&User{})
55
56 // Inizializzazione di gauth
57 g = auth.NewAuth(os.Getenv("APP_PEPPER"))
58
59 // Gestione delle route
60 http.HandleFunc("GET /", loginRequired(examplePage))
61 http.HandleFunc("GET /register", getRegisterHandler)
62 http.HandleFunc("GET /login", getLoginHandler)
63 http.HandleFunc("GET /reset-password", getResetPasswordHandler)
64 http.HandleFunc("GET /reset-password-confirm", getResetPasswordConfirmHandler)
65 http.HandleFunc("GET /logout", logoutHandler)
66
67 http.HandleFunc("POST /login", postLoginHandler)
68 http.HandleFunc("POST /register", postRegisterHandler)
69 http.HandleFunc("POST /reset-password", postResetPasswordHandler)
70 http.HandleFunc("POST /reset-password-confirm", postResetPasswordConfirmHandler)
71
72 port := ":8080"
73 log.Println("Server in ascolto su " + port)
74 log.Fatal(http.ListenAndServe(port, nil))
75}
76
77// Middleware per controllare se l'utente è loggato
78// Funzione middleware per controllare se l'utente è autenticato
79func loginRequired(next http.HandlerFunc) http.HandlerFunc {
80 return func(w http.ResponseWriter, r *http.Request) {
81 cookie, err := r.Cookie("session_token")
82 if err != nil {
83 http.Redirect(w, r, "/login", http.StatusFound)
84 return
85 }
86
87 var user User
88 db.Where("remember_token = ?", cookie.Value).First(&user)
89
90 if user.ID == 0 {
91 http.Redirect(w, r, "/login", http.StatusFound)
92 return
93 }
94
95 ctx := context.WithValue(r.Context(), userContextKey, user)
96 next(w, r.WithContext(ctx))
97 }
98}
99
100func getLoggedUser(r *http.Request) (User, bool) {
101 user, ok := r.Context().Value(userContextKey).(User)
102 return user, ok
103}
104
105func examplePage(w http.ResponseWriter, r *http.Request) {
106 user, ok := getLoggedUser(r)
107 if !ok {
108 http.Error(w, "Utente non trovato nel contesto", http.StatusInternalServerError)
109 return
110 }
111
112 templates.ExecuteTemplate(w, "example.html", map[string]interface{}{"User": user})
113}
114
115func getRegisterHandler(w http.ResponseWriter, r *http.Request) {
116 templates.ExecuteTemplate(w, "register.html", nil)
117}
118
119func getLoginHandler(w http.ResponseWriter, r *http.Request) {
120 templates.ExecuteTemplate(w, "login.html", nil)
121}
122
123func getResetPasswordHandler(w http.ResponseWriter, r *http.Request) {
124 templates.ExecuteTemplate(w, "reset_password.html", nil)
125}
126
127// Gestione della registrazione
128func postRegisterHandler(w http.ResponseWriter, r *http.Request) {
129 username := r.FormValue("username")
130 email := r.FormValue("email")
131 password := r.FormValue("password")
132
133 hashedPassword, err := g.HashPassword(password)
134 if err != nil {
135 http.Error(w, "Errore durante la registrazione", http.StatusInternalServerError)
136 return
137 }
138
139 user := User{Username: username, Email: email, PasswordHash: hashedPassword}
140 db.Create(&user)
141 http.Redirect(w, r, "/login", http.StatusFound)
142 return
143}
144
145// Gestione del login
146func postLoginHandler(w http.ResponseWriter, r *http.Request) {
147 username := r.FormValue("username")
148 password := r.FormValue("password")
149 remember := r.FormValue("remember")
150
151 var user User
152 db.Where("username = ?", username).First(&user)
153
154 if user.ID == 0 || !g.CheckPassword(password, user.PasswordHash) {
155 http.Error(w, "Credenziali non valide", http.StatusUnauthorized)
156 return
157 }
158
159 var duration time.Duration
160 if remember == "on" {
161 duration = durationWeek
162 } else {
163 duration = durationDay
164 }
165
166 cookie, err := g.GenerateCookie(duration)
167 if err != nil {
168 http.Error(w, "Errore nella generazione del token", http.StatusInternalServerError)
169 }
170
171 // user.RememberToken = cookie.Value
172 // db.Save(&user)
173
174 http.SetCookie(w, cookie)
175 http.Redirect(w, r, "/", http.StatusFound)
176 return
177}
178
179// Logout
180func logoutHandler(w http.ResponseWriter, r *http.Request) {
181 http.SetCookie(w, g.GenerateEmptyCookie())
182 http.Redirect(w, r, "/login", http.StatusFound)
183}
184
185// Funzione per gestire la richiesta di reset password
186func postResetPasswordHandler(w http.ResponseWriter, r *http.Request) {
187 email := r.FormValue("email")
188
189 var user User
190 db.Where("email = ?", email).First(&user)
191
192 if user.ID == 0 {
193 // Non riveliamo se l'email non esiste per motivi di sicurezza
194 http.Redirect(w, r, "/login", http.StatusFound)
195 return
196 }
197
198 // Genera un token di reset
199 resetToken, err := g.GenerateRandomToken()
200 if err != nil {
201 http.Error(w, "Errore nella generazione del token di reset", http.StatusInternalServerError)
202 return
203 }
204
205 // Imposta una scadenza di 1 ora per il reset token
206 ks.Set(resetToken, user.ID, time.Hour)
207
208 // Simula l'invio di un'email con il link di reset (in questo caso viene stampato sul log)
209 resetURL := fmt.Sprintf("http://localhost:8080/reset-password-confirm?token=%s", resetToken)
210 log.Printf("Invio dell'email di reset per %s: %s", user.Email, resetURL)
211
212 http.Redirect(w, r, "/login", http.StatusFound)
213 return
214
215}
216
217// Funzione per confermare il reset della password (usando il token)
218func getResetPasswordConfirmHandler(w http.ResponseWriter, r *http.Request) {
219 token := r.URL.Query().Get("token")
220 _, err := ks.Get(token)
221 if err != nil {
222 http.Error(w, "Token non valido o scaduto", http.StatusUnauthorized)
223 return
224 }
225
226 templates.ExecuteTemplate(w, "new_password.html", nil)
227}
228
229func postResetPasswordConfirmHandler(w http.ResponseWriter, r *http.Request) {
230 token := r.URL.Query().Get("token")
231 userID, err := ks.Get(token)
232 if err != nil {
233 http.Error(w, "Token non valido o scaduto", http.StatusUnauthorized)
234 return
235 }
236
237 var user User
238 db.First(&user, *userID)
239
240 password := r.FormValue("password")
241
242 // Hash della nuova password
243 hashedPassword, err := g.HashPassword(password)
244 if err != nil {
245 http.Error(w, "Errore nella modifica della password", http.StatusInternalServerError)
246 return
247 }
248
249 // Aggiorna l'utente con la nuova password e rimuove il token di reset
250 user.PasswordHash = hashedPassword
251 db.Save(&user)
252 ks.Delete(token)
253
254 // Reindirizza alla pagina di login
255 http.Redirect(w, r, "/login", http.StatusFound)
256 return
257}