Skip to content

Commit cf6ceae

Browse files
committed
[auth] allow to change password from device
1 parent a1f6067 commit cf6ceae

File tree

10 files changed

+318
-18
lines changed

10 files changed

+318
-18
lines changed

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.22.0
44

55
require (
66
firebase.google.com/go/v4 v4.12.1
7-
github.com/android-sms-gateway/client-go v1.1.0
7+
github.com/android-sms-gateway/client-go v1.2.0
88
github.com/ansrivas/fiberprometheus/v2 v2.6.1
99
github.com/capcom6/go-helpers v0.0.0-20240521035631-865ee2879fa3
1010
github.com/capcom6/go-infra-fx v0.2.0

go.sum

+4-6
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc
2626
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
2727
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
2828
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
29-
github.com/android-sms-gateway/client-go v1.1.0 h1:mAPsueRrY/qOdQAU5yO3uLQAb7Po+3jBxB1tiqyClvg=
30-
github.com/android-sms-gateway/client-go v1.1.0/go.mod h1:DQsReciU1xcaVW3T5Z2bqslNdsAwCFCtghawmA6g6L4=
29+
github.com/android-sms-gateway/client-go v1.1.1-0.20241130131931-31efddf9578d h1:8LYyHCkZP5y0Wsa+DhRUv5NCpS72IDwtvkF0+Qqy+SQ=
30+
github.com/android-sms-gateway/client-go v1.1.1-0.20241130131931-31efddf9578d/go.mod h1:DQsReciU1xcaVW3T5Z2bqslNdsAwCFCtghawmA6g6L4=
31+
github.com/android-sms-gateway/client-go v1.2.0 h1:P02e/Nm2XY6gpxVQVZiaxh1ZfInVkwfOLzz8Mp/1dy0=
32+
github.com/android-sms-gateway/client-go v1.2.0/go.mod h1:DQsReciU1xcaVW3T5Z2bqslNdsAwCFCtghawmA6g6L4=
3133
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
3234
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
3335
github.com/ansrivas/fiberprometheus/v2 v2.6.1 h1:wac3pXaE6BYYTF04AC6K0ktk6vCD+MnDOJZ3SK66kXM=
@@ -39,10 +41,6 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
3941
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
4042
github.com/capcom6/go-helpers v0.0.0-20240521035631-865ee2879fa3 h1:mq9rmBMCCzqGnZtbQqFSd+Ua3fahqUOYaTf26YFhWJc=
4143
github.com/capcom6/go-helpers v0.0.0-20240521035631-865ee2879fa3/go.mod h1:WDqc7HZNqHxUTisArkYIBZtqUfJBVyPWeQI+FMwEzAw=
42-
github.com/capcom6/go-infra-fx v0.0.2 h1:Vj2bHqCokDRa5qfHoPa4zVVKIo1QGzCPF+9EMQ9upQc=
43-
github.com/capcom6/go-infra-fx v0.0.2/go.mod h1:Mc7KClD8Z5wMiUAF9rxifMc39E9mMrSrylpqHzVfPM4=
44-
github.com/capcom6/go-infra-fx v0.1.0 h1:RZ0gxFtR2ehopDzSnXSCVJ8I2C4oBUaCz42sQQp75dM=
45-
github.com/capcom6/go-infra-fx v0.1.0/go.mod h1:T/DnT1EDrF9F+44eZw/lZnmsz5Dry0w/CTk0FB1Nct0=
4644
github.com/capcom6/go-infra-fx v0.2.0 h1:FrWtdFiG58unIK7xN7kMJn3LfOFecp20W/ZVgvN3bsM=
4745
github.com/capcom6/go-infra-fx v0.2.0/go.mod h1:T/DnT1EDrF9F+44eZw/lZnmsz5Dry0w/CTk0FB1Nct0=
4846
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=

internal/sms-gateway/handlers/mobile.go

+31
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,35 @@ func (h *mobileHandler) patchMessage(device models.Device, c *fiber.Ctx) error {
178178
return c.SendStatus(fiber.StatusNoContent)
179179
}
180180

181+
// @Summary Change password
182+
// @Description Changes the user's password
183+
// @Security MobileToken
184+
// @Tags Device
185+
// @Accept json
186+
// @Produce json
187+
// @Param request body smsgateway.MobileChangePasswordRequest true "Password change request"
188+
// @Success 204 {object} nil "Password changed successfully"
189+
// @Failure 400 {object} smsgateway.ErrorResponse "Invalid request"
190+
// @Failure 401 {object} smsgateway.ErrorResponse "Unauthorized"
191+
// @Failure 500 {object} smsgateway.ErrorResponse "Internal server error"
192+
// @Router /mobile/v1/user/password [patch]
193+
//
194+
// Change password
195+
func (h *mobileHandler) changePassword(device models.Device, c *fiber.Ctx) error {
196+
req := smsgateway.MobileChangePasswordRequest{}
197+
198+
if err := h.BodyParserValidator(c, &req); err != nil {
199+
return fiber.NewError(fiber.StatusBadRequest, err.Error())
200+
}
201+
202+
if err := h.authSvc.ChangePassword(device.UserID, req.CurrentPassword, req.NewPassword); err != nil {
203+
h.Logger.Error("failed to change password", zap.Error(err))
204+
return fiber.NewError(fiber.StatusUnauthorized, "Invalid current password")
205+
}
206+
207+
return c.SendStatus(fiber.StatusNoContent)
208+
}
209+
181210
func (h *mobileHandler) Register(router fiber.Router) {
182211
router = router.Group("/mobile/v1")
183212

@@ -226,6 +255,8 @@ func (h *mobileHandler) Register(router fiber.Router) {
226255
router.Get("/message", auth.WithDevice(h.getMessage))
227256
router.Patch("/message", auth.WithDevice(h.patchMessage))
228257

258+
router.Patch("/user/password", auth.WithDevice(h.changePassword))
259+
229260
h.webhooksCtrl.Register(router.Group("/webhooks"))
230261
}
231262

internal/sms-gateway/modules/auth/repository.go

+4
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,7 @@ func (r *repository) GetByLogin(login string) (models.User, error) {
2424
func (r *repository) Insert(user *models.User) error {
2525
return r.db.Create(user).Error
2626
}
27+
28+
func (r *repository) UpdatePassword(userID string, passwordHash string) error {
29+
return r.db.Model(&models.User{}).Where("id = ?", userID).Update("password_hash", passwordHash).Error
30+
}

internal/sms-gateway/modules/auth/service.go

+29
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,32 @@ func (s *Service) AuthorizeUser(username, password string) (models.User, error)
158158

159159
return user, nil
160160
}
161+
162+
func (s *Service) ChangePassword(userID string, currentPassword string, newPassword string) error {
163+
user, err := s.users.GetByLogin(userID)
164+
if err != nil {
165+
return fmt.Errorf("failed to get user: %w", err)
166+
}
167+
168+
if err := crypto.CompareBCryptHash(user.PasswordHash, currentPassword); err != nil {
169+
return fmt.Errorf("current password is incorrect: %w", err)
170+
}
171+
172+
newHash, err := crypto.MakeBCryptHash(newPassword)
173+
if err != nil {
174+
return fmt.Errorf("failed to hash new password: %w", err)
175+
}
176+
177+
if err := s.users.UpdatePassword(userID, newHash); err != nil {
178+
return fmt.Errorf("failed to update password: %w", err)
179+
}
180+
181+
// Invalidate cache
182+
hash := sha256.Sum256([]byte(userID + currentPassword))
183+
cacheKey := hex.EncodeToString(hash[:])
184+
if err := s.usersCache.Delete(cacheKey); err != nil {
185+
s.logger.Error("can't invalidate user cache", zap.Error(err))
186+
}
187+
188+
return nil
189+
}

pkg/swagger/docs/mobile.http

+11-1
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,14 @@ Content-Type: application/json
4444

4545
###
4646
GET {{baseUrl}}/webhooks HTTP/1.1
47-
Authorization: Bearer {{mobileToken}}
47+
Authorization: Bearer {{mobileToken}}
48+
49+
###
50+
PATCH {{baseUrl}}/user/password HTTP/1.1
51+
Authorization: Bearer {{mobileToken}}
52+
Content-Type: application/json
53+
54+
{
55+
"currentPassword": "wsmgz1akhoo24o",
56+
"newPassword": "wsmgz1akhoo24o"
57+
}

pkg/swagger/docs/swagger.json

+74
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,60 @@
641641
}
642642
}
643643
},
644+
"/mobile/v1/user/password": {
645+
"patch": {
646+
"security": [
647+
{
648+
"MobileToken": []
649+
}
650+
],
651+
"description": "Changes the user's password",
652+
"consumes": [
653+
"application/json"
654+
],
655+
"produces": [
656+
"application/json"
657+
],
658+
"tags": [
659+
"Device"
660+
],
661+
"summary": "Change password",
662+
"parameters": [
663+
{
664+
"description": "Password change request",
665+
"name": "request",
666+
"in": "body",
667+
"required": true,
668+
"schema": {
669+
"$ref": "#/definitions/smsgateway.MobileChangePasswordRequest"
670+
}
671+
}
672+
],
673+
"responses": {
674+
"204": {
675+
"description": "Password changed successfully"
676+
},
677+
"400": {
678+
"description": "Invalid request",
679+
"schema": {
680+
"$ref": "#/definitions/smsgateway.ErrorResponse"
681+
}
682+
},
683+
"401": {
684+
"description": "Unauthorized",
685+
"schema": {
686+
"$ref": "#/definitions/smsgateway.ErrorResponse"
687+
}
688+
},
689+
"500": {
690+
"description": "Internal server error",
691+
"schema": {
692+
"$ref": "#/definitions/smsgateway.ErrorResponse"
693+
}
694+
}
695+
}
696+
}
697+
},
644698
"/mobile/v1/webhooks": {
645699
"get": {
646700
"security": [
@@ -1021,6 +1075,26 @@
10211075
}
10221076
}
10231077
},
1078+
"smsgateway.MobileChangePasswordRequest": {
1079+
"type": "object",
1080+
"required": [
1081+
"currentPassword",
1082+
"newPassword"
1083+
],
1084+
"properties": {
1085+
"currentPassword": {
1086+
"description": "Current password",
1087+
"type": "string",
1088+
"example": "cp2pydvxd2zwpx"
1089+
},
1090+
"newPassword": {
1091+
"description": "New password, at least 14 characters",
1092+
"type": "string",
1093+
"minLength": 14,
1094+
"example": "cp2pydvxd2zwpx"
1095+
}
1096+
}
1097+
},
10241098
"smsgateway.MobileDeviceResponse": {
10251099
"type": "object",
10261100
"properties": {

pkg/swagger/docs/swagger.yaml

+49
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,21 @@ definitions:
211211
- recipients
212212
- state
213213
type: object
214+
smsgateway.MobileChangePasswordRequest:
215+
properties:
216+
currentPassword:
217+
description: Current password
218+
example: cp2pydvxd2zwpx
219+
type: string
220+
newPassword:
221+
description: New password, at least 14 characters
222+
example: cp2pydvxd2zwpx
223+
minLength: 14
224+
type: string
225+
required:
226+
- currentPassword
227+
- newPassword
228+
type: object
214229
smsgateway.MobileDeviceResponse:
215230
properties:
216231
device:
@@ -790,6 +805,40 @@ paths:
790805
tags:
791806
- Device
792807
- Messages
808+
/mobile/v1/user/password:
809+
patch:
810+
consumes:
811+
- application/json
812+
description: Changes the user's password
813+
parameters:
814+
- description: Password change request
815+
in: body
816+
name: request
817+
required: true
818+
schema:
819+
$ref: '#/definitions/smsgateway.MobileChangePasswordRequest'
820+
produces:
821+
- application/json
822+
responses:
823+
"204":
824+
description: Password changed successfully
825+
"400":
826+
description: Invalid request
827+
schema:
828+
$ref: '#/definitions/smsgateway.ErrorResponse'
829+
"401":
830+
description: Unauthorized
831+
schema:
832+
$ref: '#/definitions/smsgateway.ErrorResponse'
833+
"500":
834+
description: Internal server error
835+
schema:
836+
$ref: '#/definitions/smsgateway.ErrorResponse'
837+
security:
838+
- MobileToken: []
839+
summary: Change password
840+
tags:
841+
- Device
793842
/mobile/v1/webhooks:
794843
get:
795844
description: Returns list of registered webhooks for device

pkg/types/cache/cache.go

+8
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,14 @@ func (c *Cache[T]) Get(key string) (T, error) {
6161
return item.value, nil
6262
}
6363

64+
func (c *Cache[T]) Delete(key string) error {
65+
c.mux.Lock()
66+
delete(c.items, key)
67+
c.mux.Unlock()
68+
69+
return nil
70+
}
71+
6472
func (c *Cache[T]) Drain() map[string]T {
6573
t := time.Now()
6674

0 commit comments

Comments
 (0)