822 lines
25 KiB
Go
822 lines
25 KiB
Go
package service
|
||
|
||
import (
|
||
"encoding/base64"
|
||
"encoding/json"
|
||
"fmt"
|
||
"netidhub-saas-be/app/database/entity"
|
||
"netidhub-saas-be/app/database/entity/users"
|
||
approvalWorkflowsRepo "netidhub-saas-be/app/module/approval_workflows/repository"
|
||
clientApprovalSettingsRepo "netidhub-saas-be/app/module/client_approval_settings/repository"
|
||
userLevelsRepository "netidhub-saas-be/app/module/user_levels/repository"
|
||
"netidhub-saas-be/app/module/users/mapper"
|
||
"netidhub-saas-be/app/module/users/repository"
|
||
"netidhub-saas-be/app/module/users/request"
|
||
"netidhub-saas-be/app/module/users/response"
|
||
"netidhub-saas-be/config/config"
|
||
"netidhub-saas-be/utils/paginator"
|
||
utilSvc "netidhub-saas-be/utils/service"
|
||
"strings"
|
||
"time"
|
||
|
||
paseto "aidanwoods.dev/go-paseto"
|
||
"github.com/Nerzal/gocloak/v13"
|
||
"github.com/google/uuid"
|
||
"github.com/rs/zerolog"
|
||
)
|
||
|
||
// UsersService
|
||
type usersService struct {
|
||
Repo repository.UsersRepository
|
||
UserLevelsRepo userLevelsRepository.UserLevelsRepository
|
||
ApprovalWorkflowsRepo approvalWorkflowsRepo.ApprovalWorkflowsRepository
|
||
ClientApprovalSettingsRepo clientApprovalSettingsRepo.ClientApprovalSettingsRepository
|
||
Log zerolog.Logger
|
||
Keycloak *config.KeycloakConfig
|
||
Smtp *config.SmtpConfig
|
||
}
|
||
|
||
// UsersService define interface of IUsersService
|
||
type UsersService interface {
|
||
All(authToken string, req request.UsersQueryRequest) (users []*response.UsersResponse, paging paginator.Pagination, err error)
|
||
Show(authToken string, id uint) (users *response.UsersResponse, err error)
|
||
ShowByUsername(authToken string, username string) (users *response.UsersResponse, err error)
|
||
CheckUsernameExists(username string) (exists bool, err error)
|
||
ShowUserInfo(authToken string) (users *response.UsersResponse, err error)
|
||
Save(authToken string, req request.UsersCreateRequest) (userReturn *users.Users, err error)
|
||
Login(req request.UserLogin) (res *gocloak.JWT, err error)
|
||
ParetoLogin(req request.UserLogin) (res *response.ParetoLoginResponse, err error)
|
||
Update(authToken string, id uint, req request.UsersUpdateRequest) (err error)
|
||
Delete(authToken string, id uint) error
|
||
SavePassword(authToken string, req request.UserSavePassword) (err error)
|
||
ResetPassword(req request.UserResetPassword) (err error)
|
||
ForgotPassword(authToken string, req request.UserForgotPassword) (err error)
|
||
EmailValidationPreLogin(authToken string, req request.UserEmailValidationRequest) (msgResponse *string, err error)
|
||
SetupEmail(authToken string, req request.UserEmailValidationRequest) (msgResponse *string, err error)
|
||
OtpRequest(req request.UserOtpRequest) (err error)
|
||
OtpValidation(req request.UserOtpValidation) (err error)
|
||
SendLoginOtp(name string, email string, otp string) error
|
||
SendRegistrationOtp(name string, email string, otp string) error
|
||
}
|
||
|
||
// NewUsersService init UsersService
|
||
func NewUsersService(
|
||
repo repository.UsersRepository,
|
||
userLevelsRepo userLevelsRepository.UserLevelsRepository,
|
||
approvalWorkflowsRepo approvalWorkflowsRepo.ApprovalWorkflowsRepository,
|
||
clientApprovalSettingsRepo clientApprovalSettingsRepo.ClientApprovalSettingsRepository,
|
||
log zerolog.Logger,
|
||
keycloak *config.KeycloakConfig,
|
||
smtp *config.SmtpConfig,
|
||
) UsersService {
|
||
|
||
return &usersService{
|
||
Repo: repo,
|
||
UserLevelsRepo: userLevelsRepo,
|
||
ApprovalWorkflowsRepo: approvalWorkflowsRepo,
|
||
ClientApprovalSettingsRepo: clientApprovalSettingsRepo,
|
||
Log: log,
|
||
Keycloak: keycloak,
|
||
Smtp: smtp,
|
||
}
|
||
}
|
||
|
||
// All implement interface of UsersService
|
||
func (_i *usersService) All(authToken string, req request.UsersQueryRequest) (users []*response.UsersResponse, paging paginator.Pagination, err error) {
|
||
// Extract clientId from authToken
|
||
var clientId *uuid.UUID
|
||
if authToken != "" {
|
||
user := utilSvc.GetUserInfo(_i.Log, _i.Repo, authToken)
|
||
if user != nil && user.ClientId != nil {
|
||
clientId = user.ClientId
|
||
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
|
||
}
|
||
}
|
||
|
||
results, paging, err := _i.Repo.GetAll(clientId, req)
|
||
if err != nil {
|
||
return
|
||
}
|
||
|
||
for _, result := range results {
|
||
users = append(users, mapper.UsersResponseMapper(result, _i.UserLevelsRepo, clientId))
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
func (_i *usersService) Show(authToken string, id uint) (users *response.UsersResponse, err error) {
|
||
// Extract clientId from authToken
|
||
var clientId *uuid.UUID
|
||
if authToken != "" {
|
||
user := utilSvc.GetUserInfo(_i.Log, _i.Repo, authToken)
|
||
if user != nil && user.ClientId != nil {
|
||
clientId = user.ClientId
|
||
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
|
||
}
|
||
}
|
||
|
||
result, err := _i.Repo.FindOne(clientId, id)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return mapper.UsersResponseMapper(result, _i.UserLevelsRepo, clientId), nil
|
||
}
|
||
|
||
func (_i *usersService) ShowByUsername(authToken string, username string) (users *response.UsersResponse, err error) {
|
||
// Extract clientId from authToken
|
||
var clientId *uuid.UUID
|
||
if authToken != "" {
|
||
user := utilSvc.GetUserInfo(_i.Log, _i.Repo, authToken)
|
||
if user != nil && user.ClientId != nil {
|
||
clientId = user.ClientId
|
||
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
|
||
}
|
||
}
|
||
|
||
result, err := _i.Repo.FindByUsername(clientId, username)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return mapper.UsersResponseMapper(result, _i.UserLevelsRepo, clientId), nil
|
||
}
|
||
|
||
func (_i *usersService) CheckUsernameExists(username string) (exists bool, err error) {
|
||
_i.Log.Info().Str("username", username).Msg("Checking if username exists")
|
||
|
||
// Check if username exists in repository
|
||
result, err := _i.Repo.FindByUsername(nil, username)
|
||
if err != nil {
|
||
// If error is "record not found", username doesn't exist
|
||
if strings.Contains(err.Error(), "record not found") {
|
||
return false, nil
|
||
}
|
||
_i.Log.Error().Err(err).Str("username", username).Msg("Failed to check username existence")
|
||
return false, err
|
||
}
|
||
|
||
// If result is not nil, username exists
|
||
if result != nil {
|
||
return true, nil
|
||
}
|
||
|
||
return false, nil
|
||
}
|
||
|
||
func (_i *usersService) ShowUserInfo(authToken string) (users *response.UsersResponse, err error) {
|
||
// Extract clientId from authToken
|
||
var clientId *uuid.UUID
|
||
if authToken != "" {
|
||
user := utilSvc.GetUserInfo(_i.Log, _i.Repo, authToken)
|
||
if user != nil && user.ClientId != nil {
|
||
clientId = user.ClientId
|
||
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
|
||
}
|
||
}
|
||
|
||
userInfo := utilSvc.GetUserInfo(_i.Log, _i.Repo, authToken)
|
||
if userInfo == nil {
|
||
return nil, fmt.Errorf("user not found")
|
||
}
|
||
|
||
// Get approval workflow info
|
||
approvalWorkflowInfo, err := _i.getApprovalWorkflowInfo(clientId)
|
||
if err != nil {
|
||
_i.Log.Warn().Err(err).Msg("Failed to get approval workflow info")
|
||
// Don't return error, just log warning and continue without workflow info
|
||
}
|
||
|
||
// Map user response with approval workflow info
|
||
usersRes := mapper.UsersResponseMapper(userInfo, _i.UserLevelsRepo, clientId)
|
||
if usersRes != nil && approvalWorkflowInfo != nil {
|
||
usersRes.ApprovalWorkflowInfo = approvalWorkflowInfo
|
||
}
|
||
|
||
return usersRes, nil
|
||
}
|
||
|
||
// getApprovalWorkflowInfo retrieves approval workflow information for the client
|
||
func (_i *usersService) getApprovalWorkflowInfo(clientId *uuid.UUID) (*response.ApprovalWorkflowInfo, error) {
|
||
if clientId == nil {
|
||
return &response.ApprovalWorkflowInfo{
|
||
HasWorkflowSetup: false,
|
||
}, nil
|
||
}
|
||
|
||
// Check if client has approval settings
|
||
clientSettings, err := _i.ClientApprovalSettingsRepo.FindByClientId(*clientId)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to get client approval settings: %w", err)
|
||
}
|
||
|
||
workflowInfo := &response.ApprovalWorkflowInfo{
|
||
HasWorkflowSetup: clientSettings != nil,
|
||
}
|
||
|
||
if clientSettings != nil {
|
||
workflowInfo.RequiresApproval = clientSettings.RequiresApproval
|
||
workflowInfo.AutoPublishArticles = clientSettings.AutoPublishArticles
|
||
workflowInfo.IsApprovalActive = clientSettings.IsActive
|
||
|
||
// Get default workflow info if exists
|
||
if clientSettings.DefaultWorkflowId != nil {
|
||
workflowInfo.DefaultWorkflowId = clientSettings.DefaultWorkflowId
|
||
|
||
defaultWorkflow, err := _i.ApprovalWorkflowsRepo.FindOne(clientId, *clientSettings.DefaultWorkflowId)
|
||
if err != nil {
|
||
_i.Log.Warn().Err(err).Msg("Failed to get default workflow details")
|
||
} else if defaultWorkflow != nil {
|
||
workflowInfo.DefaultWorkflowName = &defaultWorkflow.Name
|
||
}
|
||
}
|
||
}
|
||
|
||
return workflowInfo, nil
|
||
}
|
||
|
||
func (_i *usersService) Save(authToken string, req request.UsersCreateRequest) (userReturn *users.Users, err error) {
|
||
// Extract clientId from authToken
|
||
var clientId *uuid.UUID
|
||
if authToken != "" {
|
||
user := utilSvc.GetUserInfo(_i.Log, _i.Repo, authToken)
|
||
if user != nil && user.ClientId != nil {
|
||
clientId = user.ClientId
|
||
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
|
||
}
|
||
} else {
|
||
clientId = req.ClientId
|
||
}
|
||
|
||
_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
|
||
newReq.ClientId = clientId
|
||
|
||
isEmailUpdated := true
|
||
newReq.IsEmailUpdated = &isEmailUpdated
|
||
|
||
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(authToken string, id uint, req request.UsersUpdateRequest) (err error) {
|
||
// Extract clientId from authToken
|
||
var clientId *uuid.UUID
|
||
if authToken != "" {
|
||
user := utilSvc.GetUserInfo(_i.Log, _i.Repo, authToken)
|
||
if user != nil && user.ClientId != nil {
|
||
clientId = user.ClientId
|
||
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
|
||
}
|
||
}
|
||
|
||
// 1️⃣ Ambil data user lama
|
||
existingUser, err := _i.Repo.FindOne(clientId, id)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 2️⃣ Update Keycloak (nama & email)
|
||
err = _i.Keycloak.UpdateUser(existingUser.KeycloakId, req.Fullname, req.Email)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 3️⃣ Update field yang BOLEH diubah
|
||
existingUser.Username = req.Username
|
||
existingUser.Email = req.Email
|
||
existingUser.Fullname = req.Fullname
|
||
existingUser.Address = req.Address
|
||
existingUser.PhoneNumber = req.PhoneNumber
|
||
existingUser.WorkType = req.WorkType
|
||
existingUser.GenderType = req.GenderType
|
||
existingUser.IdentityType = req.IdentityType
|
||
existingUser.IdentityGroup = req.IdentityGroup
|
||
existingUser.IdentityGroupNumber = req.IdentityGroupNumber
|
||
existingUser.IdentityNumber = req.IdentityNumber
|
||
existingUser.DateOfBirth = req.DateOfBirth
|
||
existingUser.LastEducation = req.LastEducation
|
||
existingUser.StatusId = req.StatusId
|
||
existingUser.UpdatedAt = time.Now()
|
||
|
||
// 4️⃣ Role & Level HANYA jika dikirim
|
||
if req.UserLevelId != nil {
|
||
existingUser.UserLevelId = *req.UserLevelId
|
||
}
|
||
if req.UserRoleId != nil {
|
||
existingUser.UserRoleId = *req.UserRoleId
|
||
}
|
||
|
||
// 5️⃣ Pastikan clientId tidak hilang
|
||
existingUser.ClientId = clientId
|
||
|
||
// 6️⃣ Simpan
|
||
return _i.Repo.Update(clientId, id, existingUser)
|
||
}
|
||
|
||
// func (_i *usersService) Update(authToken string, id uint, req request.UsersUpdateRequest) (err error) {
|
||
// // Extract clientId from authToken
|
||
// var clientId *uuid.UUID
|
||
// if authToken != "" {
|
||
// user := utilSvc.GetUserInfo(_i.Log, _i.Repo, authToken)
|
||
// if user != nil && user.ClientId != nil {
|
||
// clientId = user.ClientId
|
||
// _i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
|
||
// }
|
||
// }
|
||
|
||
// _i.Log.Info().Interface("data", req).Msg("")
|
||
// newReq := req.ToEntity()
|
||
|
||
// findUser, err := _i.Repo.FindOne(clientId, id)
|
||
// if err != nil {
|
||
// return err
|
||
// }
|
||
|
||
// err = _i.Keycloak.UpdateUser(findUser.KeycloakId, req.Fullname, req.Email)
|
||
// if err != nil {
|
||
// return err
|
||
// }
|
||
|
||
// // Set ClientId on entity
|
||
// newReq.ClientId = clientId
|
||
|
||
// return _i.Repo.Update(clientId, id, newReq)
|
||
// }
|
||
|
||
func (_i *usersService) Delete(authToken string, id uint) error {
|
||
// Extract clientId from authToken
|
||
var clientId *uuid.UUID
|
||
if authToken != "" {
|
||
user := utilSvc.GetUserInfo(_i.Log, _i.Repo, authToken)
|
||
if user != nil && user.ClientId != nil {
|
||
clientId = user.ClientId
|
||
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
|
||
}
|
||
}
|
||
|
||
result, err := _i.Repo.FindOne(clientId, id)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
isActive := false
|
||
result.IsActive = &isActive
|
||
return _i.Repo.Update(clientId, id, result)
|
||
}
|
||
|
||
func (_i *usersService) SavePassword(authToken string, req request.UserSavePassword) (err error) {
|
||
// Extract clientId from authToken
|
||
var clientId *uuid.UUID
|
||
if authToken != "" {
|
||
user := utilSvc.GetUserInfo(_i.Log, _i.Repo, authToken)
|
||
if user != nil && user.ClientId != nil {
|
||
clientId = user.ClientId
|
||
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
|
||
}
|
||
}
|
||
|
||
_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)
|
||
|
||
tokenString := strings.TrimPrefix(authToken, "Bearer ")
|
||
|
||
err := _i.Keycloak.SetPassword(tokenString, *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(authToken string, req request.UserForgotPassword) (err error) {
|
||
// Extract clientId from authToken
|
||
var clientId *uuid.UUID
|
||
if authToken != "" {
|
||
user := utilSvc.GetUserInfo(_i.Log, _i.Repo, authToken)
|
||
if user != nil && user.ClientId != nil {
|
||
clientId = user.ClientId
|
||
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
|
||
}
|
||
}
|
||
|
||
_i.Log.Info().Interface("data", req).Msg("")
|
||
|
||
user, err := _i.Repo.FindByUsername(clientId, 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 req.Name == nil {
|
||
req.Name = &req.Email
|
||
}
|
||
if err != nil {
|
||
return err
|
||
}
|
||
otpReq := entity.OneTimePasswords{
|
||
Email: req.Email,
|
||
Name: req.Name,
|
||
OtpCode: codeRequest,
|
||
IsActive: true,
|
||
}
|
||
|
||
err = _i.Repo.CreateOtp(&otpReq)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
err = _i.SendRegistrationOtp(*req.Name, req.Email, codeRequest)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func (_i *usersService) OtpValidation(req request.UserOtpValidation) (err error) {
|
||
_i.Log.Info().Interface("data", req).Msg("")
|
||
|
||
var otp *entity.OneTimePasswords
|
||
if req.Email == nil {
|
||
otp, err = _i.Repo.FindOtpByIdentity(*req.Username, req.OtpCode)
|
||
if err != nil {
|
||
return fmt.Errorf("OTP is not valid")
|
||
}
|
||
} else {
|
||
otp, err = _i.Repo.FindOtpByEmail(*req.Email, req.OtpCode)
|
||
if err != nil {
|
||
return fmt.Errorf("OTP is not valid")
|
||
}
|
||
}
|
||
|
||
if otp != nil {
|
||
if otp.ValidUntil.Before(time.Now()) {
|
||
return fmt.Errorf("OTP has expired")
|
||
}
|
||
|
||
return nil
|
||
} else {
|
||
return fmt.Errorf("OTP is not valid")
|
||
}
|
||
}
|
||
|
||
func (_i *usersService) EmailValidationPreLogin(authToken string, req request.UserEmailValidationRequest) (msgResponse *string, err error) {
|
||
// Extract clientId from authToken
|
||
var clientId *uuid.UUID
|
||
if authToken != "" {
|
||
user := utilSvc.GetUserInfo(_i.Log, _i.Repo, authToken)
|
||
if user != nil && user.ClientId != nil {
|
||
clientId = user.ClientId
|
||
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
|
||
}
|
||
}
|
||
|
||
_i.Log.Info().Interface("data", req).Msg("")
|
||
|
||
var loginResponse *gocloak.JWT
|
||
loginResponse, err = _i.Keycloak.Login(*req.Username, *req.Password)
|
||
|
||
if loginResponse == nil || err != nil {
|
||
return nil, fmt.Errorf("username / password incorrect")
|
||
}
|
||
|
||
findUser, err := _i.Repo.FindByUsername(clientId, *req.Username)
|
||
if findUser == nil || err != nil {
|
||
return nil, fmt.Errorf("username / password incorrect")
|
||
}
|
||
|
||
_i.Log.Info().Interface("data user", findUser).Msg("")
|
||
|
||
if *findUser.IsEmailUpdated != true {
|
||
message := "Continue to setup email"
|
||
msgResponse = &message
|
||
} else {
|
||
codeRequest, err := utilSvc.GenerateNumericCode(6)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
otpReq := entity.OneTimePasswords{
|
||
Email: findUser.Email,
|
||
Identity: &findUser.Username,
|
||
OtpCode: codeRequest,
|
||
IsActive: true,
|
||
}
|
||
|
||
err = _i.Repo.CreateOtp(&otpReq)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
err = _i.SendLoginOtp(findUser.Fullname, findUser.Email, codeRequest)
|
||
if err != nil {
|
||
return nil, err
|
||
} else {
|
||
msg := "Email is valid and OTP has been sent"
|
||
msgResponse = &msg
|
||
}
|
||
}
|
||
|
||
return msgResponse, nil
|
||
}
|
||
|
||
func (_i *usersService) SetupEmail(authToken string, req request.UserEmailValidationRequest) (msgResponse *string, err error) {
|
||
// Extract clientId from authToken
|
||
var clientId *uuid.UUID
|
||
if authToken != "" {
|
||
user := utilSvc.GetUserInfo(_i.Log, _i.Repo, authToken)
|
||
if user != nil && user.ClientId != nil {
|
||
clientId = user.ClientId
|
||
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
|
||
}
|
||
}
|
||
|
||
_i.Log.Info().Interface("data", req).Msg("")
|
||
|
||
var loginResponse *gocloak.JWT
|
||
loginResponse, err = _i.Keycloak.Login(*req.Username, *req.Password)
|
||
|
||
if loginResponse == nil || err != nil {
|
||
return nil, fmt.Errorf("username / password incorrect")
|
||
}
|
||
|
||
_i.Log.Info().Interface("findUser", "").Msg("")
|
||
findUser, err := _i.Repo.FindByUsername(clientId, *req.Username)
|
||
|
||
_i.Log.Info().Interface("findUser", findUser).Msg("")
|
||
if findUser == nil || err != nil {
|
||
return nil, fmt.Errorf("username / password incorrect")
|
||
}
|
||
|
||
isTrue := true
|
||
if findUser.Email == *req.OldEmail {
|
||
findUser.Email = *req.NewEmail
|
||
findUser.IsEmailUpdated = &isTrue
|
||
_i.Log.Info().Interface("Update", "").Msg("")
|
||
err = _i.Repo.Update(clientId, findUser.ID, findUser)
|
||
_i.Log.Info().Interface("Update", err).Msg("")
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
codeRequest, err := utilSvc.GenerateNumericCode(6)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
otpReq := entity.OneTimePasswords{
|
||
Email: findUser.Email,
|
||
Identity: &findUser.Username,
|
||
OtpCode: codeRequest,
|
||
IsActive: true,
|
||
}
|
||
|
||
err = _i.Repo.CreateOtp(&otpReq)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
err = _i.SendLoginOtp(findUser.Fullname, findUser.Email, codeRequest)
|
||
if err != nil {
|
||
return nil, err
|
||
} else {
|
||
msg := "Email is valid and OTP has been sent"
|
||
msgResponse = &msg
|
||
}
|
||
} else {
|
||
return nil, fmt.Errorf("the old email is not same")
|
||
}
|
||
|
||
return msgResponse, nil
|
||
}
|
||
|
||
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
|
||
}
|
||
|
||
func (_i *usersService) SendLoginOtp(name string, email string, otp string) error {
|
||
subject := "[HUMAS POLRI] Permintaan OTP"
|
||
htmlBody := fmt.Sprintf("<p>Hai %s !</p><p>Berikut kode OTP yang digunakan untuk Login.</p>", name)
|
||
htmlBody += fmt.Sprintf("<p style='padding: 10px 50px; background: #eef2f6; border-radius: 8px; max-width: 300px; text-align: center'><b>%s</b></p>", otp)
|
||
htmlBody += "<p style='padding-top: 10px;'>Kode diatas hanya berlaku selama 10 menit. Harap segera masukkan 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, email, name, htmlBody)
|
||
|
||
return err
|
||
}
|
||
|
||
func (_i *usersService) SendRegistrationOtp(name string, email string, otp string) error {
|
||
subject := "[HUMAS POLRI] Permintaan OTP"
|
||
htmlBody := fmt.Sprintf("<p>Hai %s !</p><p>Berikut kode OTP yang digunakan untuk Verifikasi Registrasi.</p>", name)
|
||
htmlBody += fmt.Sprintf("<p style='padding: 10px 50px; background: #eef2f6; border-radius: 8px; max-width: 300px; text-align: center'><b>%s</b></p>", otp)
|
||
htmlBody += "<p style='padding-top: 10px;'>Kode diatas hanya berlaku selama 10 menit. Harap segera masukkan 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, email, name, htmlBody)
|
||
|
||
return err
|
||
}
|