all repos

onasty @ 5bdd1f8fbeaa75db07d9982032cde5828406b12b

a one-time notes service

onasty/e2e/apiv1_auth_test.go (view raw)

Smirnov Oleksandr Smirnov Oleksandr
ss2316544@gmail.com
fix: show user "wrong credentials" error (#142)..., 11 months ago
1
package e2e_test
2
3
import (
4
	"net/http"
5
	"time"
6
7
	"github.com/gofrs/uuid/v5"
8
	"github.com/olexsmir/onasty/internal/hasher"
9
	"github.com/olexsmir/onasty/internal/models"
10
)
11
12
type apiv1AuthSignUpRequest struct {
13
	Email    string `json:"email"`
14
	Password string `json:"password"`
15
}
16
17
func (e *AppTestSuite) TestAuthV1_SignUP() {
18
	email := e.uuid() + "test@test.com"
19
	password := "password"
20
21
	httpResp := e.httpRequest(
22
		http.MethodPost,
23
		"/api/v1/auth/signup",
24
		e.jsonify(apiv1AuthSignUpRequest{
25
			Email:    email,
26
			Password: password,
27
		}),
28
	)
29
30
	dbUser := e.getUserByEmail(email)
31
	hashedPasswd, err := e.hasher.Hash(password)
32
	e.require.NoError(err)
33
34
	e.Equal(http.StatusCreated, httpResp.Code)
35
	e.Equal(dbUser.Email, email)
36
	e.Equal(dbUser.Password, hashedPasswd)
37
}
38
39
func (e *AppTestSuite) TestAuthV1_SignUP_badrequest() {
40
	tests := []struct {
41
		name     string
42
		email    string
43
		password string
44
	}{
45
		{name: "all fields empty", email: "", password: ""},
46
		{name: "non valid email", email: "email", password: "password"},
47
		{name: "non valid password", email: "test@test.com", password: "12345"},
48
	}
49
	for _, t := range tests {
50
		httpResp := e.httpRequest(
51
			http.MethodPost,
52
			"/api/v1/auth/signup",
53
			e.jsonify(apiv1AuthSignUpRequest{
54
				Email:    t.email,
55
				Password: t.password,
56
			}),
57
		)
58
59
		e.Equal(http.StatusBadRequest, httpResp.Code)
60
	}
61
}
62
63
type (
64
	apiv1AuthSignInRequest struct {
65
		Email    string `json:"email"`
66
		Password string `json:"password"`
67
	}
68
	apiv1AuthSignInResponse struct {
69
		AccessToken  string `json:"access_token"`
70
		RefreshToken string `json:"refresh_token"`
71
	}
72
)
73
74
func (e *AppTestSuite) TestAuthV1_VerifyEmail() {
75
	email := e.uuid() + "email@email.com"
76
	password := "qwerty"
77
78
	httpResp := e.httpRequest(
79
		http.MethodPost,
80
		"/api/v1/auth/signup",
81
		e.jsonify(apiv1AuthSignUpRequest{
82
			Email:    email,
83
			Password: password,
84
		}),
85
	)
86
87
	e.Equal(http.StatusCreated, httpResp.Code)
88
89
	user := e.getLastUserByEmail(email)
90
	token := e.getVerificationTokenByUserID(user.ID)
91
	e.Equal(token.Token, mockMailStore[email])
92
93
	httpResp = e.httpRequest(http.MethodGet, "/api/v1/auth/verify/"+token.Token, nil)
94
	e.Equal(http.StatusOK, httpResp.Code)
95
96
	user = e.getLastUserByEmail(email)
97
	e.Equal(user.Activated, true)
98
}
99
100
func (e *AppTestSuite) TestAuthV1_ResendVerificationEmail() {
101
	email, password := e.uuid()+"email@email.com", e.uuid()
102
103
	// create test user
104
	signUpHTTPResp := e.httpRequest(
105
		http.MethodPost,
106
		"/api/v1/auth/signup",
107
		e.jsonify(apiv1AuthSignUpRequest{
108
			Email:    email,
109
			Password: password,
110
		}),
111
	)
112
113
	e.Equal(http.StatusCreated, signUpHTTPResp.Code)
114
115
	// handle sending of the email
116
	httpResp := e.httpRequest(
117
		http.MethodPost,
118
		"/api/v1/auth/resend-verification-email",
119
		e.jsonify(apiv1AuthSignInRequest{
120
			Email:    email,
121
			Password: password,
122
		}),
123
	)
124
125
	e.Equal(http.StatusOK, httpResp.Code)
126
	e.NotEmpty(mockMailStore[email])
127
}
128
129
func (e *AppTestSuite) TestAuthV1_ResendVerificationEmail_wrong() {
130
	email, password := e.uuid()+"@"+e.uuid()+".com", "password"
131
	e.insertUser(email, password, true)
132
133
	tests := []struct {
134
		name         string
135
		email        string
136
		password     string
137
		expectedCode int
138
		expectedMsg  string
139
	}{
140
		{
141
			name:         "already activated account",
142
			email:        email,
143
			password:     password,
144
			expectedCode: http.StatusBadRequest,
145
			expectedMsg:  models.ErrUserIsAlreadyVerified.Error(),
146
		},
147
		{
148
			name:         "wrong credentials",
149
			email:        email,
150
			password:     e.uuid(),
151
			expectedCode: http.StatusBadRequest,
152
			expectedMsg:  models.ErrUserWrongCredentials.Error(),
153
		},
154
	}
155
156
	for _, t := range tests {
157
		httpResp := e.httpRequest(
158
			http.MethodPost,
159
			"/api/v1/auth/resend-verification-email",
160
			e.jsonify(apiv1AuthSignInRequest{
161
				Email:    t.email,
162
				Password: t.password,
163
			}))
164
165
		e.Equal(httpResp.Code, t.expectedCode)
166
167
		var body errorResponse
168
		e.readBodyAndUnjsonify(httpResp.Body, &body)
169
		e.Equal(body.Message, t.expectedMsg)
170
171
		e.Empty(mockMailStore[t.email])
172
	}
173
}
174
175
func (e *AppTestSuite) TestAuthV1_SignIn() {
176
	email := e.uuid() + "email@email.com"
177
	password := "qwerty"
178
	uid := e.insertUser(email, password, true)
179
180
	httpResp := e.httpRequest(
181
		http.MethodPost,
182
		"/api/v1/auth/signin",
183
		e.jsonify(apiv1AuthSignInRequest{
184
			Email:    email,
185
			Password: password,
186
		}),
187
	)
188
189
	var body apiv1AuthSignInResponse
190
	e.readBodyAndUnjsonify(httpResp.Body, &body)
191
192
	session := e.getLastSessionByUserID(uid)
193
	parsedToken := e.parseJwtToken(body.AccessToken)
194
195
	e.Equal(http.StatusOK, httpResp.Code)
196
	e.Equal(body.RefreshToken, session.RefreshToken)
197
	e.Equal(parsedToken.UserID, uid.String())
198
}
199
200
func (e *AppTestSuite) TestAuthV1_SignIn_wrong() {
201
	password := "password"
202
	email := e.uuid() + "@test.com"
203
	e.insertUser(email, "password", true)
204
205
	unactivatedEmail := e.uuid() + "@test.com"
206
	e.insertUser(unactivatedEmail, password, false)
207
208
	//exhaustruct:ignore
209
	tests := []struct {
210
		name         string
211
		email        string
212
		password     string
213
		expectedCode int
214
215
		expectMsg   bool
216
		expectedMsg string
217
	}{
218
		{
219
			name:         "inactivated user",
220
			email:        unactivatedEmail,
221
			password:     password,
222
			expectedCode: http.StatusBadRequest,
223
			expectMsg:    true,
224
			expectedMsg:  models.ErrUserIsNotActivated.Error(),
225
		},
226
		{
227
			name:         "wrong email",
228
			email:        "wrong@email.com",
229
			password:     password,
230
			expectedCode: http.StatusBadRequest,
231
			expectedMsg:  models.ErrUserWrongCredentials.Error(),
232
		},
233
		{
234
			name:         "wrong password",
235
			email:        email,
236
			password:     "wrong-wrong",
237
			expectedCode: http.StatusBadRequest,
238
			expectedMsg:  models.ErrUserWrongCredentials.Error(),
239
		},
240
	}
241
242
	for _, t := range tests {
243
		httpResp := e.httpRequest(
244
			http.MethodPost,
245
			"/api/v1/auth/signin",
246
			e.jsonify(apiv1AuthSignInRequest{
247
				Email:    t.email,
248
				Password: t.password,
249
			}),
250
		)
251
252
		if t.expectMsg {
253
			var body errorResponse
254
			e.readBodyAndUnjsonify(httpResp.Body, &body)
255
256
			e.Equal(body.Message, t.expectedMsg)
257
		}
258
259
		e.Equal(t.expectedCode, httpResp.Code)
260
	}
261
}
262
263
type apiv1AuthRefreshTokensRequest struct {
264
	RefreshToken string `json:"refresh_token"`
265
}
266
267
func (e *AppTestSuite) TestAuthV1_RefreshTokens() {
268
	uid, toks := e.createAndSingIn(e.uuid()+"@test.com", "password")
269
	httpResp := e.httpRequest(
270
		http.MethodPost,
271
		"/api/v1/auth/refresh-tokens",
272
		e.jsonify(apiv1AuthRefreshTokensRequest{
273
			RefreshToken: toks.RefreshToken,
274
		}),
275
	)
276
277
	var body apiv1AuthSignInResponse
278
	e.readBodyAndUnjsonify(httpResp.Body, &body)
279
280
	sessionDB := e.getLastSessionByUserID(uid)
281
	e.Equal(e.parseJwtToken(body.AccessToken).UserID, uid.String())
282
283
	e.Equal(httpResp.Code, http.StatusOK)
284
	e.NotEqual(toks.RefreshToken, body.RefreshToken)
285
	e.Equal(body.RefreshToken, sessionDB.RefreshToken)
286
}
287
288
func (e *AppTestSuite) TestAuthV1_RefreshTokens_wrong() {
289
	httpResp := e.httpRequest(
290
		http.MethodPost,
291
		"/api/v1/auth/refresh-tokens",
292
		e.jsonify(apiv1AuthRefreshTokensRequest{
293
			RefreshToken: e.uuid(),
294
		}),
295
	)
296
297
	e.Equal(httpResp.Code, http.StatusBadRequest)
298
}
299
300
type apiV1AuthLogoutRequest struct {
301
	RefreshToken string `json:"refresh_token"`
302
}
303
304
func (e *AppTestSuite) TestAuthV1_Logout() {
305
	uid, toks := e.createAndSingIn(e.uuid()+"@test.com", "password")
306
307
	sessionDB := e.getLastSessionByUserID(uid)
308
	e.NotEmpty(sessionDB.RefreshToken)
309
310
	httpResp := e.httpRequest(
311
		http.MethodPost,
312
		"/api/v1/auth/logout",
313
		e.jsonify(apiV1AuthLogoutRequest{
314
			RefreshToken: toks.RefreshToken,
315
		}),
316
		toks.AccessToken,
317
	)
318
	e.Equal(httpResp.Code, http.StatusNoContent)
319
320
	sessionDB = e.getLastSessionByUserID(uid)
321
	e.Empty(sessionDB.RefreshToken)
322
}
323
324
func (e *AppTestSuite) TestAuthV1_LogoutAll() {
325
	uid, toks := e.createAndSingIn(e.uuid()+"@test.com", "password")
326
327
	var res int
328
	query := "select count(*) from sessions where user_id = $1"
329
330
	err := e.postgresDB.QueryRow(e.ctx, query, uid).Scan(&res)
331
	e.require.NoError(err)
332
	e.NotZero(res)
333
334
	httpResp := e.httpRequest(http.MethodPost, "/api/v1/auth/logout/all", nil, toks.AccessToken)
335
	e.Equal(httpResp.Code, http.StatusNoContent)
336
337
	err = e.postgresDB.QueryRow(e.ctx, query, uid).Scan(&res)
338
	e.require.NoError(err)
339
	e.Zero(res)
340
}
341
342
type apiv1AuthChangePasswordRequest struct {
343
	CurrentPassword string `json:"current_password"`
344
	NewPassword     string `json:"new_password"`
345
}
346
347
func (e *AppTestSuite) TestAuthV1_ChangePassword() {
348
	password := e.uuid()
349
	newPassword := e.uuid()
350
	email := e.uuid() + "@test.com"
351
	_, toks := e.createAndSingIn(email, password)
352
353
	httpResp := e.httpRequest(
354
		http.MethodPost,
355
		"/api/v1/auth/change-password",
356
		e.jsonify(apiv1AuthChangePasswordRequest{
357
			CurrentPassword: password,
358
			NewPassword:     newPassword,
359
		}),
360
		toks.AccessToken,
361
	)
362
363
	e.Equal(httpResp.Code, http.StatusOK)
364
365
	userDB := e.getUserByEmail(email)
366
	e.NoError(e.hasher.Compare(userDB.Password, newPassword))
367
}
368
369
func (e *AppTestSuite) TestAuthV1_ChangePassword_wrongPassword() {
370
	password := e.uuid()
371
	newPassword := e.uuid()
372
	email := e.uuid() + "@test.com"
373
	_, toks := e.createAndSingIn(email, password)
374
375
	httpResp := e.httpRequest(
376
		http.MethodPost,
377
		"/api/v1/auth/change-password",
378
		e.jsonify(apiv1AuthChangePasswordRequest{
379
			CurrentPassword: e.uuid(),
380
			NewPassword:     newPassword,
381
		}),
382
		toks.AccessToken,
383
	)
384
385
	e.Equal(http.StatusBadRequest, httpResp.Code)
386
387
	userDB := e.getUserByEmail(email)
388
389
	err := e.hasher.Compare(userDB.Password, newPassword)
390
	e.ErrorIs(err, hasher.ErrMismatchedHashes)
391
}
392
393
type (
394
	apiV1AuthResetPasswordRequest struct {
395
		Email string `json:"email"`
396
	}
397
	apiV1AuthSetPasswordRequest struct {
398
		Password string `json:"password"`
399
	}
400
)
401
402
func (e *AppTestSuite) TestAuthV1_ResetPassword() {
403
	email := e.uuid() + "@test.com"
404
	uid, _ := e.createAndSingIn(email, "password")
405
406
	httpResp := e.httpRequest(
407
		http.MethodPost,
408
		"/api/v1/auth/reset-password",
409
		e.jsonify(apiV1AuthResetPasswordRequest{
410
			Email: email,
411
		}),
412
	)
413
414
	e.Equal(httpResp.Code, http.StatusOK)
415
416
	token := e.getResetPasswordTokenByUserID(uid)
417
	e.Empty(token.UsedAt)
418
	e.Equal(mockMailStore[email], token.Token)
419
420
	// set new password
421
	password := e.uuid()
422
	httpResp = e.httpRequest(
423
		http.MethodPost,
424
		"/api/v1/auth/reset-password/"+token.Token,
425
		e.jsonify(apiV1AuthSetPasswordRequest{
426
			Password: password,
427
		}),
428
	)
429
430
	dbUser := e.getUserByEmail(email)
431
	e.Equal(httpResp.Code, http.StatusOK)
432
	e.NoError(e.hasher.Compare(dbUser.Password, password))
433
434
	token = e.getResetPasswordTokenByUserID(uid)
435
	e.NotEmpty(token.UsedAt)
436
}
437
438
func (e *AppTestSuite) TestAuthV1_ResetPassword_nonExistentUser() {
439
	_, _ = e.createAndSingIn(e.uuid()+"@test.com", "password")
440
	httpResp := e.httpRequest(
441
		http.MethodPost,
442
		"/api/v1/auth/reset-password",
443
		e.jsonify(apiV1AuthResetPasswordRequest{
444
			Email: e.uuid() + "@testing.com",
445
		}),
446
	)
447
448
	e.Equal(httpResp.Code, http.StatusBadRequest)
449
}
450
451
type getMeResponse struct {
452
	Email     string    `json:"email"`
453
	CreatedAt time.Time `json:"created_at"`
454
}
455
456
func (e *AppTestSuite) TestApiV1_getMe() {
457
	email := e.uuid() + "@test.com"
458
	_, toks := e.createAndSingIn(email, "password")
459
460
	httpResp := e.httpRequest(http.MethodGet, "/api/v1/me", nil, toks.AccessToken)
461
462
	e.Equal(httpResp.Code, http.StatusOK)
463
464
	var body getMeResponse
465
	e.readBodyAndUnjsonify(httpResp.Body, &body)
466
467
	e.Equal(email, body.Email)
468
	e.NotZero(body.CreatedAt)
469
}
470
471
// createAndSingIn creates an activated user, logs them in,
472
// and returns their userID along with access and refresh tokens.
473
func (e *AppTestSuite) createAndSingIn(
474
	email, password string,
475
) (uuid.UUID, apiv1AuthSignInResponse) {
476
	uid := e.insertUser(email, password, true)
477
	httpResp := e.httpRequest(
478
		http.MethodPost,
479
		"/api/v1/auth/signin",
480
		e.jsonify(apiv1AuthSignInRequest{
481
			Email:    email,
482
			Password: password,
483
		}),
484
	)
485
486
	e.Equal(httpResp.Code, http.StatusOK)
487
488
	var body apiv1AuthSignInResponse
489
	e.readBodyAndUnjsonify(httpResp.Body, &body)
490
491
	return uid, body
492
}