Browse Source

Initial Commit.

master
chandrawisesa 11 months ago
parent
commit
ae810f964f
  1. 11
      .gitignore
  2. 21
      go_auth.yaml
  3. 13
      src/Dockerfile
  4. 12
      src/appinfo/appinfo.go
  5. 61
      src/db/db.go
  6. 12
      src/go.mod
  7. 12
      src/go.sum
  8. 245
      src/handlers/auth.go
  9. 167
      src/handlers/greeting.go
  10. 12
      src/handlers/hello.go
  11. 70
      src/helper/crypto.go
  12. 36
      src/helper/util.go
  13. 102
      src/logger/logger.go
  14. 129
      src/main.go
  15. 341
      src/moffas/moffas_auth.go
  16. 29
      test/test_auth1.http

11
.gitignore vendored

@ -1,10 +1,5 @@ @@ -1,10 +1,5 @@
# ---> VisualStudioCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
@ -35,3 +30,9 @@ @@ -35,3 +30,9 @@
# Go workspace file
go.work
# compressed files
*.zip
*.gz
*.rar
*.bz2
*.tar

21
go_auth.yaml

@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
services:
servoauth:
image: go_auth
container_name: servoauth
ports:
- "48002:48000"
environment:
- "DBHOST=host.docker.internal"
- "DBPORT=5432"
- "DBUSER=asterisk"
- "DBPASS=asterisk"
- "DBNAME=asterisk"
- "DBDRIVER=postgres"
- "DBPOOLSIZE=100"
extra_hosts:
- "host.docker.internal:host-gateway"
networks:
- dbcon
networks:
dbcon:
external: true

13
src/Dockerfile

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
FROM golang:alpine
RUN apk update && apk add --no-cache git
WORKDIR /app
COPY . .
RUN go mod tidy
RUN go build -o binary
ENTRYPOINT ["/app/binary"]

12
src/appinfo/appinfo.go

@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
package appinfo
var appName string = "MOFFAS-AUTH"
var version string = "1.0.0.1"
func Version() string {
return version
}
func AppName() string {
return appName
}

61
src/db/db.go

@ -0,0 +1,61 @@ @@ -0,0 +1,61 @@
package db
import (
"errors"
"fmt"
"moffas_go/logger"
"sync"
"time"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
)
type DBPool struct {
db *sqlx.DB
count int
mutex sync.Mutex
poolSize int
}
var dbpool DBPool = DBPool{}
func GetConnection() (*sqlx.DB, error) {
dbpool.mutex.Lock()
defer dbpool.mutex.Unlock()
if dbpool.count >= dbpool.poolSize {
dbpool.count = dbpool.poolSize
return nil, errors.New("no connection available in pool")
} else {
dbpool.count++
return dbpool.db, nil
}
}
func ReleaseConnection() {
dbpool.mutex.Lock()
defer dbpool.mutex.Unlock()
if dbpool.count > 0 {
dbpool.count--
} else {
dbpool.count = 0
}
}
func InitDB(driver string, host string, port int, user string, password string, dbname string, poolSize int) error {
var err error
connStr := fmt.Sprintf("%s://%s:%s@%s:%d/%s", driver, user, password, host, port, dbname)
logger.Debug("DB", "CONNSTR : ", connStr)
dbpool.db, err = sqlx.Connect(driver, connStr)
if err != nil {
return err
} else {
dbpool.db.SetMaxOpenConns(poolSize)
dbpool.db.SetMaxIdleConns(poolSize)
dbpool.db.SetConnMaxLifetime(5 * time.Minute)
dbpool.poolSize = poolSize
dbpool.count = 0
return nil
}
}

12
src/go.mod

@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
module moffas_go
go 1.23.1
require (
github.com/go-sql-driver/mysql v1.8.1
github.com/jmoiron/sqlx v1.4.0
github.com/lib/pq v1.10.9
golang.org/x/crypto v0.27.0
)
require filippo.io/edwards25519 v1.1.0 // indirect

12
src/go.sum

@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=

245
src/handlers/auth.go

@ -0,0 +1,245 @@ @@ -0,0 +1,245 @@
package handlers
import (
"encoding/json"
"fmt"
"io"
"moffas_go/helper"
"moffas_go/logger"
"moffas_go/moffas"
"net/http"
)
// ====== HANDLER FOR /auth ======
func Auth(w http.ResponseWriter, r *http.Request) {
var err error
var res string
var ctxkey HTTPContextKey = "requsetID"
reference_id := r.Context().Value(ctxkey).(string)
defer func() {
logger.Info(reference_id, "Response body: ", res)
}()
// ---- SET THE RESPONSE TYPE TO application/json ----
w.Header().Set("Content-Type", "application/json")
response := map[string]any{
"error_code": "000000000",
"error_message": "",
}
// ---- CHECK REQUEST METHOD ----
if r.Method == http.MethodPost {
// ---- VERIFY THE CONTENT IS JSON ----
if contentType := r.Header.Get("Content-Type"); contentType != "application/json" {
// NOT JSON
logger.Error(reference_id, "!!! INVALID CONTENT-TYPE: ", contentType)
response["error_code"] = "400000001"
response["error_message"] = "Bad Request. Not JSON"
res, _ = helper.JSONencode(response)
http.Error(w, res, http.StatusBadRequest)
return
}
// ---- READ THE POST BODY ----
var body []byte
body, err = io.ReadAll(r.Body)
if err != nil {
// ---- FAILED TO READ BODY ----
logger.Error(reference_id, err)
response["error_code"] = "400000002"
response["error_message"] = "Bad Request. Can't read POST body"
res, _ = helper.JSONencode(response)
http.Error(w, res, http.StatusBadRequest)
return
}
bodyString := string(body)
logger.Info(reference_id, "-- POST BODY: ", bodyString)
// ---- EXTRACT DATA FROM JSON BODY ----
type Request struct {
Half_nonce string `json:"half_nonce"`
Username string `json:"username"`
}
var req Request
err = json.Unmarshal(body, &req)
if err != nil {
// ---- FAILED TO DECODE JSON ----
logger.Error(reference_id, err)
response["error_code"] = "400000003"
response["error_message"] = "Bad Request. Invalid JSON"
res, _ = helper.JSONencode(response)
http.Error(w, res, http.StatusBadRequest)
return
}
// ---- CHECK THE MANDATORY FIELDS ----
if len(req.Half_nonce) != 8 || req.Username == "" {
// ---- MISSING OR INVALID MANDATORY FIELD ----
logger.Error(reference_id, "!!! MANDATORY FIELD NOT FOUND OR INVALID")
response["error_code"] = "400000003"
response["error_message"] = "Bad Request. Missing/Invalid Mandatory Fields"
res, _ = helper.JSONencode(response)
http.Error(w, res, http.StatusBadRequest)
return
}
// ---- GENERATE CHALLENGE ----
challenge, err := moffas.Generate_challenge(reference_id, req.Username, req.Half_nonce)
if err != nil {
// ---- ERROR QUERYING USER DATA ----
logger.Error(reference_id, "!!! ERROR GENERATING CHALLENGE")
logger.Error(reference_id, err)
challenge.Salt, _ = helper.GenerateRandomString(16)
challenge.Iterations = 10000
}
// ---- PREPARE RESPONSE BODY ----
response["full_nonce"] = challenge.Full_nonce
response["salt"] = challenge.Salt
response["iterations"] = challenge.Iterations
res, _ = helper.JSONencode(response)
fmt.Fprint(w, res)
return
} else {
// ---- METHOD NOT ALLOWED (not POST) ----
response["error_code"] = "405000001"
response["error_message"] = "Method Not Allowed"
res, _ = helper.JSONencode(response)
http.Error(w, res, http.StatusMethodNotAllowed)
return
}
}
// ====== HANDLER FOR /verify ======
func Verify(w http.ResponseWriter, r *http.Request) {
var err error
var res string
var ctxkey HTTPContextKey = "requsetID"
reference_id := r.Context().Value(ctxkey).(string)
defer func() {
logger.Info(reference_id, "Response body: ", res)
}()
// ---- SET THE RESPONSE TYPE TO application/json ----
w.Header().Set("Content-Type", "application/json")
response := map[string]any{
"error_code": "000000000",
"error_message": "",
}
// ---- CHECK REQUEST METHOD ----
if r.Method == http.MethodPost {
// ---- VERIFY THE CONTENT IS JSON ----
if contentType := r.Header.Get("Content-Type"); contentType != "application/json" {
// NOT JSON
logger.Error(reference_id, "!!! INVALID CONTENT-TYPE: ", contentType)
response["error_code"] = "400000001"
response["error_message"] = "Bad Request. Not JSON"
res, _ = helper.JSONencode(response)
http.Error(w, res, http.StatusBadRequest)
return
}
// ---- READ THE POST BODY ----
var body []byte
body, err = io.ReadAll(r.Body)
if err != nil {
// FAILED TO READ BODY
logger.Error(reference_id, err)
response["error_code"] = "400000002"
response["error_message"] = "Bad Request. Can't read POST body"
res, _ = helper.JSONencode(response)
http.Error(w, res, http.StatusBadRequest)
return
}
bodyString := string(body)
logger.Info(reference_id, "-- POST BODY: ", bodyString)
// ---- EXTRACT DATA FROM JSON BODY ----
type Request struct {
Full_nonce string `json:"full_nonce"`
Client_hash string `json:"client_hash"`
Next_nonce string `json:"next_nonce"`
}
var req Request
err = json.Unmarshal(body, &req)
if err != nil {
// ---- FAILED TO DECODE JSON ----
logger.Error(reference_id, "!!! FAILED TO DECODE JSON")
logger.Error(reference_id, err)
response["error_code"] = "400000003"
response["error_message"] = "Bad Request. Invalid JSON"
res, _ = helper.JSONencode(response)
http.Error(w, res, http.StatusBadRequest)
return
}
// ---- CHECK MANDATORY FIELDS ----
if len(req.Full_nonce) != 16 || req.Client_hash == "" || len(req.Next_nonce) != 8 {
// MISSING MANDATORY FIELD
logger.Error(reference_id, "!!! MISSING OR INVALID MANDATORY FIELD")
logger.Error(reference_id, "len(full_nonce) : ", len(req.Full_nonce))
logger.Error(reference_id, "client_hash : ", req.Client_hash)
logger.Error(reference_id, "len(next_nonce) : ", len(req.Next_nonce))
response["error_code"] = "400000003"
response["error_message"] = "Bad Request. Missing/Invalid Mandatory Fields"
res, _ = helper.JSONencode(response)
http.Error(w, res, http.StatusBadRequest)
return
}
// ---- VERIFY CHALLENGE RESPONSE ----
challenge_data, err := moffas.Verify_challenge(reference_id, req.Full_nonce, req.Client_hash, req.Next_nonce)
if err != nil {
// ---- ERROR QUERYING USER DATA ----
logger.Error(reference_id, "!!! FAILED TO VERIFY CHALLENGE")
logger.Error(reference_id, err)
response["error_code"] = "401000001"
response["error_message"] = "Unauthorized"
res, _ = helper.JSONencode(response)
http.Error(w, res, http.StatusUnauthorized)
return
}
// ---- PREPARE RESPONSE BODY ----
response["session_id"] = challenge_data.Session_id
response["final_nonce"] = challenge_data.Final_nonce
response["server_hash"] = challenge_data.Server_hash
data := make(map[string]interface{})
premium_voice, ok := challenge_data.Organization_data["premium_voice"]
if ok {
data["premium_voice"] = premium_voice
} else {
data["premium_voice"] = 0
}
data["privileges"] = challenge_data.Privileges
data["tier"] = challenge_data.Tier
data["full_name"] = challenge_data.Full_name
response["user_data"] = data
res, _ = helper.JSONencode(response)
fmt.Fprint(w, res)
return
} else {
// ---- METHOD NOT ALLOWED (not POST) ----
response["error_code"] = "405000001"
response["error_message"] = "Method Not Allowed"
res, _ = helper.JSONencode(response)
http.Error(w, res, http.StatusMethodNotAllowed)
return
}
}

167
src/handlers/greeting.go

@ -0,0 +1,167 @@ @@ -0,0 +1,167 @@
package handlers
import (
"encoding/json"
"fmt"
"io"
"moffas_go/helper"
"moffas_go/logger"
"net/http"
"net/url"
"strconv"
"strings"
)
type HTTPContextKey string
func Greeting(w http.ResponseWriter, r *http.Request) {
// GENERATE RANDOMIZED reference_id TO IDENTIFY REQUESTS UNIQUELY
var err error
var res string
type reqStruct struct {
Name string `json:"name"`
Password string `json:"password"`
Salt string `json:"salt"`
Iterations int `json:"iterations"`
}
var req reqStruct = reqStruct{}
var ctxkey HTTPContextKey = "requsetID"
reference_id := r.Context().Value(ctxkey).(string)
defer func() {
logger.Info(reference_id, "Response body: ", res)
}()
// SET THE RESPONSE TYPE TO application/json
w.Header().Set("Content-Type", "application/json")
response := map[string]any{
"error_code": "000000000",
"error_message": "",
}
// GET THE REQUEST QUERY PARAMS AND THE RAW QUERY STRING
var queryString string = string(r.URL.RawQuery)
var query url.Values = r.URL.Query()
if r.Method == http.MethodGet {
// GET NAME FROM QUERY PARAMS
names := query["name"]
req.Name = strings.Join(names, "")
// PREPARE THE RESPONSE
if req.Name == "" {
response["message"] = "Hello!"
} else {
response["message"] = "Hello, " + req.Name + "!"
}
response["reference_id"] = reference_id
response["query_string"] = queryString
req.Password = query["password"][0]
req.Salt = query["salt"][0]
req.Iterations, _ = strconv.Atoi(query["iterations"][0])
if req.Password == "" {
req.Password = "rahasia"
}
response["password"] = req.Password
if req.Salt == "" {
req.Salt, _ = helper.GenerateRandomString(16)
}
response["salt"] = req.Salt
if req.Iterations <= 0 {
req.Iterations = 10000
}
response["iterations"] = req.Iterations
response["salted_password"] = helper.PBKDF2_SHA256(req.Password, req.Salt, req.Iterations)
// SEND THE RESPONSE AS JSON
res, _ = helper.JSONencode(response)
fmt.Fprint(w, res)
return
} else if r.Method == http.MethodPost {
// THE RECEIVED REQUEST METHOD IS POST
logger.Info(reference_id, "Request Method: POST")
// VERIFY THE CONTENT IS JSON
if contentType := r.Header.Get("Content-Type"); contentType != "application/json" {
// NOT JSON
logger.Error(reference_id, "Invalid content-type: ", contentType)
response["error_code"] = "400000001"
response["error_message"] = "Bad Request. Not JSON"
res, _ = helper.JSONencode(response)
http.Error(w, res, http.StatusBadRequest)
return
}
// READ THE POST BODY
var body []byte
body, err = io.ReadAll(r.Body)
if err != nil {
// FAILED TO READ BODY
logger.Error(reference_id, err)
response["error_code"] = "400000002"
response["error_message"] = "Bad Request. Can't read POST body"
// logger.E("ERROR : ", err)
res, _ = helper.JSONencode(response)
http.Error(w, res, http.StatusBadRequest)
return
}
bodyString := string(body)
logger.Info(reference_id, "POST BODY: ", bodyString)
// EXTRACT DATA FROM JSON BODY
err = json.Unmarshal(body, &req)
if err != nil {
// FAILED TO DECODE JSON
logger.Error(reference_id, err)
response["error_code"] = "400000003"
response["error_message"] = "Bad Request. Invalid JSON"
// logger.E("ERROR : ", err)
res, _ = helper.JSONencode(response)
http.Error(w, res, http.StatusBadRequest)
return
}
if req.Password == "" {
req.Password = "rahasia"
}
response["password"] = req.Password
if req.Salt == "" {
req.Salt, _ = helper.GenerateRandomString(16)
}
response["salt"] = req.Salt
if req.Iterations <= 0 {
req.Iterations = 10000
}
response["iterations"] = req.Iterations
response["salted_password"] = helper.PBKDF2_SHA256(req.Password, req.Salt, req.Iterations)
// PREPARE THE RESPONSE
if req.Name == "" {
response["message"] = "Hello!"
} else {
response["message"] = "Hello, " + req.Name + "!"
}
response["reference_id"] = reference_id
response["query_string"] = queryString
// SEND THE RESPONSE AS JSON
res, _ = helper.JSONencode(response)
fmt.Fprint(w, res)
return
} else {
// METHOD NOT ALLOWED (not GET or POST)
response["error_code"] = "405000001"
response["error_message"] = "Method Not Allowed"
res, _ = helper.JSONencode(response)
http.Error(w, res, http.StatusMethodNotAllowed)
return
}
}

12
src/handlers/hello.go

@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
package handlers
import (
"fmt"
"net/http"
)
func Hello(w http.ResponseWriter, r *http.Request) {
res := "Hello World!"
fmt.Fprint(w, res)
}

70
src/helper/crypto.go

@ -0,0 +1,70 @@ @@ -0,0 +1,70 @@
package helper
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"fmt"
"golang.org/x/crypto/pbkdf2"
)
func GenerateRandomString(length int) (string, error) {
// Character set untuk random string
charset := "0123456789abcdefghijklmnopqrstuvwxyz"
// Membuat slice byte untuk menampung hasil random string
result := make([]byte, length)
// Menggunakan crypto/rand untuk mengisi slice dengan data acak
for i := range result {
// Membuat byte acak untuk digunakan sebagai indeks
randomByte := make([]byte, 1)
_, err := rand.Read(randomByte)
if err != nil {
fmt.Println("Error generating random string:", err)
return "", err
}
// Menentukan indeks dalam charset menggunakan byte acak
index := int(randomByte[0]) % len(charset)
result[i] = charset[index]
}
return string(result), nil
}
func SHA256(data string) string {
shaHash := sha256.Sum256([]byte(data))
shaHashStr := hex.EncodeToString(shaHash[:])
return shaHashStr
}
func HMAC_SHA256(inputData string, hexKey string) (string, error) {
key, err := hex.DecodeString(hexKey)
if err != nil {
return "", err
}
// Membuat HMAC-SHA256 menggunakan key yang didecode
hmacHash := hmac.New(sha256.New, key)
_, err = hmacHash.Write([]byte(inputData))
if err != nil {
return "", err
}
// Menghasilkan hash dalam bentuk hexadecimal string
hmacHashStr := hex.EncodeToString(hmacHash.Sum(nil))
return hmacHashStr, nil
}
func PBKDF2_SHA256(password string, salt string, iterations int) string {
// Menghasilkan key 256-bit (32 bytes)
bpassword := []byte(password)
bsalt := []byte(salt)
key := pbkdf2.Key(bpassword, bsalt, iterations, 32, sha256.New)
// Mengonversi hasil key ke hexadecimal string
return hex.EncodeToString(key)
}

36
src/helper/util.go

@ -0,0 +1,36 @@ @@ -0,0 +1,36 @@
package helper
import (
"bytes"
"encoding/json"
"fmt"
"os"
)
func JSONencode(data any) (string, error) {
var buffer bytes.Buffer
// Buat encoder yang menulis ke buffer
encoder := json.NewEncoder(&buffer)
// Set agar encoder tidak melakukan escaping HTML
encoder.SetEscapeHTML(false)
// Encode data ke JSON dan simpan ke buffer
if err := encoder.Encode(data); err != nil {
fmt.Println("Error encoding JSON:", err)
return "", err
}
// Mendapatkan hasil JSON sebagai string
jsonString := buffer.String()
return jsonString, nil
}
func GetEnv(key, fallback string) string {
value := os.Getenv(key)
if len(value) == 0 {
return fallback
}
return value
}

102
src/logger/logger.go

@ -0,0 +1,102 @@ @@ -0,0 +1,102 @@
package logger
import (
"fmt"
"moffas_go/appinfo"
"runtime"
"strconv"
"strings"
"time"
)
const (
CRITICAL = iota
ERROR
WARNING
EVENT
INFO
DEBUG
)
var debugLevel int = ERROR
func SetDebugLevel(level int) {
debugLevel = level
}
func getLogPrefix() string {
timestr := time.Now().Format(time.RFC3339Nano)
// 2024-09-22T15:54:51.665494100
timestrs := strings.Split(timestr, "+")
tstr := (timestrs[0] + "000000000")
timestrs[0] = tstr[:29]
timestr = strings.Join(timestrs, "+")
version := appinfo.Version()
// Mengambil informasi tentang caller satu level di atas (yaitu fungsi main dalam hal ini)
funcName := ""
pc, _, line, ok := runtime.Caller(2)
if !ok {
fmt.Println("No caller information")
} else {
function := runtime.FuncForPC(pc)
funcName = function.Name()
}
funcString := ""
if debugLevel == DEBUG {
funcString = funcName + ":" + strconv.Itoa(line) + " - "
}
// Mendapatkan nama fungsi dari program counter (pc)
return timestr + " - " + appinfo.AppName() + " - VERSION:" + version + " - " + funcString
}
func Debug(id string, v ...any) {
if debugLevel >= DEBUG {
prefix := getLogPrefix() + id + " - DEBUG - "
msgs := fmt.Sprint(v...)
fmt.Println(prefix + msgs)
}
}
func Info(id string, v ...any) {
if debugLevel >= INFO {
prefix := getLogPrefix() + id + " - INFO - "
msgs := fmt.Sprint(v...)
fmt.Println(prefix + msgs)
}
}
func Event(id string, v ...any) {
if debugLevel >= EVENT {
prefix := getLogPrefix() + id + " - EVENT - "
msgs := fmt.Sprint(v...)
fmt.Println(prefix + msgs)
}
}
func Warning(id string, v ...any) {
if debugLevel >= WARNING {
prefix := getLogPrefix() + id + " - WARNING - "
msgs := fmt.Sprint(v...)
fmt.Println(prefix + msgs)
}
}
func Error(id string, v ...any) {
if debugLevel >= ERROR {
prefix := getLogPrefix() + id + " - ERROR - "
msgs := fmt.Sprint(v...)
fmt.Println(prefix + msgs)
}
}
func Critical(id string, v ...any) {
if debugLevel >= CRITICAL {
prefix := getLogPrefix() + id + " - CRITICAL - "
msgs := fmt.Sprint(v...)
fmt.Println(prefix + msgs)
}
}

129
src/main.go

@ -0,0 +1,129 @@ @@ -0,0 +1,129 @@
package main
import (
"context"
"moffas_go/db"
"moffas_go/handlers"
"moffas_go/helper"
"moffas_go/logger"
"net/http"
"os"
"strconv"
"time"
)
func generate_reference_id(timer int64) string {
timeBase36 := strconv.FormatUint(uint64(timer), 36)
nonce8, _ := helper.GenerateRandomString(8)
reference_id := timeBase36 + "." + nonce8
return reference_id
}
// Middleware untuk menangani CORS
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*") // Mengizinkan semua asal
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS") // Metode yang diizinkan
w.Header().Set("Access-Control-Allow-Headers", "Content-Type") // Header yang diizinkan
if r.Method == "OPTIONS" {
// Jika request adalah OPTIONS (preflight), kembalikan status 204 tanpa body
w.WriteHeader(http.StatusNoContent)
return
}
// GENERATE UNIQUE REQUEST ID FOR REFERENCE
start := time.Now()
request_id := generate_reference_id(start.UnixNano())
// LOG START, METHOD AND PATH
logger.Event(request_id, "--- Handle Request Started", r.Method, " ", r.URL.Path)
// LOG THE REQUEST RAW QUERY STRING
logger.Info(request_id, "--- Query String: ", string(r.URL.RawQuery))
// LOG HEADERS
logger.Info(request_id, "--- Headers: ")
for name, values := range r.Header {
// Loop over all values for the name.
for _, value := range values {
logger.Info(request_id, "[", name, "] : ", value)
}
}
// CALL THE REAL HANDLER
var ctxkey handlers.HTTPContextKey = "requsetID"
ctx := context.WithValue(r.Context(), ctxkey, request_id)
next.ServeHTTP(w, r.WithContext(ctx))
// LOG COMPLETEION AND DURATION
duration := time.Since(start)
logger.Event(request_id, "--- Handle Request Completed in : ", duration)
})
}
func main() {
// CONFIGURE LOGGER
dblv := helper.GetEnv("DEBUG_LEVEL", "INFO")
if dblv == "DEBUG" {
logger.SetDebugLevel(logger.DEBUG)
} else if dblv == "INFO" {
logger.SetDebugLevel(logger.INFO)
} else if dblv == "EVENT" {
logger.SetDebugLevel(logger.EVENT)
} else if dblv == "WARNING" {
logger.SetDebugLevel(logger.WARNING)
} else if dblv == "ERROR" {
logger.SetDebugLevel(logger.ERROR)
} else {
logger.SetDebugLevel(logger.INFO)
}
// CONFIGURE AND INITIALIZE DB
DBDRIVER := helper.GetEnv("DBDRIVER", "postgres")
DBUSER := helper.GetEnv("DBUSER", "magang")
DBPASS := helper.GetEnv("DBPASS", "magang")
DBHOST := helper.GetEnv("DBHOST", "ningning.cayangqu.com")
DBPORT, err := strconv.Atoi(helper.GetEnv("DBPORT", "5432"))
if err != nil {
DBPORT = 5432
}
DBPOOLSIZE, err := strconv.Atoi(helper.GetEnv("DBPOOLSIZE", "20"))
if err != nil {
DBPOOLSIZE = 20
}
DBNAME := helper.GetEnv("DBNAME", "magang")
logger.Info("MAIN", "DBDRIVER : ", DBDRIVER)
logger.Info("MAIN", "DBHOST : ", DBHOST)
logger.Info("MAIN", "DBPORT : ", DBPORT)
logger.Debug("MAIN", "DBUSER : ", DBUSER)
logger.Debug("MAIN", "DBPASS : ", DBPASS)
logger.Info("MAIN", "DBNAME : ", DBNAME)
err = db.InitDB(DBDRIVER, DBHOST, DBPORT, DBUSER, DBPASS, DBNAME, DBPOOLSIZE)
if err != nil {
logger.Error("MAIN", "!!! FAILED TO INITIATE DB POOL..", err)
os.Exit(1)
} else {
logger.Info("MAIN", "Database Connection Pool Initated.")
}
// CONFIGURE ENDPOINTS
paths := make(map[string]func(http.ResponseWriter, *http.Request))
paths["/"] = handlers.Greeting
paths["/hello"] = handlers.Hello
paths["/auth"] = handlers.Auth
paths["/verify"] = handlers.Verify
mux := http.NewServeMux()
for path, handler := range paths {
mux.HandleFunc(path, handler)
}
// START SERVER
logger.Info("MAIN", "Server started on Port :48000")
http.ListenAndServe(":48000", corsMiddleware(mux))
}

341
src/moffas/moffas_auth.go

@ -0,0 +1,341 @@ @@ -0,0 +1,341 @@
package moffas
import (
"encoding/json"
"errors"
"moffas_go/db"
"moffas_go/helper"
"moffas_go/logger"
"strconv"
"strings"
"time"
)
type GeneratedChallenge struct {
Full_nonce string
User_id int64
Salt string
Iterations int
}
type VerificationResult struct {
Server_hash string
Final_nonce string
Session_id string
Privileges map[string]interface{}
Tier string
Full_name string
Organization_data map[string]interface{}
}
func Generate_challenge(reference_id, username string, half_nonce string) (GeneratedChallenge, error) {
logger.Debug(reference_id, " -- start generate_challenge")
startTime := time.Now()
defer func() {
dur := time.Since(startTime)
logger.Debug(reference_id, " -- generate_challenge done in ", dur)
}()
conn, err := db.GetConnection()
if err != nil {
return GeneratedChallenge{}, err
}
defer db.ReleaseConnection()
current_time := time.Now().Unix()
nonce, err := helper.GenerateRandomString(8)
if err != nil {
return GeneratedChallenge{}, err
}
full_nonce := half_nonce + nonce
logger.Info(reference_id, "HALF NONCE : ", half_nonce)
logger.Info(reference_id, "USERNAME : ", username)
logger.Info(reference_id, "FULL NONCE : ", full_nonce)
type DBResult struct {
User_id int64 `db:"id"`
Salt string `db:"salt"`
Iterations int `db:"iterations"`
Timestamp int64 `db:"tstamp"`
SaltedPasword string `db:"saltedpassword"`
}
query := `
SELECT
a.id,a.salt,a.saltedpassword,a.iterations,
COALESCE(b.tstamp,0) as tstamp
FROM servouser.user a
LEFT JOIN servouser.challenge_response b ON b.user_id = a.id
WHERE a.username = $1 AND a.st=1 LIMIT 1`
sql := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(query, "\t", " "), "\n", " "), " ", " ")
logger.Debug(reference_id, "SQL : ", sql)
dbresult := DBResult{}
err = conn.Get(&dbresult, sql, username)
if err != nil {
return GeneratedChallenge{}, err
}
logger.Info(reference_id, "USER ID : ", dbresult.User_id)
waittime, err := strconv.Atoi(helper.GetEnv("AUTH_WAIT_TIME", "10"))
if err != nil {
waittime = 10
}
wait_until := dbresult.Timestamp + int64(waittime)
logger.Debug(reference_id, "LAST AUTH REQUEST : ", dbresult.Timestamp)
logger.Debug(reference_id, "WAIT TIME : ", waittime)
logger.Debug(reference_id, "WAIT UNTIL : ", wait_until)
logger.Debug(reference_id, "CURRENT TIME : ", current_time)
if wait_until > current_time {
logger.Info(reference_id, "!!! WAIT TIME FOR THE USER ID HAS NOT ELAPSED")
err = errors.New("wait time has not elapsed")
return GeneratedChallenge{}, err
}
//----- CONTEKAN DOANG -----
sharedSecret := helper.GetEnv("SHARED_SECRET", "Winter is coming")
logger.Info(reference_id, "SHARED SECRET : ", sharedSecret)
logger.Info(reference_id, "SALTED PASSWORD : ", dbresult.SaltedPasword)
hash1, err := helper.HMAC_SHA256(sharedSecret, dbresult.SaltedPasword)
if err != nil {
return GeneratedChallenge{}, err
}
logger.Info(reference_id, "HASH1 : ", hash1)
calculated_client_hash, err := helper.HMAC_SHA256(full_nonce, hash1)
if err != nil {
return GeneratedChallenge{}, err
}
logger.Info(reference_id, "CALCULATED CLIENT HASH : ", calculated_client_hash)
//----- CONTEKAN DOANG -----
err = upsert_challenge(reference_id, full_nonce, dbresult.User_id)
if err != nil {
return GeneratedChallenge{}, err
}
return GeneratedChallenge{
User_id: dbresult.User_id,
Salt: dbresult.Salt,
Iterations: dbresult.Iterations,
Full_nonce: full_nonce,
}, nil
}
func Verify_challenge(reference_id string, full_nonce string, client_hash string, next_nonce string) (VerificationResult, error) {
logger.Debug(reference_id, " start verify_challenge")
startTime := time.Now()
defer func() {
dur := time.Since(startTime)
logger.Debug(reference_id, " -- verify_challenge done in ", dur)
}()
conn, err := db.GetConnection()
if err != nil {
return VerificationResult{}, err
}
defer db.ReleaseConnection()
current_time := time.Now().Unix()
nonce, err := helper.GenerateRandomString(8)
if err != nil {
return VerificationResult{}, err
}
logger.Info(reference_id, "FULL NONCE : ", full_nonce)
type DBResult struct {
User_id int64 `db:"user_id"`
Timestamp int64 `db:"tstamp"`
Fullname string `db:"full_name"`
Privileges string `db:"privileges"`
Tier string `db:"tier"`
SaltedPasword string `db:"saltedpassword"`
Organization_data string `db:"organization_data"`
}
dbresult := DBResult{}
query := `
SELECT
a.user_id, a.tstamp,
b.full_name, b.saltedpassword,
c.privileges,
d.tier, d.data organization_data
FROM servouser.challenge_response a
LEFT JOIN servouser.user b ON b.id = a.user_id
LEFT JOIN servouser.role c ON c.name = b.role
LEFT JOIN servouser.organization d ON d.id = b.organization_id
WHERE a.full_nonce = $1 AND b.id IS NOT NULL AND c.privileges IS NOT NULL AND b.st = 1
LIMIT 1`
sql := strings.ReplaceAll(strings.ReplaceAll(query, "\t", " "), "\n", " ")
logger.Debug(reference_id, "SQL : ", sql)
err = conn.Get(&dbresult, sql, full_nonce)
if err != nil {
return VerificationResult{}, err
}
rep_nonce := full_nonce[0:15]
logger.Debug(reference_id, "replacement nonce : ", rep_nonce)
query = "UPDATE servouser.challenge_response SET full_nonce = $1 WHERE full_nonce = $2"
logger.Debug(reference_id, "SQL : ", query)
_, err = conn.Exec(query, rep_nonce, full_nonce)
if err != nil {
return VerificationResult{}, err
}
timeout, err := strconv.Atoi(helper.GetEnv("AUTH_TIMEOUT", "300"))
if err != nil {
timeout = 300
}
logger.Info(reference_id, "AUTH TIMEOUT : ", timeout)
expire_limit := dbresult.Timestamp + int64(timeout)
logger.Info(reference_id, "AUTH EXPIRATION TIME : ", expire_limit)
logger.Info(reference_id, "CURRENT TIME : ", current_time)
if current_time > expire_limit {
logger.Error(reference_id, "!!! CHALLENGE EXPIRED")
err = errors.New("client expired")
return VerificationResult{}, err
}
sharedSecret := helper.GetEnv("SHARED_SECRET", "Winter is coming")
logger.Info(reference_id, "SHARED SECRET : ", sharedSecret)
logger.Info(reference_id, "SALTED PASSWORD : ", dbresult.SaltedPasword)
hash1, err := helper.HMAC_SHA256(sharedSecret, dbresult.SaltedPasword)
if err != nil {
return VerificationResult{}, err
}
logger.Info(reference_id, "HASH1 : ", hash1)
calculated_client_hash, err := helper.HMAC_SHA256(full_nonce, hash1)
if err != nil {
return VerificationResult{}, err
}
logger.Info(reference_id, "CALCULATED CLIENT HASH : ", calculated_client_hash)
logger.Info(reference_id, "CLIENT HASH : ", client_hash)
if calculated_client_hash != client_hash {
logger.Warning(reference_id, "!!! CLIENT HASH DOES NOT MATCH STORED CHALLENGE RESPONSE")
err = errors.New("client hash does not match stored challenge response")
return VerificationResult{}, err
}
logger.Info(reference_id, "NEXT NONCE : ", next_nonce)
final_nonce := next_nonce + nonce
logger.Info(reference_id, "FINAL NONCE : ", final_nonce)
server_hash, err := helper.HMAC_SHA256(final_nonce, hash1)
if err != nil {
return VerificationResult{}, err
}
logger.Info(reference_id, "SERVER HASH : ", server_hash)
session_secret, err := helper.HMAC_SHA256(full_nonce+final_nonce, hash1)
if err != nil {
return VerificationResult{}, err
}
logger.Info(reference_id, "SESSION SECRET : ", session_secret)
session_id, _ := helper.GenerateRandomString(16)
logger.Info(reference_id, "SESSION ID : ", session_id)
err = upsert_session(reference_id, session_id, dbresult.User_id, session_secret)
if err != nil {
return VerificationResult{}, err
}
privileges := make(map[string]interface{})
organization_data := make(map[string]interface{})
err = json.Unmarshal([]byte(dbresult.Privileges), &privileges)
if err != nil {
return VerificationResult{}, err
}
err = json.Unmarshal([]byte(dbresult.Organization_data), &organization_data)
if err != nil {
return VerificationResult{}, err
}
return VerificationResult{
Final_nonce: final_nonce,
Server_hash: server_hash,
Session_id: session_id,
Privileges: privileges,
Tier: dbresult.Tier,
Full_name: dbresult.Fullname,
Organization_data: organization_data,
}, nil
}
func upsert_challenge(reference_id, full_nonce string, user_id int64) error {
logger.Debug(reference_id, " start upsert_challenge")
startTime := time.Now()
defer func() {
dur := time.Since(startTime)
logger.Debug(reference_id, " -- upsert_challenge done in ", dur)
}()
conn, err := db.GetConnection()
if err != nil {
return err
}
defer db.ReleaseConnection()
query := "DELETE FROM servouser.challenge_response WHERE full_nonce = $1 OR user_id = $2"
sql := strings.ReplaceAll(strings.ReplaceAll(query, "\t", " "), "\n", " ")
logger.Debug(reference_id, "SQL : ", sql)
_, err = conn.Exec(query, full_nonce, user_id)
if err != nil {
return err
}
query = `
INSERT INTO
servouser.challenge_response (full_nonce,user_id,challenge_response,tstamp)
VALUES
($1,$2,'0123456789abcdefghijklmnopqrstuv',$3)`
sql = strings.ReplaceAll(strings.ReplaceAll(query, "\t", " "), "\n", " ")
logger.Debug(reference_id, "SQL : ", sql)
_, err = conn.Exec(sql, full_nonce, user_id, time.Now().Unix())
return err
}
func upsert_session(reference_id, session_id string, user_id int64, session_secret string) error {
logger.Debug(reference_id, " start upsert_challenge")
startTime := time.Now()
defer func() {
dur := time.Since(startTime)
logger.Debug(reference_id, " -- upsert_session done in ", dur)
}()
conn, err := db.GetConnection()
if err != nil {
return err
}
defer db.ReleaseConnection()
query := "DELETE FROM servouser.session WHERE session_id = $1 OR user_id = $2"
sql := strings.ReplaceAll(strings.ReplaceAll(query, "\t", " "), "\n", " ")
logger.Debug(reference_id, "SQL : ", sql)
_, err = conn.Exec(query, session_id, user_id)
if err != nil {
return err
}
query = `
INSERT INTO
servouser.session (session_id,user_id,session_secret,tstamp,st,last_ms_tstamp,last_sequence)
VALUES
($1,$2,$3,$4,1,0,0)`
sql = strings.ReplaceAll(strings.ReplaceAll(query, "\t", " "), "\n", " ")
logger.Debug(reference_id, "SQL : ", sql)
_, err = conn.Exec(query, session_id, user_id, session_secret, time.Now().Unix())
return err
}

29
test/test_auth1.http

@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
### AUTH
POST https://servobot.ai/servoauth/auth
Content-Type: application/json
{
"username":"zadahouse",
"half_nonce":"01234567"
}
### VERIFY
POST https://servobot.ai/servoauth/verify
Content-Type: application/json
{
"full_nonce":"01234567eko42163",
"client_hash":"3ec5cf7ea34a41ca4675f24b8f9cc1696cd1c60479020c083ba724bf6e93b705",
"next_nonce":"76543210"
}
### GREETING
POST https://servobot.ai/servoauth/
Content-Type: application/json
{
"name":"emil",
"password":"spider-man"
}
Loading…
Cancel
Save