tsk-30: Update password (#33)
Closes #30 Reviewed-on: phoenix/textsender-auth#33 Co-authored-by: phoenix <kundeng00@pm.me> Co-committed-by: phoenix <kundeng00@pm.me>
This commit is contained in:
@@ -6,3 +6,4 @@ const Login = "/api/v1/login"
|
||||
const CreateServiceUser = "/api/v1/service/register"
|
||||
const LoginServiceUser = "/api/v1/service/login"
|
||||
const TokenRefresh = "/api/v1/token/refresh"
|
||||
const UpdatePassword = "/api/v1/user/password/update"
|
||||
|
||||
+116
-27
@@ -1,16 +1,27 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"git.kundeng.us/phoenix/textsender-models/tx0/token"
|
||||
"git.kundeng.us/phoenix/textsender-models/tx0/user"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"git.kundeng.us/phoenix/textsender-auth/internal/config"
|
||||
"git.kundeng.us/phoenix/textsender-auth/internal/store"
|
||||
"git.kundeng.us/phoenix/textsender-auth/internal/utility"
|
||||
)
|
||||
|
||||
type LoginHandler struct {
|
||||
Config *config.Config
|
||||
UserStore store.UserStore
|
||||
}
|
||||
|
||||
func NewLoginHandler(cfg *config.Config, userStore store.UserStore) *LoginHandler {
|
||||
return &LoginHandler{Config: cfg, UserStore: userStore}
|
||||
}
|
||||
|
||||
type LoginAccount struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
@@ -21,15 +32,6 @@ type LoginResponse struct {
|
||||
Data []token.Login `json:"data"`
|
||||
}
|
||||
|
||||
type LoginHandler struct {
|
||||
Config *config.Config
|
||||
UserStore store.UserStore
|
||||
}
|
||||
|
||||
func NewLoginHandler(cfg *config.Config, userStore store.UserStore) *LoginHandler {
|
||||
return &LoginHandler{Config: cfg, UserStore: userStore}
|
||||
}
|
||||
|
||||
// Login godoc
|
||||
// @Summary Login
|
||||
// @Description Login and be given an access token (requires JWT)
|
||||
@@ -55,7 +57,7 @@ func (l *LoginHandler) Login(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
if exists, err := l.UserStore.UserExists(ctx, req.Username); err != nil {
|
||||
fmt.Printf("Error: %v", err)
|
||||
log.Println("Error:", err)
|
||||
statusCode = http.StatusInternalServerError
|
||||
resp.Message = err.Error()
|
||||
} else {
|
||||
@@ -67,23 +69,28 @@ func (l *LoginHandler) Login(w http.ResponseWriter, r *http.Request) {
|
||||
statusCode = http.StatusInternalServerError
|
||||
resp.Message = err.Error()
|
||||
} else {
|
||||
hashing := utility.HashMash{Password: req.Password}
|
||||
if hashing.CheckPasswordHash(req.Password, user.Password) {
|
||||
var tokGen utility.TokenGenerator
|
||||
secretKey := config.GetSecretKey()
|
||||
tokGen.SetSecretKey(secretKey)
|
||||
if myToken, err := tokGen.GenerateToken(*user); err != nil {
|
||||
fmt.Println(err.Error())
|
||||
statusCode = http.StatusInternalServerError
|
||||
resp.Message = "Error generating token"
|
||||
} else {
|
||||
statusCode = http.StatusOK
|
||||
resp.Data = append(resp.Data, *myToken)
|
||||
resp.Message = "Successful"
|
||||
}
|
||||
hashing := utility.HashMash{}
|
||||
if err := hashing.SetPassword(req.Password); err != nil {
|
||||
statusCode = http.StatusInternalServerError
|
||||
resp.Message = err.Error()
|
||||
} else {
|
||||
statusCode = http.StatusNotFound
|
||||
resp.Message = "User not found"
|
||||
if hashing.CheckPasswordHash(req.Password, user.Password) {
|
||||
var tokGen utility.TokenGenerator
|
||||
secretKey := config.GetSecretKey()
|
||||
tokGen.SetSecretKey(secretKey)
|
||||
if myToken, err := tokGen.GenerateToken(*user); err != nil {
|
||||
log.Println(err.Error())
|
||||
statusCode = http.StatusInternalServerError
|
||||
resp.Message = "Error generating token"
|
||||
} else {
|
||||
statusCode = http.StatusOK
|
||||
resp.Data = append(resp.Data, *myToken)
|
||||
resp.Message = "Successful"
|
||||
}
|
||||
} else {
|
||||
statusCode = http.StatusNotFound
|
||||
resp.Message = "User not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,3 +98,85 @@ func (l *LoginHandler) Login(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
RespondWithJson(w, statusCode, &resp)
|
||||
}
|
||||
|
||||
type UpdatePasswordRequest struct {
|
||||
UserId uuid.UUID `json:"user_id"`
|
||||
CurrentPassword string `json:"current_password"`
|
||||
UpdatedPassword string `json:"updated_password"`
|
||||
ConfirmedPassword string `json:"confirmed_password"`
|
||||
}
|
||||
|
||||
type UpdatePasswordResponse struct {
|
||||
Message string `json:"message"`
|
||||
Data []user.User `json:"data"`
|
||||
}
|
||||
|
||||
// UpdatePassword godoc
|
||||
// @Summary Update Password
|
||||
// @Description Update the password of a regular account (requires JWT)
|
||||
// @Tags users
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param request body UpdatePasswordRequest true "Needed data to update password"
|
||||
// @Success 200 {object} UpdatePasswordResponse
|
||||
// @Failure 400 {object} UpdatePasswordResponse
|
||||
// @Failure 500 {object} UpdatePasswordResponse
|
||||
// @Router /user/password/update [patch]
|
||||
func (l *LoginHandler) UpdatePassword(w http.ResponseWriter, r *http.Request) {
|
||||
var req UpdatePasswordRequest
|
||||
if err := ExtractFromRequest(r, &req); err != nil {
|
||||
http.Error(w, "Invalid JSON: "+err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
var statusCode int
|
||||
var resp UpdatePasswordResponse
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
if usr, err := l.UserStore.GetUserByID(ctx, req.UserId); err != nil {
|
||||
log.Println("Error:", err)
|
||||
statusCode = http.StatusInternalServerError
|
||||
resp.Message = err.Error()
|
||||
} else {
|
||||
hashing := utility.HashMash{}
|
||||
if err := hashing.SetPassword(req.CurrentPassword); err != nil {
|
||||
statusCode = http.StatusInternalServerError
|
||||
resp.Message = err.Error()
|
||||
} else {
|
||||
if hashing.CheckPasswordHash(req.CurrentPassword, usr.Password) {
|
||||
if req.UpdatedPassword == req.ConfirmedPassword {
|
||||
// Hash password
|
||||
err := hashing.SetPassword(req.UpdatedPassword)
|
||||
hashedPassword, err := hashing.HashPassword()
|
||||
if err != nil {
|
||||
statusCode = http.StatusInternalServerError
|
||||
resp.Message = err.Error()
|
||||
} else {
|
||||
// Update user password
|
||||
usr.Password = hashedPassword
|
||||
// Save user in DB
|
||||
if rowsAffected, err := l.UserStore.UpdatePassword(ctx, usr.Id, usr.Password); err != nil {
|
||||
statusCode = http.StatusInternalServerError
|
||||
resp.Message = err.Error()
|
||||
} else {
|
||||
log.Println("Rows affected:", rowsAffected)
|
||||
statusCode = http.StatusOK
|
||||
resp.Message = "Successful"
|
||||
resp.Data = append(resp.Data, *usr)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
statusCode = http.StatusBadRequest
|
||||
resp.Message = "Passwords do not match"
|
||||
}
|
||||
} else {
|
||||
statusCode = http.StatusBadRequest
|
||||
resp.Message = "User not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RespondWithJson(w, statusCode, &resp)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
|
||||
"git.kundeng.us/phoenix/textsender-auth/internal/handler/endpoint"
|
||||
"git.kundeng.us/phoenix/textsender-auth/internal/store/mock"
|
||||
"git.kundeng.us/phoenix/textsender-auth/internal/utility"
|
||||
)
|
||||
|
||||
func TestLogin(t *testing.T) {
|
||||
@@ -21,20 +20,13 @@ func TestLogin(t *testing.T) {
|
||||
mockstore := mock.NewMockUserStore()
|
||||
handler := NewLoginHandler(cfg, mockstore)
|
||||
|
||||
testUser := GetTestUser()
|
||||
unhashedPassword := testUser.Password
|
||||
hashing := utility.HashMash{Password: testUser.Password}
|
||||
hashedPassword, err := hashing.HashPassword()
|
||||
assert.NoError(t, err)
|
||||
|
||||
testUser.Password = hashedPassword
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
mockstore.CreateUser(ctx, &testUser)
|
||||
testUser, unhashedPassword, err := createUser(ctx, mockstore)
|
||||
assert.NoError(t, err, "Error Creating user")
|
||||
|
||||
loginUser := LoginAccount{Username: testUser.Username, Password: unhashedPassword}
|
||||
loginUser := LoginAccount{Username: testUser.Username, Password: *unhashedPassword}
|
||||
jsonValue, _ := json.Marshal(loginUser)
|
||||
|
||||
req, _ := http.NewRequest("POST", endpoint.Login, strings.NewReader(string(jsonValue)))
|
||||
@@ -50,3 +42,29 @@ func TestLogin(t *testing.T) {
|
||||
|
||||
assert.NotEmpty(t, response.Data, "An access token should have been returned")
|
||||
}
|
||||
|
||||
func TestUpdatePassword(t *testing.T) {
|
||||
cfg := GetConfig()
|
||||
mockstore := mock.NewMockUserStore()
|
||||
handler := NewLoginHandler(cfg, mockstore)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
testUser, unhashedPassword, err := createUser(ctx, mockstore)
|
||||
assert.NoError(t, err, "Error Creating user")
|
||||
assert.NotNil(t, testUser, "User should not be nil")
|
||||
|
||||
updatedPassword := "TakeATrip2yonder!"
|
||||
newPassword := UpdatePasswordRequest{UserId: testUser.Id, CurrentPassword: *unhashedPassword, UpdatedPassword: updatedPassword, ConfirmedPassword: updatedPassword}
|
||||
jsonValue, _ := json.Marshal(newPassword)
|
||||
|
||||
req, _ := http.NewRequest("PATCH", endpoint.UpdatePassword, strings.NewReader(string(jsonValue)))
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
handler.UpdatePassword(rr, req)
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
|
||||
var response UpdatePasswordResponse
|
||||
err = json.Unmarshal(rr.Body.Bytes(), &response)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -19,8 +19,12 @@ func TestRefreshTokenWithMock(t *testing.T) {
|
||||
var serviceUser user.ServiceUser
|
||||
var hashedPassword string
|
||||
var err error
|
||||
unhashed := "9328nr29nudx3292m320!"
|
||||
hashing := utility.HashMash{Password: unhashed}
|
||||
unhashed := "A9328nr29nudx3292m320!"
|
||||
hashing := utility.HashMash{}
|
||||
if err := hashing.SetPassword(unhashed); err != nil {
|
||||
assert.NoError(t, err, "Error setting password")
|
||||
}
|
||||
|
||||
if hashedPassword, err = hashing.HashPassword(); err != nil {
|
||||
assert.NoError(t, err, "Error hashing password: %v", err)
|
||||
} else {
|
||||
|
||||
@@ -85,20 +85,25 @@ func (u *UserHandler) Register(w http.ResponseWriter, r *http.Request) {
|
||||
statusCode = http.StatusBadRequest
|
||||
resp.Message = "Failure in creating User"
|
||||
} else {
|
||||
hashing := utility.HashMash{Password: user.Password}
|
||||
if hashedPassword, err := hashing.HashPassword(); err != nil {
|
||||
hashing := utility.HashMash{}
|
||||
if err := hashing.SetPassword(req.Password); err != nil {
|
||||
statusCode = http.StatusInternalServerError
|
||||
resp.Message = err.Error()
|
||||
} else {
|
||||
user.Password = hashedPassword
|
||||
err := u.UserStore.CreateUser(ctx, &user)
|
||||
if err != nil {
|
||||
if hashedPassword, err := hashing.HashPassword(); err != nil {
|
||||
statusCode = http.StatusInternalServerError
|
||||
resp.Message = err.Error()
|
||||
} else {
|
||||
resp.Message = "Successful"
|
||||
statusCode = http.StatusOK
|
||||
resp.Data = append(resp.Data, RegisterResponseItem{Id: user.Id, PhoneNumber: user.PhoneNumber, Username: user.Username})
|
||||
user.Password = hashedPassword
|
||||
err := u.UserStore.CreateUser(ctx, &user)
|
||||
if err != nil {
|
||||
statusCode = http.StatusInternalServerError
|
||||
resp.Message = err.Error()
|
||||
} else {
|
||||
resp.Message = "Successful"
|
||||
statusCode = http.StatusOK
|
||||
resp.Data = append(resp.Data, RegisterResponseItem{Id: user.Id, PhoneNumber: user.PhoneNumber, Username: user.Username})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+30
-20
@@ -70,19 +70,24 @@ func (s *ServiceHandler) Register(w http.ResponseWriter, r *http.Request) {
|
||||
statusCode = http.StatusBadRequest
|
||||
resp.Message = "Service user already exists"
|
||||
} else {
|
||||
hashing := utility.HashMash{Password: req.Passphrase}
|
||||
if hashedPassword, err := hashing.HashPassword(); err != nil {
|
||||
hashing := utility.HashMash{}
|
||||
if err := hashing.SetPassword(req.Passphrase); err != nil {
|
||||
statusCode = http.StatusInternalServerError
|
||||
resp.Message = err.Error()
|
||||
} else {
|
||||
serviceUser := user.ServiceUser{Username: req.Username, Passphrase: hashedPassword}
|
||||
if err := s.ServiceStore.Create(ctx, &serviceUser); err != nil {
|
||||
if hashedPassword, err := hashing.HashPassword(); err != nil {
|
||||
statusCode = http.StatusInternalServerError
|
||||
resp.Message = err.Error()
|
||||
} else {
|
||||
statusCode = http.StatusCreated
|
||||
resp.Message = "Successful"
|
||||
resp.Data = append(resp.Data, &serviceUser)
|
||||
serviceUser := user.ServiceUser{Username: req.Username, Passphrase: hashedPassword}
|
||||
if err := s.ServiceStore.Create(ctx, &serviceUser); err != nil {
|
||||
statusCode = http.StatusInternalServerError
|
||||
resp.Message = err.Error()
|
||||
} else {
|
||||
statusCode = http.StatusCreated
|
||||
resp.Message = "Successful"
|
||||
resp.Data = append(resp.Data, &serviceUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,23 +144,28 @@ func (s *ServiceHandler) Login(w http.ResponseWriter, r *http.Request) {
|
||||
statusCode = http.StatusNotFound
|
||||
resp.Message = "Not found"
|
||||
} else {
|
||||
hashing := utility.HashMash{Password: req.Passphrase}
|
||||
if !hashing.CheckPasswordHash(req.Passphrase, serviceUser.Passphrase) {
|
||||
hashing := utility.HashMash{}
|
||||
if err := hashing.SetPassword(req.Passphrase); err != nil {
|
||||
statusCode = http.StatusInternalServerError
|
||||
resp.Message = "Not valid"
|
||||
resp.Message = err.Error()
|
||||
} else {
|
||||
var tokGen utility.TokenGenerator
|
||||
tokGen.SetHourOffset(8)
|
||||
secretKey := config.GetSecretKey()
|
||||
tokGen.SetSecretKey(secretKey)
|
||||
|
||||
if myToken, err := tokGen.GenerateToken(*serviceUser); err != nil {
|
||||
if !hashing.CheckPasswordHash(req.Passphrase, serviceUser.Passphrase) {
|
||||
statusCode = http.StatusInternalServerError
|
||||
resp.Message = err.Error()
|
||||
resp.Message = "Not valid"
|
||||
} else {
|
||||
statusCode = http.StatusOK
|
||||
resp.Data = append(resp.Data, myToken)
|
||||
resp.Message = "Successful"
|
||||
var tokGen utility.TokenGenerator
|
||||
tokGen.SetHourOffset(8)
|
||||
secretKey := config.GetSecretKey()
|
||||
tokGen.SetSecretKey(secretKey)
|
||||
|
||||
if myToken, err := tokGen.GenerateToken(*serviceUser); err != nil {
|
||||
statusCode = http.StatusInternalServerError
|
||||
resp.Message = err.Error()
|
||||
} else {
|
||||
statusCode = http.StatusOK
|
||||
resp.Data = append(resp.Data, myToken)
|
||||
resp.Message = "Successful"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ func TestCreateServiceUserWithMock(t *testing.T) {
|
||||
mockStore := mock.NewMockServiceUserStore()
|
||||
handler := NewServiceHandler(cfg, mockStore)
|
||||
|
||||
testService := ServiceCreationRequest{Username: "swoon", Passphrase: "ewrewr329n12y3x2!2"}
|
||||
testService := ServiceCreationRequest{Username: "swoon", Passphrase: "Ewrewr329n12y3x2!2"}
|
||||
jsonValue, err := json.Marshal(testService)
|
||||
assert.NoError(t, err, "Error marshaling request")
|
||||
|
||||
@@ -39,8 +39,12 @@ func TestLoginServiceUserWithMock(t *testing.T) {
|
||||
var serviceUser user.ServiceUser
|
||||
var hashedPassword string
|
||||
var err error
|
||||
unhashed := "9328nr29nudx3292m320!"
|
||||
hashing := utility.HashMash{Password: unhashed}
|
||||
unhashed := "A9328nr29nudx3292m320!"
|
||||
hashing := utility.HashMash{}
|
||||
if err := hashing.SetPassword(unhashed); err != nil {
|
||||
assert.NoError(t, err, "Error setting password")
|
||||
}
|
||||
|
||||
if hashedPassword, err = hashing.HashPassword(); err != nil {
|
||||
assert.NoError(t, err, "Error hashing password: %v", err)
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
@@ -9,10 +11,12 @@ import (
|
||||
"github.com/joho/godotenv"
|
||||
|
||||
"git.kundeng.us/phoenix/textsender-auth/internal/config"
|
||||
"git.kundeng.us/phoenix/textsender-auth/internal/store/mock"
|
||||
"git.kundeng.us/phoenix/textsender-auth/internal/utility"
|
||||
)
|
||||
|
||||
func GetTestUser() user.User {
|
||||
return user.User{Username: "ghost", PhoneNumber: "+1234567890", Password: "dfgdffddfd"}
|
||||
return user.User{Username: "ghost", PhoneNumber: "+1234567890", Password: "Dfgdffd343dfd!"}
|
||||
}
|
||||
|
||||
func GetConfig() *config.Config {
|
||||
@@ -38,3 +42,22 @@ func GetConfig() *config.Config {
|
||||
EnableRegistration: config.CheckRegistration(),
|
||||
}
|
||||
}
|
||||
|
||||
func createUser(ctx context.Context, userStore *mock.MockUserStore) (*user.User, *string, error) {
|
||||
testUser := GetTestUser()
|
||||
unhashedPassword := testUser.Password
|
||||
hashing := utility.HashMash{}
|
||||
if err := hashing.SetPassword(testUser.Password); err != nil {
|
||||
return nil, nil, fmt.Errorf("Error setting password: %v", err)
|
||||
}
|
||||
|
||||
hashedPassword, err := hashing.HashPassword()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
testUser.Password = hashedPassword
|
||||
userStore.CreateUser(ctx, &testUser)
|
||||
|
||||
return &testUser, &unhashedPassword, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"git.kundeng.us/phoenix/textsender-auth/internal/services"
|
||||
)
|
||||
|
||||
type contextKey string
|
||||
|
||||
const (
|
||||
UserContextKey contextKey = "user"
|
||||
)
|
||||
|
||||
func AuthMiddleware(authService *services.JWTService) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
if authHeader == "" {
|
||||
http.Error(w, "Authorization header required", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract token from "Bearer <token>"
|
||||
parts := strings.Split(authHeader, " ")
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
http.Error(w, "Invalid authorization header format", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
token := parts[1]
|
||||
|
||||
// Validate token with auth service
|
||||
user, err := authService.ValidateToken(token)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Add user to context
|
||||
ctx := context.WithValue(r.Context(), UserContextKey, user)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
txtmodels_token "git.kundeng.us/phoenix/textsender-models/tx0/token"
|
||||
txtmodels_user "git.kundeng.us/phoenix/textsender-models/tx0/user"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
type JWTService struct {
|
||||
secretKey []byte
|
||||
}
|
||||
|
||||
func NewJWTService(secretKey string) *JWTService {
|
||||
return &JWTService{
|
||||
secretKey: []byte(secretKey),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *JWTService) ValidateToken(tokenString string) (*txtmodels_user.User, error) {
|
||||
// TODO: Include more user information in the claims to populate user
|
||||
claims := &txtmodels_token.Claims{}
|
||||
|
||||
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
|
||||
// Validate the signing method
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, jwt.ErrSignatureInvalid
|
||||
}
|
||||
return s.secretKey, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !token.Valid {
|
||||
return nil, jwt.ErrSignatureInvalid
|
||||
}
|
||||
|
||||
// Check token expiration
|
||||
if time.Now().After(claims.ExpiresAt.Time) {
|
||||
return nil, jwt.ErrTokenExpired
|
||||
}
|
||||
|
||||
return &txtmodels_user.User{
|
||||
Id: claims.UserId,
|
||||
}, nil
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
@@ -37,9 +38,11 @@ func (m *MockUserStore) CreateUser(ctx context.Context, user *user.User) error {
|
||||
}
|
||||
|
||||
if _, exists := m.UsersByUsername[user.Username]; exists {
|
||||
return errors.New("User with email already exists")
|
||||
return errors.New("User with username already exists")
|
||||
}
|
||||
|
||||
user.Created = time.Now()
|
||||
|
||||
m.Users[user.Id] = user
|
||||
m.UsersByUsername[user.Username] = user
|
||||
return nil
|
||||
@@ -112,3 +115,24 @@ func (m *MockUserStore) UserExists(ctx context.Context, username string) (bool,
|
||||
return exists, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MockUserStore) UpdatePassword(ctx context.Context, id uuid.UUID, password string) (int64, error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if m.Error != nil {
|
||||
return 0, m.Error
|
||||
}
|
||||
|
||||
user, exists := m.Users[id]
|
||||
if !exists {
|
||||
return 0, errors.New("User not found")
|
||||
}
|
||||
|
||||
user.Password = password
|
||||
|
||||
m.Users[id] = user
|
||||
m.UsersByUsername[user.Username] = user
|
||||
|
||||
return 1, nil
|
||||
}
|
||||
+18
-8
@@ -17,6 +17,7 @@ type UserStore interface {
|
||||
GetUserByUsername(ctx context.Context, username string) (*user.User, error)
|
||||
GetAllUsers(ctx context.Context) ([]*user.User, error)
|
||||
UserExists(ctx context.Context, username string) (bool, error)
|
||||
UpdatePassword(ctx context.Context, id uuid.UUID, password string) (int64, error)
|
||||
}
|
||||
|
||||
type PGUserStore struct {
|
||||
@@ -31,20 +32,20 @@ func (s *PGUserStore) CreateUser(ctx context.Context, user *user.User) error {
|
||||
query := `
|
||||
INSERT INTO users (phone_number, username, password)
|
||||
VALUES ($1, $2, $3)
|
||||
RETURNING id, phone_number, username
|
||||
RETURNING id, phone_number, username, created
|
||||
`
|
||||
|
||||
return s.db.QueryRow(ctx, query, user.PhoneNumber, user.Username, user.Password).Scan(
|
||||
&user.Id, &user.PhoneNumber, &user.Username,
|
||||
&user.Id, &user.PhoneNumber, &user.Username, &user.Created,
|
||||
)
|
||||
}
|
||||
|
||||
func (s *PGUserStore) GetUserByID(ctx context.Context, id uuid.UUID) (*user.User, error) {
|
||||
query := `SELECT id, username, password, phone_number FROM users WHERE id = $1`
|
||||
query := `SELECT id, username, password, phone_number, created FROM users WHERE id = $1`
|
||||
|
||||
var user user.User
|
||||
err := s.db.QueryRow(ctx, query, id).Scan(
|
||||
&user.Id, &user.Username, &user.Password, &user.PhoneNumber,
|
||||
&user.Id, &user.Username, &user.Password, &user.PhoneNumber, &user.Created,
|
||||
)
|
||||
|
||||
if err == pgx.ErrNoRows {
|
||||
@@ -58,11 +59,11 @@ func (s *PGUserStore) GetUserByID(ctx context.Context, id uuid.UUID) (*user.User
|
||||
}
|
||||
|
||||
func (s *PGUserStore) GetUserByUsername(ctx context.Context, username string) (*user.User, error) {
|
||||
query := `SELECT id, username, password, phone_number FROM users WHERE username = $1`
|
||||
query := `SELECT id, username, password, phone_number, created FROM users WHERE username = $1`
|
||||
|
||||
var user user.User
|
||||
err := s.db.QueryRow(ctx, query, username).Scan(
|
||||
&user.Id, &user.Username, &user.Password, &user.PhoneNumber,
|
||||
&user.Id, &user.Username, &user.Password, &user.PhoneNumber, &user.Created,
|
||||
)
|
||||
|
||||
if err == pgx.ErrNoRows {
|
||||
@@ -76,7 +77,7 @@ func (s *PGUserStore) GetUserByUsername(ctx context.Context, username string) (*
|
||||
}
|
||||
|
||||
func (s *PGUserStore) GetAllUsers(ctx context.Context) ([]*user.User, error) {
|
||||
query := `SELECT id, username, password, phone_number FROM users`
|
||||
query := `SELECT id, username, password, phone_number, created FROM users`
|
||||
|
||||
rows, err := s.db.Query(ctx, query)
|
||||
if err != nil {
|
||||
@@ -88,7 +89,7 @@ func (s *PGUserStore) GetAllUsers(ctx context.Context) ([]*user.User, error) {
|
||||
for rows.Next() {
|
||||
var user user.User
|
||||
if err := rows.Scan(
|
||||
&user.Id, &user.Username, &user.Password, &user.PhoneNumber,
|
||||
&user.Id, &user.Username, &user.Password, &user.PhoneNumber, &user.Created,
|
||||
); err != nil {
|
||||
return nil, fmt.Errorf("scanning user row: %w", err)
|
||||
}
|
||||
@@ -113,3 +114,12 @@ func (s *PGUserStore) UserExists(ctx context.Context, username string) (bool, er
|
||||
|
||||
return exists, nil
|
||||
}
|
||||
|
||||
func (s *PGUserStore) UpdatePassword(ctx context.Context, id uuid.UUID, password string) (int64, error) {
|
||||
query := `UPDATE users SET password = $1 WHERE id = $2`
|
||||
if affected, err := s.db.Exec(ctx, query, password, id); err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
return affected.RowsAffected(), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
package utility
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type HashMash struct {
|
||||
Password string
|
||||
password string
|
||||
}
|
||||
|
||||
func (h *HashMash) HashPassword() (string, error) {
|
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(h.Password), bcrypt.DefaultCost)
|
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(h.password), bcrypt.DefaultCost)
|
||||
return string(bytes), err
|
||||
}
|
||||
|
||||
@@ -18,6 +22,41 @@ func (h *HashMash) CheckPasswordHash(password string, hash string) bool {
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (h *HashMash) SetPassword(password string) {
|
||||
h.Password = password
|
||||
func (h *HashMash) SetPassword(password string) error {
|
||||
if len(password) < 8 {
|
||||
return fmt.Errorf("Password length is not enought")
|
||||
} else if len(password) > 32 {
|
||||
return fmt.Errorf("Password length is too long")
|
||||
} else {
|
||||
specialCharacters := "!@#$%^&*?"
|
||||
numbers := "0123456789"
|
||||
if strings.ContainsAny(password, specialCharacters) && strings.ContainsAny(password, numbers) {
|
||||
var hasAtleastOneUpper, hasAtleastOneLower bool
|
||||
|
||||
for _, c := range password {
|
||||
if unicode.IsUpper(c) {
|
||||
hasAtleastOneUpper = true
|
||||
} else if unicode.IsLower(c) {
|
||||
hasAtleastOneLower = true
|
||||
}
|
||||
|
||||
if hasAtleastOneLower && hasAtleastOneUpper {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if hasAtleastOneUpper && hasAtleastOneLower {
|
||||
h.password = password
|
||||
return nil
|
||||
} else {
|
||||
if !hasAtleastOneUpper {
|
||||
return fmt.Errorf("Password requires at least one upper case letter")
|
||||
} else {
|
||||
return fmt.Errorf("Password requires at least one lower case letter")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Password should contain special characters and numbers")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user