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
17type HabitDisplay struct {
18 Class string
19 Name string
20 LastAck string
21 Disabled bool
22}
23
24const (
25 minUsernameLength = 3
26 maxUsernameLength = 10
27
28 classGood = "good"
29 classWarn = "warn"
30 classBad = "bad"
31)
32
33var (
34 validUsername = regexp.MustCompile(`(?i)^[a-z0-9._-]+$`)
35 validEmail = regexp.MustCompile(`^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$`)
36)
37
38func getUserByName(username string, excluding uint) (user User, err error) {
39 err = db.Model(&User{}).Where("upper(username) == upper(?) AND id != ?", username, excluding).First(&user).Error
40 return
41}
42
43func sanitizeUsername(username string) (string, error) {
44 if !validUsername.MatchString(username) || len(username) < minUsernameLength || len(username) > maxUsernameLength {
45 return "", errors.New("invalid username")
46 }
47
48 return username, nil
49}
50
51func sanitizeEmail(email string) (string, error) {
52 email = strings.ToLower(email)
53
54 if !validEmail.MatchString(email) {
55 return "", fmt.Errorf("invalid email")
56 }
57
58 return email, nil
59}
60
61func login(w http.ResponseWriter, userID uint, remember bool) {
62 var duration time.Duration
63 if remember {
64 duration = durationWeek
65 } else {
66 duration = durationDay
67 }
68
69 cookie, err := g.GenerateCookie(duration)
70 if err != nil {
71 http.Error(w, "Could not generate session cookie.", http.StatusInternalServerError)
72 }
73
74 ks.Set("session:"+cookie.Value, userID, duration)
75 http.SetCookie(w, cookie)
76}
77
78func loadEmailConfig() *email.Client {
79 address := os.Getenv("APP_SMTP_EMAIL")
80 password := os.Getenv("APP_SMTP_PASSWORD")
81 host := os.Getenv("APP_SMTP_HOST")
82 port := os.Getenv("APP_SMTP_PORT")
83
84 if address == "" || password == "" || host == "" {
85 log.Println("Missing email configuration.")
86 return nil
87 }
88
89 if port == "" {
90 port = "587"
91 }
92
93 return email.NewClient(address, password, host, port)
94}
95
96func sendEmail(mail email.Email) error {
97 if m == nil {
98 return errors.New("email client is not initialized")
99 }
100 return m.Send(mail)
101}
102
103func sendResetEmail(address, token string) {
104 resetURL := fmt.Sprintf("%s/reset-password-confirm?token=%s", baseUrl, token)
105 err := sendEmail(email.Email{
106 To: []string{address},
107 Subject: "Reset password",
108 Body: fmt.Sprintf("Use the following link to reset your password:\n%s", resetURL),
109 })
110 if err != nil {
111 log.Printf("Could not send reset email for %s. Link: %s", address, resetURL)
112 }
113}
114
115func readSessionCookie(r *http.Request) (userID *uint, err error) {
116 cookie, err := r.Cookie("session_token")
117 if err != nil {
118 return
119 }
120 return ks.Get("session:" + cookie.Value)
121}
122
123// Middleware to check if the user is logged in
124func loginRequired(next http.HandlerFunc) http.HandlerFunc {
125 return func(w http.ResponseWriter, r *http.Request) {
126 userID, err := readSessionCookie(r)
127 if err != nil {
128 http.Redirect(w, r, "/login", http.StatusFound)
129 return
130 }
131
132 ctx := context.WithValue(r.Context(), userContextKey, *userID)
133 next(w, r.WithContext(ctx))
134 }
135}
136
137func getLoggedUser(r *http.Request) (user User, ok bool) {
138 userID, ok := r.Context().Value(userContextKey).(uint)
139 db.Find(&user, userID)
140 return user, ok
141}
142
143func formatDuration(d time.Duration) string {
144 // TODO: 48h1m13s --> 2.01 days
145 return d.String()
146}
147
148func toHabitDisplay(habit Habit) HabitDisplay {
149 return HabitDisplay{
150 Name: habit.Name,
151 LastAck: formatDuration(time.Since(habit.LastAck)),
152 Disabled: habit.Disabled,
153 Class: classGood,
154 }
155}
156
157func getAllHabits(userID uint) (positives []HabitDisplay, negatives []HabitDisplay, err error) {
158 var habits []Habit
159 err = db.Model(&Habit{}).Where(&Habit{UserID: userID}).Find(&habits).Error
160 if err != nil {
161 return
162 }
163
164 for _, habit := range habits {
165 habitDisplay := toHabitDisplay(habit)
166 if habit.Negative {
167 negatives = append(negatives, habitDisplay)
168 } else {
169 positives = append(positives, habitDisplay)
170 }
171 }
172 return
173}