all repos — auth-boilerplate @ b30162a9cd5be5a02eeb8a33bbe54b02dd03f8a3

A simple Go web-app boilerplate.

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}