@@ -2,6 +2,7 @@ package api
22
33import (
44 "context"
5+ "crypto/sha256"
56 "encoding/json"
67 "fmt"
78 "net/http"
@@ -14,7 +15,6 @@ import (
1415 "github.com/netlify/gotrue/storage"
1516 "github.com/pkg/errors"
1617 "github.com/sethvargo/go-password/password"
17- "github.com/sirupsen/logrus"
1818)
1919
2020var (
@@ -69,14 +69,18 @@ func (a *API) GenerateLink(w http.ResponseWriter, r *http.Request) error {
6969 var url string
7070 referrer := a .getRedirectURLOrReferrer (r , params .RedirectTo )
7171 now := time .Now ()
72+ otp , err := crypto .GenerateOtp (config .Mailer .OtpLength )
73+ if err != nil {
74+ return err
75+ }
7276 err = a .db .Transaction (func (tx * storage.Connection ) error {
7377 var terr error
7478 switch params .Type {
7579 case "magiclink" , "recovery" :
7680 if terr = models .NewAuditLogEntry (tx , instanceID , user , models .UserRecoveryRequestedAction , "" , nil ); terr != nil {
7781 return terr
7882 }
79- user .RecoveryToken = crypto . SecureToken ( )
83+ user .RecoveryToken = fmt . Sprintf ( "%x" , sha256 . Sum224 ([] byte ( user . GetEmail () + otp )) )
8084 user .RecoverySentAt = & now
8185 terr = errors .Wrap (tx .UpdateOnly (user , "recovery_token" , "recovery_sent_at" ), "Database error updating user for recovery" )
8286 case "invite" :
@@ -102,7 +106,7 @@ func (a *API) GenerateLink(w http.ResponseWriter, r *http.Request) error {
102106 }); terr != nil {
103107 return terr
104108 }
105- user .ConfirmationToken = crypto . SecureToken ( )
109+ user .ConfirmationToken = fmt . Sprintf ( "%x" , sha256 . Sum224 ([] byte ( user . GetEmail () + otp )) )
106110 user .ConfirmationSentAt = & now
107111 user .InvitedAt = & now
108112 terr = errors .Wrap (tx .UpdateOnly (user , "confirmation_token" , "confirmation_sent_at" , "invited_at" ), "Database error updating user for invite" )
@@ -133,7 +137,7 @@ func (a *API) GenerateLink(w http.ResponseWriter, r *http.Request) error {
133137 return terr
134138 }
135139 }
136- user .ConfirmationToken = crypto . SecureToken ( )
140+ user .ConfirmationToken = fmt . Sprintf ( "%x" , sha256 . Sum224 ([] byte ( user . GetEmail () + otp )) )
137141 user .ConfirmationSentAt = & now
138142 terr = errors .Wrap (tx .UpdateOnly (user , "confirmation_token" , "confirmation_sent_at" ), "Database error updating user for confirmation" )
139143 default :
@@ -168,34 +172,36 @@ func (a *API) GenerateLink(w http.ResponseWriter, r *http.Request) error {
168172 return sendJSON (w , http .StatusOK , resp )
169173}
170174
171- func sendConfirmation (tx * storage.Connection , u * models.User , mailer mailer.Mailer , maxFrequency time.Duration , referrerURL string ) error {
175+ func sendConfirmation (tx * storage.Connection , u * models.User , mailer mailer.Mailer , maxFrequency time.Duration , referrerURL string , otpLength int ) error {
172176 var err error
173177 if u .ConfirmationSentAt != nil && ! u .ConfirmationSentAt .Add (maxFrequency ).Before (time .Now ()) {
174178 return MaxFrequencyLimitError
175179 }
176180 oldToken := u .ConfirmationToken
177- u . ConfirmationToken , err = generateUniqueEmailOtp ( tx , confirmationToken )
181+ otp , err := crypto . GenerateOtp ( otpLength )
178182 if err != nil {
179183 return err
180184 }
185+ u .ConfirmationToken = fmt .Sprintf ("%x" , sha256 .Sum224 ([]byte (u .GetEmail ()+ otp )))
181186 now := time .Now ()
182- if err := mailer .ConfirmationMail (u , referrerURL ); err != nil {
187+ if err := mailer .ConfirmationMail (u , otp , referrerURL ); err != nil {
183188 u .ConfirmationToken = oldToken
184189 return errors .Wrap (err , "Error sending confirmation email" )
185190 }
186191 u .ConfirmationSentAt = & now
187192 return errors .Wrap (tx .UpdateOnly (u , "confirmation_token" , "confirmation_sent_at" ), "Database error updating user for confirmation" )
188193}
189194
190- func sendInvite (tx * storage.Connection , u * models.User , mailer mailer.Mailer , referrerURL string ) error {
195+ func sendInvite (tx * storage.Connection , u * models.User , mailer mailer.Mailer , referrerURL string , otpLength int ) error {
191196 var err error
192197 oldToken := u .ConfirmationToken
193- u . ConfirmationToken , err = generateUniqueEmailOtp ( tx , confirmationToken )
198+ otp , err := crypto . GenerateOtp ( otpLength )
194199 if err != nil {
195200 return err
196201 }
202+ u .ConfirmationToken = fmt .Sprintf ("%x" , sha256 .Sum224 ([]byte (u .GetEmail ()+ otp )))
197203 now := time .Now ()
198- if err := mailer .InviteMail (u , referrerURL ); err != nil {
204+ if err := mailer .InviteMail (u , otp , referrerURL ); err != nil {
199205 u .ConfirmationToken = oldToken
200206 return errors .Wrap (err , "Error sending invite email" )
201207 }
@@ -204,60 +210,66 @@ func sendInvite(tx *storage.Connection, u *models.User, mailer mailer.Mailer, re
204210 return errors .Wrap (tx .UpdateOnly (u , "confirmation_token" , "confirmation_sent_at" , "invited_at" ), "Database error updating user for invite" )
205211}
206212
207- func (a * API ) sendPasswordRecovery (tx * storage.Connection , u * models.User , mailer mailer.Mailer , maxFrequency time.Duration , referrerURL string ) error {
213+ func (a * API ) sendPasswordRecovery (tx * storage.Connection , u * models.User , mailer mailer.Mailer , maxFrequency time.Duration , referrerURL string , otpLength int ) error {
208214 var err error
209215 if u .RecoverySentAt != nil && ! u .RecoverySentAt .Add (maxFrequency ).Before (time .Now ()) {
210216 return MaxFrequencyLimitError
211217 }
212218
213219 oldToken := u .RecoveryToken
214- u . RecoveryToken , err = generateUniqueEmailOtp ( tx , recoveryToken )
220+ otp , err := crypto . GenerateOtp ( otpLength )
215221 if err != nil {
216222 return err
217223 }
224+ u .RecoveryToken = fmt .Sprintf ("%x" , sha256 .Sum224 ([]byte (u .GetEmail ()+ otp )))
218225 now := time .Now ()
219- if err := mailer .RecoveryMail (u , referrerURL ); err != nil {
226+ if err := mailer .RecoveryMail (u , otp , referrerURL ); err != nil {
220227 u .RecoveryToken = oldToken
221228 return errors .Wrap (err , "Error sending recovery email" )
222229 }
223230 u .RecoverySentAt = & now
224231 return errors .Wrap (tx .UpdateOnly (u , "recovery_token" , "recovery_sent_at" ), "Database error updating user for recovery" )
225232}
226233
227- func (a * API ) sendReauthenticationOtp (tx * storage.Connection , u * models.User , mailer mailer.Mailer , maxFrequency time.Duration ) error {
234+ func (a * API ) sendReauthenticationOtp (tx * storage.Connection , u * models.User , mailer mailer.Mailer , maxFrequency time.Duration , otpLength int ) error {
228235 var err error
229236 if u .ReauthenticationSentAt != nil && ! u .ReauthenticationSentAt .Add (maxFrequency ).Before (time .Now ()) {
230237 return MaxFrequencyLimitError
231238 }
232239
233240 oldToken := u .ReauthenticationToken
234- u .ReauthenticationToken , err = generateUniqueEmailOtp (tx , reauthenticationToken )
241+ otp , err := crypto .GenerateOtp (otpLength )
242+ if err != nil {
243+ return err
244+ }
245+ u .ReauthenticationToken = fmt .Sprintf ("%x" , sha256 .Sum224 ([]byte (u .GetEmail ()+ otp )))
235246 if err != nil {
236247 return err
237248 }
238249 now := time .Now ()
239- if err := mailer .ReauthenticateMail (u ); err != nil {
250+ if err := mailer .ReauthenticateMail (u , otp ); err != nil {
240251 u .ReauthenticationToken = oldToken
241252 return errors .Wrap (err , "Error sending reauthentication email" )
242253 }
243254 u .ReauthenticationSentAt = & now
244255 return errors .Wrap (tx .UpdateOnly (u , "reauthentication_token" , "reauthentication_sent_at" ), "Database error updating user for reauthentication" )
245256}
246257
247- func (a * API ) sendMagicLink (tx * storage.Connection , u * models.User , mailer mailer.Mailer , maxFrequency time.Duration , referrerURL string ) error {
258+ func (a * API ) sendMagicLink (tx * storage.Connection , u * models.User , mailer mailer.Mailer , maxFrequency time.Duration , referrerURL string , otpLength int ) error {
248259 var err error
249260 // since Magic Link is just a recovery with a different template and behaviour
250261 // around new users we will reuse the recovery db timer to prevent potential abuse
251262 if u .RecoverySentAt != nil && ! u .RecoverySentAt .Add (maxFrequency ).Before (time .Now ()) {
252263 return MaxFrequencyLimitError
253264 }
254265 oldToken := u .RecoveryToken
255- u . RecoveryToken , err = generateUniqueEmailOtp ( tx , recoveryToken )
266+ otp , err := crypto . GenerateOtp ( otpLength )
256267 if err != nil {
257268 return err
258269 }
270+ u .RecoveryToken = fmt .Sprintf ("%x" , sha256 .Sum224 ([]byte (u .GetEmail ()+ otp )))
259271 now := time .Now ()
260- if err := mailer .MagicLinkMail (u , referrerURL ); err != nil {
272+ if err := mailer .MagicLinkMail (u , otp , referrerURL ); err != nil {
261273 u .RecoveryToken = oldToken
262274 return errors .Wrap (err , "Error sending magic link email" )
263275 }
@@ -266,22 +278,29 @@ func (a *API) sendMagicLink(tx *storage.Connection, u *models.User, mailer maile
266278}
267279
268280// sendEmailChange sends out an email change token to the new email.
269- func (a * API ) sendEmailChange (tx * storage.Connection , config * conf.Configuration , u * models.User , mailer mailer.Mailer , email string , referrerURL string ) error {
281+ func (a * API ) sendEmailChange (tx * storage.Connection , config * conf.Configuration , u * models.User , mailer mailer.Mailer , email string , referrerURL string , otpLength int ) error {
270282 var err error
271- u . EmailChangeTokenNew , err = generateUniqueEmailOtp ( tx , emailChangeTokenNew )
283+ otpNew , err := crypto . GenerateOtp ( otpLength )
272284 if err != nil {
273285 return err
274286 }
287+ u .EmailChangeTokenNew = fmt .Sprintf ("%x" , sha256 .Sum224 ([]byte (u .EmailChange + otpNew )))
288+
289+ otpCurrent := ""
275290 if config .Mailer .SecureEmailChangeEnabled && u .GetEmail () != "" {
276- u .EmailChangeTokenCurrent , err = generateUniqueEmailOtp (tx , emailChangeTokenCurrent )
291+ otpCurrent , err = crypto .GenerateOtp (otpLength )
292+ if err != nil {
293+ return err
294+ }
295+ u .EmailChangeTokenCurrent = fmt .Sprintf ("%x" , sha256 .Sum224 ([]byte (u .GetEmail ()+ otpCurrent )))
277296 if err != nil {
278297 return err
279298 }
280299 }
281300 u .EmailChange = email
282301 u .EmailChangeConfirmStatus = zeroConfirmation
283302 now := time .Now ()
284- if err := mailer .EmailChangeMail (u , referrerURL ); err != nil {
303+ if err := mailer .EmailChangeMail (u , otpNew , otpCurrent , referrerURL ); err != nil {
285304 return err
286305 }
287306
@@ -306,30 +325,3 @@ func (a *API) validateEmail(ctx context.Context, email string) error {
306325 }
307326 return nil
308327}
309-
310- // generateUniqueEmailOtp returns a unique otp
311- func generateUniqueEmailOtp (tx * storage.Connection , tokenType tokenType ) (string , error ) {
312- maxRetries := 5
313- otpLength := 20
314- var otp string
315- var err error
316- for i := 0 ; i < maxRetries ; i ++ {
317- otp , err = crypto .GenerateEmailOtp (otpLength )
318- if err != nil {
319- return "" , err
320- }
321- _ , err = models .FindUserByTokenAndTokenType (tx , otp , string (tokenType ))
322- if err != nil {
323- if models .IsNotFoundError (err ) {
324- return otp , nil
325- }
326- return "" , err
327- }
328- logrus .Warn ("otp generated is not unique, retrying." )
329- err = errors .New ("Could not generate a unique email otp" )
330- }
331- if err != nil {
332- return "" , err
333- }
334- return "" , errors .New ("Could not generate a unique email otp" )
335- }
0 commit comments