460 lines
13 KiB
Go
460 lines
13 KiB
Go
package controller
|
|
|
|
import (
|
|
"context"
|
|
"crypto/md5"
|
|
"encoding/hex"
|
|
"fmt"
|
|
models "kemendagri/sipd/services/sipd_auth/model"
|
|
"kemendagri/sipd/services/sipd_auth/model/form"
|
|
"kemendagri/sipd/services/sipd_auth/utils"
|
|
"log"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gofiber/storage/redis/v3"
|
|
|
|
"github.com/golang-jwt/jwt/v4"
|
|
"github.com/minio/minio-go/v7"
|
|
"golang.org/x/crypto/bcrypt"
|
|
|
|
_ "github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
type UserController struct {
|
|
contextTimeout time.Duration
|
|
pgxConn *pgxpool.Pool
|
|
pgxConnPegawai *pgxpool.Pool
|
|
pgxConnMstData *pgxpool.Pool
|
|
dbConnMapsAnggaran map[string]*pgxpool.Pool
|
|
minioClient *minio.Client
|
|
redisCl *redis.Storage
|
|
}
|
|
|
|
func NewUserController(conn, connPeg, connMstData *pgxpool.Pool, mapsDbAnggaran map[string]*pgxpool.Pool, mc *minio.Client, tot time.Duration, redisClient *redis.Storage) (controller *UserController) {
|
|
controller = &UserController{
|
|
pgxConn: conn,
|
|
pgxConnPegawai: connPeg,
|
|
pgxConnMstData: connMstData,
|
|
dbConnMapsAnggaran: mapsDbAnggaran,
|
|
minioClient: mc,
|
|
contextTimeout: tot,
|
|
redisCl: redisClient,
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (c *UserController) Logout(idPegawai string) error {
|
|
var err error
|
|
|
|
err = c.redisCl.Delete("peg:" + idPegawai)
|
|
log.Println("peg:" + idPegawai)
|
|
|
|
if err != nil {
|
|
err = utils.RequestError{
|
|
Code: http.StatusInternalServerError,
|
|
Message: "Failed to remove token data. - " + err.Error(),
|
|
}
|
|
return err
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (c *UserController) GeneratePasswordHash(userId, idDaerah int64, pwd string) error {
|
|
// periksa apakah sudah hashing atau belum, blokir jika sudah
|
|
var uHashed bool
|
|
q := `SELECT "hashed" FROM "user" WHERE "id_user"=$1 AND "id_daerah"=$2`
|
|
err := c.pgxConn.QueryRow(context.Background(), q, userId, idDaerah).Scan(&uHashed)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if uHashed {
|
|
return utils.RequestError{
|
|
Code: http.StatusForbidden,
|
|
Message: "sudah hashing",
|
|
}
|
|
}
|
|
|
|
// generate password hash
|
|
pHash, err := bcrypt.GenerateFromPassword([]byte(pwd), 14)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
q = `UPDATE "user" SET "pass_user"=$1, "hashed"=true WHERE "id_user"=$2 AND "id_daerah"=$3`
|
|
_, err = c.pgxConn.Exec(context.Background(), q, pHash, userId, idDaerah)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
/*
|
|
Profile melihat detail profile user dengan id wilayah yang sama dengan si pembuat user baru
|
|
route name auth-service/user/profile
|
|
*/
|
|
func (c *UserController) Profile(user *jwt.Token) (r models.UserDetail, err error) {
|
|
claims := user.Claims.(jwt.MapClaims)
|
|
// idDaerah := int64(claims["id_daerah"].(float64))
|
|
// kodeProv := claims["kode_provinsi"].(string)
|
|
// tahun := int(claims["tahun"].(float64))
|
|
// idRole := int(claims["id_role"].(float64))
|
|
// skpdId := int64(claims["id_skpd"].(float64))
|
|
idPegawai := int64(claims["id_pegawai"].(float64))
|
|
userId := int64(claims["id_user"].(float64))
|
|
// log.Printf("Id User: %d, Id Daerah: %d\n", userId, idDaerah)
|
|
|
|
// data user
|
|
qStr := `SELECT id_user, nip_user, nama_user, nik_user, npwp_user, alamat
|
|
FROM public."sipd_user"
|
|
WHERE id_user=$1`
|
|
err = c.pgxConn.QueryRow(context.Background(), qStr, userId).
|
|
Scan(&r.IdUser, &r.Nip, &r.Nama, &r.Nik, &r.Npwp, &r.Alamat)
|
|
if err != nil {
|
|
err = utils.RequestError{
|
|
Code: http.StatusInternalServerError,
|
|
Message: "gagal mengambil data user. - " + err.Error(),
|
|
}
|
|
return
|
|
}
|
|
|
|
// data pegawai
|
|
var tahunPegawai int
|
|
qStr = `SELECT id_daerah, id_skpd, id_role, tahun_pegawai, status from public.sipd_pegawai where id=$1`
|
|
err = c.pgxConnPegawai.QueryRow(context.Background(), qStr, idPegawai).
|
|
Scan(&r.IdDaerah, &r.IdSkpdLama, &r.IdRole, &tahunPegawai, &r.Status)
|
|
if err != nil {
|
|
err = utils.RequestError{
|
|
Code: http.StatusInternalServerError,
|
|
Message: "gagal mengambil data pegawai. - " + err.Error(),
|
|
}
|
|
return
|
|
}
|
|
|
|
// data daerah
|
|
var kodeDdn string
|
|
qStr = `SELECT nama_daerah, kode_ddn FROM public.mst_daerah where id_daerah=$1 and deleted_by=0`
|
|
err = c.pgxConnMstData.QueryRow(context.Background(), qStr, r.IdDaerah).
|
|
Scan(&r.NamaDaerah, &kodeDdn)
|
|
if err != nil {
|
|
err = utils.RequestError{
|
|
Code: http.StatusInternalServerError,
|
|
Message: "gagal mengambil data daerah. - " + err.Error(),
|
|
}
|
|
return
|
|
}
|
|
fmt.Print("kode_ddn=", kodeDdn)
|
|
|
|
if r.IdSkpdLama != 0 {
|
|
// data skpd
|
|
var dbMapCode string
|
|
dbMapCodeDraft := fmt.Sprintf("%s_%d", kodeDdn, tahunPegawai)
|
|
fmt.Printf("%s_%d", kodeDdn, tahunPegawai)
|
|
_, connExists := c.dbConnMapsAnggaran[dbMapCodeDraft]
|
|
if connExists {
|
|
dbMapCode = dbMapCodeDraft
|
|
} else {
|
|
dbMapCode = fmt.Sprintf("%s_%d", strings.Split(dbMapCodeDraft, ".")[0], tahunPegawai)
|
|
}
|
|
qStr = `select id_unik_skpd, kode_skpd, nama_skpd from public.mst_skpd where id_skpd_lama=$1`
|
|
err = c.dbConnMapsAnggaran[dbMapCode].QueryRow(context.Background(), qStr, r.IdSkpdLama).Scan(&r.IdUnikSkpd, &r.KodeSkpd, &r.NamaSkpd)
|
|
if err != nil {
|
|
err = utils.RequestError{
|
|
Code: http.StatusInternalServerError,
|
|
Message: "gagal mengambil informasi skpd. - " + err.Error(),
|
|
}
|
|
return r, err
|
|
}
|
|
} else {
|
|
r.IdUnikSkpd = "00000000-0000-0000-0000-000000000000"
|
|
r.KodeSkpd = "0.00.0.00.0.00.00.0000"
|
|
r.NamaSkpd = "TIDAK ADA SKPD"
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (c *UserController) UploadAvatar(user *jwt.Token, file *multipart.FileHeader) error {
|
|
var err error
|
|
|
|
claims := user.Claims.(jwt.MapClaims)
|
|
// sub := claims["sub"].(string)
|
|
userId := int64(claims["id_user"].(float64))
|
|
/*daerahId := int64(claims["id_daerah"].(float64))
|
|
kodeProv := claims["kode_provinsi"].(string)
|
|
skpdId := int64(claims["id_skpd"].(float64))
|
|
tahun := int(claims["tahun"].(float64))
|
|
idRole := int(claims["id_role"].(float64))*/
|
|
|
|
log.Println("userId: ", userId)
|
|
|
|
if file == nil {
|
|
return utils.RequestError{
|
|
Code: http.StatusUnprocessableEntity,
|
|
Message: "file harus diunggah",
|
|
}
|
|
}
|
|
|
|
// validasi file
|
|
var fileExt string
|
|
fileExt = filepath.Ext(file.Filename)
|
|
if fileExt != ".jpg" && fileExt != ".jpeg" && fileExt != ".png" {
|
|
return utils.RequestError{
|
|
Code: http.StatusUnprocessableEntity,
|
|
Message: "jenis file tidak valid, hanya jpg, jpeg, png yang diizinkan.",
|
|
}
|
|
}
|
|
|
|
// validasi ukuran file
|
|
maxFsize := int64(1024)
|
|
fSize := file.Size / 1000 // 3028/1000 = 3.028 byte
|
|
if fSize > maxFsize { // max 1mb (1024 kb)
|
|
err = utils.RequestError{
|
|
Code: http.StatusBadRequest,
|
|
Message: fmt.Sprintf("lampiran ukuran nya terlalu besar (%d). maksimal %dkb", fSize, maxFsize),
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Get Buffer from file
|
|
buffer, err := file.Open()
|
|
if err != nil {
|
|
err = utils.RequestError{
|
|
Code: http.StatusInternalServerError,
|
|
Message: "gagal open file to buffer. - " + err.Error(),
|
|
}
|
|
}
|
|
defer buffer.Close()
|
|
|
|
buckets, err := c.minioClient.ListBuckets(context.Background())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, bucket := range buckets {
|
|
fmt.Println("bucket: ", bucket)
|
|
}
|
|
|
|
// Check to see if we already own this bucket (which happens if you run this twice)
|
|
bucketName := os.Getenv("MINIO_BUCKET_NAME")
|
|
exists, errBucketExists := c.minioClient.BucketExists(context.Background(), bucketName)
|
|
if errBucketExists != nil {
|
|
err = utils.RequestError{
|
|
Code: http.StatusInternalServerError,
|
|
Message: "gagal memeriksa ketersediaan bucket object storage. - " + err.Error(),
|
|
}
|
|
return err
|
|
}
|
|
if !exists {
|
|
err = utils.RequestError{
|
|
Code: http.StatusInternalServerError,
|
|
Message: "bucket object storage tidak tersedia. - (" + bucketName + ").",
|
|
}
|
|
return err
|
|
}
|
|
|
|
objectName := "avatar/" + file.Filename
|
|
fileBuffer := buffer
|
|
contentType := file.Header["Content-Type"][0]
|
|
fileSize := file.Size
|
|
log.Println("fileSize: ", fileSize)
|
|
|
|
// Upload the file with PutObject
|
|
info, err := c.minioClient.PutObject(
|
|
context.Background(),
|
|
bucketName,
|
|
objectName,
|
|
fileBuffer,
|
|
fileSize,
|
|
minio.PutObjectOptions{ContentType: contentType},
|
|
)
|
|
if err != nil {
|
|
err = utils.RequestError{
|
|
Code: http.StatusInternalServerError,
|
|
Message: "gagal upload file. - " + err.Error(),
|
|
}
|
|
return err
|
|
}
|
|
log.Printf("Successfully uploaded %s of size %d\n", objectName, info.Size)
|
|
|
|
return err
|
|
}
|
|
|
|
/*
|
|
UpdateProfile user update profile
|
|
route name auth-service/user/update-profile
|
|
*/
|
|
func (c *UserController) UpdateProfile(user *jwt.Token, pl form.UpdateUserProfileForm) error {
|
|
var err error
|
|
|
|
claims := user.Claims.(jwt.MapClaims)
|
|
// sub := claims["sub"].(string)
|
|
daerahId := int64(claims["id_daerah"].(float64))
|
|
userId := int64(claims["id_user"].(float64))
|
|
//skpdId := int64(claims["id_skpd"].(float64))
|
|
//roleId := int64(claims["id_role"].(float64))
|
|
// log.Printf("userId: %d, daerahId: %d", userId, daerahId)
|
|
|
|
// validasi tanggal lahir
|
|
_, err = time.Parse("2006-01-02", pl.TglLahir)
|
|
if err != nil {
|
|
err = utils.RequestError{
|
|
Code: http.StatusUnprocessableEntity,
|
|
Message: "Salah satu field tidak valid",
|
|
Fields: []utils.DataValidationError{{Field: "tgl_lahir", Message: "Format tidak valid"}},
|
|
}
|
|
return err
|
|
}
|
|
|
|
// validasi npwp
|
|
if len(pl.Npwp) < 15 && len(pl.Npwp) > 18 {
|
|
err = utils.RequestError{
|
|
Code: http.StatusUnprocessableEntity,
|
|
Message: "Salah satu field tidak valid",
|
|
Fields: []utils.DataValidationError{{Field: "npwp", Message: "NPWP tidak valid"}},
|
|
}
|
|
return err
|
|
}
|
|
|
|
q := `UPDATE "user" SET
|
|
"nama_user"=$1,
|
|
"nik_user"=$2,
|
|
"npwp_user"=$3,
|
|
"alamat"=$4,
|
|
"lahir_user"=$5,
|
|
"id_pang_gol"=$6
|
|
WHERE "id_user"=$7 AND "id_daerah"=$8`
|
|
_, err = c.pgxConn.Exec(
|
|
context.Background(), q,
|
|
pl.NamaUser,
|
|
pl.Nik,
|
|
pl.Npwp,
|
|
pl.Alamat,
|
|
pl.TglLahir,
|
|
pl.IdPangGol,
|
|
userId,
|
|
daerahId,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (c *UserController) ChangePassword(userId, idDaerah int64, pl form.ChangePasswordForm) error {
|
|
var err error
|
|
var q string
|
|
|
|
/*log.Println("idUser: ", userId)
|
|
log.Println("idDaerah: ", idDaerah)
|
|
log.Printf("%#v\\n", pl)*/
|
|
|
|
var passwordHash string
|
|
q = `SELECT "pass_user" FROM "user" WHERE id_user=$1 AND id_daerah=$2`
|
|
err = c.pgxConn.QueryRow(context.Background(), q, userId, idDaerah).Scan(&passwordHash)
|
|
if err != nil {
|
|
err = utils.RequestError{
|
|
Code: http.StatusUnprocessableEntity,
|
|
Message: "Salah satu field tidak valid",
|
|
Fields: []utils.DataValidationError{{Field: "old_password", Message: "kata sandi sebelumnya tidak valid"}},
|
|
}
|
|
return err
|
|
}
|
|
// log.Println("passwordHash: ", passwordHash)
|
|
|
|
if passwordHash == "" {
|
|
err = utils.RequestError{
|
|
Code: http.StatusUnprocessableEntity,
|
|
Message: "Salah satu field tidak valid",
|
|
Fields: []utils.DataValidationError{{Field: "old_password", Message: ""}},
|
|
}
|
|
return err
|
|
}
|
|
|
|
// validate oldPassword
|
|
err = bcrypt.CompareHashAndPassword([]byte(passwordHash), []byte(pl.OldPassword))
|
|
if err != nil {
|
|
err = utils.RequestError{
|
|
Code: http.StatusUnprocessableEntity,
|
|
Message: "Salah satu field tidak valid",
|
|
Fields: []utils.DataValidationError{{Field: "old_password", Message: "kata sandi sebelumnya tidak valid"}},
|
|
}
|
|
return err
|
|
}
|
|
|
|
// validate password
|
|
if !c.isValidPassword(pl.NewPassword) {
|
|
err = utils.RequestError{
|
|
Code: http.StatusUnprocessableEntity,
|
|
Message: "Salah satu field tidak valid",
|
|
Fields: []utils.DataValidationError{
|
|
{
|
|
Field: "new_password",
|
|
Message: "kata sandi baru tidak valid. minimal 8 karakter mengandung angka, huruf besar, huruf kecil dan karakter spesial.",
|
|
},
|
|
},
|
|
}
|
|
return err
|
|
}
|
|
|
|
// generate new password hash
|
|
var bytesPwd []byte
|
|
bytesPwd, err = bcrypt.GenerateFromPassword([]byte(pl.NewPassword), 14)
|
|
if err != nil {
|
|
err = utils.RequestError{
|
|
Code: http.StatusInternalServerError,
|
|
Message: "gagal enkripsi. - " + err.Error(),
|
|
}
|
|
return err
|
|
}
|
|
// log.Println("bytesPwd: ", bytesPwd)
|
|
|
|
// update to new password
|
|
q = `UPDATE "user" SET "pass_user"=$1 WHERE id_user=$2 AND id_daerah=$3`
|
|
_, err = c.pgxConn.Exec(context.Background(), q, bytesPwd, userId, idDaerah)
|
|
if err != nil {
|
|
err = utils.RequestError{
|
|
Code: http.StatusInternalServerError,
|
|
Message: "gagal set new password. - " + err.Error(),
|
|
}
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *UserController) validatePassword(content, encrypted string) bool {
|
|
return strings.EqualFold(c.encodePasswd(content), encrypted)
|
|
}
|
|
|
|
func (c *UserController) encodePasswd(data string) string {
|
|
h := md5.New()
|
|
h.Write([]byte(data))
|
|
return hex.EncodeToString(h.Sum(nil))
|
|
}
|
|
|
|
func (c *UserController) isValidPassword(password string) bool {
|
|
if len(password) < 8 {
|
|
return false
|
|
}
|
|
|
|
reLower := regexp.MustCompile(`[a-z]`)
|
|
reUpper := regexp.MustCompile(`[A-Z]`)
|
|
reDigit := regexp.MustCompile(`\d`)
|
|
reSpecial := regexp.MustCompile(`[@$!%*?&]`)
|
|
|
|
return reLower.MatchString(password) &&
|
|
reUpper.MatchString(password) &&
|
|
reDigit.MatchString(password) &&
|
|
reSpecial.MatchString(password)
|
|
}
|