init app
This commit is contained in:
25
internal/api/handler/helper.go
Normal file
25
internal/api/handler/helper.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
var defaultErrMessage = "could not process request"
|
||||
|
||||
func RenderError(c *fiber.Ctx, err error, message string) error {
|
||||
if err != nil {
|
||||
log.Printf("renderError: %s\n", err)
|
||||
}
|
||||
return c.Render("error", fiber.Map{
|
||||
"message": message,
|
||||
}, "")
|
||||
}
|
||||
|
||||
func JSONError(c *fiber.Ctx, status int, err error, message string) error {
|
||||
if err != nil {
|
||||
log.Printf("JSONError: %s\n", err)
|
||||
}
|
||||
return c.Status(status).SendString("error: " + message)
|
||||
}
|
||||
37
internal/api/handler/prosody_hdl.go
Normal file
37
internal/api/handler/prosody_hdl.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.urkob.com/urko/prosody-password/internal/services/prosody"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func NewProsodyHandler(prosodyService *prosody.Prosody) ProsodyHandler {
|
||||
return ProsodyHandler{
|
||||
prosodyService: prosodyService,
|
||||
}
|
||||
}
|
||||
|
||||
type ProsodyHandler struct {
|
||||
prosodyService *prosody.Prosody
|
||||
}
|
||||
|
||||
type changePasswordReq struct {
|
||||
CurrentPassword string `json:"current_password"`
|
||||
NewPassword string `json:"new_password"`
|
||||
User string `json:"user"`
|
||||
}
|
||||
|
||||
func (handler ProsodyHandler) Post(c *fiber.Ctx) error {
|
||||
req := changePasswordReq{}
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return RenderError(c, fmt.Errorf("id is empty"), defaultErrMessage)
|
||||
}
|
||||
|
||||
if err := handler.prosodyService.ChangePassword(req.User, req.CurrentPassword, req.NewPassword); err != nil {
|
||||
return RenderError(c, fmt.Errorf("ChangePassword: %w", err), defaultErrMessage)
|
||||
}
|
||||
|
||||
return c.Render("success", fiber.Map{}, "")
|
||||
}
|
||||
80
internal/api/server.go
Normal file
80
internal/api/server.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"gitea.urkob.com/urko/prosody-password/internal/api/handler"
|
||||
"gitea.urkob.com/urko/prosody-password/internal/services/prosody"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||
"github.com/gofiber/template/handlebars/v2"
|
||||
)
|
||||
|
||||
type RestServer struct {
|
||||
app *fiber.App
|
||||
prosodyService *prosody.Prosody
|
||||
}
|
||||
|
||||
func NewRestServer(
|
||||
prosodyService *prosody.Prosody,
|
||||
) *RestServer {
|
||||
return &RestServer{
|
||||
prosodyService: prosodyService,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RestServer) Start(apiPort, views string) error {
|
||||
engine := handlebars.New(views, ".hbs")
|
||||
s.app = fiber.New(fiber.Config{
|
||||
Views: engine,
|
||||
})
|
||||
|
||||
// Or extend your config for customization
|
||||
s.app.Use(cors.New(cors.Config{
|
||||
AllowMethods: "POST,OPTIONS",
|
||||
AllowOrigins: "*",
|
||||
AllowHeaders: "Origin, Accept, Content-Type, X-CSRF-Token, Authorization",
|
||||
ExposeHeaders: "Origin",
|
||||
}))
|
||||
|
||||
s.loadViews()
|
||||
|
||||
prosodyHdl := handler.NewProsodyHandler(s.prosodyService)
|
||||
s.app.Post("/changePassword", func(c *fiber.Ctx) error {
|
||||
return prosodyHdl.Post(c)
|
||||
})
|
||||
|
||||
if err := s.app.Listen(":" + apiPort); err != nil {
|
||||
log.Fatalln("app.Listen:", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *RestServer) loadViews() {
|
||||
s.app.Get("/", func(c *fiber.Ctx) error {
|
||||
return c.Render("index", fiber.Map{})
|
||||
})
|
||||
|
||||
s.app.Get("/error", func(c *fiber.Ctx) error {
|
||||
message := c.Query("message")
|
||||
return renderError(c, nil, message)
|
||||
})
|
||||
}
|
||||
|
||||
func renderError(c *fiber.Ctx, err error, message string) error {
|
||||
if err != nil {
|
||||
log.Printf("renderError: %s\n", err)
|
||||
}
|
||||
return c.Render("error", fiber.Map{
|
||||
"message": message,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *RestServer) Shutdown() error {
|
||||
if err := s.app.Server().Shutdown(); err != nil {
|
||||
log.Printf("app.Server().Shutdown(): %s\n", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
50
internal/services/prosody/account.go
Normal file
50
internal/services/prosody/account.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package prosody
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type account struct {
|
||||
salt string `prosody:"salt"`
|
||||
storedKey string `prosody:"stored_key"`
|
||||
iterationCount int `prosody:"iteration_count"`
|
||||
}
|
||||
|
||||
func (acc *account) unmarshal(data map[string]interface{}) {
|
||||
valueOfPerson := reflect.ValueOf(acc).Elem()
|
||||
typeOfPerson := valueOfPerson.Type()
|
||||
for i := 0; i < valueOfPerson.NumField(); i++ {
|
||||
field := valueOfPerson.Field(i)
|
||||
tag := typeOfPerson.Field(i).Tag.Get("mytag")
|
||||
|
||||
if val, ok := data[tag]; ok {
|
||||
field.Set(reflect.ValueOf(val))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// loadAccount read the user .dat file and retrieves the data store in it
|
||||
func (p *Prosody) loadAccount(username string) (*account, error) {
|
||||
var acc *account
|
||||
data, err := os.ReadFile(p.accountsPath + username + ".dat")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lines := strings.Split(string(data), "\n")
|
||||
mapValues := make(map[string]interface{})
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, "=") {
|
||||
parts := strings.Split(line, "=")
|
||||
key := strings.Trim(strings.TrimSpace(parts[0]), "[]\"")
|
||||
log.Println("key", key)
|
||||
value := strings.TrimSpace(strings.Trim(parts[1], "\"; "))
|
||||
mapValues[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
acc.unmarshal(mapValues)
|
||||
return acc, nil
|
||||
}
|
||||
53
internal/services/prosody/change_password.go
Normal file
53
internal/services/prosody/change_password.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package prosody
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os/exec"
|
||||
|
||||
"github.com/xdg-go/pbkdf2"
|
||||
)
|
||||
|
||||
func (p *Prosody) ChangePassword(user string, currentPwd string, newPwd string) error {
|
||||
acc, err := p.loadAccount(user)
|
||||
if err != nil {
|
||||
return fmt.Errorf("p.loadAccount %w", err)
|
||||
}
|
||||
|
||||
storedKey, err := hashPassword(currentPwd, acc.salt, acc.iterationCount)
|
||||
if err != nil {
|
||||
return fmt.Errorf("hashPassword: %w", err)
|
||||
}
|
||||
|
||||
// Compare the hashes
|
||||
if storedKey != acc.storedKey {
|
||||
return errors.New("password is incorrect")
|
||||
}
|
||||
|
||||
result, err := exec.Command("/usr/bin/prosodyctl", "-c", "passwd -s 12 -scny 1").Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("prosodcytl: %w", err)
|
||||
}
|
||||
|
||||
log.Println("string(result)", string(result))
|
||||
return nil
|
||||
}
|
||||
|
||||
func hashPassword(password, salt string, iterationCount int) (string, error) {
|
||||
// Hash the password using the SCRAM mechanism
|
||||
saltedPassword := pbkdf2.Key([]byte(password), []byte(salt), iterationCount, 20, sha1.New)
|
||||
clientKey := hmacSha1(saltedPassword, []byte("Client Key"))
|
||||
storedKey := sha1.Sum(clientKey)
|
||||
|
||||
return hex.EncodeToString(storedKey[:]), nil
|
||||
}
|
||||
|
||||
func hmacSha1(key, data []byte) []byte {
|
||||
mac := hmac.New(sha1.New, key)
|
||||
mac.Write(data)
|
||||
return mac.Sum(nil)
|
||||
}
|
||||
19
internal/services/prosody/prosody.go
Normal file
19
internal/services/prosody/prosody.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package prosody
|
||||
|
||||
type Prosody struct {
|
||||
binPath string
|
||||
accountsPath string
|
||||
}
|
||||
|
||||
// /var/lib/prosody/xmpp%%2eurkob%%2ecom/accounts/
|
||||
func NewProsody(domain string) *Prosody {
|
||||
return &Prosody{
|
||||
binPath: "/usr/bin/prosodyctl",
|
||||
accountsPath: "/var/lib/prosody/" + domain + "/accounts/",
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Prosody) WithBinPath(binPath string) *Prosody {
|
||||
p.binPath = binPath
|
||||
return p
|
||||
}
|
||||
Reference in New Issue
Block a user