all repos

onasty @ e6c5d1c07f0fcf559b84cc65671c6225b01a73de

a one-time notes service

onasty/e2e/apiv1_auth_test.go (view raw)

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