src/app/functions.go (view raw)
1package app
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "log"
8 "net/http"
9 "os"
10 "regexp"
11 "strings"
12 "time"
13
14 "github.com/birabittoh/auth-boilerplate/src/email"
15)
16
17const (
18 minUsernameLength = 3
19 maxUsernameLength = 10
20)
21
22var (
23 validUsername = regexp.MustCompile(`(?i)^[a-z0-9._-]+$`)
24 validEmail = regexp.MustCompile(`^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$`)
25)
26
27func showError(w http.ResponseWriter, text string, status int) {
28 w.WriteHeader(status)
29 xt.ExecuteTemplate(w, "error.tmpl", map[string]interface{}{"Status": status, "Text": text})
30}
31
32func getUserByName(username string, excluding uint) (user User, err error) {
33 err = db.Model(&User{}).Where("upper(username) == upper(?) AND id != ?", username, excluding).First(&user).Error
34 return
35}
36
37func sanitizeUsername(username string) (string, error) {
38 if !validUsername.MatchString(username) || len(username) < minUsernameLength || len(username) > maxUsernameLength {
39 return "", errors.New("invalid username")
40 }
41
42 return username, nil
43}
44
45func sanitizeEmail(email string) (string, error) {
46 email = strings.ToLower(email)
47
48 if !validEmail.MatchString(email) {
49 return "", fmt.Errorf("invalid email")
50 }
51
52 return email, nil
53}
54
55func login(w http.ResponseWriter, userID uint, remember bool) {
56 var duration time.Duration
57 if remember {
58 duration = durationWeek
59 } else {
60 duration = durationDay
61 }
62
63 cookie, err := g.GenerateCookie(duration)
64 if err != nil {
65 showError(w, "Could not generate session cookie.", http.StatusInternalServerError)
66 }
67
68 ks.Set("session:"+cookie.Value, userID, duration)
69 http.SetCookie(w, cookie)
70}
71
72func loadEmailConfig() *email.Client {
73 address := os.Getenv("APP_SMTP_EMAIL")
74 password := os.Getenv("APP_SMTP_PASSWORD")
75 host := os.Getenv("APP_SMTP_HOST")
76 port := os.Getenv("APP_SMTP_PORT")
77
78 if address == "" || password == "" || host == "" {
79 log.Println("Missing email configuration.")
80 return nil
81 }
82
83 if port == "" {
84 port = "587"
85 }
86
87 return email.NewClient(address, password, host, port)
88}
89
90func sendEmail(mail email.Email) error {
91 if m == nil {
92 return errors.New("email client is not initialized")
93 }
94 return m.Send(mail)
95}
96
97func sendResetEmail(address, token string) {
98 resetURL := fmt.Sprintf("%s/reset-password-confirm?token=%s", baseUrl, token)
99 err := sendEmail(email.Email{
100 To: []string{address},
101 Subject: "Reset password",
102 Body: fmt.Sprintf("Use the following link to reset your password:\n%s", resetURL),
103 })
104 if err != nil {
105 log.Printf("Could not send reset email for %s. Link: %s", address, resetURL)
106 }
107}
108
109func readSessionCookie(r *http.Request) (userID *uint, err error) {
110 cookie, err := r.Cookie("session_token")
111 if err != nil {
112 return
113 }
114 return ks.Get("session:" + cookie.Value)
115}
116
117// Middleware to check if the user is logged in
118func loginRequired(next http.HandlerFunc) http.HandlerFunc {
119 return func(w http.ResponseWriter, r *http.Request) {
120 userID, err := readSessionCookie(r)
121 if err != nil {
122 http.Redirect(w, r, "/login", http.StatusFound)
123 return
124 }
125
126 ctx := context.WithValue(r.Context(), userContextKey, *userID)
127 next(w, r.WithContext(ctx))
128 }
129}
130
131func getLoggedUser(r *http.Request) (user User, ok bool) {
132 userID, ok := r.Context().Value(userContextKey).(uint)
133 db.Find(&user, userID)
134 return user, ok
135}