Passed
Pull Request — main (#42)
by Yume
01:10
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
155
	statusCode, response := IsConnected(c) // Check if connected
156
157
	return c.Status(statusCode).JSON(response) // Return response
158
}
159
160
// Logout function to log user logout
161
// @Description Logout the user and create a record in the log
162
// @Summary logouts the user
163
// @Tags Auth
164
// @Produce json
165
// @Success 200 "Success"
166
// @Failure 401 "Forbidden"
167
// @Security Beaver
168
// @Router /v1/logout [post]
169
func Logout(c *fiber.Ctx) error {
170
	auth := CheckAuth(c, models.PermUser) // Check auth
171
	if !auth.Success {
172
		// Return error
173
		return c.Status(http.StatusUnauthorized).JSON(models.ResponseHTTP{
174
			Success: false,
175
			Message: auth.Message,
176
			Data:    nil,
177
			Count:   0,
178
		})
179
	}
180
181
	// Create log
182
	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)
183
	_ = log.SendLog()
184
185
	// Return response with success
186
	return c.JSON(fiber.Map{
187
		"message": "successfully logged out !",
188
		"token":   "",
189
	})
190
}
191
192
// AuthDebugMode function to bypass auth in debug mode
193
func AuthDebugMode(c *fiber.Ctx) models.ResponseAuth {
194
	db := database.DBConn // DB Conn
195
	var user models.User  // User object
196
197
	// Get user
198
	if res := db.Where("id = ?", 6).First(&user); res.Error != nil {
199
		c.Status(fiber.StatusInternalServerError) // InternalServerError Status
200
		// return error message as Json object
201
		return models.ResponseAuth{
202
			Success: false,
203
			Message: "Failed to get the user. Try to logout/login. Otherwise, contact the support",
204
		}
205
	}
206
207
	return models.ResponseAuth{
208
		Success: true,
209
		Message: "Authenticated",
210
		User:    user,
211
	}
212
}
213
214
// CheckAuth function to check if user is connected
215
func CheckAuth(c *fiber.Ctx, p models.Permission) models.ResponseAuth {
216
	statusCode, response := IsConnected(c) // Check if connected
217
218
	// Check statusCode
219
	if statusCode != fiber.StatusOK {
220
		c.Status(statusCode)
221
		// Return response
222
		return response
223
	}
224
225
	user := response.User // Get user from response
226
227
	// Check permission
228
	if user.Permissions < p {
229
		// Log permission error
230
		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)
231
		_ = log.SendLog()                  // Send log
232
		c.Status(fiber.StatusUnauthorized) // Unauthorized Status
233
		// Return response
234
		return models.ResponseAuth{
235
			Success: false,
236
			Message: "You don't have the right permissions to perform this request.",
237
		}
238
	}
239
240
	// Validate permissions
241
	return models.ResponseAuth{
242
		Success: true,
243
		Message: "Authenticated",
244
		User:    user,
245
	}
246
}
247
248
// IsConnected function to check if user is connected
249
func IsConnected(c *fiber.Ctx) (int, models.ResponseAuth) {
250
	db := database.DBConn          // DB Conn
251
	tokenString := extractToken(c) // Extract token
252
	var user models.User           // User object
253
254
	// Parse token
255
	token, err := jwt.Parse(tokenString, jwtKeyFunc)
256
	if err != nil {
257
		// Return error
258
		return fiber.StatusForbidden, models.ResponseAuth{
259
			Success: false,
260
			Message: "Failed to get the user. Try to logout/login. Otherwise, contact the support",
261
			User:    user,
262
		}
263
	}
264
	// Check if token is valid
265
	claims := token.Claims.(jwt.MapClaims)
266
267
	// Get user from token
268
	if res := db.Where("id = ?", claims["iss"]).First(&user); res.Error != nil {
269
		// Generate log
270
		log := models.CreateLog(fmt.Sprintf("Error on check auth: %s", res.Error), models.LogLoginError).SetType(models.LogTypeError).AttachIDs(user.ID, 0, 0)
271
		_ = log.SendLog()                         // Send log
272
		c.Status(fiber.StatusInternalServerError) // InternalServerError Status
273
		// return error
274
		return fiber.StatusInternalServerError, models.ResponseAuth{
275
			Success: false,
276
			Message: "Failed to get the user. Try to logout/login. Otherwise, contact the support",
277
			User:    user,
278
		}
279
	}
280
281
	// User is connected
282
	return fiber.StatusOK, models.ResponseAuth{
283
		Success: true,
284
		Message: "User is connected",
285
		User:    user,
286
	}
287
}
288
289
// extractToken function to extract token from header
290
func extractToken(c *fiber.Ctx) string {
291
	token := c.Get("Authorization") // Get token from header
292
	// Normally Authorization HTTP header.
293
	onlyToken := strings.Split(token, " ") // Split token
294
	if len(onlyToken) == 2 {
295
		return onlyToken[1] // Return only token
296
	}
297
	return "" // Return empty string
298
}
299
300
// jwtKeyFunc function to get the key for the token
301
func jwtKeyFunc(_ *jwt.Token) (interface{}, error) {
302
	return []byte(SecretKey), nil // Return secret key
303
}
304