first commit
This commit is contained in:
commit
e96b29f280
5
.dockerignore
Normal file
5
.dockerignore
Normal file
@ -0,0 +1,5 @@
|
||||
README.md
|
||||
.git
|
||||
.idea
|
||||
Makefile
|
||||
Makefile.example
|
||||
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
# macOS
|
||||
**/.DS_Store
|
||||
|
||||
# Jetbrains
|
||||
.idea/
|
||||
|
||||
# Run script
|
||||
Makefile
|
||||
Makefile.localhost
|
||||
Makefile.prod
|
||||
|
||||
# Dev build
|
||||
build/
|
||||
tmp/
|
||||
|
||||
# Test
|
||||
*.out
|
||||
|
||||
# Gitlab
|
||||
gitlab-ci.yml
|
||||
|
||||
# Jenkins
|
||||
Jenkinsfile
|
||||
61
.gitlab-ci.yml
Normal file
61
.gitlab-ci.yml
Normal file
@ -0,0 +1,61 @@
|
||||
stages:
|
||||
- test
|
||||
- build
|
||||
- deploy
|
||||
|
||||
variables:
|
||||
DOCKER_CONFIG_FILE: "--config .docker"
|
||||
DOCKER_REGCRED: "regcred"
|
||||
PROJECT_NAME: "sipd-v2-auth"
|
||||
PROJECT_GROUP_ID: "sipd"
|
||||
K8S_NAMESPACE: "development"
|
||||
MY_TRIGGER_TOKEN: "glptt-eb34428616c382d3240f1ae6a979e453504addee"
|
||||
|
||||
# default:
|
||||
# tags:
|
||||
# - docker
|
||||
|
||||
build:image:
|
||||
stage: build
|
||||
image: nexus.registry:8086/docker:stable
|
||||
services:
|
||||
- name: nexus.registry:8086/docker:18.09-dind
|
||||
entrypoint: ["dockerd-entrypoint.sh"]
|
||||
command: [
|
||||
"--insecure-registry=nexus.registry:8087",
|
||||
"--insecure-registry=nexus.registry:8086"
|
||||
]
|
||||
alias: dockerd
|
||||
variables:
|
||||
DOCKER_HOST: tcp://dockerd:2375
|
||||
DOCKER_DRIVER: overlay2
|
||||
DOCKER_TAGS:
|
||||
nexus.registry:8087/$PROJECT_GROUP_ID/$PROJECT_NAME
|
||||
before_script:
|
||||
- mkdir -p .docker/ && cat $DOCKER_CONF_JSON > .docker/config.json
|
||||
script:
|
||||
- echo $CI_COMMIT_SHORT_SHA
|
||||
|
||||
- docker $DOCKER_CONFIG_FILE build -q -f Dockerfile -t $DOCKER_TAGS:latest .
|
||||
- docker image tag $DOCKER_TAGS:latest $DOCKER_TAGS:$CI_COMMIT_SHORT_SHA
|
||||
- docker $DOCKER_CONFIG_FILE image push $DOCKER_TAGS:latest
|
||||
- docker $DOCKER_CONFIG_FILE image push $DOCKER_TAGS:$CI_COMMIT_SHORT_SHA
|
||||
rules:
|
||||
- if: '$CI_COMMIT_BRANCH == "main"'
|
||||
#rules:
|
||||
# - if: $CI_COMMIT_TAG =~ /-release/
|
||||
# when: always
|
||||
# - when: never
|
||||
#- if: $CI_COMMIT_SHORT_SHA
|
||||
#when: manual
|
||||
|
||||
#deploy:
|
||||
# stage: deploy
|
||||
# script:
|
||||
# - echo $CI_SERVER_URL
|
||||
|
||||
# - apk add curl
|
||||
# - 'curl -X POST --fail -F "token=$MY_TRIGGER_TOKEN" -F "ref=main" -F "variables[PROJECT_GROUP_ID]=$PROJECT_GROUP_ID" -F "variables[SERVICE_NAME]=$PROJECT_NAME" "$CI_SERVER_URL/api/v4/projects/3/trigger/pipeline"'
|
||||
# - echo "deploy success!"
|
||||
# rules:
|
||||
# - if: '$CI_COMMIT_BRANCH == "main"'
|
||||
13
Dockerfile
Normal file
13
Dockerfile
Normal file
@ -0,0 +1,13 @@
|
||||
FROM golang:1.23.12-alpine
|
||||
|
||||
RUN apk update && apk add --no-cache git
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN go mod tidy
|
||||
|
||||
RUN go build -o binary -tags musl
|
||||
|
||||
ENTRYPOINT ["/app/binary"]
|
||||
40
Makefile.example
Normal file
40
Makefile.example
Normal file
@ -0,0 +1,40 @@
|
||||
.PHONY: run
|
||||
|
||||
BUILD_DIR = $(PWD)/app
|
||||
|
||||
# App Env
|
||||
SERVER_NAME=SIPD_AUTH_SERVICE
|
||||
SERVER_URL="0.0.0.0:3000"
|
||||
SERVER_READ_TIMEOUT=60
|
||||
JWT_SECRET_KEY="jwt_secret"
|
||||
JWT_EXPIRED_MINUTES="3600"
|
||||
REFRESH_TOKEN_EXPIRED_HOUR="2000"
|
||||
SIPD_CORS_WHITELISTS="*"
|
||||
URL_SCHEME="http://"
|
||||
BASE_URL="localhost:3000"
|
||||
BASE_PATH="/"
|
||||
AVAILABLE_YEAR="2021,2022,2023"
|
||||
|
||||
DB_SIPD_AUTH="host=0.0.0.0 port=5432 user=xxx password=xxx dbname=sipd_v2_auth sslmode=disable"
|
||||
DB_SIPD_PEGAWAI="host=0.0.0.0 port=5432 user=xxx password=xxx dbname=sipd_v2_pegawai sslmode=disable"
|
||||
DB_SIPD_MST_DATA="host=0.0.0.0 port=5432 user=xxx password=xxx dbname=sipd_v2_master_data sslmode=disable"
|
||||
DB_SIPD_TRANSAKSI="[{\"ddn_prov\":\"32\",\"host\":\"0.0.0.0\",\"port\":\"5432\",\"user\":\"xxx\",\"password\":\"xxx\",\"ssl_mode\":\"disable\",\"dbname\":\"sipd_v2_transaksi\",\"database_pemda\":[]}]"
|
||||
|
||||
go:
|
||||
export SERVER_NAME=$(SERVER_NAME);\
|
||||
export SERVER_URL=$(SERVER_URL);\
|
||||
export SERVER_READ_TIMEOUT=$(SERVER_READ_TIMEOUT);\
|
||||
export JWT_SECRET_KEY=$(JWT_SECRET_KEY);\
|
||||
export JWT_EXPIRED_MINUTES=$(JWT_EXPIRED_MINUTES);\
|
||||
export REFRESH_TOKEN_EXPIRED_HOUR=$(REFRESH_TOKEN_EXPIRED_HOUR);\
|
||||
export SIPD_CORS_WHITELISTS=$(SIPD_CORS_WHITELISTS);\
|
||||
export URL_SCHEME=$(URL_SCHEME);\
|
||||
export BASE_URL=$(BASE_URL);\
|
||||
export BASE_PATH=$(BASE_PATH);\
|
||||
export AVAILABLE_YEAR=$(AVAILABLE_YEAR);\
|
||||
export DB_SIPD_AUTH=$(DB_SIPD_AUTH);\
|
||||
export DB_SIPD_PEGAWAI=$(DB_SIPD_PEGAWAI);\
|
||||
export DB_SIPD_MST_DATA=$(DB_SIPD_MST_DATA);\
|
||||
export DB_SIPD_TRANSAKSI=$(DB_SIPD_TRANSAKSI);\
|
||||
go mod tidy;\
|
||||
go run main.go
|
||||
40
README.md
Normal file
40
README.md
Normal file
@ -0,0 +1,40 @@
|
||||
# Kemendagri SIPD Service Auth
|
||||
Kemendagri SIPD Service Auth.-
|
||||
|
||||
## Prerequisites
|
||||
Prequisites package:
|
||||
* [Docker](https://www.docker.com/get-started) - for developing, shipping, and running applications (Application Containerization).
|
||||
* [Go](https://golang.org/) - Go Programming Language.
|
||||
* [Make](https://golang.org/) - Automated Execution using Makefile.
|
||||
* [swag](https://github.com/swaggo/swag) Converts Go annotations to Swagger Documentation 2.0. We've created a variety of plugins for popular Go web frameworks.
|
||||
* [golang-migrate/migrate](https://github.com/golang-migrate/migrate#cli-usage) Database migrations written in Go. Use as CLI or import as library for apply migrations.
|
||||
|
||||
Optional package:
|
||||
* [gocritic](https://github.com/go-critic/go-critic) Highly extensible Go source code linter providing checks currently missing from other linters.
|
||||
* [gosec](https://github.com/securego/gosec) Golang Security Checker. Inspects source code for security problems by scanning the Go AST.
|
||||
* [golangci-lint](https://github.com/golangci/golangci-lint) Go linters runner. It runs linters in parallel, uses caching, supports yaml config, has integrations with all major IDE and has dozens of linters included.
|
||||
|
||||
## ⚡️ Quick start
|
||||
These instructions will get you a copy of the project up and running on docker container and on your local machine.
|
||||
1. Install Prequisites and optional package to your system:
|
||||
2. Rename `Makefile.example` to `Makefile` then fill it with your make setting.
|
||||
3. Generate swagger api doc by this command
|
||||
```shell
|
||||
make swag
|
||||
```
|
||||
4. Instant run by this command
|
||||
```shell
|
||||
make instant_run
|
||||
```
|
||||
5. Bulid go binary file
|
||||
```shell
|
||||
make build
|
||||
```
|
||||
6. Build go binary file and run
|
||||
```shell
|
||||
make run
|
||||
```
|
||||
7. Run in docker container
|
||||
```shell
|
||||
make docker_run
|
||||
```
|
||||
730
controller/auth.go
Normal file
730
controller/auth.go
Normal file
@ -0,0 +1,730 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
models "kemendagri/sipd/services/sipd_auth/model"
|
||||
"kemendagri/sipd/services/sipd_auth/model/form"
|
||||
"kemendagri/sipd/services/sipd_auth/utils"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
// "github.com/dchest/captcha"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/jackc/pgx/v5"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type AuthController struct {
|
||||
contextTimeout time.Duration
|
||||
pgxConn *pgxpool.Pool
|
||||
pgxConnPegawai *pgxpool.Pool
|
||||
pgxConnMstData *pgxpool.Pool
|
||||
dbConnMapsAnggaran map[string]*pgxpool.Pool
|
||||
jwtManager *utils.JWTManager
|
||||
Validate *validator.Validate
|
||||
}
|
||||
|
||||
func NewAuthController(
|
||||
conn, connPegawai,
|
||||
connMstData *pgxpool.Pool,
|
||||
mapsDbAnggaran map[string]*pgxpool.Pool,
|
||||
timeout time.Duration,
|
||||
jm *utils.JWTManager,
|
||||
vld *validator.Validate,
|
||||
) (controller *AuthController) {
|
||||
controller = &AuthController{
|
||||
pgxConn: conn,
|
||||
pgxConnMstData: connMstData,
|
||||
pgxConnPegawai: connPegawai,
|
||||
dbConnMapsAnggaran: mapsDbAnggaran,
|
||||
contextTimeout: timeout,
|
||||
jwtManager: jm,
|
||||
Validate: vld,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func detectHashType(s string) string {
|
||||
// Cek bcrypt (biasanya panjang 60 dan prefix $2a$, $2b$, $2y$)
|
||||
if len(s) == 60 && (s[:4] == "$2a$" || s[:4] == "$2b$" || s[:4] == "$2y$") {
|
||||
return "bcrypt"
|
||||
}
|
||||
|
||||
// Cek md5 (panjang 32 dan hex)
|
||||
matchMD5, _ := regexp.MatchString("^[a-fA-F0-9]{32}$", s)
|
||||
if matchMD5 {
|
||||
return "md5"
|
||||
}
|
||||
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
func (ac *AuthController) PreLogin(f form.PreLoginForm) (r []models.PreLoginModel, err error) {
|
||||
r = make([]models.PreLoginModel, 0)
|
||||
|
||||
// validate captcha
|
||||
// if !captcha.VerifyString(f.CaptchaId, f.CaptchaSolution) {
|
||||
// err = utils.RequestError{
|
||||
// Code: http.StatusUnprocessableEntity,
|
||||
// Message: "invalid captcha",
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
|
||||
// Validate form input
|
||||
err = ac.Validate.Struct(f)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var hashed bool
|
||||
var idUser, loginAttp, nextLogin int
|
||||
var nipUser, namaUser, passUser string
|
||||
q := `SELECT id_user, nip_user, nama_user, pass_user, hashed, login_attempt, next_login
|
||||
FROM "sipd_user"
|
||||
WHERE nip_user = $1`
|
||||
err = ac.pgxConn.QueryRow(context.Background(), q, f.Username).Scan(
|
||||
&idUser,
|
||||
&nipUser,
|
||||
&namaUser,
|
||||
&passUser,
|
||||
&hashed,
|
||||
&loginAttp,
|
||||
&nextLogin,
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
err = utils.LoginError{
|
||||
NextLogin: nextLogin,
|
||||
Attempt: loginAttp,
|
||||
Message: "invalid username or password",
|
||||
}
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
tNow := time.Now()
|
||||
|
||||
if int64(nextLogin) > tNow.Unix() {
|
||||
err = utils.LoginError{
|
||||
NextLogin: nextLogin,
|
||||
Attempt: loginAttp,
|
||||
Message: "Login dibatasi",
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
switch detectHashType(passUser) {
|
||||
case "bcrypt":
|
||||
if err = bcrypt.CompareHashAndPassword([]byte(passUser), []byte(f.Password)); err != nil {
|
||||
err = utils.LoginError{
|
||||
NextLogin: nextLogin,
|
||||
Attempt: loginAttp,
|
||||
Message: "invalid username or password ----",
|
||||
}
|
||||
return
|
||||
}
|
||||
case "md5":
|
||||
if ac.validatePassword(f.Password, passUser) == false {
|
||||
err = utils.LoginError{
|
||||
NextLogin: nextLogin,
|
||||
Attempt: loginAttp,
|
||||
Message: "invalid username or password",
|
||||
}
|
||||
return
|
||||
}
|
||||
default:
|
||||
err = utils.LoginError{
|
||||
NextLogin: nextLogin,
|
||||
Attempt: loginAttp,
|
||||
Message: "invalid username or password (*)",
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ambil list data pegawai user ini di service pegawai
|
||||
type pegawaiJabatanModel struct {
|
||||
IdPegawai int64 `json:"id_pegawai" xml:"id_pegawai" example:"1"`
|
||||
IdUser int64 `json:"id_user" xml:"id_user" example:"1"` // ID user
|
||||
Nip string `json:"nip_user" xml:"nip_user" example:"196408081992011001"`
|
||||
Nama string `json:"nama_user" xml:"nama_user" example:"John Doe"`
|
||||
IdDaerah int64 `json:"id_daerah" xml:"id_daerah" example:"229"`
|
||||
NamaDaerah string `json:"nama_daerah" xml:"nama_daerah" example:"Kota Bandar Lampung"`
|
||||
IdUnikSkpd string `json:"id_unik_skpd" xml:"id_unik_skpd"`
|
||||
IdSkpdLama int64 `json:"id_skpd_lama" xml:"id_skpd_lama"`
|
||||
KodeSkpd string `json:"kode_skpd" xml:"kode_skpd"`
|
||||
NamaSkpd string `json:"nama_skpd" xml:"nama_skpd"`
|
||||
IdRole int `json:"id_role" xml:"id_role" example:"1"`
|
||||
NamaRole string `json:"nama_role" xml:"nama_role" exampe:"BENDAHARA UMUM DAERAH"`
|
||||
}
|
||||
var rows pgx.Rows
|
||||
q = `SELECT
|
||||
p.id,
|
||||
p.id_daerah,
|
||||
p.id_skpd,
|
||||
p.id_role,
|
||||
p.id_user,
|
||||
pr.nama_role
|
||||
FROM sipd_pegawai p
|
||||
LEFT JOIN sipd_roles pr on p.id_role = pr.id_role
|
||||
WHERE
|
||||
p.id_user=$1
|
||||
AND p.tahun_pegawai=$2
|
||||
and p.locked=false
|
||||
and p.deleted=false ORDER BY p.id_skpd,p.id_role`
|
||||
rows, err = ac.pgxConnPegawai.Query(context.Background(), q, idUser, f.Tahun)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
err = utils.LoginError{
|
||||
NextLogin: nextLogin,
|
||||
Attempt: loginAttp,
|
||||
Message: "invalid username or password",
|
||||
}
|
||||
} else {
|
||||
err = utils.RequestError{
|
||||
Code: http.StatusInternalServerError,
|
||||
Message: "gagal mengambil informasi pegawai. - " + err.Error(),
|
||||
}
|
||||
}
|
||||
return r, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
mPeg := pegawaiJabatanModel{}
|
||||
err = rows.Scan(
|
||||
&mPeg.IdPegawai,
|
||||
&mPeg.IdDaerah,
|
||||
&mPeg.IdSkpdLama,
|
||||
&mPeg.IdRole,
|
||||
&mPeg.IdUser,
|
||||
&mPeg.NamaRole,
|
||||
)
|
||||
if err != nil {
|
||||
err = utils.RequestError{
|
||||
Code: http.StatusInternalServerError,
|
||||
Message: "gagal scan informasi pegawai. - " + err.Error(),
|
||||
}
|
||||
return r, err
|
||||
}
|
||||
|
||||
var kodeDdn string
|
||||
q = `select kode_ddn, nama_daerah from public.mst_daerah where id_daerah=$1`
|
||||
err = ac.pgxConnMstData.QueryRow(context.Background(), q, mPeg.IdDaerah).Scan(&kodeDdn, &mPeg.NamaDaerah)
|
||||
if err != nil {
|
||||
err = utils.RequestError{
|
||||
Code: http.StatusInternalServerError,
|
||||
Message: "gagal mengambil informasi daerah. - " + err.Error(),
|
||||
}
|
||||
return r, err
|
||||
}
|
||||
|
||||
if mPeg.IdSkpdLama != 0 {
|
||||
// ambil data skpd
|
||||
var dbMapCode string
|
||||
dbMapCodeDraft := fmt.Sprintf("%s_%d", kodeDdn, f.Tahun)
|
||||
_, connExists := ac.dbConnMapsAnggaran[dbMapCodeDraft]
|
||||
if connExists {
|
||||
dbMapCode = dbMapCodeDraft
|
||||
} else {
|
||||
dbMapCode = fmt.Sprintf("%s_%d", strings.Split(dbMapCodeDraft, ".")[0], f.Tahun)
|
||||
}
|
||||
fmt.Println("ini id_skpd_lama = ", mPeg.IdSkpdLama)
|
||||
q = `select id_unik_skpd, kode_skpd, nama_skpd from public.mst_skpd where id_skpd_lama=$1`
|
||||
err = ac.dbConnMapsAnggaran[dbMapCode].QueryRow(context.Background(), q, mPeg.IdSkpdLama).Scan(&mPeg.IdUnikSkpd, &mPeg.KodeSkpd, &mPeg.NamaSkpd)
|
||||
if err != nil {
|
||||
err = utils.RequestError{
|
||||
Code: http.StatusInternalServerError,
|
||||
Message: "gagal mengambil informasi skpd. - " + err.Error(),
|
||||
}
|
||||
return r, err
|
||||
}
|
||||
} else {
|
||||
mPeg.IdUnikSkpd = "00000000-0000-0000-0000-000000000000"
|
||||
mPeg.KodeSkpd = "0.00.0.00.0.00.00.0000"
|
||||
mPeg.NamaSkpd = "TIDAK ADA SKPD"
|
||||
}
|
||||
|
||||
m := models.PreLoginModel{
|
||||
IdPegawai: mPeg.IdPegawai,
|
||||
IdUser: mPeg.IdUser,
|
||||
Nip: nipUser,
|
||||
Nama: namaUser,
|
||||
IdDaerah: mPeg.IdDaerah,
|
||||
NamaDaerah: mPeg.NamaDaerah,
|
||||
IdSkpdLama: mPeg.IdSkpdLama,
|
||||
IdUnikSkpd: mPeg.IdUnikSkpd,
|
||||
KodeSkpd: mPeg.KodeSkpd,
|
||||
NamaSkpd: mPeg.NamaSkpd,
|
||||
IdRole: mPeg.IdRole,
|
||||
NamaRole: mPeg.NamaRole,
|
||||
}
|
||||
|
||||
r = append(r, m)
|
||||
}
|
||||
if rows.Err() != nil {
|
||||
rows.Close()
|
||||
err = utils.RequestError{
|
||||
Code: http.StatusInternalServerError,
|
||||
Message: "gagal mengambil informasi pegawai (rows). - " + rows.Err().Error(),
|
||||
}
|
||||
return r, err
|
||||
}
|
||||
rows.Close()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (ac *AuthController) Login(f form.LoginForm) (token, refreshToken string, IsDefaultPassword bool, err error) {
|
||||
var q string
|
||||
|
||||
user := models.User{
|
||||
IdPegawai: f.IdPegawai,
|
||||
}
|
||||
|
||||
// Validate form input
|
||||
err = ac.Validate.Struct(f)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var tahunPegawai int
|
||||
q = `select id_user, id_skpd, id_daerah, id_role, tahun_pegawai from public.sipd_pegawai where id=$1 and locked=false and deleted=false`
|
||||
err = ac.pgxConnPegawai.QueryRow(context.Background(), q, f.IdPegawai).
|
||||
Scan(&user.IdUser, &user.IdSkpd, &user.IdDaerah, &user.IdRole, &tahunPegawai)
|
||||
if err != nil {
|
||||
err = utils.RequestError{
|
||||
Code: http.StatusInternalServerError,
|
||||
Message: "gagal mengambil data. - " + err.Error(),
|
||||
}
|
||||
return
|
||||
}
|
||||
var passUser string
|
||||
q = `SELECT pass_user FROM "sipd_user" WHERE id_user = $1 and deleted_by=0`
|
||||
err = ac.pgxConn.QueryRow(context.Background(), q, user.IdUser).Scan(&passUser)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
err = utils.RequestError{
|
||||
Code: http.StatusUnauthorized,
|
||||
Message: "invalid username or password-",
|
||||
}
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
switch detectHashType(passUser) {
|
||||
case "bcrypt":
|
||||
if err = bcrypt.CompareHashAndPassword([]byte(passUser), []byte(f.Password)); err != nil {
|
||||
err = utils.RequestError{
|
||||
Code: http.StatusUnauthorized,
|
||||
Message: "invalid username or password" + err.Error(),
|
||||
}
|
||||
return
|
||||
}
|
||||
case "md5":
|
||||
if ac.validatePassword(f.Password, passUser) == false {
|
||||
err = utils.RequestError{
|
||||
Code: http.StatusUnauthorized,
|
||||
Message: "invalid username or password",
|
||||
}
|
||||
return
|
||||
}
|
||||
default:
|
||||
err = utils.RequestError{
|
||||
Code: http.StatusUnauthorized,
|
||||
Message: "invalid username or password" + err.Error(),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ambil kode wilayah user
|
||||
q = `SELECT nama_daerah, kode_ddn FROM public.mst_daerah where id_daerah=$1 and deleted_by=0`
|
||||
err = ac.pgxConnMstData.QueryRow(context.Background(), q, user.IdDaerah).
|
||||
Scan(&user.SubDomainDaerah, &user.KodeDdn)
|
||||
if err != nil {
|
||||
err = utils.RequestError{
|
||||
Code: http.StatusInternalServerError,
|
||||
Message: "Data Pemerintah Daerah Tidak Tersedia. - " + err.Error(),
|
||||
}
|
||||
return
|
||||
}
|
||||
user.KodeProvinsi = strings.Split(user.KodeDdn, ".")[0]
|
||||
|
||||
if len(user.KodeDdn) == 2 {
|
||||
user.KodeDdn += ".00"
|
||||
}
|
||||
|
||||
token, refreshToken, _, err = ac.jwtManager.Generate(ac.pgxConn, user, tahunPegawai, f.IdPegawai)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (ac *AuthController) Register(f *form.SignupForm) (response bool, err error) {
|
||||
// Validate form input
|
||||
err = ac.Validate.Struct(f)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//currentTime := time.Now()
|
||||
|
||||
// validasi username
|
||||
var usernameExists bool
|
||||
qStr := `SELECT EXISTS(SELECT 1 FROM "user" WHERE "nip_user"=$1 and deleted_by=0)`
|
||||
err = ac.pgxConn.QueryRow(context.Background(), qStr, f.Username).Scan(&usernameExists)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if usernameExists {
|
||||
err = utils.RequestError{
|
||||
Fields: []utils.DataValidationError{{Field: "username", Message: "username already taken"}},
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// validasi nip
|
||||
/*var nipExists bool
|
||||
qStr = `SELECT EXISTS(SELECT 1 FROM "user" WHERE "nip"=$1)`
|
||||
err = ac.pgxConn.QueryRow(context.Background(), qStr, f.Nip).Scan(&nipExists)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if nipExists {
|
||||
err = utils.RequestError{
|
||||
Fields: []utils.DataValidationError{{Field: "nip", Message: "nip already taken"}},
|
||||
}
|
||||
return
|
||||
}*/
|
||||
|
||||
var maxIdUser int64
|
||||
// jika id_user tidak ditemukan maka otomatis akan menjadi 0
|
||||
qStr = `SELECT COALESCE(MAX(id_user),0) AS last_user_id FROM "user" WHERE "id_daerah"=$1`
|
||||
err = ac.pgxConn.QueryRow(context.Background(), qStr, f.IdDaerah).Scan(&maxIdUser)
|
||||
|
||||
/*strQuery := `INSERT INTO "user" (
|
||||
id_user,
|
||||
id_daerah,
|
||||
nip,
|
||||
nama_user,
|
||||
jabatan,
|
||||
id_profil,
|
||||
login_name,
|
||||
login_passwd,
|
||||
id_level,
|
||||
akses_user,
|
||||
is_locked,
|
||||
is_deleted,
|
||||
created_at,
|
||||
updated_at,
|
||||
is_login,
|
||||
last_login,
|
||||
last_ip_login,
|
||||
nama_bidang,
|
||||
id_unik,
|
||||
token_login)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20)`
|
||||
_, err = ac.pgxConn.Exec(context.Background(), strQuery,
|
||||
maxIdUser+1,
|
||||
f.IdDaerah,
|
||||
f.Nip,
|
||||
f.NamaUser,
|
||||
"-",
|
||||
0,
|
||||
f.Username,
|
||||
ac.encodePasswd(f.Password),
|
||||
2,
|
||||
"1|1|1|1|1|1|1|1|1|1|1|1|1",
|
||||
0,
|
||||
0,
|
||||
currentTime.Format("2006-01-02 15:04:05"),
|
||||
currentTime.Format("2006-01-02 15:04:05"),
|
||||
0,
|
||||
currentTime.Format("2006-01-02 15:04:05"),
|
||||
"116.206.43.97:61968, 116.206.43.97",
|
||||
f.NamaBidang,
|
||||
"37fa8cde-849d-4801-99ac-9067b6b4d5ac",
|
||||
"61dd0d091b2c5-1641876745")
|
||||
if err != nil {
|
||||
return
|
||||
}*/
|
||||
|
||||
response = true
|
||||
return
|
||||
}
|
||||
|
||||
func (ac *AuthController) RefreshToken(pl form.RefreshTokenForm) (r models.ResponseLogin, err error) {
|
||||
var user models.User
|
||||
|
||||
err = ac.Validate.Struct(pl)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := ac.jwtManager.Verify(pl.Token)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
user.IdUser = claims.IdUser
|
||||
user.IdDaerah = claims.IdDaerah
|
||||
user.IdSkpd = claims.IdSkpd
|
||||
user.IdRole = claims.IdRole
|
||||
user.IdPegawai = claims.IdPegawai
|
||||
|
||||
/*subArr := strings.Split(claims.Subject, ".")
|
||||
if len(subArr) != 2 {
|
||||
err = utils.RequestError{
|
||||
Code: http.StatusUnprocessableEntity,
|
||||
Message: "Akun tidak valid",
|
||||
}
|
||||
return
|
||||
}
|
||||
// log.Println(subArr)
|
||||
|
||||
userId, err := strconv.ParseUint(subArr[0], 10, 64)
|
||||
if err != nil {
|
||||
err = utils.RequestError{
|
||||
Code: http.StatusUnprocessableEntity,
|
||||
Message: "Akun tidak valid",
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
userIdDaerah, err := strconv.ParseUint(subArr[1], 10, 64)
|
||||
if err != nil {
|
||||
err = utils.RequestError{
|
||||
Code: http.StatusUnprocessableEntity,
|
||||
Message: "Akun tidak valid",
|
||||
}
|
||||
return
|
||||
}*/
|
||||
|
||||
var idUser, idDaerah uint64
|
||||
var passwdUser string
|
||||
q := `SELECT id_user, id_daerah, pass_user FROM "user" WHERE id_user = $1 and deleted_by=0`
|
||||
err = ac.pgxConn.QueryRow(context.Background(), q, claims.IdUser).Scan(&idUser, &idDaerah, &passwdUser)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
err = utils.RequestError{
|
||||
Code: http.StatusUnprocessableEntity,
|
||||
Message: "Akun tidak valid",
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
r.Token, r.RefreshToken, _, err = ac.jwtManager.Generate(ac.pgxConn, user, claims.Tahun, claims.IdPegawai)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *AuthController) AmankanKataSandi(pl form.ChangePasswordFormPublik) error {
|
||||
var err error
|
||||
var q string
|
||||
|
||||
/*log.Println("idUser: ", userId)
|
||||
log.Println("idDaerah: ", idDaerah)
|
||||
log.Printf("%#v\\n", pl)*/
|
||||
|
||||
var hashed bool
|
||||
var idUser, idDaerah, loginAttp, nextLogin int
|
||||
var passUser string
|
||||
q = `SELECT id_user, id_daerah, pass_user, hashed, login_attempt, next_login
|
||||
FROM "user"
|
||||
WHERE nip_user = $1 and deleted_by = 0`
|
||||
err = c.pgxConn.QueryRow(context.Background(), q, pl.Username).Scan(
|
||||
&idUser,
|
||||
&idDaerah,
|
||||
&passUser,
|
||||
&hashed,
|
||||
&loginAttp,
|
||||
&nextLogin,
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
err = utils.LoginError{
|
||||
NextLogin: nextLogin,
|
||||
Attempt: loginAttp,
|
||||
Message: "invalid username or password",
|
||||
}
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
tNow := time.Now()
|
||||
|
||||
if int64(nextLogin) > tNow.Unix() {
|
||||
err = utils.LoginError{
|
||||
NextLogin: nextLogin,
|
||||
Attempt: loginAttp,
|
||||
Message: "Login dibatasi",
|
||||
}
|
||||
return err
|
||||
}
|
||||
if pl.NewPassword != pl.NewPasswordRepeat {
|
||||
err = utils.LoginError{
|
||||
NextLogin: nextLogin,
|
||||
Attempt: loginAttp,
|
||||
Message: "Password tidak sama dengan konfirmasi password",
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if hashed {
|
||||
// validasi password terhadap hash
|
||||
if err = bcrypt.CompareHashAndPassword([]byte(passUser), []byte(pl.OldPassword)); err != nil {
|
||||
/*errAtp := ac.wrongLoginCounter(user.IdUser, user.IdDaerah, tNow)
|
||||
if errAtp != nil {
|
||||
err = utils.RequestError{Code: http.StatusBadRequest, Message: errAtp.Error()}
|
||||
return err
|
||||
}*/
|
||||
|
||||
err = utils.LoginError{
|
||||
NextLogin: nextLogin,
|
||||
Attempt: loginAttp,
|
||||
Message: "invalid username or password",
|
||||
}
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if c.validatePassword(pl.OldPassword, passUser) == false {
|
||||
/*errAtp := ac.wrongLoginCounter(user.IdUser, user.IdDaerah, tNow)
|
||||
if errAtp != nil {
|
||||
err = utils.RequestError{Code: http.StatusBadRequest, Message: errAtp.Error()}
|
||||
return err
|
||||
}*/
|
||||
|
||||
err = utils.LoginError{
|
||||
NextLogin: nextLogin,
|
||||
Attempt: loginAttp,
|
||||
Message: "invalid username or password",
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
// log.Println("passwordHash: ", passwordHash)
|
||||
|
||||
if passUser == "" {
|
||||
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(passUser), []byte(pl.OldPassword))
|
||||
if err != nil {
|
||||
err = utils.LoginError{
|
||||
NextLogin: nextLogin,
|
||||
Attempt: loginAttp,
|
||||
Message: "invalid username or password",
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// validate password
|
||||
if !c.isValidPassword(pl.NewPassword) {
|
||||
err = utils.LoginError{
|
||||
NextLogin: nextLogin,
|
||||
Attempt: loginAttp,
|
||||
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, idUser, idDaerah)
|
||||
if err != nil {
|
||||
err = utils.RequestError{
|
||||
Code: http.StatusInternalServerError,
|
||||
Message: "gagal set new password. - " + err.Error(),
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ac *AuthController) validatePassword(content, encrypted string) bool {
|
||||
return strings.EqualFold(ac.encodePasswd(content), encrypted)
|
||||
}
|
||||
|
||||
func (ac *AuthController) encodePasswd(data string) string {
|
||||
h := md5.New()
|
||||
h.Write([]byte(data))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
func (ac *AuthController) generatePasswordHash(password string) (string, error) {
|
||||
bytesPwd, err := bcrypt.GenerateFromPassword([]byte(password), 14)
|
||||
return string(bytesPwd), err
|
||||
}
|
||||
|
||||
func (ac *AuthController) wrongLoginCounter(idUser, idDaerah int64, t time.Time) error {
|
||||
var loginAtp uint32
|
||||
q := `UPDATE "user" SET "login_attempt"="user"."login_attempt"-1 WHERE "id_user"=$1 AND "id_daerah"=$2 RETURNING "login_attempt"`
|
||||
err := ac.pgxConn.QueryRow(context.Background(), q, idUser, idDaerah).Scan(&loginAtp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if loginAtp <= 0 {
|
||||
q = `UPDATE "user" SET "next_login"=$1 WHERE "id_user"=$2 AND "id_daerah"=$3 RETURNING "login_attempt"`
|
||||
_, err = ac.pgxConn.Exec(context.Background(), q, t.Add(time.Minute*5).Unix(), idUser, idDaerah)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ac *AuthController) 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)
|
||||
}
|
||||
143
controller/captcha.go
Normal file
143
controller/captcha.go
Normal file
@ -0,0 +1,143 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
models "kemendagri/sipd/services/sipd_auth/model"
|
||||
"kemendagri/sipd/services/sipd_auth/utils"
|
||||
"kemendagri/sipd/services/sipd_auth/utils/captcha_store"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/dchest/captcha"
|
||||
)
|
||||
|
||||
type CaptchaController struct {
|
||||
captchaStore *captcha_store.PostgreSQLStore
|
||||
contextTimeout time.Duration
|
||||
validate *validator.Validate
|
||||
}
|
||||
|
||||
// https://www.machinet.net/tutorial-eng/implement-go-based-captcha-generator-validator
|
||||
|
||||
func NewCaptchaController(store *captcha_store.PostgreSQLStore, timeout time.Duration, vld *validator.Validate) *CaptchaController {
|
||||
return &CaptchaController{
|
||||
captchaStore: store,
|
||||
contextTimeout: timeout,
|
||||
validate: vld,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CaptchaController) New() (string, string, string, error) {
|
||||
var err error
|
||||
var captchaId, captchaBase64, audioBase64 string
|
||||
|
||||
captcha.SetCustomStore(c.captchaStore)
|
||||
|
||||
captchaId = uuid.New().String()
|
||||
//captchaId = captcha.New()
|
||||
c.captchaStore.Set(captchaId, captcha.RandomDigits(captcha.DefaultLen))
|
||||
//c.captchaStore.Set(captchaId, captcha.RandomDigits(captcha.DefaultLen))
|
||||
//c.captchaStore.Set(captchaId, []byte("123456"))
|
||||
|
||||
var captchaImage bytes.Buffer
|
||||
err = captcha.WriteImage(&captchaImage, captchaId, captcha.StdWidth, captcha.StdHeight)
|
||||
if err != nil {
|
||||
err = utils.RequestError{
|
||||
Code: http.StatusInternalServerError,
|
||||
Message: "failed to generate image. - " + err.Error(),
|
||||
}
|
||||
return captchaId, captchaBase64, audioBase64, err
|
||||
}
|
||||
captchaBase64 = base64.StdEncoding.EncodeToString(captchaImage.Bytes())
|
||||
|
||||
var captchaAudio bytes.Buffer
|
||||
err = captcha.WriteAudio(&captchaAudio, captchaId, "id")
|
||||
if err != nil {
|
||||
err = utils.RequestError{
|
||||
Code: http.StatusInternalServerError,
|
||||
Message: "failed to generate audio. - " + err.Error(),
|
||||
}
|
||||
return captchaId, captchaBase64, audioBase64, err
|
||||
}
|
||||
|
||||
// Encode the audio to Base64
|
||||
audioBase64 = base64.StdEncoding.EncodeToString(captchaAudio.Bytes())
|
||||
|
||||
/*log.Println("captchaId: ", captchaId)
|
||||
log.Println("captchaBase64: ", captchaBase64)*/
|
||||
|
||||
return captchaId, captchaBase64, audioBase64, err
|
||||
}
|
||||
|
||||
func (c *CaptchaController) Reload(captchaId string) (string, string, error) {
|
||||
var err error
|
||||
var captchaBase64, audioBase64 string
|
||||
|
||||
captcha.SetCustomStore(c.captchaStore)
|
||||
|
||||
if !captcha.Reload(captchaId) {
|
||||
err = utils.RequestError{
|
||||
Code: http.StatusBadRequest,
|
||||
Message: "captcha reload failed, invalid captcha ID.",
|
||||
}
|
||||
return captchaBase64, audioBase64, err
|
||||
}
|
||||
|
||||
var captchaImage bytes.Buffer
|
||||
|
||||
err = captcha.WriteImage(&captchaImage, captchaId, captcha.StdWidth, captcha.StdHeight)
|
||||
if err != nil {
|
||||
err = utils.RequestError{
|
||||
Code: http.StatusInternalServerError,
|
||||
Message: "failed to reload captcha. - " + err.Error(),
|
||||
}
|
||||
return captchaBase64, audioBase64, err
|
||||
}
|
||||
captchaBase64 = base64.StdEncoding.EncodeToString(captchaImage.Bytes())
|
||||
|
||||
var captchaAudio bytes.Buffer
|
||||
err = captcha.WriteAudio(&captchaAudio, captchaId, "id")
|
||||
if err != nil {
|
||||
err = utils.RequestError{
|
||||
Code: http.StatusInternalServerError,
|
||||
Message: "failed to generate audio. - " + err.Error(),
|
||||
}
|
||||
return captchaBase64, audioBase64, err
|
||||
}
|
||||
|
||||
// Encode the audio to Base64
|
||||
audioBase64 = base64.StdEncoding.EncodeToString(captchaAudio.Bytes())
|
||||
|
||||
/*log.Println("captchaId: ", captchaId)
|
||||
log.Println("captchaBase64: ", captchaBase64)*/
|
||||
|
||||
return captchaBase64, audioBase64, nil
|
||||
}
|
||||
|
||||
func (c *CaptchaController) Validate(pl models.ValidateCaptcha) error {
|
||||
var err error
|
||||
|
||||
err = c.validate.Struct(pl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
captcha.SetCustomStore(c.captchaStore)
|
||||
|
||||
log.Printf("%+v\n", pl)
|
||||
|
||||
if !captcha.VerifyString(pl.Id, pl.Solution) {
|
||||
err = utils.RequestError{
|
||||
Code: http.StatusUnprocessableEntity,
|
||||
Message: "invalid captcha",
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
27
controller/site.go
Normal file
27
controller/site.go
Normal file
@ -0,0 +1,27 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
type SiteController struct {
|
||||
pgxConn *pgxpool.Pool
|
||||
contextTimeout time.Duration
|
||||
}
|
||||
|
||||
func NewSiteController(conn *pgxpool.Pool, tot time.Duration) *SiteController {
|
||||
return &SiteController{
|
||||
contextTimeout: tot,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SiteController) Index() (interface{}, error) {
|
||||
var err error
|
||||
var r interface{}
|
||||
|
||||
r = "index"
|
||||
|
||||
return r, err
|
||||
}
|
||||
459
controller/user.go
Normal file
459
controller/user.go
Normal file
@ -0,0 +1,459 @@
|
||||
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)
|
||||
}
|
||||
1428
docs/docs.go
Normal file
1428
docs/docs.go
Normal file
File diff suppressed because it is too large
Load Diff
1402
docs/swagger.json
Normal file
1402
docs/swagger.json
Normal file
File diff suppressed because it is too large
Load Diff
953
docs/swagger.yaml
Normal file
953
docs/swagger.yaml
Normal file
@ -0,0 +1,953 @@
|
||||
definitions:
|
||||
form.ChangePasswordForm:
|
||||
properties:
|
||||
new_password:
|
||||
description: New password
|
||||
example: "123456"
|
||||
type: string
|
||||
new_password_repeat:
|
||||
description: New password confirmation, must equal to password
|
||||
example: "123456"
|
||||
type: string
|
||||
old_password:
|
||||
description: Old password
|
||||
example: "123456"
|
||||
type: string
|
||||
required:
|
||||
- new_password
|
||||
- new_password_repeat
|
||||
- old_password
|
||||
type: object
|
||||
form.ChangePasswordFormPublik:
|
||||
properties:
|
||||
new_password:
|
||||
description: New password
|
||||
example: "123456"
|
||||
type: string
|
||||
new_password_repeat:
|
||||
description: New password confirmation, must equal to password
|
||||
example: "123456"
|
||||
type: string
|
||||
old_password:
|
||||
description: Old password
|
||||
example: "123456"
|
||||
type: string
|
||||
username:
|
||||
description: |-
|
||||
IdDaerah int64 `json:"id_daerah" xml:"id_daerah" form:"id_daerah" example:"101" validate:"gte=0"` //Id daerah target user
|
||||
IdUser int64 `json:"id_user" xml:"id_user" form:"id_user" example:"18" validate:"gte=0"` //Id target user
|
||||
example: user
|
||||
type: string
|
||||
required:
|
||||
- new_password
|
||||
- new_password_repeat
|
||||
- old_password
|
||||
- username
|
||||
type: object
|
||||
form.GenerateHashForm:
|
||||
properties:
|
||||
password:
|
||||
example: "123456"
|
||||
type: string
|
||||
password_repeat:
|
||||
example: "123456"
|
||||
type: string
|
||||
required:
|
||||
- password
|
||||
- password_repeat
|
||||
type: object
|
||||
form.LoginForm:
|
||||
properties:
|
||||
id_daerah:
|
||||
description: Id daerah user
|
||||
example: 34
|
||||
type: integer
|
||||
id_pegawai:
|
||||
example: 36107
|
||||
type: integer
|
||||
password:
|
||||
description: User password
|
||||
example: "1"
|
||||
type: string
|
||||
required:
|
||||
- password
|
||||
type: object
|
||||
form.PreLoginForm:
|
||||
properties:
|
||||
captcha_id:
|
||||
type: string
|
||||
captcha_solution:
|
||||
type: string
|
||||
password:
|
||||
description: User password
|
||||
example: "1"
|
||||
type: string
|
||||
tahun:
|
||||
example: 2023
|
||||
minimum: 1
|
||||
type: integer
|
||||
username:
|
||||
description: Username of user (NIP)
|
||||
example: "198604292011011004"
|
||||
type: string
|
||||
required:
|
||||
- captcha_id
|
||||
- captcha_solution
|
||||
- password
|
||||
- username
|
||||
type: object
|
||||
form.RefreshTokenForm:
|
||||
properties:
|
||||
token:
|
||||
description: JWT expired token
|
||||
example: xxxxx
|
||||
type: string
|
||||
required:
|
||||
- token
|
||||
type: object
|
||||
form.SignupForm:
|
||||
properties:
|
||||
id_daerah:
|
||||
description: ID Daerah
|
||||
example: 251
|
||||
type: integer
|
||||
nama_bidang:
|
||||
description: Nama Bidang
|
||||
type: string
|
||||
nama_user:
|
||||
description: 'Nama User (Ex: Kab Tanggamus)'
|
||||
example: Kab. Tanggamus
|
||||
type: string
|
||||
nip:
|
||||
description: NIP
|
||||
example: "123456789876543213"
|
||||
type: string
|
||||
password:
|
||||
description: Password user
|
||||
example: "123456"
|
||||
type: string
|
||||
password_repeat:
|
||||
description: Confirm Password user
|
||||
example: "123456"
|
||||
type: string
|
||||
username:
|
||||
description: Username user
|
||||
example: admlambar
|
||||
type: string
|
||||
required:
|
||||
- id_daerah
|
||||
- password
|
||||
- password_repeat
|
||||
- username
|
||||
type: object
|
||||
form.UpdateUserProfileForm:
|
||||
properties:
|
||||
alamat:
|
||||
description: Alamat
|
||||
example: xxxx
|
||||
type: string
|
||||
id_pang_gol:
|
||||
description: ID pangkat/golongan
|
||||
example: 1
|
||||
type: integer
|
||||
nama_user:
|
||||
description: 'Nama User (Ex: Kab Tanggamus)'
|
||||
example: Kab. Tanggamus
|
||||
type: string
|
||||
nik:
|
||||
description: NIK
|
||||
example: "123456789876543213"
|
||||
type: string
|
||||
npwp:
|
||||
description: NPWP
|
||||
example: "123456789876543213"
|
||||
type: string
|
||||
tgl_lahir:
|
||||
description: Tanggal lahir
|
||||
example: "1945-08-17"
|
||||
type: string
|
||||
required:
|
||||
- id_pang_gol
|
||||
- nama_user
|
||||
- nik
|
||||
- npwp
|
||||
type: object
|
||||
http_util.JSONResultLogin:
|
||||
properties:
|
||||
is_default_password:
|
||||
example: false
|
||||
type: boolean
|
||||
refresh_token:
|
||||
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MzYxNjgyMjksImlk._aYI7pV2c9SU9VOp3RY_mxtFenYFQuKPJtVfk
|
||||
type: string
|
||||
token:
|
||||
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MzYwODU0MjksImlkIjoyLCJwaG9uZSI6Iis2MjgxMjM0NTYyIiwidXNlcm5hbWUiOi.dl_ojy9ojLnWqpW589YltLPV61TCsON-3yQ2
|
||||
type: string
|
||||
type: object
|
||||
models.PreLoginModel:
|
||||
properties:
|
||||
id_daerah:
|
||||
type: integer
|
||||
id_pegawai:
|
||||
type: integer
|
||||
id_role:
|
||||
type: integer
|
||||
id_skpd_lama:
|
||||
type: integer
|
||||
id_unik_skpd:
|
||||
type: string
|
||||
id_user:
|
||||
type: integer
|
||||
kode_skpd:
|
||||
type: string
|
||||
nama_daerah:
|
||||
example: Kota Bandar Lampung
|
||||
type: string
|
||||
nama_role:
|
||||
type: string
|
||||
nama_skpd:
|
||||
type: string
|
||||
nama_user:
|
||||
example: John Doe
|
||||
type: string
|
||||
nip_user:
|
||||
example: "196408081992011001"
|
||||
type: string
|
||||
type: object
|
||||
models.ResponseLogin:
|
||||
properties:
|
||||
refresh_token:
|
||||
description: Jwt refresh token
|
||||
example: sdfsfsfsdfsfsdfsfsdfsf
|
||||
type: string
|
||||
token:
|
||||
description: Jwt token
|
||||
example: sdfsfsfsdfsfsdfsfsdfsf
|
||||
type: string
|
||||
type: object
|
||||
models.UserDetail:
|
||||
properties:
|
||||
alamat:
|
||||
example: sddsfsd
|
||||
type: string
|
||||
id_daerah:
|
||||
example: 111
|
||||
type: integer
|
||||
id_role:
|
||||
type: integer
|
||||
id_skpd_lama:
|
||||
type: integer
|
||||
id_unik_skpd:
|
||||
type: string
|
||||
id_user:
|
||||
example: 581
|
||||
type: integer
|
||||
kode_skpd:
|
||||
type: string
|
||||
nama_daerah:
|
||||
example: Kota Bandar Lampung
|
||||
type: string
|
||||
nama_skpd:
|
||||
type: string
|
||||
nama_user:
|
||||
example: John Doe
|
||||
type: string
|
||||
nik_user:
|
||||
example: "222112323324"
|
||||
type: string
|
||||
nip_user:
|
||||
example: "196408081992011001"
|
||||
type: string
|
||||
npwp_user:
|
||||
example: "222112323324"
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
type: object
|
||||
models.ValidateCaptcha:
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
solution:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- solution
|
||||
type: object
|
||||
utils.DataValidationError:
|
||||
properties:
|
||||
field:
|
||||
example: email
|
||||
type: string
|
||||
message:
|
||||
example: Invalid email address
|
||||
type: string
|
||||
type: object
|
||||
utils.LoginError:
|
||||
properties:
|
||||
attempt:
|
||||
description: sisa kesempatan login sebelum diblokir 5 menit
|
||||
example: 3
|
||||
type: integer
|
||||
message:
|
||||
description: keterangan error
|
||||
example: invalid username or password
|
||||
type: string
|
||||
next_login:
|
||||
description: unix timestamp UTC blokir login dibuka kembali
|
||||
example: 123233213
|
||||
type: integer
|
||||
type: object
|
||||
utils.RequestError:
|
||||
properties:
|
||||
code:
|
||||
example: 422
|
||||
type: integer
|
||||
fields:
|
||||
items:
|
||||
$ref: '#/definitions/utils.DataValidationError'
|
||||
type: array
|
||||
message:
|
||||
example: Invalid email address
|
||||
type: string
|
||||
type: object
|
||||
info:
|
||||
contact:
|
||||
email: lifelinejar@mail.com
|
||||
name: API Support
|
||||
description: SIPD Service Auth Rest API.
|
||||
license:
|
||||
name: Apache 2.0
|
||||
url: http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
termsOfService: http://swagger.io/terms/
|
||||
title: SIPD Service Auth
|
||||
version: "1.0"
|
||||
paths:
|
||||
/auth/amankan-kata-sandi:
|
||||
post:
|
||||
description: User melakukan Amankan Kata Sandi.
|
||||
operationId: auth-amankan-kata-sandi
|
||||
parameters:
|
||||
- description: Payload
|
||||
in: body
|
||||
name: payload
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/form.ChangePasswordFormPublik'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Success
|
||||
schema:
|
||||
type: boolean
|
||||
"400":
|
||||
description: Bad request
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"403":
|
||||
description: Forbidden
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"404":
|
||||
description: Data not found
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"422":
|
||||
description: Data validation failed
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
type: array
|
||||
"500":
|
||||
description: Server error
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
summary: Amankan Kata Sandi
|
||||
tags:
|
||||
- Auth
|
||||
/auth/login:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Login to get JWT token and refresh token.
|
||||
operationId: auth-login
|
||||
parameters:
|
||||
- description: Login payload
|
||||
in: body
|
||||
name: payload
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/form.LoginForm'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Login Success, jwt provided
|
||||
schema:
|
||||
$ref: '#/definitions/http_util.JSONResultLogin'
|
||||
"400":
|
||||
description: Bad request
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"403":
|
||||
description: Login forbidden
|
||||
schema:
|
||||
$ref: '#/definitions/utils.LoginError'
|
||||
"404":
|
||||
description: Data not found
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"422":
|
||||
description: Data validation failed
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
type: array
|
||||
"500":
|
||||
description: Server error
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
summary: user login
|
||||
tags:
|
||||
- Auth
|
||||
/auth/pre-login:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Login to get JWT token and refresh token.
|
||||
operationId: auth-pre-login
|
||||
parameters:
|
||||
- description: Pre login payload
|
||||
in: body
|
||||
name: payload
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/form.PreLoginForm'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Success
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/models.PreLoginModel'
|
||||
type: array
|
||||
"400":
|
||||
description: Bad request
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"403":
|
||||
description: Login forbidden
|
||||
schema:
|
||||
$ref: '#/definitions/utils.LoginError'
|
||||
"404":
|
||||
description: Data not found
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"422":
|
||||
description: Data validation failed
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
type: array
|
||||
"500":
|
||||
description: Server error
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
summary: user login
|
||||
tags:
|
||||
- Auth
|
||||
/auth/register:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Register user.
|
||||
operationId: auth-register
|
||||
parameters:
|
||||
- description: Register payload
|
||||
in: body
|
||||
name: payload
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/form.SignupForm'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Register Success
|
||||
schema:
|
||||
type: boolean
|
||||
"400":
|
||||
description: Bad request
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"404":
|
||||
description: Data not found
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"422":
|
||||
description: Data validation failed
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
type: array
|
||||
"500":
|
||||
description: Server error
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
summary: user register
|
||||
tags:
|
||||
- Auth
|
||||
/auth/token-refresh/{token}:
|
||||
post:
|
||||
description: Refresh token to get new valid JWT token and refresh token
|
||||
operationId: auth-refresh-token
|
||||
parameters:
|
||||
- description: Payload
|
||||
in: body
|
||||
name: payload
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/form.RefreshTokenForm'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Refresh Token Success, new JWT token provided
|
||||
schema:
|
||||
$ref: '#/definitions/models.ResponseLogin'
|
||||
"400":
|
||||
description: Bad request
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"404":
|
||||
description: Data not found
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"422":
|
||||
description: Data validation failed
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
type: array
|
||||
"500":
|
||||
description: Server error
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
summary: Refresh Token
|
||||
tags:
|
||||
- Auth
|
||||
/captcha/new:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: generate new captcha.
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Base64 image string
|
||||
schema: {}
|
||||
"400":
|
||||
description: Bad request
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"403":
|
||||
description: Login forbidden
|
||||
schema:
|
||||
$ref: '#/definitions/utils.LoginError'
|
||||
"404":
|
||||
description: Data not found
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"422":
|
||||
description: Data validation failed
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
type: array
|
||||
"500":
|
||||
description: Server error
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
summary: generate new captcha
|
||||
tags:
|
||||
- Captcha
|
||||
/captcha/reload/{id}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: reload captcha.
|
||||
parameters:
|
||||
- description: captcha ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Base64 image string
|
||||
schema: {}
|
||||
"400":
|
||||
description: Bad request
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"403":
|
||||
description: Login forbidden
|
||||
schema:
|
||||
$ref: '#/definitions/utils.LoginError'
|
||||
"404":
|
||||
description: Data not found
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"422":
|
||||
description: Data validation failed
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
type: array
|
||||
"500":
|
||||
description: Server error
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
summary: reload captcha
|
||||
tags:
|
||||
- Captcha
|
||||
/captcha/validate:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: validate captcha.
|
||||
parameters:
|
||||
- description: payload
|
||||
in: body
|
||||
name: payload
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/models.ValidateCaptcha'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: validate success
|
||||
schema:
|
||||
type: boolean
|
||||
"400":
|
||||
description: Bad request
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"403":
|
||||
description: Login forbidden
|
||||
schema:
|
||||
$ref: '#/definitions/utils.LoginError'
|
||||
"404":
|
||||
description: Data not found
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"422":
|
||||
description: Data validation failed
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
type: array
|
||||
"500":
|
||||
description: Server error
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
summary: validate captcha
|
||||
tags:
|
||||
- Captcha
|
||||
/site/index:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: index page.
|
||||
operationId: index
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Success
|
||||
schema: {}
|
||||
"400":
|
||||
description: Bad request
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"403":
|
||||
description: Login forbidden
|
||||
schema:
|
||||
$ref: '#/definitions/utils.LoginError'
|
||||
"404":
|
||||
description: Data not found
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"422":
|
||||
description: Data validation failed
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
type: array
|
||||
"500":
|
||||
description: Server error
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
summary: index
|
||||
tags:
|
||||
- Site
|
||||
/strict/user/change-password:
|
||||
post:
|
||||
description: change password.
|
||||
operationId: user-change-password
|
||||
parameters:
|
||||
- description: Payload
|
||||
in: body
|
||||
name: payload
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/form.ChangePasswordForm'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Success
|
||||
schema:
|
||||
type: boolean
|
||||
"400":
|
||||
description: Bad request
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"403":
|
||||
description: Forbidden
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"404":
|
||||
description: Data not found
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"422":
|
||||
description: Data validation failed
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
type: array
|
||||
"500":
|
||||
description: Server error
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: change password
|
||||
tags:
|
||||
- User
|
||||
/strict/user/generate-password-hash:
|
||||
post:
|
||||
description: generate password hash.
|
||||
operationId: user-generate-password-hash
|
||||
parameters:
|
||||
- description: Payload
|
||||
in: body
|
||||
name: payload
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/form.GenerateHashForm'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Success
|
||||
schema:
|
||||
type: boolean
|
||||
"400":
|
||||
description: Bad request
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"403":
|
||||
description: Forbidden
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"404":
|
||||
description: Data not found
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"422":
|
||||
description: Data validation failed
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
type: array
|
||||
"500":
|
||||
description: Server error
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: generate password hash
|
||||
tags:
|
||||
- User
|
||||
/strict/user/logout:
|
||||
get:
|
||||
description: user logout.
|
||||
operationId: user-logout
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Success
|
||||
schema:
|
||||
type: boolean
|
||||
"400":
|
||||
description: Bad request
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"403":
|
||||
description: Forbidden
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"404":
|
||||
description: Data not found
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"422":
|
||||
description: Data validation failed
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
type: array
|
||||
"500":
|
||||
description: Server error
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: logout
|
||||
tags:
|
||||
- User
|
||||
/strict/user/profile:
|
||||
get:
|
||||
description: get profile info.
|
||||
operationId: user-profile
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Success
|
||||
schema:
|
||||
$ref: '#/definitions/models.UserDetail'
|
||||
"400":
|
||||
description: Bad request
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"404":
|
||||
description: Data not found
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"422":
|
||||
description: Data validation failed
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
type: array
|
||||
"500":
|
||||
description: Server error
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: user get profile info
|
||||
tags:
|
||||
- User
|
||||
/strict/user/update-profile:
|
||||
put:
|
||||
description: update profile.
|
||||
parameters:
|
||||
- description: Payload
|
||||
in: body
|
||||
name: payload
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/form.UpdateUserProfileForm'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Success
|
||||
schema:
|
||||
type: boolean
|
||||
"400":
|
||||
description: Bad request
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"404":
|
||||
description: Data not found
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"422":
|
||||
description: Data validation failed
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
type: array
|
||||
"500":
|
||||
description: Server error
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: update profile
|
||||
tags:
|
||||
- User
|
||||
/strict/user/upload-avatar:
|
||||
post:
|
||||
consumes:
|
||||
- application/x-www-form-urlencoded
|
||||
description: upload avatar
|
||||
operationId: user-upload avatar
|
||||
parameters:
|
||||
- description: Image avatar
|
||||
in: formData
|
||||
name: file
|
||||
required: true
|
||||
type: file
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Success
|
||||
schema:
|
||||
type: boolean
|
||||
"400":
|
||||
description: Bad request
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"403":
|
||||
description: Forbidden
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"404":
|
||||
description: Data not found
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
"422":
|
||||
description: Data validation failed
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
type: array
|
||||
"500":
|
||||
description: Server error
|
||||
schema:
|
||||
$ref: '#/definitions/utils.RequestError'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: upload avatar
|
||||
tags:
|
||||
- User
|
||||
securityDefinitions:
|
||||
ApiKeyAuth:
|
||||
in: header
|
||||
name: Authorization
|
||||
type: apiKey
|
||||
swagger: "2.0"
|
||||
74
go.mod
Normal file
74
go.mod
Normal file
@ -0,0 +1,74 @@
|
||||
module kemendagri/sipd/services/sipd_auth
|
||||
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.24.7
|
||||
|
||||
require (
|
||||
github.com/arsmn/fiber-swagger/v2 v2.31.1
|
||||
github.com/dchest/captcha v1.1.0
|
||||
github.com/go-playground/validator/v10 v10.27.0
|
||||
github.com/gofiber/fiber/v2 v2.52.9
|
||||
github.com/gofiber/jwt/v3 v3.3.10
|
||||
github.com/gofiber/storage/redis/v3 v3.4.1
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2
|
||||
github.com/golang-migrate/migrate/v4 v4.19.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/jackc/pgx/v5 v5.7.6
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/minio/minio-go/v7 v7.0.95
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/swaggo/swag v1.16.6
|
||||
golang.org/x/crypto v0.39.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.6 // indirect
|
||||
github.com/go-openapi/spec v0.20.4 // indirect
|
||||
github.com/go-openapi/swag v0.19.15 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/minio/crc64nvme v1.0.2 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/philhofer/fwd v1.2.0 // indirect
|
||||
github.com/redis/go-redis/v9 v9.12.1 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 // indirect
|
||||
github.com/tinylib/msgp v1.3.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.51.0 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
golang.org/x/mod v0.25.0 // indirect
|
||||
golang.org/x/net v0.41.0 // indirect
|
||||
golang.org/x/sync v0.15.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
golang.org/x/tools v0.33.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
373
go.sum
Normal file
373
go.sum
Normal file
@ -0,0 +1,373 @@
|
||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY=
|
||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
github.com/arsmn/fiber-swagger/v2 v2.31.1 h1:VmX+flXiGGNqLX3loMEEzL3BMOZFSPwBEWR04GA6Mco=
|
||||
github.com/arsmn/fiber-swagger/v2 v2.31.1/go.mod h1:ZHhMprtB3M6jd2mleG03lPGhHH0lk9u3PtfWS1cBhMA=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
|
||||
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
|
||||
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
|
||||
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dchest/captcha v1.1.0 h1:2kt47EoYUUkaISobUdTbqwx55xvKOJxyScVfw25xzhQ=
|
||||
github.com/dchest/captcha v1.1.0/go.mod h1:7zoElIawLp7GUMLcj54K9kbw+jEyvz2K0FDdRRYhvWo=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
|
||||
github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
||||
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
|
||||
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
|
||||
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
|
||||
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
||||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/gofiber/fiber/v2 v2.31.0/go.mod h1:1Ega6O199a3Y7yDGuM9FyXDPYQfv+7/y48wl6WCwUF4=
|
||||
github.com/gofiber/fiber/v2 v2.45.0/go.mod h1:DNl0/c37WLe0g92U6lx1VMQuxGUQY5V7EIaVoEsUffc=
|
||||
github.com/gofiber/fiber/v2 v2.52.9 h1:YjKl5DOiyP3j0mO61u3NTmK7or8GzzWzCFzkboyP5cw=
|
||||
github.com/gofiber/fiber/v2 v2.52.9/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
||||
github.com/gofiber/jwt/v3 v3.3.10 h1:0bpWtFKaGepjwYTU4efHfy0o+matSqZwTxGMo5a+uuc=
|
||||
github.com/gofiber/jwt/v3 v3.3.10/go.mod h1:GJorFVaDyfMPSK9RB8RG4NQ3s1oXKTmYaoL/ny08O1A=
|
||||
github.com/gofiber/storage/redis/v3 v3.4.1 h1:feZc1xv1UuW+a1qnpISPaak7r/r0SkNVFHmg9R7PJ/c=
|
||||
github.com/gofiber/storage/redis/v3 v3.4.1/go.mod h1:rbycYIeewyFZ1uMf9I6t/C3RHZWIOmSRortjvyErhyA=
|
||||
github.com/gofiber/storage/testhelpers/redis v0.0.0-20250822074218-ba2347199921 h1:32Fh8t9QK2u2y8WnitCxIhf1AxKXBFFYk9tousVn/Fo=
|
||||
github.com/gofiber/storage/testhelpers/redis v0.0.0-20250822074218-ba2347199921/go.mod h1:PU9dj9E5K6+TLw7pF87y4yOf5HUH6S9uxTlhuRAVMEY=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-migrate/migrate/v4 v4.19.0 h1:RcjOnCGz3Or6HQYEJ/EEVLfWnmw9KnoigPSjzhCuaSE=
|
||||
github.com/golang-migrate/migrate/v4 v4.19.0/go.mod h1:9dyEcu+hO+G9hPSw8AIg50yg622pXJsoHItQnDGZkI0=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
|
||||
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU=
|
||||
github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
|
||||
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5LJHI=
|
||||
github.com/mdelapenya/tlscert v0.2.0/go.mod h1:O4njj3ELLnJjGdkN7M/vIVCpZ+Cf0L6muqOG4tLSl8o=
|
||||
github.com/minio/crc64nvme v1.0.2 h1:6uO1UxGAD+kwqWWp7mBFsi5gAse66C4NXO8cmcVculg=
|
||||
github.com/minio/crc64nvme v1.0.2/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.0.95 h1:ywOUPg+PebTMTzn9VDsoFJy32ZuARN9zhB+K3IYEvYU=
|
||||
github.com/minio/minio-go/v7 v7.0.95/go.mod h1:wOOX3uxS334vImCNRVyIDdXX9OsXDm89ToynKgqUKlo=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
|
||||
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
|
||||
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
||||
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
||||
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
|
||||
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
||||
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U=
|
||||
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
|
||||
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
|
||||
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
|
||||
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
|
||||
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
|
||||
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
||||
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/redis/go-redis/v9 v9.12.1 h1:k5iquqv27aBtnTm2tIkROUDp8JBXhXZIVu1InSgvovg=
|
||||
github.com/redis/go-redis/v9 v9.12.1/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94/go.mod h1:90zrgN3D/WJsDd1iXHT96alCoN2KJo6/4x1DZC3wZs8=
|
||||
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4=
|
||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
|
||||
github.com/shirou/gopsutil/v4 v4.25.5 h1:rtd9piuSMGeU8g1RMXjZs9y9luK5BwtnG7dZaQUJAsc=
|
||||
github.com/shirou/gopsutil/v4 v4.25.5/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 h1:+iNTcqQJy0OZ5jk6a5NLib47eqXK8uYcPX+O4+cBpEM=
|
||||
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
|
||||
github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ=
|
||||
github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
|
||||
github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
|
||||
github.com/testcontainers/testcontainers-go v0.38.0 h1:d7uEapLcv2P8AvH8ahLqDMMxda2W9gQN1nRbHS28HBw=
|
||||
github.com/testcontainers/testcontainers-go v0.38.0/go.mod h1:C52c9MoHpWO+C4aqmgSU+hxlR5jlEayWtgYrb8Pzz1w=
|
||||
github.com/testcontainers/testcontainers-go/modules/redis v0.38.0 h1:289pn0BFmGqDrd6BrImZAprFef9aaPZacx07YOQaPV4=
|
||||
github.com/testcontainers/testcontainers-go/modules/redis v0.38.0/go.mod h1:EcKPWRzOglnQfYe+ekA8RPEIWSNJTGwaC5oE5bQV+D0=
|
||||
github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw=
|
||||
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
|
||||
github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww=
|
||||
github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.34.0/go.mod h1:epZA5N+7pY6ZaEKRmstzOuYJx9HI8DI1oaCGZpdH4h0=
|
||||
github.com/valyala/fasthttp v1.47.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
|
||||
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
|
||||
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
|
||||
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
||||
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
189
handler/http/auth.go
Normal file
189
handler/http/auth.go
Normal file
@ -0,0 +1,189 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"kemendagri/sipd/services/sipd_auth/controller"
|
||||
"kemendagri/sipd/services/sipd_auth/handler/http/http_util"
|
||||
"kemendagri/sipd/services/sipd_auth/model/form"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type AuthHandler struct {
|
||||
Controller *controller.AuthController
|
||||
Validate *validator.Validate
|
||||
}
|
||||
|
||||
func NewAuthHandler(app *fiber.App, controller *controller.AuthController, vld *validator.Validate) {
|
||||
handler := &AuthHandler{
|
||||
Controller: controller,
|
||||
Validate: vld,
|
||||
}
|
||||
|
||||
// public route
|
||||
rPub := app.Group("/auth")
|
||||
rPub.Post("/pre-login", handler.PreLogin)
|
||||
rPub.Post("/login", handler.Login)
|
||||
rPub.Post("/register", handler.Register)
|
||||
rPub.Post("/token-refresh/:token", handler.TokenRefresh)
|
||||
rPub.Post("/amankan-kata-sandi", handler.AmankanKataSandi)
|
||||
|
||||
// strict route
|
||||
// rStrict := r.Group("auth")
|
||||
}
|
||||
|
||||
// PreLogin func for login.
|
||||
//
|
||||
// @Summary user login
|
||||
// @Description Login to get JWT token and refresh token.
|
||||
// @ID auth-pre-login
|
||||
// @Tags Auth
|
||||
// @Accept json
|
||||
// @Param payload body form.PreLoginForm true "Pre login payload"
|
||||
// @Produce json
|
||||
// @Success 200 {array} models.PreLoginModel "Success"
|
||||
// @Failure 400 {object} utils.RequestError "Bad request"
|
||||
// @Failure 403 {object} utils.LoginError "Login forbidden"
|
||||
// @Failure 404 {object} utils.RequestError "Data not found"
|
||||
// @Failure 422 {array} utils.RequestError "Data validation failed"
|
||||
// @Failure 500 {object} utils.RequestError "Server error"
|
||||
// @Router /auth/pre-login [post]
|
||||
func (ah *AuthHandler) PreLogin(c *fiber.Ctx) error {
|
||||
formModel := new(form.PreLoginForm)
|
||||
if err := c.BodyParser(formModel); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r, err := ah.Controller.PreLogin(*formModel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(r)
|
||||
}
|
||||
|
||||
// Login func for login.
|
||||
//
|
||||
// @Summary user login
|
||||
// @Description Login to get JWT token and refresh token.
|
||||
// @ID auth-login
|
||||
// @Tags Auth
|
||||
// @Accept json
|
||||
// @Param payload body form.LoginForm true "Login payload"
|
||||
// @Produce json
|
||||
// @Success 200 {object} http_util.JSONResultLogin "Login Success, jwt provided"
|
||||
// @Failure 400 {object} utils.RequestError "Bad request"
|
||||
// @Failure 403 {object} utils.LoginError "Login forbidden"
|
||||
// @Failure 404 {object} utils.RequestError "Data not found"
|
||||
// @Failure 422 {array} utils.RequestError "Data validation failed"
|
||||
// @Failure 500 {object} utils.RequestError "Server error"
|
||||
// @Router /auth/login [post]
|
||||
func (ah *AuthHandler) Login(c *fiber.Ctx) error {
|
||||
formModel := new(form.LoginForm)
|
||||
if err := c.BodyParser(formModel); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
token, refreshToken, isDefaultPassword, err := ah.Controller.Login(*formModel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(
|
||||
http_util.JSONResultLogin{Token: token, RefreshToken: refreshToken, IsDefaultPassword: isDefaultPassword},
|
||||
)
|
||||
}
|
||||
|
||||
// Register func for register.
|
||||
//
|
||||
// @Summary user register
|
||||
// @Description Register user.
|
||||
// @ID auth-register
|
||||
// @Tags Auth
|
||||
// @Accept json
|
||||
// @Param payload body form.SignupForm true "Register payload"
|
||||
// @Produce json
|
||||
// @Success 200 {object} bool "Register Success"
|
||||
// @Failure 400 {object} utils.RequestError "Bad request"
|
||||
// @Failure 404 {object} utils.RequestError "Data not found"
|
||||
// @Failure 422 {array} utils.RequestError "Data validation failed"
|
||||
// @Failure 500 {object} utils.RequestError "Server error"
|
||||
// @Router /auth/register [post]
|
||||
func (ah *AuthHandler) Register(c *fiber.Ctx) error {
|
||||
formModel := new(form.SignupForm)
|
||||
if err := c.BodyParser(formModel); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := ah.Controller.Register(formModel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(resp)
|
||||
}
|
||||
|
||||
// TokenRefresh godoc
|
||||
//
|
||||
// @Summary Refresh Token
|
||||
// @Description Refresh token to get new valid JWT token and refresh token
|
||||
// @ID auth-refresh-token
|
||||
// @Tags Auth
|
||||
// @Produce json
|
||||
// @Param payload body form.RefreshTokenForm true "Payload"
|
||||
// @Success 200 {object} models.ResponseLogin "Refresh Token Success, new JWT token provided"
|
||||
// @Failure 400 {object} utils.RequestError "Bad request"
|
||||
// @Failure 404 {object} utils.RequestError "Data not found"
|
||||
// @Failure 422 {array} utils.RequestError "Data validation failed"
|
||||
// @Failure 500 {object} utils.RequestError "Server error"
|
||||
// @Router /auth/token-refresh/{token} [post]
|
||||
func (ah *AuthHandler) TokenRefresh(c *fiber.Ctx) error {
|
||||
pl := form.RefreshTokenForm{}
|
||||
if err := c.BodyParser(&pl); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r, err := ah.Controller.RefreshToken(pl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(r)
|
||||
}
|
||||
|
||||
// AmankanKataSandi User func for change password.
|
||||
//
|
||||
// @Summary Amankan Kata Sandi
|
||||
// @Description User melakukan Amankan Kata Sandi.
|
||||
// @ID auth-amankan-kata-sandi
|
||||
// @Tags Auth
|
||||
// @Param payload body form.ChangePasswordFormPublik true "Payload"
|
||||
// @Produce json
|
||||
// @success 200 {object} bool "Success"
|
||||
// @Failure 400 {object} utils.RequestError "Bad request"
|
||||
// @Failure 403 {object} utils.RequestError "Forbidden"
|
||||
// @Failure 404 {object} utils.RequestError "Data not found"
|
||||
// @Failure 422 {array} utils.RequestError "Data validation failed"
|
||||
// @Failure 500 {object} utils.RequestError "Server error"
|
||||
// @Router /auth/amankan-kata-sandi [post]
|
||||
func (ah *AuthHandler) AmankanKataSandi(c *fiber.Ctx) error {
|
||||
formModel := new(form.ChangePasswordFormPublik)
|
||||
if err := c.BodyParser(formModel); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate form input
|
||||
err := ah.Validate.Struct(formModel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ah.Controller.AmankanKataSandi(*formModel)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(true)
|
||||
}
|
||||
100
handler/http/captcha.go
Normal file
100
handler/http/captcha.go
Normal file
@ -0,0 +1,100 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"kemendagri/sipd/services/sipd_auth/controller"
|
||||
models "kemendagri/sipd/services/sipd_auth/model"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type CaptchaHandler struct {
|
||||
Controller *controller.CaptchaController
|
||||
}
|
||||
|
||||
func NewCaptchaHandler(app *fiber.App, controller *controller.CaptchaController) {
|
||||
handler := &CaptchaHandler{
|
||||
Controller: controller,
|
||||
}
|
||||
|
||||
// public route
|
||||
rPub := app.Group("/captcha")
|
||||
rPub.Get("/new", handler.New)
|
||||
rPub.Get("/reload/:id", handler.Reload)
|
||||
rPub.Post("/validate", handler.Validate)
|
||||
}
|
||||
|
||||
// New func for generate new captcha.
|
||||
//
|
||||
// @Summary generate new captcha
|
||||
// @Description generate new captcha.
|
||||
// @Tags Captcha
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} interface{} "Base64 image string"
|
||||
// @Failure 400 {object} utils.RequestError "Bad request"
|
||||
// @Failure 403 {object} utils.LoginError "Login forbidden"
|
||||
// @Failure 404 {object} utils.RequestError "Data not found"
|
||||
// @Failure 422 {array} utils.RequestError "Data validation failed"
|
||||
// @Failure 500 {object} utils.RequestError "Server error"
|
||||
// @Router /captcha/new [get]
|
||||
func (h *CaptchaHandler) New(c *fiber.Ctx) error {
|
||||
id, base64, audio, err := h.Controller.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"id": id, "base64": base64, "audio": audio})
|
||||
}
|
||||
|
||||
// Reload func for reload captcha.
|
||||
//
|
||||
// @Summary reload captcha
|
||||
// @Description reload captcha.
|
||||
// @Tags Captcha
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "captcha ID"
|
||||
// @Success 200 {object} interface{} "Base64 image string"
|
||||
// @Failure 400 {object} utils.RequestError "Bad request"
|
||||
// @Failure 403 {object} utils.LoginError "Login forbidden"
|
||||
// @Failure 404 {object} utils.RequestError "Data not found"
|
||||
// @Failure 422 {array} utils.RequestError "Data validation failed"
|
||||
// @Failure 500 {object} utils.RequestError "Server error"
|
||||
// @Router /captcha/reload/{id} [get]
|
||||
func (h *CaptchaHandler) Reload(c *fiber.Ctx) error {
|
||||
base64, audio, err := h.Controller.Reload(c.Params("id"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"base64": base64, "audio": audio})
|
||||
}
|
||||
|
||||
// Validate func for validate captcha.
|
||||
//
|
||||
// @Summary validate captcha
|
||||
// @Description validate captcha.
|
||||
// @Tags Captcha
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param payload body models.ValidateCaptcha true "payload"
|
||||
// @Success 200 {object} boolean "validate success"
|
||||
// @Failure 400 {object} utils.RequestError "Bad request"
|
||||
// @Failure 403 {object} utils.LoginError "Login forbidden"
|
||||
// @Failure 404 {object} utils.RequestError "Data not found"
|
||||
// @Failure 422 {array} utils.RequestError "Data validation failed"
|
||||
// @Failure 500 {object} utils.RequestError "Server error"
|
||||
// @Router /captcha/validate [post]
|
||||
func (h *CaptchaHandler) Validate(c *fiber.Ctx) error {
|
||||
formModel := new(models.ValidateCaptcha)
|
||||
if err := c.BodyParser(formModel); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err := h.Controller.Validate(*formModel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(true)
|
||||
}
|
||||
77
handler/http/configs/fiber_config.go
Normal file
77
handler/http/configs/fiber_config.go
Normal file
@ -0,0 +1,77 @@
|
||||
package configs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"kemendagri/sipd/services/sipd_auth/utils"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// FiberConfig func for configuration Fiber app.
|
||||
// See: https://docs.gofiber.io/api/fiber#config
|
||||
func FiberConfig() fiber.Config {
|
||||
// Define server settings.
|
||||
readTimeoutSecondsCount, _ := strconv.Atoi(os.Getenv("SERVER_READ_TIMEOUT"))
|
||||
|
||||
// Return Fiber configuration.
|
||||
return fiber.Config{
|
||||
AppName: os.Getenv("APP_NAME"),
|
||||
ReadTimeout: time.Second * time.Duration(readTimeoutSecondsCount),
|
||||
//Prefork: true,
|
||||
//CaseSensitive: true,
|
||||
//StrictRouting: true,
|
||||
ServerHeader: os.Getenv("SERVER_NAME"),
|
||||
ErrorHandler: fiberErrorHandler,
|
||||
}
|
||||
}
|
||||
|
||||
func fiberErrorHandler(ctx *fiber.Ctx, err error) error {
|
||||
var re utils.RequestError
|
||||
var vlde validator.ValidationErrors
|
||||
|
||||
switch {
|
||||
case errors.As(err, &re):
|
||||
_ = ctx.Status(re.Code).JSON(re)
|
||||
case errors.As(err, &vlde):
|
||||
var eReq = new(utils.RequestError)
|
||||
eReq.Code = fiber.StatusUnprocessableEntity
|
||||
|
||||
for _, err := range vlde {
|
||||
errObj := utils.DataValidationError{Field: err.Field()}
|
||||
eReq.Message = "Salah satu field tidak valid"
|
||||
|
||||
// penyesuaian pesan error berdasarkan jenis validasinya
|
||||
switch err.Tag() {
|
||||
case "required":
|
||||
errObj.Message = fmt.Sprintf("%s harus diisi.", err.Field())
|
||||
case "len":
|
||||
errObj.Message = fmt.Sprintf("panjang %s harus sama dengan %s", err.Field(), err.Param())
|
||||
case "gte":
|
||||
errObj.Message = fmt.Sprintf("%s harus lebih besar atau sama dengan %s", err.Field(), err.Param())
|
||||
case "gt":
|
||||
errObj.Message = fmt.Sprintf("%s harus lebih besar dari %s", err.Field(), err.Param())
|
||||
case "e164":
|
||||
errObj.Message = "Invalid phone number format (E.164)"
|
||||
case "alphanumspace":
|
||||
errObj.Message = "Only alphanumeric and space allowed"
|
||||
case "alphanumslashasterisk":
|
||||
errObj.Message = "Only alphanumeric, slash and asterisk allowed"
|
||||
default:
|
||||
errObj.Message = err.Tag()
|
||||
}
|
||||
|
||||
eReq.Fields = append(eReq.Fields, errObj)
|
||||
}
|
||||
_ = ctx.Status(fiber.StatusUnprocessableEntity).JSON(eReq)
|
||||
default:
|
||||
_ = ctx.Status(fiber.StatusInternalServerError).JSON(utils.GlobalError{Message: err.Error()})
|
||||
}
|
||||
|
||||
// Return from handler
|
||||
return nil
|
||||
}
|
||||
21
handler/http/http_util/json_result.go
Normal file
21
handler/http/http_util/json_result.go
Normal file
@ -0,0 +1,21 @@
|
||||
package http_util
|
||||
|
||||
type JSONResult struct {
|
||||
Code int `json:"code" example:"404"`
|
||||
Message string `json:"message" example:"Not Found"`
|
||||
Data interface{} `json:"data"`
|
||||
Meta interface{} `json:"meta"`
|
||||
}
|
||||
|
||||
type JSONResultMeta struct {
|
||||
TotalCount int `json:"total_count"`
|
||||
PageCount int `json:"page_count"`
|
||||
CurrentPage int `json:"current_page"`
|
||||
PerPage int `json:"per_page"`
|
||||
}
|
||||
|
||||
type JSONResultLogin struct {
|
||||
IsDefaultPassword bool `json:"is_default_password" example:"false"`
|
||||
Token string `json:"token" example:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MzYwODU0MjksImlkIjoyLCJwaG9uZSI6Iis2MjgxMjM0NTYyIiwidXNlcm5hbWUiOi.dl_ojy9ojLnWqpW589YltLPV61TCsON-3yQ2"`
|
||||
RefreshToken string `json:"refresh_token" example:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MzYxNjgyMjksImlk._aYI7pV2c9SU9VOp3RY_mxtFenYFQuKPJtVfk"`
|
||||
}
|
||||
44
handler/http/http_util/server.go
Normal file
44
handler/http/http_util/server.go
Normal file
@ -0,0 +1,44 @@
|
||||
package http_util
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// StartServerWithGracefulShutdown function for starting server with a graceful shutdown.
|
||||
func StartServerWithGracefulShutdown(a *fiber.App) {
|
||||
// Create channel for idle connections.
|
||||
idleConnsClosed := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
sigint := make(chan os.Signal, 1)
|
||||
signal.Notify(sigint, os.Interrupt) // Catch OS signals.
|
||||
<-sigint
|
||||
|
||||
// Received an interrupt signal, shutdown.
|
||||
if err := a.Shutdown(); err != nil {
|
||||
// Error from closing listeners, or context timeout:
|
||||
log.Printf("Oops... Server is not shutting down! Reason: %v", err)
|
||||
}
|
||||
|
||||
close(idleConnsClosed)
|
||||
}()
|
||||
|
||||
// Run server.
|
||||
if err := a.Listen(os.Getenv("SERVER_URL")); err != nil {
|
||||
log.Printf("Oops... Server is not running! Reason: %v", err)
|
||||
}
|
||||
|
||||
<-idleConnsClosed
|
||||
}
|
||||
|
||||
// StartServer func for starting a simple server.
|
||||
func StartServer(a *fiber.App) {
|
||||
// Run server.
|
||||
if err := a.Listen(os.Getenv("SERVER_URL")); err != nil {
|
||||
log.Printf("Oops... Server is not running! Reason: %v", err)
|
||||
}
|
||||
}
|
||||
50
handler/http/http_util/token_generator.go
Normal file
50
handler/http/http_util/token_generator.go
Normal file
@ -0,0 +1,50 @@
|
||||
package http_util
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// GenerateRandomBytes returns securely generated random bytes.
|
||||
// It will return an error if the system's secure random
|
||||
// number generator fails to function correctly, in which
|
||||
// case the caller should not continue.
|
||||
func GenerateRandomBytes(n int) ([]byte, error) {
|
||||
b := make([]byte, n)
|
||||
_, err := rand.Read(b)
|
||||
// Note that err == nil only if we read len(b) bytes.
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// GenerateRandomString returns a securely generated random string.
|
||||
// It will return an error if the system's secure random
|
||||
// number generator fails to function correctly, in which
|
||||
// case the caller should not continue.
|
||||
func GenerateRandomString(n int) (string, error) {
|
||||
const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-"
|
||||
ret := make([]byte, n)
|
||||
for i := 0; i < n; i++ {
|
||||
num, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ret[i] = letters[num.Int64()]
|
||||
}
|
||||
|
||||
return string(ret), nil
|
||||
}
|
||||
|
||||
// GenerateRandomStringURLSafe returns a URL-safe, base64 encoded
|
||||
// securely generated random string.
|
||||
// It will return an error if the system's secure random
|
||||
// number generator fails to function correctly, in which
|
||||
// case the caller should not continue.
|
||||
func GenerateRandomStringURLSafe(n int) (string, error) {
|
||||
b, err := GenerateRandomBytes(n)
|
||||
return base64.URLEncoding.EncodeToString(b), err
|
||||
}
|
||||
207
handler/http/middleware/middleware.go
Normal file
207
handler/http/middleware/middleware.go
Normal file
@ -0,0 +1,207 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2/middleware/logger"
|
||||
|
||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||
"github.com/gofiber/fiber/v2/middleware/limiter"
|
||||
"github.com/gofiber/storage/redis/v3"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
jwtware "github.com/gofiber/jwt/v3"
|
||||
)
|
||||
|
||||
// GoMiddleware represent the data-struct for middleware
|
||||
type GoMiddleware struct {
|
||||
appCtx *fiber.App
|
||||
redisCl *redis.Storage
|
||||
appLog *logrus.Logger
|
||||
// another stuff , may be needed by middleware
|
||||
}
|
||||
|
||||
func (m *GoMiddleware) RateLimiter() fiber.Handler {
|
||||
limiterCfg := limiter.Config{
|
||||
/*Next: func(c *fiber.Ctx) bool {
|
||||
return c.IP() == "127.0.0.1"
|
||||
},*/
|
||||
Storage: m.redisCl,
|
||||
Max: 10,
|
||||
Expiration: 30 * time.Second,
|
||||
LimitReached: func(c *fiber.Ctx) error {
|
||||
return c.SendStatus(fiber.StatusTooManyRequests)
|
||||
},
|
||||
KeyGenerator: func(c *fiber.Ctx) string {
|
||||
authKey := c.Get("Authorization")
|
||||
if authKey == "" {
|
||||
return c.IP() + c.Get("User-Agent")
|
||||
} else {
|
||||
if len(authKey) > 7 && strings.HasPrefix(authKey, "Bearer ") {
|
||||
authKey = authKey[7:]
|
||||
}
|
||||
return authKey
|
||||
}
|
||||
},
|
||||
/*KeyGenerator: func(c *fiber.Ctx) string {
|
||||
return c.IP() + c.Get("User-Agent")
|
||||
},*/
|
||||
}
|
||||
|
||||
return limiter.New(limiterCfg)
|
||||
}
|
||||
|
||||
func (m *GoMiddleware) RateLimiterFull() fiber.Handler {
|
||||
limiterCfg := limiter.Config{
|
||||
Max: 10,
|
||||
Expiration: 30 * time.Second,
|
||||
KeyGenerator: func(c *fiber.Ctx) string {
|
||||
authKey := c.Get("Authorization")
|
||||
if authKey == "" {
|
||||
uniqueID := c.Cookies("sipd_penatausahaan_uk_")
|
||||
if uniqueID == "" {
|
||||
uniqueID = fmt.Sprintf("%d", time.Now().UnixNano())
|
||||
c.Cookie(&fiber.Cookie{
|
||||
Name: "sipd_penatausahaan_uk_",
|
||||
Value: uniqueID,
|
||||
Expires: time.Now().Add(24 * time.Hour),
|
||||
HTTPOnly: true,
|
||||
Secure: true,
|
||||
})
|
||||
}
|
||||
|
||||
return c.IP() + c.Get("User-Agent") + uniqueID
|
||||
} else {
|
||||
return authKey
|
||||
}
|
||||
},
|
||||
LimitReached: func(c *fiber.Ctx) error {
|
||||
return c.SendStatus(fiber.StatusTooManyRequests)
|
||||
},
|
||||
}
|
||||
|
||||
return limiter.New(limiterCfg)
|
||||
}
|
||||
|
||||
// CORS will handle the CORS middleware
|
||||
func (m *GoMiddleware) CORS() fiber.Handler {
|
||||
crs := os.Getenv("SIPD_CORS_WHITELISTS")
|
||||
|
||||
if crs == "*" {
|
||||
return cors.New(cors.Config{
|
||||
AllowOrigins: "*",
|
||||
AllowHeaders: "Content-Type, Accept, Authorization",
|
||||
AllowMethods: "GET, HEAD, PUT, PATCH, POST, DELETE",
|
||||
ExposeHeaders: "*", //"X-Pagination-Current-Page,X-Pagination-Next-Page,X-Pagination-Page-Count,X-Pagination-Page-Size,X-Pagination-Total-Count"
|
||||
})
|
||||
}
|
||||
|
||||
return cors.New(cors.Config{
|
||||
AllowOrigins: crs,
|
||||
AllowCredentials: true,
|
||||
AllowHeaders: "Content-Type, Accept, Authorization",
|
||||
AllowMethods: "GET, HEAD, PUT, PATCH, POST, DELETE",
|
||||
ExposeHeaders: "*", //"X-Pagination-Current-Page,X-Pagination-Next-Page,X-Pagination-Page-Count,X-Pagination-Page-Size,X-Pagination-Total-Count"
|
||||
})
|
||||
}
|
||||
|
||||
// LOGGER simple logger.
|
||||
func (m *GoMiddleware) LOGGER() fiber.Handler {
|
||||
return logger.New()
|
||||
|
||||
/*return func(c *fiber.Ctx) error {
|
||||
err := c.Next()
|
||||
if err != nil {
|
||||
log.Print("sdfsdsfdsddf")
|
||||
|
||||
// Log incoming requests
|
||||
m.appLog.WithFields(logrus.Fields{
|
||||
"method": c.Method(),
|
||||
"path": c.Path(),
|
||||
"ip": c.IP(),
|
||||
"message": err.Error(),
|
||||
}).Error("Error occurred")
|
||||
return err
|
||||
}
|
||||
|
||||
// Proceed with the request
|
||||
return nil
|
||||
}*/
|
||||
}
|
||||
|
||||
// JWT jwt.
|
||||
func (m *GoMiddleware) JWT() fiber.Handler {
|
||||
// Create config for JWT authentication middleware.
|
||||
config := jwtware.Config{
|
||||
SigningKey: []byte(os.Getenv("JWT_SECRET_KEY")),
|
||||
ContextKey: "jwt", // used in private routes
|
||||
// SuccessHandler: m.jwtSuccess,
|
||||
ErrorHandler: jwtError,
|
||||
}
|
||||
|
||||
return jwtware.New(config)
|
||||
}
|
||||
|
||||
func jwtError(c *fiber.Ctx, err error) error {
|
||||
// Return status 400 and failed authentication error.
|
||||
if err.Error() == "Missing or malformed JWT" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"error": true,
|
||||
"msg": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
// Return status 401 and failed authentication error.
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||
"error": true,
|
||||
"msg": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
func (m *GoMiddleware) jwtSuccess(c *fiber.Ctx) error {
|
||||
var err error
|
||||
|
||||
// log.Println("jwt success")
|
||||
|
||||
user := c.Locals("jwt").(*jwt.Token)
|
||||
claims := user.Claims.(jwt.MapClaims)
|
||||
tokenString := user.Raw
|
||||
pegId := int64(claims["id_pegawai"].(float64))
|
||||
redisKey := fmt.Sprintf("peg:%d", pegId)
|
||||
|
||||
existingToken, err := m.redisCl.Get(redisKey)
|
||||
if err != nil {
|
||||
err = c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||
"error": true,
|
||||
"msg": "Failed to check token data. - " + err.Error(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
if len(existingToken) == 0 {
|
||||
err = c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||
"error": true,
|
||||
"msg": "not authorized",
|
||||
})
|
||||
return err
|
||||
} else {
|
||||
if fmt.Sprintf("%s", existingToken) != tokenString {
|
||||
err = c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||
"error": true,
|
||||
"msg": "forbidden",
|
||||
})
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
// InitMiddleware initialize the middleware
|
||||
func InitMiddleware(ctx *fiber.App, store *redis.Storage, appLog *logrus.Logger) *GoMiddleware {
|
||||
return &GoMiddleware{appCtx: ctx, redisCl: store, appLog: appLog}
|
||||
}
|
||||
48
handler/http/site.go
Normal file
48
handler/http/site.go
Normal file
@ -0,0 +1,48 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"kemendagri/sipd/services/sipd_auth/controller"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type SiteHandler struct {
|
||||
Controller *controller.SiteController
|
||||
Validate *validator.Validate
|
||||
}
|
||||
|
||||
func NewSiteHandler(app *fiber.App, controller *controller.SiteController, vld *validator.Validate) {
|
||||
handler := &SiteHandler{
|
||||
Controller: controller,
|
||||
Validate: vld,
|
||||
}
|
||||
|
||||
// public route
|
||||
rPub := app.Group("/site")
|
||||
rPub.Get("/index", handler.Index)
|
||||
}
|
||||
|
||||
// Index func for index.
|
||||
//
|
||||
// @Summary index
|
||||
// @Description index page.
|
||||
// @ID index
|
||||
// @Tags Site
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} interface{} "Success"
|
||||
// @Failure 400 {object} utils.RequestError "Bad request"
|
||||
// @Failure 403 {object} utils.LoginError "Login forbidden"
|
||||
// @Failure 404 {object} utils.RequestError "Data not found"
|
||||
// @Failure 422 {array} utils.RequestError "Data validation failed"
|
||||
// @Failure 500 {object} utils.RequestError "Server error"
|
||||
// @Router /site/index [get]
|
||||
func (h *SiteHandler) Index(c *fiber.Ctx) error {
|
||||
r, err := h.Controller.Index()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(r)
|
||||
}
|
||||
249
handler/http/user.go
Normal file
249
handler/http/user.go
Normal file
@ -0,0 +1,249 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"kemendagri/sipd/services/sipd_auth/controller"
|
||||
"kemendagri/sipd/services/sipd_auth/model/form"
|
||||
"log"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
)
|
||||
|
||||
type UserHandler struct {
|
||||
Controller *controller.UserController
|
||||
Validate *validator.Validate
|
||||
}
|
||||
|
||||
func NewUserHandler(
|
||||
r fiber.Router,
|
||||
validator *validator.Validate,
|
||||
controller *controller.UserController,
|
||||
) {
|
||||
handler := &UserHandler{
|
||||
Controller: controller,
|
||||
Validate: validator,
|
||||
}
|
||||
|
||||
// strict route
|
||||
rStrict := r.Group("user")
|
||||
rStrict.Get("/logout", handler.Logout)
|
||||
rStrict.Post("/generate-password-hash", handler.GeneratePasswordHash)
|
||||
rStrict.Post("/change-password", handler.ChangePassword)
|
||||
rStrict.Get("/profile", handler.Profile)
|
||||
rStrict.Put("/update-profile", handler.UpdateProfile)
|
||||
rStrict.Post("/upload-avatar", handler.UploadAvatar)
|
||||
}
|
||||
|
||||
// Logout User func for logout.
|
||||
//
|
||||
// @Summary logout
|
||||
// @Description user logout.
|
||||
// @ID user-logout
|
||||
// @Tags User
|
||||
// @Produce json
|
||||
// @success 200 {object} bool "Success"
|
||||
// @Failure 400 {object} utils.RequestError "Bad request"
|
||||
// @Failure 403 {object} utils.RequestError "Forbidden"
|
||||
// @Failure 404 {object} utils.RequestError "Data not found"
|
||||
// @Failure 422 {array} utils.RequestError "Data validation failed"
|
||||
// @Failure 500 {object} utils.RequestError "Server error"
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /strict/user/logout [get]
|
||||
func (h *UserHandler) Logout(c *fiber.Ctx) error {
|
||||
log.Println("xxxxx")
|
||||
|
||||
user := c.Locals("jwt").(*jwt.Token)
|
||||
claims := user.Claims.(jwt.MapClaims)
|
||||
|
||||
err := h.Controller.Logout(
|
||||
fmt.Sprintf("%v", claims["id_pegawai"]),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Println("sddsdsfs")
|
||||
|
||||
return c.JSON(true)
|
||||
}
|
||||
|
||||
// GeneratePasswordHash User func for generate password hash.
|
||||
//
|
||||
// @Summary generate password hash
|
||||
// @Description generate password hash.
|
||||
// @ID user-generate-password-hash
|
||||
// @Tags User
|
||||
// @Param payload body form.GenerateHashForm true "Payload"
|
||||
// @Produce json
|
||||
// @success 200 {object} bool "Success"
|
||||
// @Failure 400 {object} utils.RequestError "Bad request"
|
||||
// @Failure 403 {object} utils.RequestError "Forbidden"
|
||||
// @Failure 404 {object} utils.RequestError "Data not found"
|
||||
// @Failure 422 {array} utils.RequestError "Data validation failed"
|
||||
// @Failure 500 {object} utils.RequestError "Server error"
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /strict/user/generate-password-hash [post]
|
||||
func (h *UserHandler) GeneratePasswordHash(c *fiber.Ctx) error {
|
||||
formModel := new(form.GenerateHashForm)
|
||||
if err := c.BodyParser(formModel); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate form input
|
||||
err := h.Validate.Struct(formModel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user := c.Locals("jwt").(*jwt.Token)
|
||||
claims := user.Claims.(jwt.MapClaims)
|
||||
|
||||
err = h.Controller.GeneratePasswordHash(
|
||||
int64(claims["id_user"].(float64)),
|
||||
int64(claims["id_daerah"].(float64)),
|
||||
formModel.Password,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(true)
|
||||
}
|
||||
|
||||
// ChangePassword User func for change password.
|
||||
//
|
||||
// @Summary change password
|
||||
// @Description change password.
|
||||
// @ID user-change-password
|
||||
// @Tags User
|
||||
// @Param payload body form.ChangePasswordForm true "Payload"
|
||||
// @Produce json
|
||||
// @success 200 {object} bool "Success"
|
||||
// @Failure 400 {object} utils.RequestError "Bad request"
|
||||
// @Failure 403 {object} utils.RequestError "Forbidden"
|
||||
// @Failure 404 {object} utils.RequestError "Data not found"
|
||||
// @Failure 422 {array} utils.RequestError "Data validation failed"
|
||||
// @Failure 500 {object} utils.RequestError "Server error"
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /strict/user/change-password [post]
|
||||
func (h *UserHandler) ChangePassword(c *fiber.Ctx) error {
|
||||
formModel := new(form.ChangePasswordForm)
|
||||
if err := c.BodyParser(formModel); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate form input
|
||||
err := h.Validate.Struct(formModel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user := c.Locals("jwt").(*jwt.Token)
|
||||
claims := user.Claims.(jwt.MapClaims)
|
||||
|
||||
err = h.Controller.ChangePassword(
|
||||
int64(claims["id_user"].(float64)),
|
||||
int64(claims["id_daerah"].(float64)),
|
||||
*formModel,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(true)
|
||||
}
|
||||
|
||||
// Profile func for get profile info.
|
||||
//
|
||||
// @Summary user get profile info
|
||||
// @Description get profile info.
|
||||
// @ID user-profile
|
||||
// @Tags User
|
||||
// @Produce json
|
||||
// @success 200 {object} models.UserDetail "Success"
|
||||
// @Failure 400 {object} utils.RequestError "Bad request"
|
||||
// @Failure 404 {object} utils.RequestError "Data not found"
|
||||
// @Failure 422 {array} utils.RequestError "Data validation failed"
|
||||
// @Failure 500 {object} utils.RequestError "Server error"
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /strict/user/profile [get]
|
||||
func (h *UserHandler) Profile(c *fiber.Ctx) error {
|
||||
|
||||
userModel, err := h.Controller.Profile(c.Locals("jwt").(*jwt.Token))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(userModel)
|
||||
}
|
||||
|
||||
// UpdateProfile func for update profile.
|
||||
//
|
||||
// @Summary update profile
|
||||
// @Description update profile.
|
||||
// @Tags User
|
||||
// @Param payload body form.UpdateUserProfileForm true "Payload"
|
||||
// @Produce json
|
||||
// @success 200 {object} bool "Success"
|
||||
// @Failure 400 {object} utils.RequestError "Bad request"
|
||||
// @Failure 404 {object} utils.RequestError "Data not found"
|
||||
// @Failure 422 {array} utils.RequestError "Data validation failed"
|
||||
// @Failure 500 {object} utils.RequestError "Server error"
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /strict/user/update-profile [put]
|
||||
func (h *UserHandler) UpdateProfile(c *fiber.Ctx) error {
|
||||
payload := new(form.UpdateUserProfileForm)
|
||||
if err := c.BodyParser(payload); err != nil {
|
||||
return err
|
||||
}
|
||||
//log.Println(payload)
|
||||
|
||||
// Validate form input
|
||||
err := h.Validate.Struct(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = h.Controller.UpdateProfile(c.Locals("jwt").(*jwt.Token), *payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(true)
|
||||
}
|
||||
|
||||
// UploadAvatar User func for upload avatar.
|
||||
//
|
||||
// @Summary upload avatar
|
||||
// @Description upload avatar
|
||||
// @ID user-upload avatar
|
||||
// @Tags User
|
||||
// @Accept x-www-form-urlencoded
|
||||
// @Produce json
|
||||
// @Param file formData file true "Image avatar"
|
||||
// @success 200 {object} bool "Success"
|
||||
// @Failure 400 {object} utils.RequestError "Bad request"
|
||||
// @Failure 403 {object} utils.RequestError "Forbidden"
|
||||
// @Failure 404 {object} utils.RequestError "Data not found"
|
||||
// @Failure 422 {array} utils.RequestError "Data validation failed"
|
||||
// @Failure 500 {object} utils.RequestError "Server error"
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /strict/user/upload-avatar [post]
|
||||
func (h *UserHandler) UploadAvatar(c *fiber.Ctx) error {
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = h.Controller.UploadAvatar(c.Locals("jwt").(*jwt.Token), file)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(true)
|
||||
}
|
||||
359
main.go
Normal file
359
main.go
Normal file
@ -0,0 +1,359 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"kemendagri/sipd/services/sipd_auth/controller"
|
||||
"kemendagri/sipd/services/sipd_auth/handler/http"
|
||||
"kemendagri/sipd/services/sipd_auth/handler/http/configs"
|
||||
"kemendagri/sipd/services/sipd_auth/handler/http/http_util"
|
||||
_deliveryMiddleware "kemendagri/sipd/services/sipd_auth/handler/http/middleware"
|
||||
db2 "kemendagri/sipd/services/sipd_auth/model/db"
|
||||
"kemendagri/sipd/services/sipd_auth/utils"
|
||||
"kemendagri/sipd/services/sipd_auth/utils/captcha_store"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
swagger "github.com/arsmn/fiber-swagger/v2"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/storage/redis/v3"
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
"github.com/golang-migrate/migrate/v4/database"
|
||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
swagDoc "kemendagri/sipd/services/sipd_auth/docs" // load API Docs files (Swagger)
|
||||
)
|
||||
|
||||
var serverName,
|
||||
serverUrl,
|
||||
serverReadTimeout,
|
||||
dbServerUrl,
|
||||
dbServerUrlPegawai,
|
||||
dbServerUrlMstData,
|
||||
dbServerUrlTransaksi,
|
||||
jwtSecertKey,
|
||||
jwtExpiredMinutes,
|
||||
refreshTokenExpiredHour,
|
||||
alwOrg,
|
||||
redisHost,
|
||||
redisUsername,
|
||||
redisPassword,
|
||||
urlScheme,
|
||||
baseUrl,
|
||||
basePath,
|
||||
avYear string
|
||||
var usedProvArr, avYearArr []string
|
||||
|
||||
var redisPort int
|
||||
|
||||
var db *sql.DB
|
||||
var pgxConn, pgxConnPegawai, pgxConnMstData *pgxpool.Pool
|
||||
var dbConnMapsAnggaran map[string]*pgxpool.Pool
|
||||
var driver database.Driver
|
||||
var migration *migrate.Migrate
|
||||
var jwtMgr *utils.JWTManager
|
||||
var vld *validator.Validate
|
||||
var minioClient *minio.Client
|
||||
var redisCl *redis.Storage
|
||||
var logger *logrus.Logger
|
||||
var err error
|
||||
|
||||
func init() {
|
||||
// Server Env
|
||||
serverName = os.Getenv("SERVER_NAME")
|
||||
if serverName == "" {
|
||||
exitf("SERVER_NAME env is required")
|
||||
}
|
||||
serverUrl = os.Getenv("SERVER_URL")
|
||||
if serverUrl == "" {
|
||||
exitf("SERVER_URL env is required")
|
||||
}
|
||||
serverReadTimeout = os.Getenv("SERVER_READ_TIMEOUT")
|
||||
if serverReadTimeout == "" {
|
||||
exitf("SERVER_READ_TIMEOUT env is required")
|
||||
}
|
||||
|
||||
// JWT Env
|
||||
jwtSecertKey = os.Getenv("JWT_SECRET_KEY")
|
||||
if jwtSecertKey == "" {
|
||||
exitf("JWT_SECRET_KEY env is required")
|
||||
}
|
||||
jwtExpiredMinutes = os.Getenv("JWT_EXPIRED_MINUTES")
|
||||
if jwtExpiredMinutes == "" {
|
||||
exitf("JWT_EXPIRED_MINUTES env is required")
|
||||
}
|
||||
refreshTokenExpiredHour = os.Getenv("REFRESH_TOKEN_EXPIRED_HOUR")
|
||||
if refreshTokenExpiredHour == "" {
|
||||
exitf("REFRESH_TOKEN_EXPIRED_HOUR env is required")
|
||||
}
|
||||
|
||||
// CORS
|
||||
alwOrg = os.Getenv("SIPD_CORS_WHITELISTS")
|
||||
if alwOrg == "" {
|
||||
exitf("SIPD_CORS_WHITELISTS config is required")
|
||||
}
|
||||
|
||||
urlScheme = os.Getenv("URL_SCHEME")
|
||||
if urlScheme == "" {
|
||||
exitf("URL_SCHEME config is required")
|
||||
}
|
||||
baseUrl = os.Getenv("BASE_URL")
|
||||
if baseUrl == "" {
|
||||
exitf("BASE_URL config is required")
|
||||
}
|
||||
/*basePath = os.Getenv("BASE_PATH")
|
||||
if basePath == "" {
|
||||
exitf("BASE_PATH config is required")
|
||||
}*/
|
||||
|
||||
avYear = os.Getenv("AVAILABLE_YEAR")
|
||||
if avYear == "" {
|
||||
exitf("AVAILABLE_YEAR config is required")
|
||||
}
|
||||
avYearArr = strings.Split(avYear, ",")
|
||||
|
||||
// Databse Env
|
||||
dbServerUrl = os.Getenv("DB_SIPD_AUTH")
|
||||
if dbServerUrl == "" {
|
||||
exitf("DB_SIPD_AUTH config is required")
|
||||
}
|
||||
dbServerUrlPegawai = os.Getenv("DB_SIPD_PEGAWAI")
|
||||
if dbServerUrlPegawai == "" {
|
||||
exitf("DB_SIPD_PEGAWAI config is required")
|
||||
}
|
||||
dbServerUrlMstData = os.Getenv("DB_SIPD_MST_DATA")
|
||||
if dbServerUrlMstData == "" {
|
||||
exitf("DB_SIPD_MST_DATA config is required")
|
||||
}
|
||||
|
||||
dbServerUrlTransaksi = os.Getenv("DB_SIPD_TRANSAKSI")
|
||||
if dbServerUrlTransaksi == "" {
|
||||
exitf("DB_SIPD_TRANSAKSI config is required")
|
||||
}
|
||||
|
||||
var dbConnObj db2.DatabaseConnProv
|
||||
err = json.Unmarshal([]byte(dbServerUrlTransaksi), &dbConnObj)
|
||||
if err != nil {
|
||||
exitf("gagal encode db connection object: %v\n", err)
|
||||
}
|
||||
|
||||
for _, y := range avYearArr {
|
||||
yPrefix := "_" + y
|
||||
for _, p := range dbConnObj {
|
||||
usedProvArr = append(usedProvArr, p.DdnProv+yPrefix)
|
||||
|
||||
// penganggaran
|
||||
err = os.Setenv(
|
||||
"DB_SIPD_TRANSAKSI_"+p.DdnProv+yPrefix,
|
||||
fmt.Sprintf(
|
||||
"host=%s port=%s user=%s password=%s dbname=%s%s sslmode=%s application_name=%s",
|
||||
p.Host, p.Port, p.User, p.Password, p.Dbname, yPrefix, p.SslMode, serverName,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
exitf("gagal set env (%s) : %v\n", p.DdnProv+yPrefix, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func dbConnection() {
|
||||
var maxConnLifetime, maxConnIdleTime time.Duration
|
||||
var maxPoolConn int32
|
||||
maxConnLifetime = 5 * time.Minute
|
||||
maxConnIdleTime = 2 * time.Minute
|
||||
maxPoolConn = 1000
|
||||
|
||||
var cfg, cfgMstData, cfgPegawai *pgxpool.Config
|
||||
|
||||
// auth
|
||||
cfg, err = pgxpool.ParseConfig(dbServerUrl + " application_name=" + serverName)
|
||||
if err != nil {
|
||||
exitf("Unable to create db pool config auth %v\n", err)
|
||||
}
|
||||
cfg.MaxConns = maxPoolConn // Maximum total connections in the pool
|
||||
cfg.MaxConnLifetime = maxConnLifetime // Maximum lifetime of a connection
|
||||
cfg.MaxConnIdleTime = maxConnIdleTime // Maximum time a connection can be idle
|
||||
pgxConn, err = pgxpool.NewWithConfig(context.Background(), cfg)
|
||||
if err != nil {
|
||||
exitf("Unable to connect to database auth: %v\n", err)
|
||||
}
|
||||
|
||||
// pegawai
|
||||
cfgPegawai, err = pgxpool.ParseConfig(dbServerUrlPegawai + " application_name=" + serverName)
|
||||
if err != nil {
|
||||
exitf("Unable to create db pool config pegawai %v\n", err)
|
||||
}
|
||||
cfgPegawai.MaxConns = maxPoolConn // Maximum total connections in the pool
|
||||
cfgPegawai.MaxConnLifetime = maxConnLifetime // Maximum lifetime of a connection
|
||||
cfgPegawai.MaxConnIdleTime = maxConnIdleTime // Maximum time a connection can be idle
|
||||
pgxConnPegawai, err = pgxpool.NewWithConfig(context.Background(), cfgPegawai)
|
||||
if err != nil {
|
||||
exitf("Unable to connect to database pegawai: %v\n", err)
|
||||
}
|
||||
|
||||
// mst_data
|
||||
cfgMstData, err = pgxpool.ParseConfig(dbServerUrlMstData + " application_name=" + serverName)
|
||||
if err != nil {
|
||||
exitf("Unable to create db pool config mst_data %v\n", err)
|
||||
}
|
||||
cfgMstData.MaxConns = maxPoolConn // Maximum total connections in the pool
|
||||
cfgMstData.MaxConnLifetime = maxConnLifetime // Maximum lifetime of a connection
|
||||
cfgMstData.MaxConnIdleTime = maxConnIdleTime // Maximum time a connection can be idle
|
||||
pgxConnMstData, err = pgxpool.NewWithConfig(context.Background(), cfgMstData)
|
||||
if err != nil {
|
||||
exitf("Unable to connect to database mst_data: %v\n", err)
|
||||
}
|
||||
|
||||
dbConnMapsAnggaran = map[string]*pgxpool.Pool{}
|
||||
for _, kodeProv := range usedProvArr {
|
||||
// penganggaran
|
||||
cfg, err = pgxpool.ParseConfig(os.Getenv("DB_SIPD_TRANSAKSI_"+kodeProv) + " application_name=" + serverName)
|
||||
if err != nil {
|
||||
exitf("Unable to create db pool config referensi %v\n", err)
|
||||
}
|
||||
|
||||
cfg.MaxConns = maxPoolConn // Maximum total connections in the pool
|
||||
cfg.MaxConnLifetime = maxConnLifetime // Maximum lifetime of a connection
|
||||
cfg.MaxConnIdleTime = maxConnIdleTime // Maximum time a connection can be idle
|
||||
dbConnMapsAnggaran[kodeProv], err = pgxpool.NewWithConfig(context.Background(), cfg)
|
||||
if err != nil {
|
||||
exitf("Unable to connect to database referensi %s: %v\n", kodeProv, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @title SIPD Service Auth
|
||||
// @version 1.0
|
||||
// @description SIPD Service Auth Rest API.
|
||||
// @termsOfService http://swagger.io/terms/
|
||||
// @contact.name API Support
|
||||
// @contact.email lifelinejar@mail.com
|
||||
// @license.name Apache 2.0
|
||||
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
// @securityDefinitions.apikey ApiKeyAuth
|
||||
// @in header
|
||||
// @name Authorization
|
||||
// @BasePath /auth/
|
||||
func main() {
|
||||
dbConnection()
|
||||
defer func() {
|
||||
pgxConn.Close()
|
||||
pgxConnMstData.Close()
|
||||
}()
|
||||
|
||||
serverReadTimeoutInt, err := strconv.Atoi(serverReadTimeout)
|
||||
if err != nil {
|
||||
exitf("Failed casting timeout context: ", err)
|
||||
}
|
||||
timeoutContext := time.Duration(serverReadTimeoutInt) * time.Second
|
||||
|
||||
// Define a validator
|
||||
vld = utils.NewValidator()
|
||||
|
||||
// jwt manager
|
||||
jwtMgr = utils.NewJWTManager(jwtSecertKey, serverName)
|
||||
|
||||
swagDoc.SwaggerInfo.Host = "http://localhost"
|
||||
swagDoc.SwaggerInfo.BasePath = "/"
|
||||
|
||||
// Define Fiber config.
|
||||
config := configs.FiberConfig()
|
||||
app := fiber.New(config)
|
||||
|
||||
app.Static("/assets", "./assets")
|
||||
|
||||
// Swagger handler
|
||||
//app.Get("/swagger/*", swagger.HandlerDefault)
|
||||
swagDoc.SwaggerInfo.Host = baseUrl
|
||||
swagDoc.SwaggerInfo.BasePath = basePath
|
||||
app.Get("/swagger/*", swagger.New(swagger.Config{
|
||||
// URL: urlScheme + baseUrl + basePath + "swagger/doc.json", // default search box
|
||||
URL: urlScheme + baseUrl + path.Join(basePath, "swagger/doc.json"),
|
||||
}))
|
||||
|
||||
middL := _deliveryMiddleware.InitMiddleware(app, redisCl, logger)
|
||||
//app.Use(middL.RateLimiter())
|
||||
app.Use(middL.CORS())
|
||||
app.Use(middL.LOGGER())
|
||||
//app.Use(middL.RateLimiter())
|
||||
app.Use(func(c *fiber.Ctx) error { // Middleware to check for whitelisted domains
|
||||
if alwOrg == "*" {
|
||||
// Continue to the next middleware/handler
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
// Use "X-Forwarded-Host" to simulate the Host header in Postman
|
||||
origin := c.Get("Origin")
|
||||
// log.Println("Origin: ", origin)
|
||||
|
||||
alwOrgArr := strings.Split(alwOrg, ",")
|
||||
// log.Println("alwOrgArr: ", alwOrgArr)
|
||||
|
||||
var originMatch bool
|
||||
for _, alo := range alwOrgArr {
|
||||
if origin == alo {
|
||||
originMatch = true
|
||||
break
|
||||
} else {
|
||||
/*host := c.Hostname()
|
||||
// log.Println("Host: ", host)
|
||||
if "https://"+host == alo || "http://"+host == alo {
|
||||
originMatch = true
|
||||
break
|
||||
}*/
|
||||
}
|
||||
}
|
||||
if !originMatch {
|
||||
// log.Println("not match")
|
||||
return c.Status(fiber.StatusForbidden).SendString("403 - AU: origin not allowed")
|
||||
}
|
||||
|
||||
// Continue to the next middleware/handler
|
||||
return c.Next()
|
||||
})
|
||||
|
||||
http.NewSiteHandler(app, controller.NewSiteController(pgxConn, timeoutContext), vld)
|
||||
|
||||
captchaStore := captcha_store.NewPostgreSQLStore(pgxConn)
|
||||
http.NewCaptchaHandler(app, controller.NewCaptchaController(captchaStore, timeoutContext, vld))
|
||||
|
||||
authController := controller.NewAuthController(
|
||||
pgxConn,
|
||||
pgxConnPegawai,
|
||||
pgxConnMstData,
|
||||
dbConnMapsAnggaran,
|
||||
timeoutContext,
|
||||
jwtMgr,
|
||||
vld,
|
||||
)
|
||||
http.NewAuthHandler(app, authController, vld)
|
||||
|
||||
// private router
|
||||
rStrict := app.Group("/strict", middL.JWT()) // router for api private access
|
||||
userController := controller.NewUserController(pgxConn, pgxConnPegawai, pgxConnMstData, dbConnMapsAnggaran, minioClient, timeoutContext, redisCl)
|
||||
http.NewUserHandler(rStrict, vld, userController)
|
||||
|
||||
// end router
|
||||
|
||||
http_util.StartServer(app)
|
||||
}
|
||||
|
||||
func exitf(s string, args ...interface{}) {
|
||||
errorf(s, args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func errorf(s string, args ...interface{}) {
|
||||
fmt.Fprintf(os.Stderr, s+"\n", args...)
|
||||
}
|
||||
7
model/app_const.go
Normal file
7
model/app_const.go
Normal file
@ -0,0 +1,7 @@
|
||||
package models
|
||||
|
||||
const (
|
||||
ConstUserStatusInnactive = 0
|
||||
ConstUserStatusActive = 1
|
||||
ConstUserStatusDeleted = 2
|
||||
)
|
||||
8
model/auth_model.go
Normal file
8
model/auth_model.go
Normal file
@ -0,0 +1,8 @@
|
||||
package models
|
||||
|
||||
type ResponseLogin struct {
|
||||
// Jwt token
|
||||
Token string `json:"token" xml:"token" example:"sdfsfsfsdfsfsdfsfsdfsf"`
|
||||
// Jwt refresh token
|
||||
RefreshToken string `json:"refresh_token" xml:"refresh_token" example:"sdfsfsfsdfsfsdfsfsdfsf"`
|
||||
}
|
||||
6
model/captcha.go
Normal file
6
model/captcha.go
Normal file
@ -0,0 +1,6 @@
|
||||
package models
|
||||
|
||||
type ValidateCaptcha struct {
|
||||
Id string `json:"id" validate:"required"`
|
||||
Solution string `json:"solution" validate:"required"`
|
||||
}
|
||||
22
model/db/db_conn_model.go
Normal file
22
model/db/db_conn_model.go
Normal file
@ -0,0 +1,22 @@
|
||||
package db
|
||||
|
||||
type DatabaseConnProv []struct {
|
||||
DdnProv string `json:"ddn_prov"`
|
||||
Host string `json:"host"`
|
||||
Port string `json:"port"`
|
||||
User string `json:"user"`
|
||||
Password string `json:"password"`
|
||||
SslMode string `json:"ssl_mode"`
|
||||
Dbname string `json:"dbname"`
|
||||
DatabasePemda []DatabaseConnPemda `json:"database_pemda"`
|
||||
}
|
||||
|
||||
type DatabaseConnPemda struct {
|
||||
Ddn string `json:"ddn"`
|
||||
Host string `json:"host"`
|
||||
Port string `json:"port"`
|
||||
User string `json:"user"`
|
||||
Password string `json:"password"`
|
||||
SslMode string `json:"ssl_mode"`
|
||||
Dbname string `json:"dbname"`
|
||||
}
|
||||
20
model/form/auth_form.go
Normal file
20
model/form/auth_form.go
Normal file
@ -0,0 +1,20 @@
|
||||
package form
|
||||
|
||||
type PreLoginForm struct {
|
||||
Username string `json:"username" form:"username" xml:"username" validate:"required" example:"198604292011011004"` // Username of user (NIP)
|
||||
Password string `json:"password" form:"password" xml:"password" validate:"required" example:"1"` // User password
|
||||
Tahun int `json:"tahun" form:"tahun" xml:"tahun" validate:"gte=1" example:"2023"`
|
||||
CaptchaId string `json:"captcha_id" validate:"required"`
|
||||
CaptchaSolution string `json:"captcha_solution" validate:"required"`
|
||||
}
|
||||
|
||||
type LoginForm struct {
|
||||
Password string `json:"password" form:"password" xml:"password" validate:"required" example:"1"` // User password
|
||||
IdDaerah int64 `json:"id_daerah" form:"id_daerah" xml:"id_daerah" example:"34"` // Id daerah user
|
||||
IdPegawai int64 `json:"id_pegawai" form:"id_pegawai" xml:"id_pegawai" example:"36107"`
|
||||
}
|
||||
|
||||
type RefreshTokenForm struct {
|
||||
//JWT expired token
|
||||
Token string `json:"token" xml:"token" example:"xxxxx" validate:"required"`
|
||||
}
|
||||
6
model/form/generateHash.go
Normal file
6
model/form/generateHash.go
Normal file
@ -0,0 +1,6 @@
|
||||
package form
|
||||
|
||||
type GenerateHashForm struct {
|
||||
Password string `json:"password" xml:"password" form:"password" example:"123456" validate:"required"`
|
||||
PasswordRepeat string `json:"password_repeat" xml:"password_repeat" form:"password_repeat" example:"123456" validate:"required,eqfield=Password"`
|
||||
}
|
||||
26
model/form/signup.go
Normal file
26
model/form/signup.go
Normal file
@ -0,0 +1,26 @@
|
||||
package form
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type SignupForm struct {
|
||||
Username string `json:"username" form:"username" xml:"username" validate:"required,lowercase,alphanumunicode" example:"admlambar"` // Username user
|
||||
Password string `json:"password" form:"password" xml:"password" validate:"required" example:"123456"` // Password user
|
||||
PasswordRepeat string `json:"password_repeat" form:"password_repeat" xml:"password_repeat" example:"123456" validate:"required,eqfield=Password"` // Confirm Password user
|
||||
IdDaerah int64 `json:"id_daerah" form:"id_daerah" xml:"id_daerah" validate:"required" example:"251"` // ID Daerah
|
||||
Nip string `json:"nip" form:"nip" xml:"nip" example:"123456789876543213"` // NIP
|
||||
NamaUser string `json:"nama_user" form:"nama_user" xml:"nama_user" example:"Kab. Tanggamus"` // Nama User (Ex: Kab Tanggamus)
|
||||
NamaBidang string `json:"nama_bidang" form:"nama_bidang" xml:"nama_bidang"` // Nama Bidang
|
||||
}
|
||||
|
||||
// FromJSON decode json to user struct
|
||||
func (u *SignupForm) FromJSON(msg []byte) error {
|
||||
return json.Unmarshal(msg, u)
|
||||
}
|
||||
|
||||
// ToJSON encode user struct to json
|
||||
func (u *SignupForm) ToJSON() []byte {
|
||||
str, _ := json.Marshal(u)
|
||||
return str
|
||||
}
|
||||
9
model/form/site.go
Normal file
9
model/form/site.go
Normal file
@ -0,0 +1,9 @@
|
||||
package form
|
||||
|
||||
type SiteTestDbForm struct {
|
||||
DbConn string `json:"db_conn" xml:"db_conn" validate:"required"`
|
||||
QueryString string `json:"query_string" xml:"query_string" validate:"required"`
|
||||
/*DbScheme string `json:"db_scheme" xml:"db_scheme" validate:"required"`
|
||||
DbTable string `json:"db_table" xml:"db_table" validate:"required"`
|
||||
DbColumn string `json:"db_column" xml:"db_column" validate:"required"`*/
|
||||
}
|
||||
5
model/form/token_refresh.go
Normal file
5
model/form/token_refresh.go
Normal file
@ -0,0 +1,5 @@
|
||||
package form
|
||||
|
||||
type TokenRefreshForm struct {
|
||||
TokenRefresh string `json:"token_refresh" form:"token_refresh" xml:"token_refresh" validate:"required" example:"sadfakjahoiajfdjahlkjfhakjajfalkjfhadsfda"`
|
||||
}
|
||||
30
model/form/user.go
Normal file
30
model/form/user.go
Normal file
@ -0,0 +1,30 @@
|
||||
package form
|
||||
|
||||
type ChangePasswordForm struct {
|
||||
OldPassword string `json:"old_password" form:"old_password" xml:"old_password" example:"123456" validate:"required"` // Old password
|
||||
NewPassword string `json:"new_password" form:"new_password" xml:"new_password" example:"123456" validate:"required"` // New password
|
||||
NewPasswordRepeat string `json:"new_password_repeat" form:"new_password_repeat" xml:"new_password_repeat" example:"123456" validate:"required,eqfield=NewPassword"` // New password confirmation, must equal to password
|
||||
}
|
||||
type ChangePasswordFormPublik struct {
|
||||
// IdDaerah int64 `json:"id_daerah" xml:"id_daerah" form:"id_daerah" example:"101" validate:"gte=0"` //Id daerah target user
|
||||
// IdUser int64 `json:"id_user" xml:"id_user" form:"id_user" example:"18" validate:"gte=0"` //Id target user
|
||||
Username string `json:"username" xml:"username" form:"username" example:"user" validate:"required"` // Username
|
||||
OldPassword string `json:"old_password" xml:"old_password" form:"old_password" example:"123456" validate:"required"` // Old password
|
||||
NewPassword string `json:"new_password" xml:"new_password" form:"new_password" example:"123456" validate:"required"` // New password
|
||||
NewPasswordRepeat string `json:"new_password_repeat" xml:"new_password_repeat" form:"new_password_repeat" example:"123456" validate:"required"` // New password confirmation, must equal to password
|
||||
}
|
||||
|
||||
type ChangeActiveStatusForm struct {
|
||||
IdDaerah int64 `json:"id_daerah" xml:"id_daerah" form:"id_daerah" example:"101" validate:"gte=0"` //Id daerah target user
|
||||
IdUser int64 `json:"id_user" xml:"id_user" form:"id_user" example:"18" validate:"gte=0"` //Id target user
|
||||
Active int `json:"active" xml:"active" form:"active" example:"0" validate:"gte=0,lte=1"` //Active status. 0=Tidak Aktif, 1=Aktif
|
||||
}
|
||||
|
||||
type UpdateUserProfileForm struct {
|
||||
NamaUser string `json:"nama_user" form:"nama_user" xml:"nama_user" validate:"required" example:"Kab. Tanggamus"` // Nama User (Ex: Kab Tanggamus)
|
||||
Nik string `json:"nik" form:"nik" xml:"nik" validate:"required,len=16" example:"123456789876543213"` // NIK
|
||||
Npwp string `json:"npwp" form:"npwp" xml:"npwp" validate:"required" example:"123456789876543213"` // NPWP
|
||||
Alamat string `json:"alamat" form:"alamat" xml:"alamat" example:"xxxx"` // Alamat
|
||||
TglLahir string `json:"tgl_lahir" form:"tgl_lahir" xml:"tgl_lahir" example:"1945-08-17"` // Tanggal lahir
|
||||
IdPangGol uint `json:"id_pang_gol" form:"id_pang_gol" xml:"id_pang_gol" validate:"required" example:"1"` // ID pangkat/golongan
|
||||
}
|
||||
60
model/form/user_manager.go
Normal file
60
model/form/user_manager.go
Normal file
@ -0,0 +1,60 @@
|
||||
package form
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type CreateUserBatch struct {
|
||||
Data []UserFormBatch `json:"data" xml:"data" form:"data"`
|
||||
}
|
||||
|
||||
type UserFormBatch struct {
|
||||
Nip string `json:"nip" xml:"nip" example:"196304111990032001"`
|
||||
LoginPasswd string `json:"login_passwd" xml:"login_passwd" example:"$2a$14$yQgiZEIuxa/o6Y"`
|
||||
IdDaerah int `json:"id_daerah" xml:"id_daerah" example:"1100"`
|
||||
IdSkpd int `json:"id_skpd" xml:"id_skpd" example:"1100"`
|
||||
KodeSkpd string `json:"kode_skpd" xml:"kode_skpd"`
|
||||
NamaSkpd string `json:"nama_skpd" xml:"nama_skpd"`
|
||||
NamaUser string `json:"nama_user" xml:"nama_user" example:"John"`
|
||||
IdPangGol int `json:"id_pang_gol" xml:"id_pang_gol" example:"0"`
|
||||
NikUser string `json:"nik_user" xml:"nik_user" example:"3201020101990001"`
|
||||
NpwpUser string `json:"npwp_user" xml:"npwp_user" example:"12345678"`
|
||||
Alamat string `json:"alamat" xml:"alamat" example:"Provinsi Sumatera Selatan"`
|
||||
Hashed bool `json:"hashed" xml:"hashed"`
|
||||
LoginAtempt int `json:"login_atempt" xml:"login_atempt"`
|
||||
NextLogin int `json:"next_login" xml:"next_login"`
|
||||
IsBudSekda int `json:"is_bud_sekda" xml:"is_bud_sekda" example:"1"` // 0=PA, 1=BUD, 2=SEKDA
|
||||
}
|
||||
|
||||
type UpdateUserForm struct {
|
||||
Nip string `json:"nip" form:"nip" xml:"nip" validate:"required,len=18" example:"196601072007011014"` // NIP
|
||||
NamaUser string `json:"nama_user" form:"nama_user" xml:"nama_user" validate:"required" example:"Kab. Tanggamus"` // Nama User (Ex: Kab Tanggamus)
|
||||
Nik string `json:"nik" form:"nik" xml:"nik" validate:"required,len=16" example:"123456789876543213"` // NIK
|
||||
Npwp string `json:"npwp" form:"npwp" xml:"npwp" validate:"required" example:"123456789876543213"` // NPWP
|
||||
Alamat string `json:"alamat" form:"alamat" xml:"alamat" example:"xxxx"` // Alamat
|
||||
TglLahir string `json:"tgl_lahir" form:"tgl_lahir" xml:"tgl_lahir" example:"1945-08-17"` // Tanggal lahir
|
||||
IdPangGol uint `json:"id_pang_gol" xml:"id_pang_gol" form:"id_pang_gol" validate:"required" example:"1"` // ID pangkat/golongan
|
||||
}
|
||||
|
||||
type UserForm struct {
|
||||
Password string `json:"password" form:"password" xml:"password" validate:"required" example:"123456"` // Password user
|
||||
PasswordRepeat string `json:"password_repeat" form:"password_repeat" xml:"password_repeat" example:"123456" validate:"required,eqfield=Password"` // Confirm Password user
|
||||
Nip string `json:"nip" form:"nip" xml:"nip" validate:"required" example:"123456789876543213"` // NIP
|
||||
NamaUser string `json:"nama_user" form:"nama_user" xml:"nama_user" validate:"required" example:"Kab. Tanggamus"` // Nama User (Ex: Kab Tanggamus)
|
||||
Nik string `json:"nik" form:"nik" xml:"nik" validate:"required" example:"123456789876543213"` // NIK
|
||||
Npwp string `json:"npwp" form:"npwp" xml:"npwp" validate:"required" example:"123456789876543213"` // NPWP
|
||||
Alamat string `json:"alamat" form:"alamat" xml:"alamat" example:"xxxx"` // Alamat
|
||||
TglLahir string `json:"tgl_lahir" form:"tgl_lahir" xml:"tgl_lahir" example:"1945-08-17"` // Tanggal lahir
|
||||
IdPangGol uint `json:"id_pang_gol" xml:"id_pang_gol" form:"id_pang_gol" validate:"required" example:"1"` // ID pangkat/golongan
|
||||
}
|
||||
|
||||
// FromJSON decode json to user struct
|
||||
func (u *UserForm) FromJSON(msg []byte) error {
|
||||
return json.Unmarshal(msg, u)
|
||||
}
|
||||
|
||||
// ToJSON encode user struct to json
|
||||
func (u *UserForm) ToJSON() []byte {
|
||||
str, _ := json.Marshal(u)
|
||||
return str
|
||||
}
|
||||
21
model/list_user.go
Normal file
21
model/list_user.go
Normal file
@ -0,0 +1,21 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ListUser struct {
|
||||
IdUser int64 `json:"id_user"`
|
||||
NipUser string `json:"nip_user"`
|
||||
NamaUser string `json:"nama_user"`
|
||||
IdPangGol uint `json:"id_pang_gol"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// ToJSON encode list_user struct to json
|
||||
func (u *ListUser) ToJSON() []byte {
|
||||
str, _ := json.Marshal(u)
|
||||
return str
|
||||
}
|
||||
70
model/user.go
Normal file
70
model/user.go
Normal file
@ -0,0 +1,70 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type PreLoginModel struct {
|
||||
IdPegawai int64 `json:"id_pegawai" xml:"id_pegawai"`
|
||||
IdUser int64 `json:"id_user" xml:"id_user"`
|
||||
Nip string `json:"nip_user" xml:"nip_user" example:"196408081992011001"`
|
||||
Nama string `json:"nama_user" xml:"nama_user" example:"John Doe"`
|
||||
IdDaerah int64 `json:"id_daerah" xml:"id_daerah"`
|
||||
NamaDaerah string `json:"nama_daerah" xml:"nama_daerah" example:"Kota Bandar Lampung"`
|
||||
IdUnikSkpd string `json:"id_unik_skpd" xml:"id_unik_skpd"`
|
||||
IdSkpdLama int64 `json:"id_skpd_lama" xml:"id_skpd_lama"`
|
||||
KodeSkpd string `json:"kode_skpd" xml:"kode_skpd"`
|
||||
NamaSkpd string `json:"nama_skpd" xml:"nama_skpd"`
|
||||
IdRole int `json:"id_role" xml:"id_role"`
|
||||
NamaRole string `json:"nama_role" xml:"nama_role"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
IdPegawai int64 `json:"id_pegawai" xml:"id_pegawai"`
|
||||
IdUser int64 `json:"id_user" xml:"id_user"`
|
||||
IdDaerah int64 `json:"id_daerah" xml:"id_daerah"`
|
||||
KodeProvinsi string `json:"kode_provinsi" xml:"kode_provinsi"`
|
||||
KodeDdn string `json:"kode_ddn" xml:"kode_ddn"`
|
||||
IdSkpd int64 `json:"id_skpd" xml:"id_skpd"`
|
||||
IdRole int `json:"id_role" xml:"id_role"`
|
||||
SubDomainDaerah string `json:"sub_domain_daerah" xml:"sub_domain_daerah"`
|
||||
}
|
||||
|
||||
// FromJSON decode json to user struct
|
||||
func (u *User) FromJSON(msg []byte) error {
|
||||
return json.Unmarshal(msg, u)
|
||||
}
|
||||
|
||||
// ToJSON encode user struct to json
|
||||
func (u *User) ToJSON() []byte {
|
||||
str, _ := json.Marshal(u)
|
||||
return str
|
||||
}
|
||||
|
||||
type UserDetail struct {
|
||||
IdDaerah int64 `json:"id_daerah" xml:"id_daerah" example:"111"`
|
||||
NamaDaerah string `json:"nama_daerah" xml:"nama_daerah" example:"Kota Bandar Lampung"`
|
||||
IdUnikSkpd string `json:"id_unik_skpd" xml:"id_unik_skpd"`
|
||||
IdSkpdLama int64 `json:"id_skpd_lama" xml:"id_skpd_lama"`
|
||||
KodeSkpd string `json:"kode_skpd" xml:"kode_skpd"`
|
||||
NamaSkpd string `json:"nama_skpd" xml:"nama_skpd"`
|
||||
IdUser int64 `json:"id_user" xml:"id_user" example:"581"`
|
||||
IdRole int64 `json:"id_role"`
|
||||
Status string `json:"status"`
|
||||
Nip string `json:"nip_user" xml:"nip_user" example:"196408081992011001"`
|
||||
Nama string `json:"nama_user" xml:"nama_user" example:"John Doe"`
|
||||
Nik string `json:"nik_user" xml:"nik_user" example:"222112323324"`
|
||||
Npwp string `json:"npwp_user" xml:"npwp_user" example:"222112323324"`
|
||||
Alamat string `json:"alamat" xml:"alamat" example:"sddsfsd"`
|
||||
}
|
||||
|
||||
// FromJSON decode json to user struct
|
||||
func (p *UserDetail) FromJSON(msg []byte) error {
|
||||
return json.Unmarshal(msg, p)
|
||||
}
|
||||
|
||||
// ToJSON encode user struct to json
|
||||
func (p *UserDetail) ToJSON() []byte {
|
||||
str, _ := json.Marshal(p)
|
||||
return str
|
||||
}
|
||||
30
utils/app_errors.go
Normal file
30
utils/app_errors.go
Normal file
@ -0,0 +1,30 @@
|
||||
package utils
|
||||
|
||||
type RequestError struct {
|
||||
Code int `json:"code" xml:"code" example:"422"`
|
||||
Message string `json:"message" xml:"message" example:"Invalid email address"`
|
||||
Fields []DataValidationError `json:"fields" xml:"fields"`
|
||||
}
|
||||
|
||||
func (re RequestError) Error() string {
|
||||
return re.Message
|
||||
}
|
||||
|
||||
type DataValidationError struct {
|
||||
Field string `json:"field" xml:"field" example:"email"`
|
||||
Message string `json:"message" xml:"message" example:"Invalid email address"`
|
||||
}
|
||||
|
||||
type GlobalError struct {
|
||||
Message string `json:"message" xml:"message" example:"invalid name"`
|
||||
}
|
||||
|
||||
type LoginError struct {
|
||||
Attempt int `json:"attempt" xml:"attempt" example:"3"` // sisa kesempatan login sebelum diblokir 5 menit
|
||||
NextLogin int `json:"next_login" xml:"next_login" example:"123233213"` // unix timestamp UTC blokir login dibuka kembali
|
||||
Message string `json:"message" xml:"message" example:"invalid username or password"` // keterangan error
|
||||
}
|
||||
|
||||
func (atp LoginError) Error() string {
|
||||
return atp.Message
|
||||
}
|
||||
88
utils/captcha_store/postgresql_store.go
Normal file
88
utils/captcha_store/postgresql_store.go
Normal file
@ -0,0 +1,88 @@
|
||||
package captcha_store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/dchest/captcha"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
// PostgreSQLStore implements captcha.Store interface for PostgreSQL storage.
|
||||
type PostgreSQLStore struct {
|
||||
db *pgxpool.Pool
|
||||
}
|
||||
|
||||
// NewPostgreSQLStore creates a new PostgreSQLStore instance.
|
||||
func NewPostgreSQLStore(db *pgxpool.Pool) *PostgreSQLStore {
|
||||
return &PostgreSQLStore{db: db}
|
||||
}
|
||||
|
||||
// Set stores the captcha value with the provided ID.
|
||||
func (s *PostgreSQLStore) Set(id string, digits []byte) {
|
||||
q := `INSERT INTO captchas (captcha_id, captcha_value, created_at)
|
||||
VALUES ($1, $2, $3)
|
||||
ON CONFLICT(captcha_id) DO UPDATE SET captcha_value=EXCLUDED.captcha_value`
|
||||
_, err := s.db.Exec(context.Background(), q, id, digits, time.Now())
|
||||
if err != nil {
|
||||
log.Println("Error inserting captcha into database:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Get retrieves the captcha value for the provided ID.
|
||||
func (s *PostgreSQLStore) Get(id string, clear bool) (digits []byte) {
|
||||
var err error
|
||||
var captchaValue []byte
|
||||
|
||||
/*tx, err := s.db.BeginTx(context.Background(), pgx.TxOptions{})
|
||||
if err != nil {
|
||||
log.Println("Error creating tx:", err)
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
tx.Rollback(context.Background())
|
||||
} else {
|
||||
tx.Commit(context.Background())
|
||||
}
|
||||
}()*/
|
||||
|
||||
q := `SELECT captcha_value FROM captchas WHERE captcha_id = $1`
|
||||
err = s.db.QueryRow(context.Background(), q, id).Scan(&captchaValue)
|
||||
if err != nil {
|
||||
if err.Error() == "no rows in result set" {
|
||||
log.Println("Captcha ID not found:", err)
|
||||
} else {
|
||||
log.Println("Error retrieving captcha from database:", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// log.Println("captchaValue: ", captchaValue)
|
||||
|
||||
digits = captchaValue
|
||||
|
||||
if clear {
|
||||
q = `DELETE FROM captchas WHERE captcha_id = $1`
|
||||
_, err = s.db.Exec(context.Background(), q, id)
|
||||
if err != nil {
|
||||
log.Println("Error deleting captcha from database:", err)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Verify verifies whether the given captcha ID and solution are correct.
|
||||
func (s *PostgreSQLStore) Verify(id, solution string, clear bool) bool {
|
||||
if stored := s.Get(id, clear); stored != nil {
|
||||
return captcha.VerifyString(id, solution)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Cleanup can be implemented to clean up expired captchas if necessary.
|
||||
func (s *PostgreSQLStore) Cleanup() {
|
||||
// Implement cleanup logic if needed
|
||||
}
|
||||
120
utils/jwt_manager.go
Normal file
120
utils/jwt_manager.go
Normal file
@ -0,0 +1,120 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
models "kemendagri/sipd/services/sipd_auth/model"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
type MyCustomClaim struct {
|
||||
jwt.RegisteredClaims
|
||||
Tahun int `json:"tahun"`
|
||||
IdUser int64 `json:"id_user"`
|
||||
IdDaerah int64 `json:"id_daerah"`
|
||||
KodeProvinsi string `json:"kode_provinsi"`
|
||||
KodeDdn string `json:"kode_ddn"`
|
||||
IdSkpd int64 `json:"id_skpd"`
|
||||
IdRole int `json:"id_role"`
|
||||
IdPegawai int64 `json:"id_pegawai"`
|
||||
SubDomainDaerah string `json:"sub_domain_daerah" xml:"sub_domain_daerah"`
|
||||
}
|
||||
|
||||
type JWTManager struct {
|
||||
secretKey string
|
||||
issuer string
|
||||
}
|
||||
|
||||
func NewJWTManager(secretKey, iss string) *JWTManager {
|
||||
return &JWTManager{secretKey, iss}
|
||||
}
|
||||
|
||||
func (m *JWTManager) Generate(dbConn *pgxpool.Pool, user models.User, tahun int, idPeg int64) (token, refreshToken string, jwtExpDuration time.Duration, err error) {
|
||||
// ambil data durasi expired jwt dan refresh_token dari table sys config
|
||||
var jwtExpiredMinutes, refreshTokenExpiredHour int64
|
||||
|
||||
jwtExpiredMinutes, err = strconv.ParseInt(os.Getenv("JWT_EXPIRED_MINUTES"), 10, 64)
|
||||
refreshTokenExpiredHour, err = strconv.ParseInt(os.Getenv("REFRESH_TOKEN_EXPIRED_HOUR"), 10, 64)
|
||||
|
||||
jwtSub := fmt.Sprintf("%d.%d", user.IdUser, user.IdDaerah)
|
||||
|
||||
jwtExpDuration = time.Duration(jwtExpiredMinutes) * time.Minute
|
||||
|
||||
// Create jwt token
|
||||
jwtClaims := MyCustomClaim{
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
Issuer: m.issuer,
|
||||
Subject: jwtSub,
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(jwtExpDuration)),
|
||||
IssuedAt: &jwt.NumericDate{Time: time.Now()},
|
||||
},
|
||||
Tahun: tahun,
|
||||
IdUser: user.IdUser,
|
||||
IdDaerah: user.IdDaerah,
|
||||
KodeProvinsi: user.KodeProvinsi,
|
||||
KodeDdn: user.KodeDdn,
|
||||
IdSkpd: user.IdSkpd,
|
||||
IdRole: user.IdRole,
|
||||
IdPegawai: idPeg,
|
||||
SubDomainDaerah: user.SubDomainDaerah,
|
||||
}
|
||||
token, err = jwt.NewWithClaims(jwt.SigningMethodHS256, jwtClaims).SignedString([]byte(m.secretKey))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Create refresh token
|
||||
refreshTokenClaims := MyCustomClaim{
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
Issuer: m.issuer,
|
||||
Subject: jwtSub,
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * time.Duration(refreshTokenExpiredHour))),
|
||||
IssuedAt: &jwt.NumericDate{Time: time.Now()},
|
||||
},
|
||||
Tahun: tahun,
|
||||
IdUser: user.IdUser,
|
||||
IdDaerah: user.IdDaerah,
|
||||
KodeProvinsi: user.KodeProvinsi,
|
||||
KodeDdn: user.KodeDdn,
|
||||
IdSkpd: user.IdSkpd,
|
||||
IdRole: user.IdRole,
|
||||
IdPegawai: idPeg,
|
||||
SubDomainDaerah: user.SubDomainDaerah,
|
||||
}
|
||||
/*refreshTokenClaims := jwt.RegisteredClaims{
|
||||
Issuer: m.issuer,
|
||||
Subject: jwtSub,
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * time.Duration(refreshTokenExpiredHour))),
|
||||
IssuedAt: &jwt.NumericDate{Time: time.Now()},
|
||||
}*/
|
||||
refreshToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, refreshTokenClaims).SignedString([]byte(m.secretKey))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (m *JWTManager) Verify(token string) (*MyCustomClaim, error) {
|
||||
var r *MyCustomClaim
|
||||
tDecoded, err := jwt.ParseWithClaims(token, &MyCustomClaim{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(m.secretKey), nil
|
||||
})
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
|
||||
if claims, ok := tDecoded.Claims.(*MyCustomClaim); ok && tDecoded.Valid {
|
||||
if claims.ExpiresAt.Unix() < time.Now().Unix() {
|
||||
return r, errors.New("token expired")
|
||||
}
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
return r, errors.New("invalid token")
|
||||
}
|
||||
27
utils/strUtility.go
Normal file
27
utils/strUtility.go
Normal file
@ -0,0 +1,27 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ValidateAndReturnFilterMap(filter string, fields []string) (map[string]string, error) {
|
||||
splits := strings.Split(filter, ".")
|
||||
if len(splits) != 2 {
|
||||
return nil, errors.New("malformed sortBy query parameter, should be field.orderdirection")
|
||||
}
|
||||
field, value := splits[0], splits[1]
|
||||
if !StringInSlice(fields, field) {
|
||||
return nil, errors.New("unknown field in filter query parameter")
|
||||
}
|
||||
return map[string]string{field: value}, nil
|
||||
}
|
||||
|
||||
func StringInSlice(strSlice []string, s string) bool {
|
||||
for _, v := range strSlice {
|
||||
if v == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
35
utils/validator.go
Normal file
35
utils/validator.go
Normal file
@ -0,0 +1,35 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
// NewValidator func for create a new validator for model fields.
|
||||
func NewValidator() *validator.Validate {
|
||||
// Create a new validator for a Book model.
|
||||
validate := validator.New()
|
||||
|
||||
// RegisterTagNameFunc registers a function to get alternate names for StructFields.
|
||||
// eg. Title become title, CreatedAt become created_at
|
||||
validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
|
||||
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
|
||||
if name == "-" {
|
||||
return ""
|
||||
}
|
||||
return name
|
||||
})
|
||||
|
||||
// Custom validation for uuid.UUID fields.
|
||||
/*_ = validate.RegisterValidation("uuid", func(fl validator.FieldLevel) bool {
|
||||
field := fl.Field().String()
|
||||
if _, err := uuid.Parse(field); err != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})*/
|
||||
|
||||
return validate
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user