Passed
Push — main ( c3a2ae...e8e52b )
by Yume
01:16
created

controllers.Init   A

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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