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:
phoenix
2025-12-26 21:14:40 +00:00
committed by phoenix
parent 760a778a27
commit d34cad033d
22 changed files with 727 additions and 98 deletions
+1
View File
@@ -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
View File
@@ -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)
}
+29 -11
View File
@@ -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)
}
+6 -2
View File
@@ -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 {
+13 -8
View File
@@ -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
View File
@@ -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"
}
}
}
}
+7 -3
View File
@@ -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 {
+24 -1
View File
@@ -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
}