init
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
const (
|
||||||
|
metricsPath = "/metrics"
|
||||||
|
)
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DB is the global database connection pool
|
||||||
|
var DB *sql.DB
|
||||||
|
|
||||||
|
func InitDB() (*sql.DB, error) {
|
||||||
|
// Get connection details from environment variables (loaded in main)
|
||||||
|
connStr := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true",
|
||||||
|
os.Getenv("DB_USER"),
|
||||||
|
os.Getenv("DB_PASSWORD"),
|
||||||
|
os.Getenv("DB_HOST"),
|
||||||
|
os.Getenv("DB_PORT"),
|
||||||
|
os.Getenv("DB_NAME"),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Open the database connection
|
||||||
|
var err error
|
||||||
|
DB, err = sql.Open("mysql", connStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error opening database: %v", err)
|
||||||
|
}
|
||||||
|
// Set connection pool parameters
|
||||||
|
DB.SetMaxOpenConns(100) // Maximum number of open connections to the database
|
||||||
|
DB.SetMaxIdleConns(100) // Maximum number of connections in the idle connection pool
|
||||||
|
DB.SetConnMaxLifetime(5 * time.Minute) // Maximum amount of time a connection may be reused
|
||||||
|
|
||||||
|
// Check if the database connection is working
|
||||||
|
if err := DB.Ping(); err != nil {
|
||||||
|
log.Printf("Database connection lost: %v. Reconnecting...", err)
|
||||||
|
DB, err = InitDB()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to reconnect to database: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Print("Database connected successfully!")
|
||||||
|
return DB, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
// Package docs Code generated by swaggo/swag. DO NOT EDIT
|
||||||
|
package docs
|
||||||
|
|
||||||
|
import "github.com/swaggo/swag"
|
||||||
|
|
||||||
|
const docTemplate = `{
|
||||||
|
"schemes": {{ marshal .Schemes }},
|
||||||
|
"swagger": "2.0",
|
||||||
|
"info": {
|
||||||
|
"description": "{{escape .Description}}",
|
||||||
|
"title": "{{.Title}}",
|
||||||
|
"contact": {
|
||||||
|
"name": "Darrel Israel",
|
||||||
|
"email": "d.israel.psa@gmail.com"
|
||||||
|
},
|
||||||
|
"version": "{{.Version}}"
|
||||||
|
},
|
||||||
|
"host": "{{.Host}}",
|
||||||
|
"basePath": "{{.BasePath}}",
|
||||||
|
"paths": {},
|
||||||
|
"securityDefinitions": {
|
||||||
|
"BearerToken": {
|
||||||
|
"type": "apiKey",
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
||||||
|
var SwaggerInfo = &swag.Spec{
|
||||||
|
Version: "1.0",
|
||||||
|
Host: "",
|
||||||
|
BasePath: "/",
|
||||||
|
Schemes: []string{},
|
||||||
|
Title: "UESS Authentication Microservice",
|
||||||
|
Description: "This is the API for Authentication Microservice for UESS. It doesn't support OAS 3.0 and is only for documentation purposes. The library used doesn't support @server annotation.",
|
||||||
|
InfoInstanceName: "swagger",
|
||||||
|
SwaggerTemplate: docTemplate,
|
||||||
|
LeftDelim: "{{",
|
||||||
|
RightDelim: "}}",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"swagger": "2.0",
|
||||||
|
"info": {
|
||||||
|
"description": "This is the API for Authentication Microservice for UESS. It doesn't support OAS 3.0 and is only for documentation purposes. The library used doesn't support @server annotation.",
|
||||||
|
"title": "UESS Authentication Microservice",
|
||||||
|
"contact": {
|
||||||
|
"name": "Darrel Israel",
|
||||||
|
"email": "d.israel.psa@gmail.com"
|
||||||
|
},
|
||||||
|
"version": "1.0"
|
||||||
|
},
|
||||||
|
"basePath": "/",
|
||||||
|
"paths": {},
|
||||||
|
"securityDefinitions": {
|
||||||
|
"BearerToken": {
|
||||||
|
"type": "apiKey",
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
basePath: /
|
||||||
|
info:
|
||||||
|
contact:
|
||||||
|
email: d.israel.psa@gmail.com
|
||||||
|
name: Darrel Israel
|
||||||
|
description: This is the API for Authentication Microservice for UESS. It doesn't
|
||||||
|
support OAS 3.0 and is only for documentation purposes. The library used doesn't
|
||||||
|
support @server annotation.
|
||||||
|
title: UESS Authentication Microservice
|
||||||
|
version: "1.0"
|
||||||
|
paths: {}
|
||||||
|
securityDefinitions:
|
||||||
|
BearerToken:
|
||||||
|
in: header
|
||||||
|
name: Authorization
|
||||||
|
type: apiKey
|
||||||
|
swagger: "2.0"
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
module authorization
|
||||||
|
|
||||||
|
go 1.25.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/getsentry/sentry-go v0.39.0
|
||||||
|
github.com/go-sql-driver/mysql v1.9.3
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||||
|
github.com/gorilla/mux v1.8.1
|
||||||
|
github.com/joho/godotenv v1.5.1
|
||||||
|
github.com/prometheus/client_golang v1.23.2
|
||||||
|
github.com/redis/go-redis/v9 v9.17.0
|
||||||
|
github.com/rs/cors v1.11.1
|
||||||
|
github.com/swaggo/http-swagger v1.3.4
|
||||||
|
github.com/swaggo/swag v1.16.6
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
|
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||||
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||||
|
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
||||||
|
github.com/go-openapi/spec v0.20.6 // indirect
|
||||||
|
github.com/go-openapi/swag v0.19.15 // indirect
|
||||||
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/mailru/easyjson v0.7.6 // indirect
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
|
github.com/prometheus/common v0.66.1 // indirect
|
||||||
|
github.com/prometheus/procfs v0.16.1 // indirect
|
||||||
|
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect
|
||||||
|
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||||
|
golang.org/x/mod v0.26.0 // indirect
|
||||||
|
golang.org/x/net v0.43.0 // indirect
|
||||||
|
golang.org/x/sync v0.16.0 // indirect
|
||||||
|
golang.org/x/sys v0.35.0 // indirect
|
||||||
|
golang.org/x/text v0.28.0 // indirect
|
||||||
|
golang.org/x/tools v0.35.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.8 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
)
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||||
|
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||||
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
|
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/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
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/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/getsentry/sentry-go v0.39.0 h1:uhnexj8PNCyCve37GSqxXOeXHh4cJNLNNB4w70Jtgo0=
|
||||||
|
github.com/getsentry/sentry-go v0.39.0/go.mod h1:eRXCoh3uvmjQLY6qu63BjUZnaBu5L5WhMV1RwYO8W5s=
|
||||||
|
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||||
|
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||||
|
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.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
|
||||||
|
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
|
||||||
|
github.com/go-openapi/spec v0.20.6 h1:ich1RQ3WDbfoeTqTAb+5EIxNmpKVJZWBNah9RAT0jIQ=
|
||||||
|
github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
|
||||||
|
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-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||||
|
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||||
|
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||||
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
|
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/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/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
|
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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
|
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
||||||
|
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
||||||
|
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/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||||
|
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||||
|
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||||
|
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||||
|
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||||
|
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||||
|
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||||
|
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||||
|
github.com/redis/go-redis/v9 v9.17.0 h1:K6E+ZlYN95KSMmZeEQPbU/c++wfmEvfFB17yEAq/VhM=
|
||||||
|
github.com/redis/go-redis/v9 v9.17.0/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
|
||||||
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
|
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
|
||||||
|
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||||
|
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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe h1:K8pHPVoTgxFJt1lXuIzzOX7zZhZFldJQK/CgKx9BFIc=
|
||||||
|
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
|
||||||
|
github.com/swaggo/http-swagger v1.3.4 h1:q7t/XLx0n15H1Q9/tk3Y9L4n210XzJF5WtnDX64a5ww=
|
||||||
|
github.com/swaggo/http-swagger v1.3.4/go.mod h1:9dAh0unqMBAlbp1uE2Uc2mQTxNMU/ha4UbucIg1MFkQ=
|
||||||
|
github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
|
||||||
|
github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
|
||||||
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
|
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||||
|
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||||
|
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
||||||
|
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
|
||||||
|
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||||
|
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||||
|
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||||
|
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||||
|
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
|
||||||
|
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||||
|
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||||
|
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||||
|
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.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=
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"authorization/helper"
|
||||||
|
"authorization/models"
|
||||||
|
"authorization/services"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AuthorizeHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
var request models.AuthorizationRequest
|
||||||
|
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&request)
|
||||||
|
if err != nil {
|
||||||
|
helper.RespondWithError(w, http.StatusBadRequest, "Invalid request payload")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
allowed := services.Authorize()
|
||||||
|
if !allowed {
|
||||||
|
helper.RespondWithError(w, http.StatusForbidden, "Access denied")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package helper
|
||||||
|
|
||||||
|
const (
|
||||||
|
ContentTypeHeader = "Content-Type"
|
||||||
|
ApplicationJSON = "application/json"
|
||||||
|
ErrorLabel = "error"
|
||||||
|
MessageLabel = "message"
|
||||||
|
ErrorEncodingResponse = "Error encoding response"
|
||||||
|
ErrorFailedtoLogLoginEvent = "Failed to log login event"
|
||||||
|
WarningLabel = "WARNING:"
|
||||||
|
)
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package helper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/getsentry/sentry-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LogInfo logs an info message to both the local log and Sentry based on the environment.
|
||||||
|
func LogInfo(message string) {
|
||||||
|
goEnv := os.Getenv("GO_ENV")
|
||||||
|
|
||||||
|
if goEnv == "" {
|
||||||
|
log.Fatal("GO_ENV is not set in error_logging LogInfo. Please set the GO_ENV environment variable.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if goEnv == "development" || goEnv == "debug" {
|
||||||
|
log.Println("INFO:", message)
|
||||||
|
}
|
||||||
|
if goEnv == "production" || goEnv == "canary" {
|
||||||
|
log.Println("INFO:", message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogWarn logs a warning message to both the local log and Sentry based on the environment.
|
||||||
|
func LogWarn(message string) {
|
||||||
|
goEnv := os.Getenv("GO_ENV")
|
||||||
|
|
||||||
|
if goEnv == "" {
|
||||||
|
log.Fatal("GO_ENV is not set in error_logging LogWarn. Please set the GO_ENV environment variable.")
|
||||||
|
}
|
||||||
|
switch goEnv {
|
||||||
|
case "production", "canary":
|
||||||
|
sentry.CaptureMessage("WARNING: " + message)
|
||||||
|
case "development", "debug":
|
||||||
|
log.Println("WARNING:", message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogError logs an error message to both the local log and Sentry based on the environment.
|
||||||
|
func LogError(err error, message string) {
|
||||||
|
goEnv := os.Getenv("GO_ENV")
|
||||||
|
|
||||||
|
if goEnv == "" {
|
||||||
|
log.Fatal("GO_ENV is not set in error_logging LogError. Please set the GO_ENV environment variable.")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch goEnv {
|
||||||
|
case "production", "canary":
|
||||||
|
if err != nil {
|
||||||
|
sentry.CaptureException(err)
|
||||||
|
} else {
|
||||||
|
sentry.CaptureMessage("ERROR: " + message)
|
||||||
|
}
|
||||||
|
log.Printf("ERROR: %s: %v", message, err)
|
||||||
|
case "development", "debug":
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("ERROR: %s: %v", message, err)
|
||||||
|
} else {
|
||||||
|
log.Println("ERROR:", message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogFatal logs a fatal error message to both the local log and Sentry based on the environment and then exits the application.
|
||||||
|
func LogFatal(err error, message string) {
|
||||||
|
goEnv := os.Getenv("GO_ENV")
|
||||||
|
|
||||||
|
if goEnv == "" {
|
||||||
|
log.Fatal("GO_ENV is not set in error_logging LogFatal. Please set the GO_ENV environment variable.")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch goEnv {
|
||||||
|
case "production", "canary":
|
||||||
|
if err != nil {
|
||||||
|
sentry.CaptureException(err)
|
||||||
|
} else {
|
||||||
|
sentry.CaptureMessage("FATAL: " + message)
|
||||||
|
}
|
||||||
|
log.Fatalf("FATAL: %s: %v", message, err)
|
||||||
|
case "development", "debug":
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("FATAL: %s: %v", message, err)
|
||||||
|
} else {
|
||||||
|
log.Fatalf("FATAL: %s", message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package helper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RespondWithError(w http.ResponseWriter, statusCode int, message string) {
|
||||||
|
w.Header().Set(ContentTypeHeader, ApplicationJSON)
|
||||||
|
w.WriteHeader(statusCode)
|
||||||
|
if encodeErr := json.NewEncoder(w).Encode(map[string]string{ErrorLabel: message}); encodeErr != nil {
|
||||||
|
LogError(encodeErr, ErrorEncodingResponse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RespondWithMessage(w http.ResponseWriter, message string) {
|
||||||
|
if encodeErr := json.NewEncoder(w).Encode(map[string]string{MessageLabel: message}); encodeErr != nil {
|
||||||
|
LogError(encodeErr, ErrorEncodingResponse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RespondWithJSON(w http.ResponseWriter, statusCode int, data interface{}) {
|
||||||
|
w.Header().Set(ContentTypeHeader, ApplicationJSON)
|
||||||
|
w.WriteHeader(statusCode)
|
||||||
|
if encodeErr := json.NewEncoder(w).Encode(data); encodeErr != nil {
|
||||||
|
LogError(encodeErr, ErrorEncodingResponse)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,246 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"authorization/db"
|
||||||
|
"authorization/docs"
|
||||||
|
"authorization/helper"
|
||||||
|
"authorization/models"
|
||||||
|
"authorization/redisclient"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"authorization/routes"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/getsentry/sentry-go"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
"github.com/rs/cors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @swagger: "2.0"
|
||||||
|
// @title UESS Authentication Microservice
|
||||||
|
// @version 1.0
|
||||||
|
|
||||||
|
// @description This is the API for Authentication Microservice for UESS. It doesn't support OAS 3.0 and is only for documentation purposes. The library used doesn't support @server annotation.
|
||||||
|
// @contact.name Darrel Israel
|
||||||
|
// @contact.email d.israel.psa@gmail.com
|
||||||
|
|
||||||
|
// @BasePath /
|
||||||
|
|
||||||
|
// @securityDefinitions.apikey BearerToken
|
||||||
|
// @in header
|
||||||
|
// @name Authorization
|
||||||
|
|
||||||
|
var (
|
||||||
|
dbOpenConnections = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "db_open_connections",
|
||||||
|
Help: "Number of open database connections",
|
||||||
|
})
|
||||||
|
dbInUseConnections = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "db_in_use_connections",
|
||||||
|
Help: "Number of in-use database connections",
|
||||||
|
})
|
||||||
|
dbIdleConnections = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "db_idle_connections",
|
||||||
|
Help: "Number of idle database connections",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
httpRequestsTotal = prometheus.NewCounterVec(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Name: "http_requests_total",
|
||||||
|
Help: "Total number of HTTP requests",
|
||||||
|
},
|
||||||
|
[]string{"path", "method"},
|
||||||
|
)
|
||||||
|
httpRequestDuration = prometheus.NewHistogramVec(
|
||||||
|
prometheus.HistogramOpts{
|
||||||
|
Name: "http_request_duration_seconds",
|
||||||
|
Help: "Duration of HTTP requests in seconds",
|
||||||
|
Buckets: prometheus.DefBuckets,
|
||||||
|
},
|
||||||
|
[]string{"path", "method"},
|
||||||
|
)
|
||||||
|
httpRequestSize = prometheus.NewHistogramVec(
|
||||||
|
prometheus.HistogramOpts{
|
||||||
|
Name: "http_request_size_bytes",
|
||||||
|
Help: "Size of HTTP requests in bytes",
|
||||||
|
Buckets: prometheus.ExponentialBuckets(100, 10, 8),
|
||||||
|
},
|
||||||
|
[]string{"path", "method"},
|
||||||
|
)
|
||||||
|
httpResponseSize = prometheus.NewHistogramVec(
|
||||||
|
prometheus.HistogramOpts{
|
||||||
|
Name: "http_response_size_bytes",
|
||||||
|
Help: "Size of HTTP responses in bytes",
|
||||||
|
Buckets: prometheus.ExponentialBuckets(100, 10, 8),
|
||||||
|
},
|
||||||
|
[]string{"path", "method"},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
prometheus.MustRegister(httpRequestsTotal)
|
||||||
|
prometheus.MustRegister(httpRequestDuration)
|
||||||
|
prometheus.MustRegister(httpRequestSize)
|
||||||
|
prometheus.MustRegister(httpResponseSize)
|
||||||
|
prometheus.MustRegister(dbOpenConnections)
|
||||||
|
prometheus.MustRegister(dbInUseConnections)
|
||||||
|
prometheus.MustRegister(dbIdleConnections)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loggingMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
if r.URL.Path != metricsPath {
|
||||||
|
helper.LogInfo(fmt.Sprintf("INFO: Started %s %s", r.Method, r.URL.Path))
|
||||||
|
}
|
||||||
|
|
||||||
|
httpRequestsTotal.WithLabelValues(r.URL.Path, r.Method).Inc()
|
||||||
|
|
||||||
|
requestSize := float64(r.ContentLength)
|
||||||
|
if requestSize < 0 {
|
||||||
|
requestSize = 0
|
||||||
|
}
|
||||||
|
httpRequestSize.WithLabelValues(r.URL.Path, r.Method).Observe(requestSize)
|
||||||
|
|
||||||
|
rw := &models.ResponseWriter{ResponseWriter: w}
|
||||||
|
next.ServeHTTP(rw, r)
|
||||||
|
|
||||||
|
duration := time.Since(start).Seconds()
|
||||||
|
httpRequestDuration.WithLabelValues(r.URL.Path, r.Method).Observe(duration)
|
||||||
|
httpResponseSize.WithLabelValues(r.URL.Path, r.Method).Observe(float64(rw.Size))
|
||||||
|
|
||||||
|
// Log completion for non-metrics endpoints
|
||||||
|
if r.URL.Path != metricsPath {
|
||||||
|
helper.LogInfo(fmt.Sprintf("INFO: Completed %s %s in %.3f seconds", r.Method, r.URL.Path, duration))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectDBMetrics(database *sql.DB) {
|
||||||
|
for {
|
||||||
|
stats := database.Stats()
|
||||||
|
dbOpenConnections.Set(float64(stats.OpenConnections))
|
||||||
|
dbInUseConnections.Set(float64(stats.InUse))
|
||||||
|
dbIdleConnections.Set(float64(stats.Idle))
|
||||||
|
time.Sleep(10 * time.Second) // Adjust the interval as needed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func allowOnlyGrafana(next http.Handler, allowedIP string) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
remoteIP, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if remoteIP == allowedIP {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Load environment variables from .env file first
|
||||||
|
// Get current working directory for debugging
|
||||||
|
cwd, _ := os.Getwd()
|
||||||
|
log.Printf("Current working directory: %s", cwd)
|
||||||
|
|
||||||
|
err := godotenv.Load()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("ERROR: Failed to load .env file from default location: %v", err)
|
||||||
|
// Try with explicit path
|
||||||
|
err = godotenv.Load(".env")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("FATAL: Could not load .env file: %v. Tried paths: default and ./.env", err)
|
||||||
|
}
|
||||||
|
log.Println(".env file loaded successfully from ./.env")
|
||||||
|
} else {
|
||||||
|
log.Println(".env file loaded successfully")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify GO_ENV is loaded
|
||||||
|
goEnv := os.Getenv("GO_ENV")
|
||||||
|
log.Printf("GO_ENV value after loading .env: '%s'", goEnv)
|
||||||
|
|
||||||
|
if goEnv == "" {
|
||||||
|
log.Fatal("GO_ENV is not set in main. Please set the GO_ENV environment variable.")
|
||||||
|
}
|
||||||
|
|
||||||
|
DSN := os.Getenv("DSN")
|
||||||
|
if DSN == "" {
|
||||||
|
log.Fatal("Sentry DSN is not set. Please set the DSN environment variable.")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = sentry.Init(sentry.ClientOptions{
|
||||||
|
Dsn: os.Getenv("DSN"),
|
||||||
|
TracesSampleRate: 1.0,
|
||||||
|
Environment: goEnv,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("sentry.Init: %s", err)
|
||||||
|
}
|
||||||
|
defer sentry.Flush(2 * time.Second)
|
||||||
|
|
||||||
|
docs.SwaggerInfo.Host = "localhost:8080"
|
||||||
|
docs.SwaggerInfo.Schemes = []string{"http"}
|
||||||
|
|
||||||
|
helper.LogInfo("INFO: Initializing database connection...")
|
||||||
|
var database *sql.DB
|
||||||
|
for {
|
||||||
|
database, err = db.InitDB()
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
helper.LogError(fmt.Errorf("ERROR: error initializing database: %v", err), "database initialization error")
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
go collectDBMetrics(database)
|
||||||
|
router := mux.NewRouter()
|
||||||
|
routes.SetupRoutes(router, database)
|
||||||
|
helper.LogInfo("INFO: Database initialized successfully.")
|
||||||
|
|
||||||
|
allowedIP := os.Getenv("ALLOWED_IP")
|
||||||
|
helper.LogInfo("INFO: Setting up routes...")
|
||||||
|
router.Handle(metricsPath, allowOnlyGrafana(promhttp.Handler(), allowedIP))
|
||||||
|
router.Use(loggingMiddleware)
|
||||||
|
|
||||||
|
c := cors.New(cors.Options{
|
||||||
|
AllowedOrigins: []string{"http://localhost:4173", "http://localhost:5173"}, // Your frontend URL
|
||||||
|
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"},
|
||||||
|
AllowedHeaders: []string{"*"}, // Allow all headers temporarily
|
||||||
|
AllowCredentials: true, // Critical for withCredentials requests
|
||||||
|
MaxAge: 86400, // Cache preflight results
|
||||||
|
})
|
||||||
|
|
||||||
|
handler := c.Handler(router)
|
||||||
|
|
||||||
|
redisclient.Init()
|
||||||
|
|
||||||
|
helper.LogInfo("INFO: Connected to Redis successfully!")
|
||||||
|
|
||||||
|
helper.LogInfo("WARNING: Ensure Redis is secured to prevent unauthorized access. Use a strong password and bind Redis to localhost or a secure network.")
|
||||||
|
|
||||||
|
helper.LogInfo("INFO: Authentication Microservice is running on http://localhost:8080")
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: ":8080",
|
||||||
|
Handler: handler,
|
||||||
|
ReadTimeout: 15 * time.Second,
|
||||||
|
WriteTimeout: 300 * time.Second,
|
||||||
|
IdleTimeout: 60 * time.Second,
|
||||||
|
}
|
||||||
|
log.Fatal(server.ListenAndServe())
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
const (
|
||||||
|
Authorization = "Authorization"
|
||||||
|
Unauthorized = "Unauthorized"
|
||||||
|
)
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"authorization/helper"
|
||||||
|
"authorization/models"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
// contextKey is a custom type for context keys to avoid collisions
|
||||||
|
type contextKey string
|
||||||
|
|
||||||
|
const (
|
||||||
|
claimsKey contextKey = "claims"
|
||||||
|
userIDKey contextKey = "user_id"
|
||||||
|
usernameKey contextKey = "username"
|
||||||
|
roleKey contextKey = "role"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Token cache entry
|
||||||
|
type cacheEntry struct {
|
||||||
|
claims *models.Claims
|
||||||
|
expiresAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Token cache for high-frequency requests
|
||||||
|
tokenCache = make(map[string]*cacheEntry)
|
||||||
|
tokenCacheMutex sync.RWMutex
|
||||||
|
|
||||||
|
// Cache JWT secret to avoid repeated os.Getenv calls
|
||||||
|
jwtSecretOnce sync.Once
|
||||||
|
jwtSecretCached []byte
|
||||||
|
jwtSecretError error
|
||||||
|
|
||||||
|
// Pre-allocate error messages to avoid repeated allocations
|
||||||
|
errMissingAuth = "missing authorization header"
|
||||||
|
errInvalidAuthFormat = "invalid authorization header format"
|
||||||
|
errInvalidToken = "Invalid token"
|
||||||
|
errExpiredToken = "Invalid or expired token"
|
||||||
|
errInvalidClaims = "Invalid token claims"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Initialize JWT secret once
|
||||||
|
func getJWTSecret() ([]byte, error) {
|
||||||
|
jwtSecretOnce.Do(func() {
|
||||||
|
secret := os.Getenv("JWT_KEY")
|
||||||
|
if secret == "" {
|
||||||
|
jwtSecretError = fmt.Errorf("JWT_KEY not set in environment")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jwtSecretCached = []byte(secret)
|
||||||
|
})
|
||||||
|
return jwtSecretCached, jwtSecretError
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean expired cache entries periodically
|
||||||
|
func init() {
|
||||||
|
go func() {
|
||||||
|
ticker := time.NewTicker(5 * time.Minute)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for range ticker.C {
|
||||||
|
cleanExpiredTokens()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanExpiredTokens() {
|
||||||
|
tokenCacheMutex.Lock()
|
||||||
|
defer tokenCacheMutex.Unlock()
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
for token, entry := range tokenCache {
|
||||||
|
if now.After(entry.expiresAt) {
|
||||||
|
delete(tokenCache, token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// JWTAuth is a middleware that validates JWT tokens with caching for high-frequency requests
|
||||||
|
func JWTAuth(next http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Get the Authorization header
|
||||||
|
authHeader := r.Header.Get("Authorization")
|
||||||
|
if authHeader == "" {
|
||||||
|
helper.RespondWithError(w, http.StatusUnauthorized, "Unauthorized")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fast path: check if header has Bearer prefix without allocation
|
||||||
|
if len(authHeader) < 8 || authHeader[:7] != "Bearer " {
|
||||||
|
helper.RespondWithError(w, http.StatusUnauthorized, errInvalidAuthFormat)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenString := authHeader[7:] // Skip "Bearer " without strings.Split allocation
|
||||||
|
|
||||||
|
// Check cache first (read lock)
|
||||||
|
tokenCacheMutex.RLock()
|
||||||
|
if cached, exists := tokenCache[tokenString]; exists {
|
||||||
|
if time.Now().Before(cached.expiresAt) {
|
||||||
|
claims := cached.claims
|
||||||
|
tokenCacheMutex.RUnlock()
|
||||||
|
|
||||||
|
// Add claims to context and proceed
|
||||||
|
ctx := buildContext(r.Context(), claims)
|
||||||
|
next.ServeHTTP(w, r.WithContext(ctx))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Token expired in cache, remove it
|
||||||
|
tokenCacheMutex.RUnlock()
|
||||||
|
tokenCacheMutex.Lock()
|
||||||
|
delete(tokenCache, tokenString)
|
||||||
|
tokenCacheMutex.Unlock()
|
||||||
|
} else {
|
||||||
|
tokenCacheMutex.RUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse and validate the token
|
||||||
|
token, err := jwt.ParseWithClaims(tokenString, &models.Claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
// Validate the signing method
|
||||||
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||||
|
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get cached JWT secret
|
||||||
|
return getJWTSecret()
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
helper.RespondWithError(w, http.StatusUnauthorized, errExpiredToken)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if token is valid
|
||||||
|
if !token.Valid {
|
||||||
|
helper.RespondWithError(w, http.StatusUnauthorized, errInvalidToken)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract claims
|
||||||
|
claims, ok := token.Claims.(*models.Claims)
|
||||||
|
if !ok {
|
||||||
|
helper.RespondWithError(w, http.StatusUnauthorized, errInvalidClaims)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache the validated token
|
||||||
|
expiresAt := time.Now().Add(5 * time.Minute) // Cache for 5 minutes
|
||||||
|
if claims.ExpiresAt != nil && claims.ExpiresAt.Time.Before(expiresAt) {
|
||||||
|
expiresAt = claims.ExpiresAt.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenCacheMutex.Lock()
|
||||||
|
// Limit cache size to prevent memory issues
|
||||||
|
if len(tokenCache) > 10000000 {
|
||||||
|
// Remove oldest 10% when cache is full
|
||||||
|
count := 0
|
||||||
|
for k := range tokenCache {
|
||||||
|
delete(tokenCache, k)
|
||||||
|
count++
|
||||||
|
if count >= 1000000 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tokenCache[tokenString] = &cacheEntry{
|
||||||
|
claims: claims,
|
||||||
|
expiresAt: expiresAt,
|
||||||
|
}
|
||||||
|
tokenCacheMutex.Unlock()
|
||||||
|
|
||||||
|
// Add claims to request context
|
||||||
|
ctx := buildContext(r.Context(), claims)
|
||||||
|
|
||||||
|
// Call the next handler with updated context
|
||||||
|
next.ServeHTTP(w, r.WithContext(ctx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildContext efficiently builds context with claims (reduces allocations)
|
||||||
|
func buildContext(parent context.Context, claims *models.Claims) context.Context {
|
||||||
|
ctx := context.WithValue(parent, claimsKey, claims)
|
||||||
|
ctx = context.WithValue(ctx, userIDKey, claims.UserID)
|
||||||
|
ctx = context.WithValue(ctx, usernameKey, claims.Username)
|
||||||
|
ctx = context.WithValue(ctx, roleKey, claims.Role)
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetClaims retrieves the JWT claims from the request context
|
||||||
|
func GetClaims(r *http.Request) (*models.Claims, bool) {
|
||||||
|
claims, ok := r.Context().Value(claimsKey).(*models.Claims)
|
||||||
|
return claims, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserID retrieves the user ID from the request context
|
||||||
|
func GetUserID(r *http.Request) (string, bool) {
|
||||||
|
userID, ok := r.Context().Value(userIDKey).(string)
|
||||||
|
return userID, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUsername retrieves the username from the request context
|
||||||
|
func GetUsername(r *http.Request) (string, bool) {
|
||||||
|
username, ok := r.Context().Value(usernameKey).(string)
|
||||||
|
return username, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRole retrieves the role from the request context
|
||||||
|
func GetRole(r *http.Request) (string, bool) {
|
||||||
|
role, ok := r.Context().Value(roleKey).(string)
|
||||||
|
return role, ok
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "github.com/golang-jwt/jwt/v5"
|
||||||
|
|
||||||
|
type AuthorizationRequest struct {
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
Resource string `json:"resource"`
|
||||||
|
Action string `json:"action"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthorizationResponse struct {
|
||||||
|
Allowed bool `json:"allowed"`
|
||||||
|
Reason string `json:"reason,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Claims struct {
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Role string `json:"role"`
|
||||||
|
jwt.RegisteredClaims
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// FlusherPreservingResponseWriter wraps http.ResponseWriter and preserves http.Flusher for SSE endpoints.
|
||||||
|
type FlusherPreservingResponseWriter struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *FlusherPreservingResponseWriter) Flush() {
|
||||||
|
if f, ok := w.ResponseWriter.(http.Flusher); ok {
|
||||||
|
f.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResponseWriter wraps http.ResponseWriter to track response size for metrics
|
||||||
|
type ResponseWriter struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
Size int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *ResponseWriter) Write(b []byte) (int, error) {
|
||||||
|
size, err := rw.ResponseWriter.Write(b)
|
||||||
|
rw.Size += size
|
||||||
|
return size, err
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
// pkg/redisclient/redis.go
|
||||||
|
|
||||||
|
package redisclient
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
)
|
||||||
|
|
||||||
|
var RDB *redis.Client
|
||||||
|
|
||||||
|
func Init() {
|
||||||
|
redisHost := os.Getenv("REDIS_HOST")
|
||||||
|
if redisHost == "" {
|
||||||
|
redisHost = "localhost"
|
||||||
|
}
|
||||||
|
|
||||||
|
redisPort := os.Getenv("REDIS_PORT")
|
||||||
|
if redisPort == "" {
|
||||||
|
redisPort = "6379"
|
||||||
|
}
|
||||||
|
|
||||||
|
redisPassword := os.Getenv("REDIS_PASSWORD")
|
||||||
|
if redisPassword == "" {
|
||||||
|
redisPassword = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure Redis client with security settings
|
||||||
|
opts := &redis.Options{
|
||||||
|
Addr: fmt.Sprintf("%s:%s", redisHost, redisPort),
|
||||||
|
Password: redisPassword,
|
||||||
|
DB: 0,
|
||||||
|
DisableIndentity: true, // Disable client-side caching to prevent protocol confusion
|
||||||
|
IdentitySuffix: "", // Disable identity suffix
|
||||||
|
}
|
||||||
|
|
||||||
|
RDB = redis.NewClient(opts)
|
||||||
|
|
||||||
|
// Test connection with authentication
|
||||||
|
ctx := context.Background()
|
||||||
|
if _, err := RDB.Ping(ctx).Result(); err != nil {
|
||||||
|
panic(fmt.Sprintf("Could not connect to Redis: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log connection security status
|
||||||
|
if redisPassword != "" {
|
||||||
|
fmt.Println("✓ Redis connection secured with password authentication")
|
||||||
|
} else {
|
||||||
|
fmt.Println("⚠ WARNING: Redis connection without password - security risk!")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"authorization/handlers"
|
||||||
|
"authorization/middleware"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
httpSwagger "github.com/swaggo/http-swagger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetupRoutes(router *mux.Router, db *sql.DB) {
|
||||||
|
authzRoutes := router.PathPrefix("/v1/auth").Subrouter()
|
||||||
|
authzRoutes.HandleFunc("/check", middleware.JWTAuth(handlers.AuthorizeHandler)).Methods("POST")
|
||||||
|
|
||||||
|
router.PathPrefix("/swagger/").Handler(httpSwagger.WrapHandler)
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
func Authorize() bool {
|
||||||
|
// Authorization logic here
|
||||||
|
return true
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user