11package auth
22
33import (
4+ "context"
5+ "crypto/rand"
46 "crypto/sha256"
57 "crypto/subtle"
68 "encoding/hex"
@@ -36,6 +38,7 @@ type Service struct {
3638 config Config
3739
3840 users * repository
41+ codesCache * cache.Cache [string ]
3942 usersCache * cache.Cache [models.User ]
4043
4144 devicesSvc * devices.Service
@@ -55,10 +58,39 @@ func New(params Params) *Service {
5558 logger : params .Logger .Named ("Service" ),
5659 idgen : idgen ,
5760
61+ codesCache : cache.New [string ](cache.Config {}),
5862 usersCache : cache.New [models.User ](cache.Config {TTL : 1 * time .Hour }),
5963 }
6064}
6165
66+ // GenerateUserCode generates a unique one-time user authorization code
67+ func (s * Service ) GenerateUserCode (userID string ) (AuthCode , error ) {
68+ var code string
69+ var err error
70+
71+ b := make ([]byte , 3 )
72+ validUntil := time .Now ().Add (codeTTL )
73+ for range 3 {
74+ if _ , err = rand .Read (b ); err != nil {
75+ continue
76+ }
77+ num := (int (b [0 ]) << 16 ) | (int (b [1 ]) << 8 ) | int (b [2 ])
78+ code = fmt .Sprintf ("%06d" , num % 1000000 )
79+
80+ if err = s .codesCache .SetOrFail (code , userID , cache .WithValidUntil (validUntil )); err != nil {
81+ continue
82+ }
83+
84+ break
85+ }
86+
87+ if err != nil {
88+ return AuthCode {}, fmt .Errorf ("can't generate code: %w" , err )
89+ }
90+
91+ return AuthCode {Code : code , ValidUntil : validUntil }, nil
92+ }
93+
6294func (s * Service ) RegisterUser (login , password string ) (models.User , error ) {
6395 user := models.User {
6496 ID : login ,
@@ -143,6 +175,21 @@ func (s *Service) AuthorizeUser(username, password string) (models.User, error)
143175 return user , nil
144176}
145177
178+ // AuthorizeUserByCode authorizes a user by one-time code.
179+ func (s * Service ) AuthorizeUserByCode (code string ) (models.User , error ) {
180+ userID , err := s .codesCache .GetAndDelete (code )
181+ if err != nil {
182+ return models.User {}, err
183+ }
184+
185+ user , err := s .users .GetByID (userID )
186+ if err != nil {
187+ return models.User {}, err
188+ }
189+
190+ return user , nil
191+ }
192+
146193func (s * Service ) ChangePassword (userID string , currentPassword string , newPassword string ) error {
147194 user , err := s .users .GetByLogin (userID )
148195 if err != nil {
@@ -171,3 +218,24 @@ func (s *Service) ChangePassword(userID string, currentPassword string, newPassw
171218
172219 return nil
173220}
221+
222+ // Run starts a ticker that triggers the clean function every hour.
223+ // It runs indefinitely until the provided context is canceled.
224+ func (s * Service ) Run (ctx context.Context ) {
225+ ticker := time .NewTicker (1 * time .Hour )
226+ defer ticker .Stop ()
227+
228+ for {
229+ select {
230+ case <- ctx .Done ():
231+ return
232+ case <- ticker .C :
233+ s .clean (ctx )
234+ }
235+ }
236+ }
237+
238+ func (s * Service ) clean (_ context.Context ) {
239+ s .codesCache .Cleanup ()
240+ s .usersCache .Cleanup ()
241+ }
0 commit comments