all repos — auth-boilerplate @ e20c137b8edaa2e68e8dbe16bb78cf54655749c2

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	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}