medol-be/app/module/users/service/users.service.go

404 lines
12 KiB
Go

package service
import (
paseto "aidanwoods.dev/go-paseto"
"encoding/base64"
"encoding/json"
"fmt"
"github.com/Nerzal/gocloak/v13"
"github.com/rs/zerolog"
"go-humas-be/app/database/entity"
userLevelsRepository "go-humas-be/app/module/user_levels/repository"
"go-humas-be/app/module/users/mapper"
"go-humas-be/app/module/users/repository"
"go-humas-be/app/module/users/request"
"go-humas-be/app/module/users/response"
"go-humas-be/config/config"
"go-humas-be/utils/paginator"
utilSvc "go-humas-be/utils/service"
"strings"
"time"
)
// UsersService
type usersService struct {
Repo repository.UsersRepository
UserLevelsRepo userLevelsRepository.UserLevelsRepository
Log zerolog.Logger
Keycloak *config.KeycloakConfig
Smtp *config.SmtpConfig
}
// UsersService define interface of IUsersService
type UsersService interface {
All(req request.UsersQueryRequest) (users []*response.UsersResponse, paging paginator.Pagination, err error)
Show(id uint) (users *response.UsersResponse, err error)
ShowUserInfo(authToken string) (users *response.UsersResponse, err error)
Save(req request.UsersCreateRequest, authToken string) (userReturn *entity.Users, err error)
Login(req request.UserLogin) (res *gocloak.JWT, err error)
ParetoLogin(req request.UserLogin) (res *response.ParetoLoginResponse, err error)
Update(id uint, req request.UsersUpdateRequest) (err error)
Delete(id uint) error
SavePassword(req request.UserSavePassword, authToken string) (err error)
ResetPassword(req request.UserResetPassword) (err error)
ForgotPassword(req request.UserForgotPassword) (err error)
OtpRequest(req request.UserOtpRequest) (err error)
OtpValidation(req request.UserOtpValidation) (err error)
}
// NewUsersService init UsersService
func NewUsersService(repo repository.UsersRepository, userLevelsRepo userLevelsRepository.UserLevelsRepository, log zerolog.Logger, keycloak *config.KeycloakConfig, smtp *config.SmtpConfig) UsersService {
return &usersService{
Repo: repo,
UserLevelsRepo: userLevelsRepo,
Log: log,
Keycloak: keycloak,
Smtp: smtp,
}
}
// All implement interface of UsersService
func (_i *usersService) All(req request.UsersQueryRequest) (users []*response.UsersResponse, paging paginator.Pagination, err error) {
results, paging, err := _i.Repo.GetAll(req)
if err != nil {
return
}
for _, result := range results {
users = append(users, mapper.UsersResponseMapper(result, _i.UserLevelsRepo))
}
return
}
func (_i *usersService) Show(id uint) (users *response.UsersResponse, err error) {
result, err := _i.Repo.FindOne(id)
if err != nil {
return nil, err
}
return mapper.UsersResponseMapper(result, _i.UserLevelsRepo), nil
}
func (_i *usersService) ShowUserInfo(authToken string) (users *response.UsersResponse, err error) {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.Repo, authToken)
return mapper.UsersResponseMapper(userInfo, _i.UserLevelsRepo), nil
}
func (_i *usersService) Save(req request.UsersCreateRequest, authToken string) (userReturn *entity.Users, err error) {
_i.Log.Info().Interface("data", req).Msg("")
newReq := req.ToEntity()
_i.Log.Info().Interface("AUTH TOKEN", authToken).Msg("")
if authToken != "" {
createdBy := utilSvc.GetUserInfo(_i.Log, _i.Repo, authToken)
newReq.CreatedById = &createdBy.ID
}
keycloakId, err := _i.Keycloak.CreateUser(req.Fullname, req.Email, req.Username, req.Password)
if err != nil {
return nil, err
}
newReq.KeycloakId = &keycloakId
newReq.TempPassword = &req.Password
return _i.Repo.Create(newReq)
}
func (_i *usersService) Login(req request.UserLogin) (res *gocloak.JWT, err error) {
_i.Log.Info().Interface("data", req).Msg("")
var loginResponse *gocloak.JWT
if req.RefreshToken == nil {
loginResponse, err = _i.Keycloak.Login(*req.Username, *req.Password)
} else {
loginResponse, err = _i.Keycloak.RefreshToken(*req.RefreshToken)
}
if err != nil {
return nil, err
}
return loginResponse, nil
}
func (_i *usersService) ParetoLogin(req request.UserLogin) (res *response.ParetoLoginResponse, err error) {
_i.Log.Info().Interface("data", req).Msg("")
var loginResponse *gocloak.JWT
token := paseto.NewToken()
secretKeyHex := "bdc42b1a0ba2bac3e27ba84241f9de06dee71b70f838af8d1beb0417f03d1d00"
secretKey, _ := paseto.V4SymmetricKeyFromHex(secretKeyHex)
// secretKey := paseto.NewV4SymmetricKey() // to change the secretKey periodically
if req.RefreshToken == nil {
loginResponse, err = _i.Keycloak.Login(*req.Username, *req.Password)
} else {
// Retrieve Refresh Token
parser := paseto.NewParser()
verifiedToken, err := parser.ParseV4Local(secretKey, *req.RefreshToken, nil)
if err != nil {
panic(err)
}
refreshToken, _ := verifiedToken.GetString("refresh_token")
_i.Log.Info().Interface("Pareto parse refresh token", refreshToken).Msg("")
loginResponse, err = _i.Keycloak.RefreshToken(refreshToken)
}
_i.Log.Info().Interface("loginResponse", loginResponse).Msg("")
if err != nil {
return nil, err
}
parseToken, err := ParseJWTToken(loginResponse.AccessToken)
if err != nil {
return nil, err
}
issuedAt := parseToken["iat"].(float64)
expirationTime := parseToken["exp"].(float64)
issuer := parseToken["iss"].(string)
jti := parseToken["jti"].(string)
subject := parseToken["sub"].(string)
token.SetIssuedAt(time.Unix(int64(issuedAt), 0))
token.SetNotBefore(time.Unix(int64(issuedAt), 0))
token.SetExpiration(time.Unix(int64(expirationTime), 0))
token.SetIssuer(issuer)
token.SetJti(jti)
token.SetSubject(subject)
token.SetString("access_token", loginResponse.AccessToken)
token.SetString("refresh_token", loginResponse.RefreshToken)
_i.Log.Info().Interface("Pareto Generated Key", secretKey.ExportHex()).Msg("")
tokenEncrypted := token.V4Encrypt(secretKey, nil)
parser := paseto.NewParser()
verifiedToken, err := parser.ParseV4Local(secretKey, tokenEncrypted, nil)
if err != nil {
panic(err)
}
tokenParsing, _ := verifiedToken.GetString("access_token")
_i.Log.Info().Interface("Pareto parse token", tokenParsing).Msg("")
resLogin := &response.ParetoLoginResponse{
AccessToken: tokenEncrypted,
}
if err != nil {
return nil, err
}
return resLogin, nil
}
func (_i *usersService) Update(id uint, req request.UsersUpdateRequest) (err error) {
_i.Log.Info().Interface("data", req).Msg("")
newReq := req.ToEntity()
findUser, err := _i.Repo.FindOne(id)
if err != nil {
return err
}
err = _i.Keycloak.UpdateUser(findUser.KeycloakId, req.Fullname, req.Email)
if err != nil {
return err
}
return _i.Repo.Update(id, newReq)
}
func (_i *usersService) Delete(id uint) error {
result, err := _i.Repo.FindOne(id)
if err != nil {
return err
}
isActive := false
result.IsActive = &isActive
return _i.Repo.Update(id, result)
}
func (_i *usersService) SavePassword(req request.UserSavePassword, authToken string) (err error) {
_i.Log.Info().Interface("data", req).Msg("")
_i.Log.Info().Interface("AUTH TOKEN", authToken).Msg("")
if authToken != "" {
createdBy := utilSvc.GetUserInfo(_i.Log, _i.Repo, authToken)
err := _i.Keycloak.SetPassword(authToken, *createdBy.KeycloakId, req.Password)
if err != nil {
return err
}
return nil
} else {
return fmt.Errorf("Invalid token")
}
}
func (_i *usersService) ResetPassword(req request.UserResetPassword) (err error) {
_i.Log.Info().Interface("data", req).Msg("")
if req.Password != req.ConfirmPassword {
return fmt.Errorf("Invalid Password")
}
user, err := _i.Repo.FindByKeycloakId(req.UserId)
if err != nil {
return fmt.Errorf("User Id Not Found")
}
forgotPassword, err := _i.Repo.FindForgotPassword(req.UserId, req.CodeRequest)
if err != nil {
return fmt.Errorf("Invalid Request")
}
_i.Log.Info().Interface("data", forgotPassword).Msg("")
_i.Log.Info().Interface("dataForgotPassword", forgotPassword).Msg("")
if user != nil {
err := _i.Keycloak.SetPasswordWithoutToken(req.UserId, req.Password)
if err != nil {
return err
}
forgotPassword.IsActive = false
forgotPassword.UpdatedAt = time.Now()
err = _i.Repo.UpdateForgotPassword(forgotPassword.ID, forgotPassword)
if err != nil {
return err
}
return nil
} else {
return fmt.Errorf("User not found")
}
}
func (_i *usersService) ForgotPassword(req request.UserForgotPassword) (err error) {
_i.Log.Info().Interface("data", req).Msg("")
user, err := _i.Repo.FindByUsername(req.Username)
if err != nil {
return err
}
if user != nil {
codeRequest, err := utilSvc.GenerateNumericCode(8)
if err != nil {
return nil
}
forgotPasswordReq := entity.ForgotPasswords{
KeycloakID: *user.KeycloakId,
CodeRequest: codeRequest,
IsActive: true,
}
err = _i.Repo.CreateForgotPassword(&forgotPasswordReq)
if err != nil {
return err
}
// send email forgot password
url := fmt.Sprintf("https://kontenhumas.com/setup-password?userId=%s&code=%s", *user.KeycloakId, codeRequest)
subject := "[HUMAS POLRI] Forgot Password"
htmlBody := "<p>Anda telah mengirimkan permintaan untuk melakukan reset password.</p>"
htmlBody += "<p style='padding-bottom: 10px;'>Silahkan buat password akun anda dengan menekan tombol di bawah ini, untuk membuat password baru</p>"
htmlBody += fmt.Sprintf("<a style='padding:8px 50px;border-radius:28px;background-color:#37c2b6;text-decoration:none;color:#f4f5f5;' href='%s'>Reset Password</a>", url)
htmlBody += "<p style='padding-top: 10px;'>Terimakasih.</p>"
err = _i.Smtp.SendEmail(subject, user.Email, user.Fullname, htmlBody)
return nil
} else {
return fmt.Errorf("User not found")
}
}
func (_i *usersService) OtpRequest(req request.UserOtpRequest) (err error) {
_i.Log.Info().Interface("data", req).Msg("")
codeRequest, err := utilSvc.GenerateNumericCode(6)
if err != nil {
return err
}
otpReq := entity.RegistrationOtps{
Email: req.Email,
Name: req.Name,
OtpCode: codeRequest,
IsActive: true,
}
subject := "[HUMAS POLRI] Permintaan OTP"
htmlBody := fmt.Sprintf("<p>Hai %s !</p><p>Berikut kode OTP yang digunakan untuk verifikasi.</p>", *req.Name)
htmlBody += fmt.Sprintf("<p style='padding: 10px 50px; background: #eef2f6; border-radius: 8px; max-width: 300px; text-align: center'><b>%s</b></p>", codeRequest)
htmlBody += "<p style='padding-top: 10px;'>Kode diatas hanya berlaku selama 10 menit. Harap segera masukan kode tersebut pada aplikasi HUMAS POLRI.</p>"
htmlBody += "<p style='padding-top: 10px; padding-bottom: 10px'>Demi menjaga kerahasiaan data kamu, mohon jangan membagikan kode OTP ke siapapun.</p>"
err = _i.Smtp.SendEmail(subject, req.Email, req.Email, htmlBody)
if err != nil {
return err
}
err = _i.Repo.CreateRegistrationOtps(&otpReq)
if err != nil {
return err
}
// send otp to email
return nil
}
func (_i *usersService) OtpValidation(req request.UserOtpValidation) (err error) {
_i.Log.Info().Interface("data", req).Msg("")
registrationOtp, err := _i.Repo.FindRegistrationOtps(req.Email, req.OtpCode)
if err != nil {
return fmt.Errorf("OTP is not valid")
}
if registrationOtp != nil {
if registrationOtp.ValidUntil.Before(time.Now()) {
return fmt.Errorf("OTP has expired")
}
return nil
} else {
return fmt.Errorf("OTP is not valid")
}
}
func ParseJWTToken(token string) (map[string]interface{}, error) {
// Pisahkan JWT menjadi 3 bagian: header, payload, dan signature
parts := strings.Split(token, ".")
if len(parts) != 3 {
return nil, fmt.Errorf("Invalid JWT token")
}
// Decode bagian payload (index ke-1)
payloadData, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
return nil, fmt.Errorf("Failed to decode payload: %v", err)
}
// Ubah payload menjadi map[string]interface{}
var payload map[string]interface{}
if err := json.Unmarshal(payloadData, &payload); err != nil {
return nil, fmt.Errorf("Failed to parse payload JSON: %v", err)
}
return payload, nil
}