Passed
Pull Request — main (#42)
by Yume
01:17
created

controllers.CheckAuth   A

Complexity

Conditions 3

Size

Total Lines 30
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 17
nop 2
dl 0
loc 30
rs 9.55
c 0
b 0
f 0
1
package controllers
2
3
import (
4
	"fmt"
5
	"github.com/memnix/memnixrest/app/models"
6
	"github.com/memnix/memnixrest/app/queries"
7
	"github.com/memnix/memnixrest/pkg/database"
8
	"github.com/memnix/memnixrest/pkg/utils"
9
	"net/http"
10
	"os"
11
	"strconv"
12
	"strings"
13
	"time"
14
15
	"github.com/gofiber/fiber/v2"
16
	"github.com/golang-jwt/jwt"
17
	"golang.org/x/crypto/bcrypt"
18
)
19
20
var SecretKey = os.Getenv("SECRET") // SecretKey env variable
21
22
// Register function to create a new user
23
// @Description Create a new user
24
// @Summary creates a new user
25
// @Tags Auth
26
// @Produce json
27
// @Param credentials body models.RegisterStruct true "Credentials"
28
// @Success 200 {object} models.User
29
// @Failure 403 "Forbidden"
30
// @Router /v1/register [post]
31
func Register(c *fiber.Ctx) error {
32
	db := database.DBConn // DB Conn
33
34
	var data models.RegisterStruct // Data object
35
36
	if err := c.BodyParser(&data); err != nil {
37
		return err
38
	} // Parse body
39
40
	// Register checks
41
	if len(data.Password) > utils.MaxPasswordLen || len(data.Username) > utils.MaxUsernameLen || len(data.Email) > utils.MaxEmailLen {
42
		log := models.CreateLog(fmt.Sprintf("Error on register: %s - %s", data.Username, data.Email), models.LogBadRequest).SetType(models.LogTypeWarning).AttachIDs(0, 0, 0)
43
		_ = log.SendLog()
44
		return queries.RequestError(c, http.StatusForbidden, utils.ErrorRequestFailed)
45
	}
46
47
	password, _ := bcrypt.GenerateFromPassword([]byte(data.Password), 10) // Hash password
48
	user := models.User{
49
		Username: data.Username,
50
		Email:    strings.ToLower(data.Email),
51
		Password: password,
52
	} // Create object
53
54
	//TODO: manual checking for unique username and email
55
	if err := db.Create(&user).Error; err != nil {
56
		log := models.CreateLog(fmt.Sprintf("Error on register: %s - %s", data.Username, data.Email), models.LogAlreadyUsedEmail).SetType(models.LogTypeWarning).AttachIDs(user.ID, 0, 0)
57
		_ = log.SendLog()
58
		return queries.RequestError(c, http.StatusForbidden, utils.ErrorAlreadyUsedEmail)
59
	} // Add user to DB
60
61
	// Create log
62
	log := models.CreateLog(fmt.Sprintf("Register: %s - %s", user.Username, user.Email), models.LogUserRegister).SetType(models.LogTypeInfo).AttachIDs(user.ID, 0, 0)
63
	_ = log.SendLog() // Send log
64
65
	return c.JSON(user) // Return user
66
}
67
68
// Login function to log in a user and return access with fresh token
69
// @Description Login the user and return a fresh token
70
// @Summary logins user and return a fresh token
71
// @Tags Auth
72
// @Produce json
73
// @Param credentials body models.LoginStruct true "Credentials"
74
// @Success 200 {object} models.LoginResponse
75
// @Failure 400 "Incorrect password or email"
76
// @Failure 500 "Internal error"
77
// @Router /v1/login [post]
78
func Login(c *fiber.Ctx) error {
79
	db := database.DBConn // DB Conn
80
81
	var data models.LoginStruct // Data object
82
83
	if err := c.BodyParser(&data); err != nil {
84
		return err
85
	} // Parse body
86
87
	var user models.User // User object
88
89
	db.Where("email = ?", strings.ToLower(data.Email)).First(&user) // Get user
90
91
	// handle error
92
	if user.ID == 0 { // default Id when return nil
93
		// Create log
94
		log := models.CreateLog(fmt.Sprintf("Error on login: %s", data.Email), models.LogIncorrectEmail).SetType(models.LogTypeWarning).AttachIDs(user.ID, 0, 0)
95
		_ = log.SendLog()                // Send log
96
		c.Status(fiber.StatusBadRequest) // BadRequest Status
97
		// return error message as Json object
98
		return c.JSON(models.LoginResponse{
99
			Message: "Incorrect email or password !",
100
			Token:   "",
101
		})
102
	}
103
104
	// Check password
105
	if err := bcrypt.CompareHashAndPassword(user.Password, []byte(data.Password)); err != nil {
106
		c.Status(fiber.StatusBadRequest) // BadRequest Status
107
		log := models.CreateLog(fmt.Sprintf("Error on login: %s", data.Email), models.LogIncorrectPassword).SetType(models.LogTypeWarning).AttachIDs(user.ID, 0, 0)
108
		_ = log.SendLog() // Send log
109
		// return error message as Json object
110
		return c.JSON(models.LoginResponse{
111
			Message: "Incorrect email or password !",
112
			Token:   "",
113
		})
114
	}
115
116
	// Create token
117
	claims := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.StandardClaims{
118
		Issuer:    strconv.Itoa(int(user.ID)),
119
		ExpiresAt: time.Now().Add(time.Hour * 336).Unix(), // 14 day
120
	}) // expires after 2 weeks
121
122
	token, err := claims.SignedString([]byte(SecretKey)) // Sign token
123
	if err != nil {
124
		log := models.CreateLog(fmt.Sprintf("Error on login: %s", err.Error()), models.LogLoginError).SetType(models.LogTypeError).AttachIDs(user.ID, 0, 0)
125
		_ = log.SendLog()                         // Send log
126
		c.Status(fiber.StatusInternalServerError) // InternalServerError Status
127
		// return error message as Json object
128
		return c.JSON(models.LoginResponse{
129
			Message: "Incorrect email or password !",
130
			Token:   "",
131
		})
132
	}
133
134
	log := models.CreateLog(fmt.Sprintf("Login: %s - %s", user.Username, user.Email), models.LogUserLogin).SetType(models.LogTypeInfo).AttachIDs(user.ID, 0, 0)
135
	_ = log.SendLog() // Send log
136
137
	// return token as Json object
138
	return c.JSON(models.LoginResponse{
139
		Message: "Login Succeeded",
140
		Token:   token,
141
	})
142
}
143
144
// User function to get connected user
145
// @Description Get connected user
146
// @Summary  gets connected user
147
// @Tags Auth
148
// @Produce json
149
// @Success 200 {object} models.ResponseAuth
150
// @Failure 401 "Forbidden"
151
// @Security Beaver
152
// @Router /v1/user [get]
153
func User(c *fiber.Ctx) error {
154
	statusCode, response := IsConnected(c) // Check if connected
155
156
	return c.Status(statusCode).JSON(response) // Return response
157
}
158
159
// Logout function to log user logout
160
// @Description Logout the user and create a record in the log
161
// @Summary logouts the user
162
// @Tags Auth
163
// @Produce json
164
// @Success 200 "Success"
165
// @Failure 401 "Forbidden"
166
// @Security Beaver
167
// @Router /v1/logout [post]
168
func Logout(c *fiber.Ctx) error {
169
	auth := CheckAuth(c, models.PermUser) // Check auth
170
	if !auth.Success {
171
		// Return error
172
		return c.Status(http.StatusUnauthorized).JSON(models.ResponseHTTP{
173
			Success: false,
174
			Message: auth.Message,
175
			Data:    nil,
176
			Count:   0,
177
		})
178
	}
179
180
	// Create log
181
	log := models.CreateLog(fmt.Sprintf("Logout: %s - %s", auth.User.Username, auth.User.Email), models.LogUserLogout).SetType(models.LogTypeInfo).AttachIDs(auth.User.ID, 0, 0)
182
	_ = log.SendLog()
183
184
	// Return response with success
185
	return c.JSON(fiber.Map{
186
		"message": "successfully logged out !",
187
		"token":   "",
188
	})
189
}
190
191
// AuthDebugMode function to bypass auth in debug mode
192
func AuthDebugMode(c *fiber.Ctx) models.ResponseAuth {
193
	db := database.DBConn // DB Conn
194
	var user models.User  // User object
195
196
	// Get user
197
	if res := db.Where("id = ?", 6).First(&user); res.Error != nil {
198
		c.Status(fiber.StatusInternalServerError) // InternalServerError Status
199
		// return error message as Json object
200
		return models.ResponseAuth{
201
			Success: false,
202
			Message: "Failed to get the user. Try to logout/login. Otherwise, contact the support",
203
		}
204
	}
205
206
	return models.ResponseAuth{
207
		Success: true,
208
		Message: "Authenticated",
209
		User:    user,
210
	}
211
}
212
213
// CheckAuth function to check if user is connected
214
func CheckAuth(c *fiber.Ctx, p models.Permission) models.ResponseAuth {
215
	statusCode, response := IsConnected(c) // Check if connected
216
217
	// Check statusCode
218
	if statusCode != fiber.StatusOK {
219
		c.Status(statusCode)
220
		// Return response
221
		return response
222
	}
223
224
	user := response.User // Get user from response
225
226
	// Check permission
227
	if user.Permissions < p {
228
		// Log permission error
229
		log := models.CreateLog(fmt.Sprintf("Permission error: %s | had %s but tried %s", user.Email, user.Permissions.ToString(), p.ToString()), models.LogPermissionForbidden).SetType(models.LogTypeWarning).AttachIDs(user.ID, 0, 0)
230
		_ = log.SendLog()                  // Send log
231
		c.Status(fiber.StatusUnauthorized) // Unauthorized Status
232
		// Return response
233
		return models.ResponseAuth{
234
			Success: false,
235
			Message: "You don't have the right permissions to perform this request.",
236
		}
237
	}
238
239
	// Validate permissions
240
	return models.ResponseAuth{
241
		Success: true,
242
		Message: "Authenticated",
243
		User:    user,
244
	}
245
}
246
247
// IsConnected function to check if user is connected
248
func IsConnected(c *fiber.Ctx) (int, models.ResponseAuth) {
249
	db := database.DBConn          // DB Conn
250
	tokenString := extractToken(c) // Extract token
251
	var user models.User           // User object
252
253
	// Parse token
254
	token, err := jwt.Parse(tokenString, jwtKeyFunc)
255
	if err != nil {
256
		// Return error
257
		return fiber.StatusForbidden, models.ResponseAuth{
258
			Success: false,
259
			Message: "Failed to get the user. Try to logout/login. Otherwise, contact the support",
260
			User:    user,
261
		}
262
	}
263
	// Check if token is valid
264
	claims := token.Claims.(jwt.MapClaims)
265
266
	// Get user from token
267
	if res := db.Where("id = ?", claims["iss"]).First(&user); res.Error != nil {
268
		// Generate log
269
		log := models.CreateLog(fmt.Sprintf("Error on check auth: %s", res.Error), models.LogLoginError).SetType(models.LogTypeError).AttachIDs(user.ID, 0, 0)
270
		_ = log.SendLog()                         // Send log
271
		c.Status(fiber.StatusInternalServerError) // InternalServerError Status
272
		// return error
273
		return fiber.StatusInternalServerError, models.ResponseAuth{
274
			Success: false,
275
			Message: "Failed to get the user. Try to logout/login. Otherwise, contact the support",
276
			User:    user,
277
		}
278
	}
279
280
	// User is connected
281
	return fiber.StatusOK, models.ResponseAuth{
282
		Success: true,
283
		Message: "User is connected",
284
		User:    user,
285
	}
286
}
287
288
// extractToken function to extract token from header
289
func extractToken(c *fiber.Ctx) string {
290
	token := c.Get("Authorization") // Get token from header
291
	// Normally Authorization HTTP header.
292
	onlyToken := strings.Split(token, " ") // Split token
293
	if len(onlyToken) == 2 {
294
		return onlyToken[1] // Return only token
295
	}
296
	return "" // Return empty string
297
}
298
299
// jwtKeyFunc function to get the key for the token
300
func jwtKeyFunc(_ *jwt.Token) (interface{}, error) {
301
	return []byte(SecretKey), nil // Return secret key
302
}
303