diff --git a/server/db/db.go b/server/db/db.go index 4589dc6..9e6b098 100644 --- a/server/db/db.go +++ b/server/db/db.go @@ -16,7 +16,7 @@ type Manager interface { AddUser(user User) (User, error) GetUsers() ([]User, error) GetUserByEmail(email string) (User, error) - UpdateVerificationTime(verifiedAt int64, email string) error + UpdateVerificationTime(verifiedAt int64, id uint) error AddVerification(verification Verification) (Verification, error) GetVerificationByToken(token string) (Verification, error) DeleteToken(email string) error diff --git a/server/db/user.go b/server/db/user.go index ccd2ab0..dfd48dd 100644 --- a/server/db/user.go +++ b/server/db/user.go @@ -24,8 +24,10 @@ type User struct { func (user *User) BeforeSave(tx *gorm.DB) error { // Modify current operation through tx.Statement, e.g: - if pw, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost); err == nil { - tx.Statement.SetColumn("Password", pw) + if user.Password != "" { + if pw, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost); err == nil { + tx.Statement.SetColumn("Password", string(pw)) + } } return nil @@ -63,8 +65,11 @@ func (mgr *manager) GetUserByEmail(email string) (User, error) { return user, nil } -func (mgr *manager) UpdateVerificationTime(verifiedAt int64, email string) error { - result := mgr.db.Model(&User{}).Where("email = ?", email).Update("email_verified_at", verifiedAt) +func (mgr *manager) UpdateVerificationTime(verifiedAt int64, id uint) error { + user := &User{ + ID: id, + } + result := mgr.db.Model(&user).Where("id = ?", id).Update("email_verified_at", verifiedAt) if result.Error != nil { return result.Error diff --git a/server/graph/generated/generated.go b/server/graph/generated/generated.go index c38b739..70382d2 100644 --- a/server/graph/generated/generated.go +++ b/server/graph/generated/generated.go @@ -39,7 +39,8 @@ type ResolverRoot interface { Query() QueryResolver } -type DirectiveRoot struct{} +type DirectiveRoot struct { +} type ComplexityRoot struct { Error struct { @@ -48,9 +49,10 @@ type ComplexityRoot struct { } LoginResponse struct { - AccessToken func(childComplexity int) int - Message func(childComplexity int) int - User func(childComplexity int) int + AccessToken func(childComplexity int) int + AccessTokenExpiresAt func(childComplexity int) int + Message func(childComplexity int) int + User func(childComplexity int) int } Mutation struct { @@ -104,7 +106,6 @@ type MutationResolver interface { Login(ctx context.Context, params model.LoginInput) (*model.LoginResponse, error) Logout(ctx context.Context) (*model.Response, error) } - type QueryResolver interface { Users(ctx context.Context) ([]*model.User, error) Token(ctx context.Context) (*model.LoginResponse, error) @@ -146,6 +147,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.LoginResponse.AccessToken(childComplexity), true + case "LoginResponse.accessTokenExpiresAt": + if e.complexity.LoginResponse.AccessTokenExpiresAt == nil { + break + } + + return e.complexity.LoginResponse.AccessTokenExpiresAt(childComplexity), true + case "LoginResponse.message": if e.complexity.LoginResponse.Message == nil { break @@ -457,6 +465,7 @@ type Error { type LoginResponse { message: String! accessToken: String + accessTokenExpiresAt: Int64 user: User } @@ -741,6 +750,38 @@ func (ec *executionContext) _LoginResponse_accessToken(ctx context.Context, fiel return ec.marshalOString2ᚖstring(ctx, field.Selections, res) } +func (ec *executionContext) _LoginResponse_accessTokenExpiresAt(ctx context.Context, field graphql.CollectedField, obj *model.LoginResponse) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "LoginResponse", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.AccessTokenExpiresAt, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*int64) + fc.Result = res + return ec.marshalOInt642ᚖint64(ctx, field.Selections, res) +} + func (ec *executionContext) _LoginResponse_user(ctx context.Context, field graphql.CollectedField, obj *model.LoginResponse) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -2819,7 +2860,7 @@ func (ec *executionContext) ___Type_ofType(ctx context.Context, field graphql.Co func (ec *executionContext) unmarshalInputLoginInput(ctx context.Context, obj interface{}) (model.LoginInput, error) { var it model.LoginInput - asMap := obj.(map[string]interface{}) + var asMap = obj.(map[string]interface{}) for k, v := range asMap { switch k { @@ -2847,7 +2888,7 @@ func (ec *executionContext) unmarshalInputLoginInput(ctx context.Context, obj in func (ec *executionContext) unmarshalInputSignUpInput(ctx context.Context, obj interface{}) (model.SignUpInput, error) { var it model.SignUpInput - asMap := obj.(map[string]interface{}) + var asMap = obj.(map[string]interface{}) for k, v := range asMap { switch k { @@ -2907,7 +2948,7 @@ func (ec *executionContext) unmarshalInputSignUpInput(ctx context.Context, obj i func (ec *executionContext) unmarshalInputVerifySignupTokenInput(ctx context.Context, obj interface{}) (model.VerifySignupTokenInput, error) { var it model.VerifySignupTokenInput - asMap := obj.(map[string]interface{}) + var asMap = obj.(map[string]interface{}) for k, v := range asMap { switch k { @@ -2983,6 +3024,8 @@ func (ec *executionContext) _LoginResponse(ctx context.Context, sel ast.Selectio } case "accessToken": out.Values[i] = ec._LoginResponse_accessToken(ctx, field, obj) + case "accessTokenExpiresAt": + out.Values[i] = ec._LoginResponse_accessTokenExpiresAt(ctx, field, obj) case "user": out.Values[i] = ec._LoginResponse_user(ctx, field, obj) default: diff --git a/server/graph/model/models_gen.go b/server/graph/model/models_gen.go index 9d03a47..754cc09 100644 --- a/server/graph/model/models_gen.go +++ b/server/graph/model/models_gen.go @@ -13,9 +13,10 @@ type LoginInput struct { } type LoginResponse struct { - Message string `json:"message"` - AccessToken *string `json:"accessToken"` - User *User `json:"user"` + Message string `json:"message"` + AccessToken *string `json:"accessToken"` + AccessTokenExpiresAt *int64 `json:"accessTokenExpiresAt"` + User *User `json:"user"` } type Response struct { diff --git a/server/graph/schema.graphqls b/server/graph/schema.graphqls index 18e211b..dcbecd7 100644 --- a/server/graph/schema.graphqls +++ b/server/graph/schema.graphqls @@ -34,6 +34,7 @@ type Error { type LoginResponse { message: String! accessToken: String + accessTokenExpiresAt: Int64 user: User } diff --git a/server/graph/schema.resolvers.go b/server/graph/schema.resolvers.go index 67f9c3c..fc63824 100644 --- a/server/graph/schema.resolvers.go +++ b/server/graph/schema.resolvers.go @@ -38,23 +38,23 @@ func (r *mutationResolver) VerifySignupToken(ctx context.Context, params model.V return res, errors.New(`Invalid token`) } - // update email_verified_at in users table - db.Mgr.UpdateVerificationTime(time.Now().Unix(), claim.Email) - // delete from verification table - db.Mgr.DeleteToken(claim.Email) - user, err := db.Mgr.GetUserByEmail(claim.Email) if err != nil { return res, err } + // update email_verified_at in users table + db.Mgr.UpdateVerificationTime(time.Now().Unix(), user.ID) + // delete from verification table + db.Mgr.DeleteToken(claim.Email) + userIdStr := fmt.Sprintf("%d", user.ID) - refreshToken, _ := utils.CreateAuthToken(utils.UserAuthInfo{ + refreshToken, _, _ := utils.CreateAuthToken(utils.UserAuthInfo{ ID: userIdStr, Email: user.Email, }, enum.RefreshToken) - accessToken, _ := utils.CreateAuthToken(utils.UserAuthInfo{ + accessToken, expiresAt, _ := utils.CreateAuthToken(utils.UserAuthInfo{ ID: userIdStr, Email: user.Email, }, enum.AccessToken) @@ -62,8 +62,9 @@ func (r *mutationResolver) VerifySignupToken(ctx context.Context, params model.V session.SetToken(userIdStr, refreshToken) res = &model.LoginResponse{ - Message: `Email verified successfully.`, - AccessToken: &accessToken, + Message: `Email verified successfully.`, + AccessToken: &accessToken, + AccessTokenExpiresAt: &expiresAt, User: &model.User{ ID: userIdStr, Email: user.Email, @@ -165,18 +166,23 @@ func (r *mutationResolver) Login(ctx context.Context, params model.LoginInput) ( return res, errors.New(`Email not verified`) } // match password + log.Println("params Pass", params.Password) + log.Println("hashed pass", user.Password) + cost, err := bcrypt.Cost([]byte(user.Password)) + log.Println(cost, err) err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(params.Password)) + if err != nil { log.Println("Compare password error:", err) return res, errors.New(`Invalid Password`) } userIdStr := fmt.Sprintf("%d", user.ID) - refreshToken, _ := utils.CreateAuthToken(utils.UserAuthInfo{ + refreshToken, _, _ := utils.CreateAuthToken(utils.UserAuthInfo{ ID: userIdStr, Email: user.Email, }, enum.RefreshToken) - accessToken, _ := utils.CreateAuthToken(utils.UserAuthInfo{ + accessToken, expiresAt, _ := utils.CreateAuthToken(utils.UserAuthInfo{ ID: userIdStr, Email: user.Email, }, enum.AccessToken) @@ -184,8 +190,9 @@ func (r *mutationResolver) Login(ctx context.Context, params model.LoginInput) ( session.SetToken(userIdStr, refreshToken) res = &model.LoginResponse{ - Message: `Logged in successfully`, - AccessToken: &accessToken, + Message: `Logged in successfully`, + AccessToken: &accessToken, + AccessTokenExpiresAt: &expiresAt, User: &model.User{ ID: userIdStr, Email: user.Email, @@ -259,22 +266,40 @@ func (r *queryResolver) Token(ctx context.Context) (*model.LoginResponse, error) return res, err } - claim, err := utils.VerifyAuthToken(token) - if err != nil { - // generate new accessToken - return res, err - } + claim, accessTokenErr := utils.VerifyAuthToken(token) + expiresAt := claim.ExpiresAt user, err := db.Mgr.GetUserByEmail(claim.Email) if err != nil { return res, err } + userIdStr := fmt.Sprintf("%d", user.ID) + + sessionToken := session.GetToken(userIdStr) + + if sessionToken == "" { + return res, errors.New(`Unauthorized`) + } + // TODO check if session token has expired + + if accessTokenErr != nil { + // if access token has expired and refresh/session token is valid + // generate new accessToken + fmt.Println(`here... getting new accesstoken`) + token, expiresAt, _ = utils.CreateAuthToken(utils.UserAuthInfo{ + ID: userIdStr, + Email: user.Email, + }, enum.AccessToken) + + } + utils.SetCookie(gc, token) res = &model.LoginResponse{ - Message: `Email verified successfully.`, - AccessToken: &token, + Message: `Email verified successfully.`, + AccessToken: &token, + AccessTokenExpiresAt: &expiresAt, User: &model.User{ - ID: fmt.Sprintf("%d", user.ID), + ID: userIdStr, Email: user.Email, Image: &user.Image, FirstName: &user.FirstName, diff --git a/server/utils/authToken.go b/server/utils/authToken.go index d3fac37..76634f0 100644 --- a/server/utils/authToken.go +++ b/server/utils/authToken.go @@ -23,23 +23,29 @@ type UserAuthClaim struct { UserAuthInfo } -func CreateAuthToken(user UserAuthInfo, tokenType enum.TokenType) (string, error) { +func CreateAuthToken(user UserAuthInfo, tokenType enum.TokenType) (string, int64, error) { t := jwt.New(jwt.GetSigningMethod(constants.JWT_TYPE)) expiryBound := time.Hour if tokenType == enum.RefreshToken { - // expires in 90 days - expiryBound = time.Hour * 2160 + // expires in 1 year + expiryBound = time.Hour * 8760 } + expiresAt := time.Now().Add(expiryBound).Unix() + t.Claims = &UserAuthClaim{ &jwt.StandardClaims{ - ExpiresAt: time.Now().Add(expiryBound).Unix(), + ExpiresAt: expiresAt, }, tokenType.String(), user, } - return t.SignedString([]byte(constants.JWT_SECRET)) + token, err := t.SignedString([]byte(constants.JWT_SECRET)) + if err != nil { + return "", 0, err + } + return token, expiresAt, nil } func GetAuthToken(gc *gin.Context) (string, error) {