-
Notifications
You must be signed in to change notification settings - Fork 0
/
login.go
148 lines (125 loc) · 4.27 KB
/
login.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package controller
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"net/http"
"os"
"strconv"
"time"
"github.com/1chickin/go-social-network-server/config"
"github.com/1chickin/go-social-network-server/internal/model"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/redis/go-redis/v9"
"golang.org/x/crypto/bcrypt"
)
func Login(c *gin.Context, redisClient *redis.Client) {
// get username & password from request
var requestBody struct {
Username string
Password string
}
if c.Bind(&requestBody) != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Failed to load request body!",
})
return
}
// check username exist
var user model.User
config.DB.Where("username = ?", requestBody.Username).First(&user)
// result := config.DB.First(&user, "username = ?", requestBody.Username)
// ref sql injection: https://gorm.io/docs/security.html
if user.ID == 0 {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Username or password was wrong!",
})
return
}
// get salt (string) from database and pepper from env
salt := user.Salt
if salt == "" {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get salt"})
return
}
pepper := os.Getenv("PEPPER")
if pepper == "" {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get pepper"})
return
}
// check password to user hashed password
err := bcrypt.CompareHashAndPassword([]byte(user.HashedPassword), []byte(requestBody.Password+salt+pepper))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Username or password was wrong!",
})
return
}
// check exist session token in database, if not found, don't show error, just create new session
var oldSession model.Session
result := config.DB.Where("user_id = ?", user.ID).First(&oldSession)
// get HTTP_FLAG from env to boolean type, if not exist, set to false
httpFlagStr := os.Getenv("HTTP_FLAG")
httpFlag, err := strconv.ParseBool(httpFlagStr)
if err != nil {
httpFlag = false
}
// If the session exists, delete it
if result.Error == nil && oldSession.ID != 0 {
// delete session_token in cookie
c.SetCookie("session_token", "", -1, "/", "", httpFlag, true) // todo set https secure by true: c.SetCookie("session_token", sessionToken, 30*60, "/", "", true, true)
// delete session in Redis
redisClient.Del(context.Background(), oldSession.SessionToken)
// delete physical session in database
config.DB.Unscoped().Delete(&oldSession)
}
// create session
sessionToken, err := generateSessionToken()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate session token"})
return
}
// save session to Redis with expiration time
redisClient.Set(context.Background(), sessionToken, user.ID, 30*time.Minute)
// save session to database
expirationTime := time.Now().Add(30 * time.Minute)
session := model.Session{UserID: user.ID, SessionToken: sessionToken, ExpiresAt: expirationTime}
config.DB.Create(&session)
// set cookie
c.SetCookie("session_token", sessionToken, 30*60, "/", "", false, true)
// response
c.JSON(http.StatusOK, gin.H{"sessionToken": sessionToken})
}
// generateSessionToken create a new session token for the one user.
// token is generated by HMAC with sessionUUID and SECRET_KEY.
func generateSessionToken() (string, error) {
secretKey := os.Getenv("SECRET_KEY")
if secretKey == "" {
return "", fmt.Errorf("secret key not found")
}
// create a new UUID for session
sessionUUID := uuid.New().String()
// create HMAC signature
h := hmac.New(sha256.New, []byte(secretKey))
h.Write([]byte(sessionUUID))
signature := hex.EncodeToString(h.Sum(nil))
// create session token
token := fmt.Sprintf("%s.%s", sessionUUID, signature)
return token, nil
}
// validateSessionToken: return true if token is valid
func validateSessionToken(token string) bool {
// get sessionUUID and clientSignature from token
var sessionUUID, clientSignature string
fmt.Sscanf(token, "%s.%s", &sessionUUID, &clientSignature)
// recreate HMAC signature
secretKey := os.Getenv("SECRET_KEY")
h := hmac.New(sha256.New, []byte(secretKey))
h.Write([]byte(sessionUUID))
serverSignature := hex.EncodeToString(h.Sum(nil))
// compare clientSignature and serverSignature
return hmac.Equal([]byte(clientSignature), []byte(serverSignature))
}