all repos — auth-boilerplate @ 035e20d4a4e353e787fa5819f5efec137794db33

A simple Go web-app boilerplate.

main.go (view raw)

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