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 Salt 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 userID, err := ks.Get(cookie.Value)
88 if err != nil {
89 http.Redirect(w, r, "/login", http.StatusFound)
90 return
91 }
92
93 ctx := context.WithValue(r.Context(), userContextKey, *userID)
94 next(w, r.WithContext(ctx))
95 }
96}
97
98func getLoggedUser(r *http.Request) (user User, ok bool) {
99 userID, ok := r.Context().Value(userContextKey).(uint)
100 db.Find(&user, userID)
101 return user, ok
102}
103
104func examplePage(w http.ResponseWriter, r *http.Request) {
105 user, ok := getLoggedUser(r)
106 if !ok {
107 http.Error(w, "Utente non trovato nel contesto", http.StatusInternalServerError)
108 return
109 }
110
111 templates.ExecuteTemplate(w, "example.html", map[string]interface{}{"User": user})
112}
113
114func getRegisterHandler(w http.ResponseWriter, r *http.Request) {
115 templates.ExecuteTemplate(w, "register.html", nil)
116}
117
118func getLoginHandler(w http.ResponseWriter, r *http.Request) {
119 templates.ExecuteTemplate(w, "login.html", nil)
120}
121
122func getResetPasswordHandler(w http.ResponseWriter, r *http.Request) {
123 templates.ExecuteTemplate(w, "reset_password.html", nil)
124}
125
126// Gestione della registrazione
127func postRegisterHandler(w http.ResponseWriter, r *http.Request) {
128 username := r.FormValue("username")
129 email := r.FormValue("email")
130 password := r.FormValue("password")
131
132 hashedPassword, salt, err := g.HashPassword(password)
133 if err != nil {
134 log.Printf("Error: %v", err)
135 http.Error(w, "Errore durante la registrazione", http.StatusInternalServerError)
136 return
137 }
138
139 user := User{
140 Username: username,
141 Email: email,
142 PasswordHash: hashedPassword,
143 Salt: salt,
144 }
145 db.Create(&user)
146 http.Redirect(w, r, "/login", http.StatusFound)
147 return
148}
149
150// Gestione del login
151func postLoginHandler(w http.ResponseWriter, r *http.Request) {
152 username := r.FormValue("username")
153 password := r.FormValue("password")
154 remember := r.FormValue("remember")
155
156 var user User
157 db.Where("username = ?", username).First(&user)
158
159 if user.ID == 0 || !g.CheckPassword(password, user.Salt, user.PasswordHash) {
160 http.Error(w, "Credenziali non valide", http.StatusUnauthorized)
161 return
162 }
163
164 var duration time.Duration
165 if remember == "on" {
166 duration = durationWeek
167 } else {
168 duration = durationDay
169 }
170
171 cookie, err := g.GenerateCookie(duration)
172 if err != nil {
173 http.Error(w, "Errore nella generazione del token", http.StatusInternalServerError)
174 }
175
176 ks.Set(cookie.Value, user.ID, duration)
177 http.SetCookie(w, cookie)
178 http.Redirect(w, r, "/", http.StatusFound)
179 return
180}
181
182// Logout
183func logoutHandler(w http.ResponseWriter, r *http.Request) {
184 http.SetCookie(w, g.GenerateEmptyCookie())
185 http.Redirect(w, r, "/login", http.StatusFound)
186}
187
188// Funzione per gestire la richiesta di reset password
189func postResetPasswordHandler(w http.ResponseWriter, r *http.Request) {
190 email := r.FormValue("email")
191
192 var user User
193 db.Where("email = ?", email).First(&user)
194
195 if user.ID == 0 {
196 // Non riveliamo se l'email non esiste per motivi di sicurezza
197 http.Redirect(w, r, "/login", http.StatusFound)
198 return
199 }
200
201 // Genera un token di reset
202 resetToken, err := g.GenerateRandomToken(32)
203 if err != nil {
204 http.Error(w, "Errore nella generazione del token di reset", http.StatusInternalServerError)
205 return
206 }
207
208 // Imposta una scadenza di 1 ora per il reset token
209 ks.Set(resetToken, user.ID, time.Hour)
210
211 // Simula l'invio di un'email con il link di reset (in questo caso viene stampato sul log)
212 resetURL := fmt.Sprintf("http://localhost:8080/reset-password-confirm?token=%s", resetToken)
213 log.Printf("Invio dell'email di reset per %s: %s", user.Email, resetURL)
214
215 http.Redirect(w, r, "/login", http.StatusFound)
216 return
217
218}
219
220// Funzione per confermare il reset della password (usando il token)
221func getResetPasswordConfirmHandler(w http.ResponseWriter, r *http.Request) {
222 token := r.URL.Query().Get("token")
223 _, err := ks.Get(token)
224 if err != nil {
225 http.Error(w, "Token non valido o scaduto", http.StatusUnauthorized)
226 return
227 }
228
229 templates.ExecuteTemplate(w, "new_password.html", nil)
230}
231
232func postResetPasswordConfirmHandler(w http.ResponseWriter, r *http.Request) {
233 token := r.URL.Query().Get("token")
234 userID, err := ks.Get(token)
235 if err != nil {
236 http.Error(w, "Token non valido o scaduto", http.StatusUnauthorized)
237 return
238 }
239
240 var user User
241 db.First(&user, *userID)
242
243 password := r.FormValue("password")
244
245 // Hash della nuova password
246 hashedPassword, salt, err := g.HashPassword(password)
247 if err != nil {
248 http.Error(w, "Errore nella modifica della password", http.StatusInternalServerError)
249 return
250 }
251
252 // Aggiorna l'utente con la nuova password e rimuove il token di reset
253 user.PasswordHash = hashedPassword
254 user.Salt = salt
255 db.Save(&user)
256 ks.Delete(token)
257
258 // Reindirizza alla pagina di login
259 http.Redirect(w, r, "/login", http.StatusFound)
260 return
261}