Skip to content

Commit

Permalink
adjusted to comments
Browse files Browse the repository at this point in the history
  • Loading branch information
remoterami committed Jun 21, 2024
1 parent a4f90de commit cb46f0a
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 12 deletions.
22 changes: 21 additions & 1 deletion backend/pkg/api/data_access/dummy.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,27 @@ func (d *DummyService) GetUserExists(email string) (bool, error) {
return r, err
}

func (d *DummyService) AddUser(email, password string) error {
func (d *DummyService) CreateUser(email, password string) error {
return nil
}

func (d *DummyService) GetEmailConfirmationTime(email string) (time.Time, error) {
r := time.Time{}
err := commonFakeData(&r)
return r, err
}

func (d *DummyService) UpdateEmailConfirmationTime(email string) error {
return nil
}

func (d *DummyService) GetEmailConfirmationHash(email string) (string, error) {
r := ""
err := commonFakeData(&r)
return r, err
}

func (d *DummyService) UpdateEmailConfirmationHash(email, confirmationHash string) error {
return nil
}

Expand Down
36 changes: 29 additions & 7 deletions backend/pkg/api/data_access/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@ import (
"math"
"time"

"github.com/gobitfly/beaconchain/pkg/api/services"
t "github.com/gobitfly/beaconchain/pkg/api/types"
"github.com/gobitfly/beaconchain/pkg/commons/utils"
"github.com/pkg/errors"
)

type UserRepository interface {
GetUserExists(email string) (bool, error)
AddUser(email, password string) error
CreateUser(email, password string) error
GetEmailConfirmationTime(email string) (time.Time, error)
UpdateEmailConfirmationTime(email string) error
GetEmailConfirmationHash(email string) (string, error)
UpdateEmailConfirmationHash(email, confirmationHash string) error
GetUserCredentialInfo(email string) (*t.UserCredentialInfo, error)
GetUserIdByApiKey(apiKey string) (uint64, error)
GetUserInfo(id uint64) (*t.UserInfo, error)
Expand All @@ -23,14 +26,33 @@ type UserRepository interface {
}

func (d *DataAccessService) GetUserExists(email string) (bool, error) {
// TODO @patrick
// TODO @DATA-ACCESS
return d.dummy.GetUserExists(email)
}

func (d *DataAccessService) AddUser(email, password string) error {
// TODO @patrick
d.services.QueueEmail(services.EMail{Recipient: email, Message: "Click to confirm email"}, 60*5)
return d.dummy.AddUser(email, password)
func (d *DataAccessService) CreateUser(email, password string) error {
// TODO @DATA-ACCESS
return d.dummy.CreateUser(email, password)
}

func (d *DataAccessService) GetEmailConfirmationTime(email string) (time.Time, error) {
// TODO @DATA-ACCESS
return d.dummy.GetEmailConfirmationTime(email)
}

func (d *DataAccessService) UpdateEmailConfirmationTime(email string) error {
// TODO @DATA-ACCESS
return d.dummy.UpdateEmailConfirmationTime(email)
}

func (d *DataAccessService) GetEmailConfirmationHash(email string) (string, error) {
// TODO @DATA-ACCESS
return d.dummy.GetEmailConfirmationHash(email)
}

func (d *DataAccessService) UpdateEmailConfirmationHash(email, confirmationHash string) error {
// TODO @DATA-ACCESS
return d.dummy.UpdateEmailConfirmationHash(email, confirmationHash)
}

func (d *DataAccessService) GetUserCredentialInfo(email string) (*t.UserCredentialInfo, error) {
Expand Down
60 changes: 57 additions & 3 deletions backend/pkg/api/handlers/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@ package handlers
import (
"context"
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"time"

dataaccess "github.com/gobitfly/beaconchain/pkg/api/data_access"
"github.com/gobitfly/beaconchain/pkg/api/types"
"github.com/gobitfly/beaconchain/pkg/commons/log"
"github.com/gobitfly/beaconchain/pkg/commons/mail"
commontsTypes "github.com/gobitfly/beaconchain/pkg/commons/types"
"github.com/gobitfly/beaconchain/pkg/commons/utils"
"github.com/gorilla/mux"
"golang.org/x/crypto/bcrypt"
)
Expand All @@ -20,6 +26,8 @@ const (
userGroupKey = "user_group"
)

const authConfirmEmailRateLimit time.Duration = time.Minute * 2

type ctxKet string

const ctxUserIdKey ctxKet = "user_id"
Expand All @@ -43,6 +51,48 @@ func (h *HandlerService) getUserBySession(r *http.Request) (types.UserCredential
}, nil
}

// TODO move to service?
func (h *HandlerService) sendConfirmationEmail(email string) error {
// 1. check last confirmation time to enforce ratelimit
lastTs, err := h.dai.GetEmailConfirmationTime(email)
if err != nil {
return errors.New("error getting confirmation-ts")
}
if lastTs.Add(authConfirmEmailRateLimit).After(time.Now()) {
return errors.New("rate limit reached, try again later")
}

// 2. update confirmation hash (before sending so there's no hash mismatch on failure)
confirmationHash := utils.RandomString(40)
err = h.dai.UpdateEmailConfirmationHash(email, confirmationHash)
if err != nil {
return errors.New("error updating confirmation hash")
}

// 3. send confirmation email
subject := fmt.Sprintf("%s: Verify your email-address", utils.Config.Frontend.SiteDomain)
msg := fmt.Sprintf(`Please verify your email on %[1]s by clicking this link:
https://%[1]s/confirm/%[2]s
Best regards,
%[1]s
`, utils.Config.Frontend.SiteDomain, confirmationHash)
err = mail.SendTextMail(email, subject, msg, []commontsTypes.EmailAttachment{})
if err != nil {
return errors.New("error sending confirmation email, try again later")
}

// 4. update confirmation time (only after mail was sent)
err = h.dai.UpdateEmailConfirmationTime(email)
if err != nil {
// shouldn't present this as error to user, confirmation works fine
log.Error(err, "error updating email confirmation time, rate limiting won't be enforced", 0, nil)
}
return nil
}

func (h *HandlerService) GetUserIdBySession(r *http.Request) (uint64, error) {
user, err := h.getUserBySession(r)
if err != nil {
Expand Down Expand Up @@ -127,14 +177,18 @@ func (h *HandlerService) InternalPostUsers(w http.ResponseWriter, r *http.Reques
}

// add user
err = h.dai.AddUser(email, string(passwordHash))
err = h.dai.CreateUser(email, string(passwordHash))
if err != nil {
handleErr(w, err)
return
}

// queue confirmation email for sending
// TODO who should manage sender service, who should've access?
// email confirmation
err = h.sendConfirmationEmail(email)
if err != nil {
handleErr(w, err)
return
}

returnOk(w, nil)
}
Expand Down
2 changes: 1 addition & 1 deletion backend/pkg/api/handlers/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ func (v *validationError) checkNameNotEmpty(name string) string {
}

func (v *validationError) checkEmail(email string) string {
return v.checkRegex(reEmail, email, "email")
return v.checkRegex(reEmail, strings.ToLower(email), "email")
}

func (v *validationError) checkPassword(password string) string {
Expand Down
20 changes: 20 additions & 0 deletions backend/pkg/commons/utils/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,23 @@ func GenerateRandomAPIKey() (string, error) {
apiKeyBase64 := base64.RawURLEncoding.EncodeToString(key)
return apiKeyBase64, nil
}

// RandomString returns a random hex-string
func RandomString(length int) string {
b, _ := GenerateRandomBytesSecure(length)
const charset = "abcdefghijklmnopqrstuvwxyz0123456789"
for i := range b {
b[i] = charset[int(b[i])%len(charset)]
}
return string(b)
}

func GenerateRandomBytesSecure(n int) ([]byte, error) {
b := make([]byte, n)
_, err := securerand.Read(b)
if err != nil {
return nil, err
}

return b, nil
}

0 comments on commit cb46f0a

Please sign in to comment.