all repos

onasty @ 5d72e79

a one-time notes service

onasty/internal/service/usersrv/usersrv.go (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
refactor(usersrv): remove unsed dep, 1 year ago
1
package usersrv
2
3
import (
4
	"context"
5
	"errors"
6
	"log/slog"
7
	"time"
8
9
	"github.com/gofrs/uuid/v5"
10
	"github.com/olexsmir/onasty/internal/dtos"
11
	"github.com/olexsmir/onasty/internal/events/mailermq"
12
	"github.com/olexsmir/onasty/internal/hasher"
13
	"github.com/olexsmir/onasty/internal/jwtutil"
14
	"github.com/olexsmir/onasty/internal/models"
15
	"github.com/olexsmir/onasty/internal/store/psql/sessionrepo"
16
	"github.com/olexsmir/onasty/internal/store/psql/userepo"
17
	"github.com/olexsmir/onasty/internal/store/psql/vertokrepo"
18
	"github.com/olexsmir/onasty/internal/store/rdb/usercache"
19
)
20
21
type UserServicer interface {
22
	SignUp(ctx context.Context, inp dtos.CreateUserDTO) (uuid.UUID, error)
23
	SignIn(ctx context.Context, inp dtos.SignInDTO) (dtos.TokensDTO, error)
24
	RefreshTokens(ctx context.Context, refreshToken string) (dtos.TokensDTO, error)
25
	Logout(ctx context.Context, userID uuid.UUID) error
26
27
	ChangePassword(ctx context.Context, userID uuid.UUID, inp dtos.ResetUserPasswordDTO) error
28
29
	Verify(ctx context.Context, verificationKey string) error
30
	ResendVerificationEmail(ctx context.Context, credentials dtos.SignInDTO) error
31
32
	ParseJWTToken(token string) (jwtutil.Payload, error)
33
34
	CheckIfUserExists(ctx context.Context, userID uuid.UUID) (bool, error)
35
	CheckIfUserIsActivated(ctx context.Context, userID uuid.UUID) (bool, error)
36
}
37
38
var _ UserServicer = (*UserSrv)(nil)
39
40
type UserSrv struct {
41
	userstore    userepo.UserStorer
42
	sessionstore sessionrepo.SessionStorer
43
	vertokrepo   vertokrepo.VerificationTokenStorer
44
	hasher       hasher.Hasher
45
	jwtTokenizer jwtutil.JWTTokenizer
46
	mailermq     mailermq.Mailer
47
	cache        usercache.UserCacheer
48
49
	refreshTokenTTL      time.Duration
50
	verificationTokenTTL time.Duration
51
}
52
53
func New(
54
	userstore userepo.UserStorer,
55
	sessionstore sessionrepo.SessionStorer,
56
	vertokrepo vertokrepo.VerificationTokenStorer,
57
	hasher hasher.Hasher,
58
	jwtTokenizer jwtutil.JWTTokenizer,
59
	mailermq mailermq.Mailer,
60
	cache usercache.UserCacheer,
61
	refreshTokenTTL, verificationTokenTTL time.Duration,
62
) *UserSrv {
63
	return &UserSrv{
64
		userstore:            userstore,
65
		sessionstore:         sessionstore,
66
		vertokrepo:           vertokrepo,
67
		hasher:               hasher,
68
		jwtTokenizer:         jwtTokenizer,
69
		mailermq:             mailermq,
70
		cache:                cache,
71
		refreshTokenTTL:      refreshTokenTTL,
72
		verificationTokenTTL: verificationTokenTTL,
73
	}
74
}
75
76
func (u *UserSrv) SignUp(ctx context.Context, inp dtos.CreateUserDTO) (uuid.UUID, error) {
77
	hashedPassword, err := u.hasher.Hash(inp.Password)
78
	if err != nil {
79
		return uuid.UUID{}, err
80
	}
81
82
	uid, err := u.userstore.Create(ctx, dtos.CreateUserDTO{
83
		Username:    inp.Username,
84
		Email:       inp.Email,
85
		Password:    hashedPassword,
86
		CreatedAt:   inp.CreatedAt,
87
		LastLoginAt: inp.LastLoginAt,
88
	})
89
	if err != nil {
90
		return uuid.Nil, err
91
	}
92
93
	vtok := uuid.Must(uuid.NewV4()).String()
94
	if err := u.vertokrepo.Create(ctx, vtok, uid, time.Now(), time.Now().Add(u.verificationTokenTTL)); err != nil {
95
		return uuid.Nil, err
96
	}
97
98
	if err := u.mailermq.SendVerificationEmail(ctx, mailermq.SendVerificationEmailRequest{
99
		Receiver: inp.Email,
100
		Token:    vtok,
101
	}); err != nil {
102
		return uuid.Nil, err
103
	}
104
105
	return uid, nil
106
}
107
108
func (u *UserSrv) SignIn(ctx context.Context, inp dtos.SignInDTO) (dtos.TokensDTO, error) {
109
	hashedPassword, err := u.hasher.Hash(inp.Password)
110
	if err != nil {
111
		return dtos.TokensDTO{}, err
112
	}
113
114
	user, err := u.userstore.GetUserByCredentials(ctx, inp.Email, hashedPassword)
115
	if err != nil {
116
		if errors.Is(err, models.ErrUserNotFound) {
117
			return dtos.TokensDTO{}, models.ErrUserWrongCredentials
118
		}
119
		return dtos.TokensDTO{}, err
120
	}
121
122
	if !user.Activated {
123
		return dtos.TokensDTO{}, models.ErrUserIsNotActivated
124
	}
125
126
	tokens, err := u.getTokens(user.ID)
127
	if err != nil {
128
		return dtos.TokensDTO{}, err
129
	}
130
131
	if err := u.sessionstore.Set(ctx, user.ID, tokens.Refresh, time.Now().Add(u.refreshTokenTTL)); err != nil {
132
		return dtos.TokensDTO{}, err
133
	}
134
135
	return dtos.TokensDTO{
136
		Access:  tokens.Access,
137
		Refresh: tokens.Refresh,
138
	}, nil
139
}
140
141
func (u *UserSrv) Logout(ctx context.Context, userID uuid.UUID) error {
142
	return u.sessionstore.Delete(ctx, userID)
143
}
144
145
func (u *UserSrv) RefreshTokens(ctx context.Context, rtoken string) (dtos.TokensDTO, error) {
146
	userID, err := u.sessionstore.GetUserIDByRefreshToken(ctx, rtoken)
147
	if err != nil {
148
		return dtos.TokensDTO{}, err
149
	}
150
151
	tokens, err := u.getTokens(userID)
152
	if err != nil {
153
		return dtos.TokensDTO{}, err
154
	}
155
156
	if err := u.sessionstore.Update(ctx, userID, rtoken, tokens.Refresh); err != nil {
157
		return dtos.TokensDTO{}, err
158
	}
159
160
	return dtos.TokensDTO{
161
		Access:  tokens.Access,
162
		Refresh: tokens.Refresh,
163
	}, nil
164
}
165
166
func (u *UserSrv) ChangePassword(
167
	ctx context.Context,
168
	userID uuid.UUID,
169
	inp dtos.ResetUserPasswordDTO,
170
) error {
171
	oldPass, err := u.hasher.Hash(inp.CurrentPassword)
172
	if err != nil {
173
		return err
174
	}
175
176
	newPass, err := u.hasher.Hash(inp.NewPassword)
177
	if err != nil {
178
		return err
179
	}
180
181
	if err := u.userstore.ChangePassword(ctx, userID, oldPass, newPass); err != nil {
182
		return err
183
	}
184
185
	return nil
186
}
187
188
func (u *UserSrv) Verify(ctx context.Context, verificationKey string) error {
189
	uid, err := u.vertokrepo.GetUserIDByTokenAndMarkAsUsed(ctx, verificationKey, time.Now())
190
	if err != nil {
191
		return err
192
	}
193
194
	return u.userstore.MarkUserAsActivated(ctx, uid)
195
}
196
197
func (u *UserSrv) ResendVerificationEmail(ctx context.Context, inp dtos.SignInDTO) error {
198
	hashedPassword, err := u.hasher.Hash(inp.Password)
199
	if err != nil {
200
		return err
201
	}
202
203
	user, err := u.userstore.GetUserByCredentials(ctx, inp.Email, hashedPassword)
204
	if err != nil {
205
		if errors.Is(err, models.ErrUserNotFound) {
206
			return models.ErrUserWrongCredentials
207
		}
208
		return err
209
	}
210
211
	if user.Activated {
212
		return models.ErrUserIsAlreeadyVerified
213
	}
214
215
	token, err := u.vertokrepo.GetTokenOrUpdateTokenByUserID(
216
		ctx,
217
		user.ID,
218
		uuid.Must(uuid.NewV4()).String(),
219
		time.Now().Add(u.verificationTokenTTL))
220
	if err != nil {
221
		return err
222
	}
223
224
	if err := u.mailermq.SendVerificationEmail(ctx, mailermq.SendVerificationEmailRequest{
225
		Receiver: inp.Email,
226
		Token:    token,
227
	}); err != nil {
228
		return err
229
	}
230
231
	return nil
232
}
233
234
func (u *UserSrv) ParseJWTToken(token string) (jwtutil.Payload, error) {
235
	return u.jwtTokenizer.Parse(token)
236
}
237
238
func (u UserSrv) CheckIfUserExists(ctx context.Context, id uuid.UUID) (bool, error) {
239
	if r, err := u.cache.GetIsExists(ctx, id.String()); err == nil {
240
		return r, nil
241
	} else { //nolint:revive
242
		slog.ErrorContext(ctx, "usercache", "err", err)
243
	}
244
245
	isExists, err := u.userstore.CheckIfUserExists(ctx, id)
246
	if err != nil {
247
		return false, err
248
	}
249
250
	if err := u.cache.SetIsExists(ctx, id.String(), isExists); err != nil {
251
		slog.Error("usercache", "err", err)
252
	}
253
254
	return isExists, nil
255
}
256
257
func (u UserSrv) CheckIfUserIsActivated(ctx context.Context, userID uuid.UUID) (bool, error) {
258
	if r, err := u.cache.GetIsActivated(ctx, userID.String()); err == nil {
259
		return r, nil
260
	} else { //nolint:revive
261
		slog.ErrorContext(ctx, "usercache", "err", err)
262
	}
263
264
	isActivated, err := u.userstore.CheckIfUserExists(ctx, userID)
265
	if err != nil {
266
		return false, err
267
	}
268
269
	if err := u.cache.SetIsActivated(ctx, userID.String(), isActivated); err != nil {
270
		slog.Error("usercache", "err", err)
271
	}
272
273
	return isActivated, nil
274
}
275
276
func (u UserSrv) getTokens(userID uuid.UUID) (dtos.TokensDTO, error) {
277
	accessToken, err := u.jwtTokenizer.AccessToken(jwtutil.Payload{UserID: userID.String()})
278
	if err != nil {
279
		return dtos.TokensDTO{}, err
280
	}
281
282
	refreshToken, err := u.jwtTokenizer.RefreshToken()
283
	if err != nil {
284
		return dtos.TokensDTO{}, err
285
	}
286
287
	return dtos.TokensDTO{
288
		Access:  accessToken,
289
		Refresh: refreshToken,
290
	}, err
291
}