first
This commit is contained in:
commit
fbaf9393ea
18 changed files with 1855 additions and 0 deletions
52
.air.toml
Normal file
52
.air.toml
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
root = "."
|
||||
testdata_dir = "testdata"
|
||||
tmp_dir = "tmp"
|
||||
|
||||
[build]
|
||||
args_bin = []
|
||||
bin = "./tmp/main"
|
||||
cmd = "go build -o ./tmp/main ./cmd/server/."
|
||||
delay = 1000
|
||||
exclude_dir = ["assets", "tmp", "vendor", "testdata","docker"]
|
||||
exclude_file = []
|
||||
exclude_regex = ["_test.go"]
|
||||
exclude_unchanged = false
|
||||
follow_symlink = false
|
||||
full_bin = ""
|
||||
include_dir = []
|
||||
include_ext = ["go", "tpl", "tmpl", "html","yml","yaml"]
|
||||
include_file = []
|
||||
kill_delay = "0s"
|
||||
log = "build-errors.log"
|
||||
poll = false
|
||||
poll_interval = 0
|
||||
post_cmd = []
|
||||
pre_cmd = []
|
||||
rerun = false
|
||||
rerun_delay = 500
|
||||
send_interrupt = false
|
||||
stop_on_error = false
|
||||
|
||||
[color]
|
||||
app = ""
|
||||
build = "yellow"
|
||||
main = "magenta"
|
||||
runner = "green"
|
||||
watcher = "cyan"
|
||||
|
||||
[log]
|
||||
main_only = false
|
||||
silent = false
|
||||
time = false
|
||||
|
||||
[misc]
|
||||
clean_on_exit = false
|
||||
|
||||
[proxy]
|
||||
app_port = 0
|
||||
enabled = false
|
||||
proxy_port = 0
|
||||
|
||||
[screen]
|
||||
clear_on_rebuild = false
|
||||
keep_scroll = true
|
||||
16
.env
Normal file
16
.env
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# Keycloak openid provider configuration
|
||||
KEYCLOAK_REALM=dev
|
||||
KEYCLOAK_CLIENT_ID=dev_client
|
||||
KEYCLOAK_CLIENT_SECRET=dWhSJgARBAuBAXN7sUTpqpIq2sKQdugs
|
||||
KEYCLOAK_HOST_URL=http://192.168.10.2:8080
|
||||
KEYCLOAK_REDIRECT_URI=https://app.z.com/auth/callback
|
||||
|
||||
# session configuration
|
||||
SESSION_SECRET=dbemG9m84LmgdYLj4o_wai9Mz18QFHSNZeH92lgxytE
|
||||
SECRET_KEY=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwSuI+Iyxl4vUzFniKCJQfxAKzvx0wioUlPZc7YsFYGHQ9vIhTNI3kdSD75El6VYy3QSt1jHo/6fu1Oy5Brj95KFf496IQZ3gTLOpu3yVcB55r8nuO07o/9aOex4XItV9Gs9gqdTTq8p5uQrBH1ykq6fCDU57qLCWhijT04MN3DlgRTaNCY2h7XVxmiPORgz+JCAz4OcDM3Xq/ejWZToX+aphYVWIQRxU1mzyq9BuKZzU5tJIkVDQDhDZQyZNY61q4MHfqMKRUS6+5fJZbQWcgt3/4B+yUp/oVlmJjaEMuFDPyzZCHtm+r1Idw/ajMTzlwOFbnj6/8qteFIP/b9uWdQIDAQAB
|
||||
|
||||
# Auth configuration
|
||||
AUTH_PREFIX=/auth
|
||||
CALLBACK_PATH=/callback
|
||||
LOGIN_PATH=/login
|
||||
LOGOUT_PATH=/logout
|
||||
14
cmd/server/functions.go
Normal file
14
cmd/server/functions.go
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
)
|
||||
|
||||
// loadCertificate dynamically loads the certificate from files
|
||||
func loadCertificate(certFile, keyFile string) (tls.Certificate, error) {
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
return cert, nil
|
||||
}
|
||||
437
cmd/server/main.go
Normal file
437
cmd/server/main.go
Normal file
|
|
@ -0,0 +1,437 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/gookit/goutil/dump"
|
||||
"zeevdiukman.com/zprox/internal/config"
|
||||
"zeevdiukman.com/zprox/internal/logic"
|
||||
"zeevdiukman.com/zprox/internal/reverse_proxy"
|
||||
"zeevdiukman.com/zprox/internal/router"
|
||||
"zeevdiukman.com/zprox/pkg/helper"
|
||||
)
|
||||
|
||||
const DEVELOPMENT bool = true
|
||||
|
||||
type EntryPoints map[string]EntryPoint
|
||||
type EntryPoint struct {
|
||||
Name string
|
||||
Group string
|
||||
*http.Server
|
||||
}
|
||||
type ReverseProxies map[string]ReverseProxy
|
||||
type ReverseProxy *httputil.ReverseProxy
|
||||
|
||||
var app = logic.NewApp()
|
||||
|
||||
func main() {
|
||||
helper.AppRunner(func() {
|
||||
config.Wrapper(func(c *config.Config) {
|
||||
groups := logic.NewGroups()
|
||||
mainRouter := router.New()
|
||||
groups.ForEach(func(k string, g *logic.Group) {
|
||||
groupSubRouter := mainRouter.Mux.NewRoute().Subrouter()
|
||||
// groupSubRouter.Use(Domain)
|
||||
for k := range g.ReverseProxies {
|
||||
rpConfig := c.ReverseProxies[k]
|
||||
domain := rpConfig.Domain
|
||||
proxy := reverse_proxy.New(rpConfig.Host)
|
||||
proxy.Name = domain
|
||||
newRoute := groupSubRouter.NewRoute()
|
||||
subRouter := newRoute.Host(domain).Subrouter()
|
||||
|
||||
if rpConfig.Auth != "" {
|
||||
if _, ok := c.Auth[rpConfig.Auth]; !ok {
|
||||
err := errors.New("Error: Auth " + rpConfig.Auth + " not exist!")
|
||||
panic(err.Error())
|
||||
}
|
||||
pths := c.Auth[rpConfig.Auth].Paths
|
||||
authRoute := subRouter.NewRoute()
|
||||
subRouter.Use(Middleware_SetHeaders)
|
||||
authSubRouter := authRoute.PathPrefix(pths.Prefix).Subrouter()
|
||||
authSubRouter.Path(pths.Login).Handler(http.HandlerFunc(LoginHandler))
|
||||
authSubRouter.Path(pths.Logout).Handler(http.HandlerFunc(LogoutHandler))
|
||||
authSubRouter.Path(pths.Callback).Handler(http.HandlerFunc(CallbackHandler))
|
||||
subRouter.Use(authMiddleware)
|
||||
}
|
||||
subRouter.PathPrefix("/").Handler(proxy.Httputil)
|
||||
|
||||
}
|
||||
|
||||
if len(g.ReverseProxies) > 0 {
|
||||
tlsConfig := &tls.Config{
|
||||
|
||||
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
// crt, key := "", ""
|
||||
|
||||
crt, key := c.GetCertsPairByDomain(info.ServerName)
|
||||
|
||||
if crt == "" && key == "" {
|
||||
// crt = c.TLS.Certs["default"].Cert
|
||||
// key = c.TLS.Certs["default"].Key
|
||||
// panic("Error: TLS cert and key not found!")
|
||||
|
||||
}
|
||||
cert, err := loadCertificate(crt, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cert, nil
|
||||
},
|
||||
}
|
||||
server := &http.Server{
|
||||
Addr: ":" + g.Port,
|
||||
Handler: app.SessionManager.LoadAndSave(groupSubRouter),
|
||||
TLSConfig: tlsConfig,
|
||||
}
|
||||
var err error
|
||||
go func() {
|
||||
ipAddr := helper.GetIP()
|
||||
log.Println("Test server is running at http://" + ipAddr + ":" + g.Port)
|
||||
if g.TLS {
|
||||
err = server.ListenAndServeTLS("", "")
|
||||
} else {
|
||||
err = server.ListenAndServe()
|
||||
}
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
})
|
||||
helper.StartTestHTTPServer(3000)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func authMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
config.Wrapper(func(c *config.Config) {
|
||||
currentPath := r.URL.Path
|
||||
authName := c.GetAuthNameByDomain(r.Host)
|
||||
// authName := c.DataMaps.DomainToAuth[r.Host]
|
||||
loginPath := c.Auth[authName].Paths.Prefix + c.Auth[authName].Paths.Login
|
||||
logoutPath := c.Auth[authName].Paths.Prefix + c.Auth[authName].Paths.Logout
|
||||
callbackPath := c.Auth[authName].Paths.Prefix + c.Auth[authName].Paths.Callback
|
||||
// loginPath := c.Auth[authName].Paths.Prefix + c.Auth[authName].Paths.Login
|
||||
// logoutPath := c.Auth[authName].Paths.Prefix + c.Auth[authName].Paths.Logout
|
||||
// callbackPath := c.Auth[authName].Paths.Prefix + c.Auth[authName].Paths.Callback
|
||||
// TODO: mark auth reverse proxy in yaml
|
||||
// AuthHostUrl, _ := url.Parse(c.Auth.Default.OpenID.Host)
|
||||
|
||||
if r.Host == "keycloak.z.com" {
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
switch currentPath {
|
||||
case loginPath:
|
||||
{
|
||||
// fmt.Fprintln(w, "LOGIN")
|
||||
next.ServeHTTP(w, r)
|
||||
|
||||
}
|
||||
case logoutPath:
|
||||
{
|
||||
next.ServeHTTP(w, r)
|
||||
// return
|
||||
}
|
||||
case callbackPath:
|
||||
{
|
||||
next.ServeHTTP(w, r)
|
||||
// return
|
||||
}
|
||||
default:
|
||||
{
|
||||
accessToken := app.SessionManager.GetString(r.Context(), "access_token")
|
||||
if accessToken == "" {
|
||||
authName := c.DataMaps.DomainToAuth[r.Host]
|
||||
http.Redirect(w, r, c.Auth[authName].Paths.Prefix+c.Auth[authName].Paths.Login, http.StatusFound)
|
||||
return
|
||||
}
|
||||
// auth.SetAuthHeader(w, accessToken)
|
||||
a := c.Auth[authName]
|
||||
pths := a.Paths
|
||||
prefix := pths.Prefix
|
||||
login := pths.Login
|
||||
logout := pths.Logout
|
||||
loginPath := prefix + login
|
||||
logoutPath := prefix + logout
|
||||
if loginPath == r.URL.Path || logoutPath == r.URL.Path {
|
||||
next.ServeHTTP(w, r)
|
||||
// return
|
||||
}
|
||||
|
||||
// tokenOk := IsAuthorizedJWT(accessToken, c, "default")
|
||||
// if tokenOk {
|
||||
// } else {
|
||||
// // p := a.OpenID
|
||||
// // Redirect to login
|
||||
// }
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func Domain(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// c := config.New().Auth.Default.Paths
|
||||
|
||||
// requestedPath := r.URL.Path
|
||||
// a := c
|
||||
// excludedPaths := []string{
|
||||
// a.Prefix + a.Login,
|
||||
// a.Prefix + a.Callback,
|
||||
// a.Prefix + a.Logout,
|
||||
// }
|
||||
// contains := helper.IsSliceContains(excludedPaths, requestedPath)
|
||||
// if !contains {
|
||||
// app.SessionManager.Put(r.Context(), "original_path", requestedPath)
|
||||
// }
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func Middleware_SetHeaders(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
r.Header.Set("X-Forwarded-Proto", getProto(r))
|
||||
r.Header.Set("X-Forwarded-For", r.RemoteAddr)
|
||||
r.Header.Set("X-Forwarded-Host", r.Host)
|
||||
r.Header.Set("X-Real-IP", r.RemoteAddr)
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
func getProto(req *http.Request) string {
|
||||
if req.TLS != nil {
|
||||
return "https"
|
||||
} else {
|
||||
return "http"
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type TokenResponse struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
RefreshExpiresIn int `json:"refresh_expires_in"`
|
||||
TokenType string `json:"token_type"`
|
||||
NotBeforePolicy int `json:"not-before-policy"`
|
||||
SessionState string `json:"session_state"`
|
||||
Scope string `json:"scope"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
Error string `json:"error"`
|
||||
ErrorDescription string `json:"error_description"`
|
||||
}
|
||||
|
||||
type Res401Struct struct {
|
||||
Status string `json:"status" example:"FAILED"`
|
||||
HTTPCode int `json:"httpCode" example:"401"`
|
||||
Message string `json:"message" example:"authorisation failed"`
|
||||
}
|
||||
|
||||
type Claims struct {
|
||||
ResourceAccess client `json:"resource_access,omitempty"`
|
||||
JTI string `json:"jti,omitempty"`
|
||||
}
|
||||
|
||||
type client struct {
|
||||
DemoServiceClient clientRoles `json:"DemoServiceClient,omitempty"`
|
||||
}
|
||||
|
||||
type clientRoles struct {
|
||||
Roles []string `json:"roles,omitempty"`
|
||||
}
|
||||
type HandlerFuncConfigWrapper func(*config.Config, http.ResponseWriter, *http.Request) *http.Handler
|
||||
|
||||
// HANDLERS
|
||||
// ////////////
|
||||
func CallbackHandler(w http.ResponseWriter, r *http.Request) {
|
||||
config.Wrapper(func(c *config.Config) {
|
||||
query := r.URL.Query()
|
||||
code := query.Get("code")
|
||||
state := query.Get("state")
|
||||
verifier := app.SessionManager.GetString(r.Context(), "code_verifier")
|
||||
if verifier == "" {
|
||||
http.Error(w, "Code verifier not found in session", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
expectedState := app.SessionManager.GetString(r.Context(), "state")
|
||||
if state != expectedState {
|
||||
http.Error(w, "Invalid state parameter", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
originalPath := app.SessionManager.GetString(r.Context(), "original_path")
|
||||
authName := c.GetAuthNameByDomain(r.Host)
|
||||
token, fullResponse, e := exchangeCode(code, verifier, c, authName)
|
||||
if e != nil {
|
||||
dump.Println("exchangeCode: " + e.Error())
|
||||
}
|
||||
|
||||
app.SessionManager.Put(r.Context(), "access_token", token.AccessToken)
|
||||
app.SessionManager.Put(r.Context(), "full_token", fullResponse)
|
||||
|
||||
// SetAuthHeader(w, token.AccessToken)
|
||||
http.Redirect(w, r, originalPath, http.StatusFound)
|
||||
})
|
||||
}
|
||||
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
|
||||
config.Wrapper(func(c *config.Config) {
|
||||
app.SessionManager.Remove(r.Context(), "access_token")
|
||||
app.SessionManager.Remove(r.Context(), "full_token")
|
||||
authName := c.DataMaps.DomainToAuth[r.Host]
|
||||
a := c.Auth[authName]
|
||||
u := a.OpenID.EndPoints.Logout
|
||||
http.Redirect(w, r, u, http.StatusFound)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func LoginHandler(w http.ResponseWriter, r *http.Request) {
|
||||
config.Wrapper(func(c *config.Config) {
|
||||
authName := c.DataMaps.DomainToAuth[r.Host]
|
||||
codeVerifier, _ := generateCodeVerifier()
|
||||
codeChallenge := generateCodeChallenge(codeVerifier)
|
||||
state := helper.RandStringByBits(128)
|
||||
nonce := helper.RandStringByBits(128)
|
||||
authURL, _ := url.Parse(c.Auth[authName].OpenID.EndPoints.Auth)
|
||||
query := authURL.Query()
|
||||
query.Set("client_id", c.Auth[authName].OpenID.ClientID)
|
||||
query.Set("response_type", "code")
|
||||
query.Set("scope", "openid")
|
||||
query.Set("redirect_uri", c.Auth[authName].OpenID.RedirectURI)
|
||||
query.Set("code_challenge", codeChallenge)
|
||||
query.Set("code_challenge_method", "S256")
|
||||
query.Set("state", state)
|
||||
query.Set("nonce", nonce)
|
||||
authURL.RawQuery = query.Encode()
|
||||
app.SessionManager.Put(r.Context(), "state", state)
|
||||
app.SessionManager.Put(r.Context(), "code_verifier", codeVerifier)
|
||||
http.Redirect(w, r, authURL.String(), http.StatusFound)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// AUTH FUNCTIONS
|
||||
////////////////////
|
||||
|
||||
func exchangeCode(code string, verifier string, c *config.Config, authName string) (*TokenResponse, string, error) {
|
||||
data := url.Values{}
|
||||
data.Set("grant_type", "authorization_code")
|
||||
data.Set("client_id", c.Auth[authName].OpenID.ClientID)
|
||||
data.Set("client_secret", c.Auth[authName].OpenID.ClientSecert)
|
||||
data.Set("redirect_uri", c.Auth[authName].OpenID.RedirectURI)
|
||||
data.Set("code", code)
|
||||
data.Set("scope", "openid zapp")
|
||||
if verifier != "" {
|
||||
data.Set("code_verifier", verifier)
|
||||
}
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: DEVELOPMENT},
|
||||
}
|
||||
client := &http.Client{Transport: tr}
|
||||
u := c.Auth[authName].OpenID.EndPoints.Token
|
||||
r, _ := http.NewRequest(http.MethodPost, u, strings.NewReader(data.Encode()))
|
||||
r.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
resp, err := client.Do(r)
|
||||
|
||||
if err != nil {
|
||||
dump.Println("ERROR exchange code: " + err.Error())
|
||||
return nil, "", err
|
||||
}
|
||||
respBytes, err := io.ReadAll(resp.Body)
|
||||
tokenResponse := &TokenResponse{}
|
||||
|
||||
json.Unmarshal(respBytes, &tokenResponse)
|
||||
|
||||
if err != nil {
|
||||
dump.Println("ERROR exchange code Unmarshal: " + err.Error())
|
||||
return nil, "", err
|
||||
}
|
||||
if tokenResponse.Error != "" {
|
||||
dump.Println(tokenResponse.Error + ": " + tokenResponse.ErrorDescription)
|
||||
}
|
||||
fullResponse := string(respBytes)
|
||||
|
||||
return tokenResponse, fullResponse, nil
|
||||
}
|
||||
|
||||
func generateCodeVerifier() (string, error) {
|
||||
verifier := make([]byte, 32)
|
||||
_, err := rand.Read(verifier)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.RawURLEncoding.EncodeToString(verifier), nil
|
||||
}
|
||||
|
||||
func generateCodeChallenge(verifier string) string {
|
||||
hash := sha256.Sum256([]byte(verifier))
|
||||
return base64.RawURLEncoding.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
func IsAuthorizedJWT(rawAccessToken string, c *config.Config, authName string) bool {
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
client := &http.Client{
|
||||
Timeout: time.Duration(6000) * time.Second,
|
||||
Transport: tr,
|
||||
}
|
||||
ctx := oidc.ClientContext(context.Background(), client)
|
||||
provider, err := oidc.NewProvider(ctx, c.Auth[authName].OpenID.EndPoints.Issuer)
|
||||
if err != nil {
|
||||
dump.Println("authorisation failed while getting the provider: " + err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
oidcConfig := &oidc.Config{
|
||||
ClientID: c.Auth[authName].OpenID.ClientID,
|
||||
}
|
||||
verifier := provider.Verifier(oidcConfig)
|
||||
idToken, err := verifier.Verify(ctx, rawAccessToken)
|
||||
if err != nil {
|
||||
dump.Println("authorisation failed while verifying the token: " + err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
var IDTokenClaims Claims // ID Token payload is just JSON.
|
||||
if err := idToken.Claims(&IDTokenClaims); err != nil {
|
||||
dump.Println("claims: " + err.Error())
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
///////////////////////////////////
|
||||
///////////////////////////////////
|
||||
81
config.yml
Normal file
81
config.yml
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
reverse_proxies:
|
||||
|
||||
kc:
|
||||
domain: keycloak.z.com
|
||||
host: http://127.0.0.1:8080
|
||||
entry_point: https
|
||||
tls:
|
||||
enabled: true
|
||||
certs: default
|
||||
|
||||
|
||||
app:
|
||||
domain: app.z.com
|
||||
host: http://127.0.0.1:3000
|
||||
entry_point: https
|
||||
tls:
|
||||
enabled: true
|
||||
certs: default
|
||||
auth: default
|
||||
|
||||
tls:
|
||||
certs:
|
||||
default:
|
||||
cert: z.com.cert.pem
|
||||
key: z.com.key.pem
|
||||
|
||||
entry_points:
|
||||
https:
|
||||
tls: true
|
||||
port: 443
|
||||
http:
|
||||
port: 80
|
||||
|
||||
auth:
|
||||
default:
|
||||
paths:
|
||||
prefix: /auth
|
||||
login: /login
|
||||
logout: /logout
|
||||
callback: /callback
|
||||
open_id:
|
||||
host: http://127.0.0.1:8080
|
||||
realm: dev
|
||||
client_id: dev_client
|
||||
client_secret: dWhSJgARBAuBAXN7sUTpqpIq2sKQdugs
|
||||
redirect_uri: https://app.z.com/auth/callback
|
||||
post_logout_redirect_uri: https://app.z.com/auth/logout
|
||||
config_path: /realms/{{realm}}/.well-known/openid-configuration
|
||||
# config_fields:
|
||||
# - issuer
|
||||
# - authorization_endpoint
|
||||
# - token_endpoint
|
||||
# - introspection_endpoint
|
||||
# - userinfo_endpoint
|
||||
# - end_session_endpoint
|
||||
# - jwks_uri
|
||||
# issuer: http://127.0.0.1:8080/realms/dev
|
||||
|
||||
|
||||
|
||||
# scope: openid profile email
|
||||
|
||||
# response_type: code
|
||||
# response_mode: query
|
||||
# prompt: none
|
||||
# post_logout_redirect_uri: https://app.z.com/auth/logout
|
||||
# token_endpoint_auth_method: client_secret_post
|
||||
# userinfo_endpoint: https://keycloak.z.com/auth/realms/z/protocol/openid-connect/userinfo
|
||||
# authorization_endpoint: https://keycloak.z.com/auth/realms/z/protocol/openid-connect/auth
|
||||
# token_endpoint: https://keycloak.z.com/auth/realms/z/protocol/openid-connect/token
|
||||
# end_session_endpoint: https://keycloak.z.com/auth/realms/z/protocol/openid-connect/logout
|
||||
# jwks_uri: https://keycloak.z.com/auth/realms/z/protocol/openid-connect/certs
|
||||
# issuer: https://keycloak.z.com/auth/realms/z
|
||||
# registration_endpoint: https://keycloak.z.com/auth/realms/z/clients-registrations/openid-connect
|
||||
# check_session_iframe: https://keycloak.z.com/auth/realms/z/protocol/openid-connect/login-status-iframe.html
|
||||
# client_name: zapp
|
||||
# client_uri: https://app.z.com
|
||||
# logo_uri: https://app.z.com/logo.png
|
||||
# policy_uri: https://app.z.com/policy
|
||||
# tos_uri: https://app.z.com/tos
|
||||
# jwks: https://keycloak.z.com/auth/realms/z/protocol/openid-connect/certs
|
||||
38
go.mod
Normal file
38
go.mod
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
module zeevdiukman.com/zprox
|
||||
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
|
||||
github.com/gookit/color v1.5.4 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
golang.org/x/crypto v0.25.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/oauth2 v0.21.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/alexedwards/scs/v2 v2.8.0
|
||||
github.com/coreos/go-oidc/v3 v3.12.0
|
||||
github.com/gookit/goutil v0.6.18
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/spf13/viper v1.19.0
|
||||
)
|
||||
89
go.sum
Normal file
89
go.sum
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
github.com/alexedwards/scs/v2 v2.8.0 h1:h31yUYoycPuL0zt14c0gd+oqxfRwIj6SOjHdKRZxhEw=
|
||||
github.com/alexedwards/scs/v2 v2.8.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8=
|
||||
github.com/coreos/go-oidc/v3 v3.12.0 h1:sJk+8G2qq94rDI6ehZ71Bol3oUHy63qNYmkiSjrc/Jo=
|
||||
github.com/coreos/go-oidc/v3 v3.12.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
|
||||
github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
|
||||
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
|
||||
github.com/gookit/goutil v0.6.18 h1:MUVj0G16flubWT8zYVicIuisUiHdgirPAkmnfD2kKgw=
|
||||
github.com/gookit/goutil v0.6.18/go.mod h1:AY/5sAwKe7Xck+mEbuxj0n/bc3qwrGNe3Oeulln7zBA=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
|
||||
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
|
||||
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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=
|
||||
178
internal/config/config.go
Normal file
178
internal/config/config.go
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"zeevdiukman.com/zprox/pkg/helper"
|
||||
)
|
||||
|
||||
const Viper_File_Name string = "config"
|
||||
const Viper_File_Type string = "yaml"
|
||||
const Viper_File_Path string = "."
|
||||
|
||||
var c *Config
|
||||
|
||||
func init() {
|
||||
helper.New().Screen.Clear()
|
||||
c = createConfig()
|
||||
|
||||
}
|
||||
func Wrapper(fn func(c *Config)) {
|
||||
fn(c)
|
||||
}
|
||||
func Get() *Config {
|
||||
return c
|
||||
}
|
||||
func createConfig() *Config {
|
||||
c := &Config{}
|
||||
c.initViper()
|
||||
c.setViperOptions(Viper_File_Name, Viper_File_Type, Viper_File_Path)
|
||||
c.viperReadYaml()
|
||||
c.viperUnmarshalYaml()
|
||||
c.initDomainToProxyNameMap()
|
||||
c.initDomainToProxyAuthName()
|
||||
c.initDomainToCertName()
|
||||
c.initOpenIDEndPoints()
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Config) initViper() {
|
||||
c.Viper = viper.New()
|
||||
}
|
||||
|
||||
func (c *Config) setViperOptions(fileName string, fileExtention string, filePath string) {
|
||||
c.Viper.SetConfigName(fileName)
|
||||
c.Viper.SetConfigType(fileExtention)
|
||||
c.Viper.AddConfigPath(filePath)
|
||||
}
|
||||
|
||||
func (c *Config) viperReadYaml() {
|
||||
err := c.Viper.ReadInConfig()
|
||||
if err != nil {
|
||||
log.Fatalf("Error reading config file, %s", err)
|
||||
}
|
||||
}
|
||||
func (c *Config) viperUnmarshalYaml() {
|
||||
err := c.Viper.Unmarshal(&c)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to decode into struct, %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (eps *EntryPoints) ForEach(fn func(epName string, epConfig *EntryPoint)) {
|
||||
for epName, epConfig := range *eps {
|
||||
fn(epName, &epConfig)
|
||||
}
|
||||
}
|
||||
|
||||
func (rp *ReverseProxies) ForEach(fn func(rpName string, rpConfig *ReverseProxy)) {
|
||||
for rpName, rpData := range *rp {
|
||||
fn(rpName, &rpData)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) fetchEndPointsByAuthName(authName string) map[string]any {
|
||||
configURL := c.KeycloakWellknownURL(authName)
|
||||
resp := helper.IsFetchOK[EndPoints](configURL, "", http.Get)
|
||||
respByes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
}
|
||||
data := map[string]any{}
|
||||
json.Unmarshal(respByes, &data)
|
||||
return data
|
||||
}
|
||||
|
||||
func (c *Config) initOpenIDEndPoints() {
|
||||
for authName := range c.Auth {
|
||||
data := c.fetchEndPointsByAuthName(authName)
|
||||
|
||||
c.Auth[authName].OpenID.EndPoints = &EndPoints{
|
||||
Issuer: data["issuer"].(string),
|
||||
Auth: data["authorization_endpoint"].(string),
|
||||
Introspection: data["introspection_endpoint"].(string),
|
||||
Token: data["token_endpoint"].(string),
|
||||
UserInfo: data["userinfo_endpoint"].(string),
|
||||
Logout: data["end_session_endpoint"].(string),
|
||||
JwksUri: data["jwks_uri"].(string),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
func (c *Config) KeycloakWellknownURL(authName string) string {
|
||||
|
||||
if _, ok := c.Auth[authName]; !ok {
|
||||
return ""
|
||||
}
|
||||
hostUrl := c.Auth[authName].OpenID.Host
|
||||
realm := c.Auth[authName].OpenID.Realm
|
||||
configPath := c.Auth[authName].OpenID.ConfigPath
|
||||
u := hostUrl
|
||||
u += strings.ReplaceAll(configPath, "{{realm}}", realm)
|
||||
return u
|
||||
}
|
||||
|
||||
func (c *Config) initDomainToProxyNameMap() {
|
||||
mp := make(map[string]string)
|
||||
c.ReverseProxies.ForEach(func(rpName string, rpConfig *ReverseProxy) {
|
||||
mp[rpConfig.Domain] = rpName
|
||||
})
|
||||
c.DataMaps.DomainToProxy = mp
|
||||
}
|
||||
func (c *Config) initDomainToProxyAuthName() {
|
||||
authMap := map[string]int{}
|
||||
for authName := range c.Auth {
|
||||
authMap[authName] = 1
|
||||
}
|
||||
mp := make(map[string]string)
|
||||
c.ReverseProxies.ForEach(func(rpName string, rpConfig *ReverseProxy) {
|
||||
if v, ok := authMap[rpConfig.Auth]; ok && v == 1 {
|
||||
mp[rpConfig.Domain] = rpConfig.Auth
|
||||
}
|
||||
})
|
||||
c.DataMaps.DomainToAuth = mp
|
||||
}
|
||||
func (c *Config) initDomainToCertName() {
|
||||
crtMap := map[string]int{}
|
||||
for crtName := range c.TLS.Certs {
|
||||
crtMap[crtName] = 1
|
||||
}
|
||||
mp := make(map[string]string)
|
||||
c.ReverseProxies.ForEach(func(rpName string, rpConfig *ReverseProxy) {
|
||||
if v, ok := crtMap[rpConfig.TLS.Certs]; ok && v == 1 {
|
||||
mp[rpConfig.Domain] = rpConfig.TLS.Certs
|
||||
}
|
||||
mp[rpConfig.Domain] = rpConfig.TLS.Certs
|
||||
})
|
||||
c.DataMaps.DomainToCert = mp
|
||||
}
|
||||
func (c *Config) GetAuthNameByDomain(domain string) string {
|
||||
return c.DataMaps.DomainToAuth[domain]
|
||||
}
|
||||
func (c *Config) GetProxyNameByDomain(domain string) string {
|
||||
return c.DataMaps.DomainToProxy[domain]
|
||||
}
|
||||
func (c *Config) GetCertNameByDomain(domain string) string {
|
||||
return c.DataMaps.DomainToCert[domain]
|
||||
}
|
||||
func (c *Config) GetCertsPairByDomain(domain string) (string, string) {
|
||||
var crt string
|
||||
var key string
|
||||
certName := c.DataMaps.DomainToCert[domain]
|
||||
crt = c.TLS.Certs[certName].Cert
|
||||
key = c.TLS.Certs[certName].Key
|
||||
if !(certName != "" && crt == "" && key != "") {
|
||||
certName = "default"
|
||||
crt = c.TLS.Certs[certName].Cert
|
||||
key = c.TLS.Certs[certName].Key
|
||||
}
|
||||
|
||||
return crt, key
|
||||
|
||||
}
|
||||
93
internal/config/types.go
Normal file
93
internal/config/types.go
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
package config
|
||||
|
||||
import "github.com/spf13/viper"
|
||||
|
||||
type Config struct {
|
||||
Viper *viper.Viper
|
||||
DataMaps DataMaps
|
||||
ReverseProxies ReverseProxies `mapstructure:"reverse_proxies"`
|
||||
TLS TLS `mapstructure:"tls"`
|
||||
EntryPoints EntryPoints `mapstructure:"entry_points"`
|
||||
Auth map[string]*AuthInstance `mapstructure:"auth"`
|
||||
}
|
||||
|
||||
type DataMaps struct {
|
||||
DomainToProxy map[string]string
|
||||
DomainToAuth map[string]string
|
||||
DomainToCert map[string]string
|
||||
}
|
||||
|
||||
// AUTH
|
||||
// type Auth map[string]AuthInstance
|
||||
|
||||
type AuthInstance struct {
|
||||
Paths Paths `mapstructure:"paths"`
|
||||
OpenID OpenID `mapstructure:"open_id"`
|
||||
}
|
||||
type Paths struct {
|
||||
Prefix string `mapstructure:"prefix"`
|
||||
Login string `mapstructure:"login"`
|
||||
Logout string `mapstructure:"logout"`
|
||||
Callback string `mapstructure:"callback"`
|
||||
}
|
||||
type OpenID struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Realm string `mapstructure:"realm"`
|
||||
ClientID string `mapstructure:"client_id"`
|
||||
ClientSecert string `mapstructure:"client_secret"`
|
||||
RedirectURI string `mapstructure:"redirect_uri"`
|
||||
PostLogoutRedirectURI string `mapstructure:"post_logout_redirect_uri"`
|
||||
ConfigPath string `mapstructure:"config_path"`
|
||||
EndPoints *EndPoints
|
||||
}
|
||||
|
||||
type EndPoints struct {
|
||||
Issuer string
|
||||
Auth string
|
||||
Introspection string
|
||||
Token string
|
||||
UserInfo string
|
||||
Logout string
|
||||
JwksUri string
|
||||
}
|
||||
|
||||
// type OpenIdEndPoints struct {
|
||||
// Issuer string
|
||||
// Authorization string
|
||||
// Token string
|
||||
// Introspection string
|
||||
// UserInfo string
|
||||
// EndSession string
|
||||
// }
|
||||
|
||||
// ReverseProxies
|
||||
|
||||
type ReverseProxies map[string]ReverseProxy
|
||||
|
||||
type ReverseProxy struct {
|
||||
Domain string `mapstructure:"domain"`
|
||||
Host string `mapstructure:"host"`
|
||||
EntryPoint string `mapstructure:"entry_point"`
|
||||
TLS TLS_RP `mapstructure:"tls"`
|
||||
Auth string `mapstructure:"auth"`
|
||||
}
|
||||
type TLS_RP struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
Certs string `mapstructure:"certs"`
|
||||
}
|
||||
|
||||
// TLS
|
||||
type TLS struct {
|
||||
Certs map[string]Certs `mapstructure:"certs"`
|
||||
}
|
||||
type Certs struct {
|
||||
Cert string `mapstructure:"cert"`
|
||||
Key string `mapstructure:"key"`
|
||||
}
|
||||
|
||||
// EntryPoints
|
||||
type EntryPoints map[string]EntryPoint
|
||||
type EntryPoint struct {
|
||||
Port string `mapstructure:"port"`
|
||||
TLS bool `mapstructure:"tls"`
|
||||
}
|
||||
99
internal/logic/logic.go
Normal file
99
internal/logic/logic.go
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
package logic
|
||||
|
||||
import (
|
||||
"github.com/alexedwards/scs/v2"
|
||||
conf "zeevdiukman.com/zprox/internal/config"
|
||||
)
|
||||
|
||||
var config = conf.Get()
|
||||
|
||||
type App struct {
|
||||
SessionManager *scs.SessionManager
|
||||
}
|
||||
|
||||
func (app *App) Get() *App {
|
||||
return app
|
||||
}
|
||||
|
||||
func NewApp() *App {
|
||||
app := &App{
|
||||
SessionManager: scs.New(),
|
||||
}
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
type Groups map[string]*Group
|
||||
type Group struct {
|
||||
Port string
|
||||
TLS bool
|
||||
// ReverseProxies []string
|
||||
ReverseProxies ReverseProxies
|
||||
|
||||
// Server string
|
||||
// GroupRouter string
|
||||
}
|
||||
type ReverseProxies map[string]ReverseProxy
|
||||
type ReverseProxy struct {
|
||||
Certs string
|
||||
}
|
||||
|
||||
func NewGroups() *Groups {
|
||||
grps := &Groups{}
|
||||
grps.initGroups()
|
||||
return grps
|
||||
}
|
||||
|
||||
func (grps Groups) ForEach(fn func(k string, g *Group)) {
|
||||
for k, g := range grps {
|
||||
fn(k, g)
|
||||
}
|
||||
}
|
||||
func (rpxs ReverseProxies) Get() ReverseProxies {
|
||||
n := make(ReverseProxies)
|
||||
for k, v := range rpxs {
|
||||
n[k] = v
|
||||
}
|
||||
return n
|
||||
}
|
||||
func (rpxs ReverseProxies) Set(key string, rp ReverseProxy) ReverseProxies {
|
||||
newProxies := rpxs.Get()
|
||||
newProxies[key] = rp
|
||||
return newProxies
|
||||
}
|
||||
|
||||
// func (rpxs ReverseProxy) Set(newRp *ReverseProxy) {
|
||||
// rpxs = *newRp
|
||||
// }
|
||||
|
||||
func (grps Groups) initGroups() {
|
||||
config.EntryPoints.ForEach(func(epName string, epConfig *conf.EntryPoint) {
|
||||
if _, ok := grps[epName]; !ok {
|
||||
grps[epName] = &Group{}
|
||||
}
|
||||
grps[epName].TLS = epConfig.TLS
|
||||
grps[epName].Port = epConfig.Port
|
||||
})
|
||||
config.ReverseProxies.ForEach(func(rpNameA string, rpConfigA *conf.ReverseProxy) {
|
||||
rps := grps[rpConfigA.EntryPoint].ReverseProxies
|
||||
certName := rpConfigA.TLS.Certs
|
||||
// if _, ok := rps[rpNameA]; !ok {
|
||||
// // grps[rpConfigA.EntryPoint] = &Group{}
|
||||
// // rps[rpNameA]
|
||||
// }
|
||||
rp := ReverseProxy{Certs: certName}
|
||||
a := insertValue(rps, rpNameA, rp)
|
||||
grps[rpConfigA.EntryPoint].ReverseProxies = a
|
||||
})
|
||||
}
|
||||
|
||||
func insertValue[T any](mp map[string]T, key string, val T) map[string]T {
|
||||
n := make(map[string]T)
|
||||
for k, v := range mp {
|
||||
n[k] = v
|
||||
}
|
||||
n[key] = val
|
||||
mp = n
|
||||
return mp
|
||||
// grps[rpConfigA.EntryPoint].ReverseProxies = a
|
||||
}
|
||||
53
internal/middleware/middleware.go
Normal file
53
internal/middleware/middleware.go
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
package middleware
|
||||
|
||||
// import (
|
||||
// "net/http"
|
||||
|
||||
// "github.com/alexedwards/scs/v2"
|
||||
// conf "zeevdiukman.com/zprox/internal/config"
|
||||
// )
|
||||
|
||||
// // import (
|
||||
// // "net/http"
|
||||
// // )
|
||||
|
||||
// func Auth(c *conf.Config, sessionManager *scs.SessionManager) func(http.Handler) http.Handler {
|
||||
// return func(next http.Handler) http.Handler {
|
||||
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// accessToken := sessionManager.GetString(r.Context(), "access_token")
|
||||
// // auth.SetAuthHeader(w, accessToken)
|
||||
// loginPath := c.Auth.Prefix + c.Auth.LoginPath
|
||||
// logoutPath := c.Auth.Prefix + c.Auth.LogoutPath
|
||||
// if loginPath == r.URL.Path || logoutPath == r.URL.Path {
|
||||
// next.ServeHTTP(w, r)
|
||||
// return
|
||||
// }
|
||||
|
||||
// tokenOk := Domain.IsAuthorizedJWT(accessToken)
|
||||
// if tokenOk {
|
||||
// next.ServeHTTP(w, r)
|
||||
// } else {
|
||||
// p := c.Auth
|
||||
// http.Redirect(w, r, p.Prefix+p.LoginPath, http.StatusFound) // Redirect to login
|
||||
// }
|
||||
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
// func Domain(next http.Handler) http.Handler {
|
||||
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// // requestedPath := r.URL.Path
|
||||
// // a := c.Auth
|
||||
// // excludedPaths := []string{
|
||||
// // a.Prefix + a.LoginPath,
|
||||
// // a.Prefix + a.CallbackPath,
|
||||
// // a.Prefix + a.LogoutPath,
|
||||
// // }
|
||||
// // contains := helper.IsSliceContains(excludedPaths, requestedPath)
|
||||
// // if !contains {
|
||||
// // sessionManager.Put(r.Context(), "original_path", requestedPath)
|
||||
// // }
|
||||
// next.ServeHTTP(w, r)
|
||||
// })
|
||||
// }
|
||||
107
internal/reverse_proxy/reverse_proxy.go
Normal file
107
internal/reverse_proxy/reverse_proxy.go
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
package reverse_proxy
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
conf "zeevdiukman.com/zprox/internal/config"
|
||||
)
|
||||
|
||||
// var config = conf.New()
|
||||
|
||||
type ReverseProxy struct {
|
||||
Name string
|
||||
Host string
|
||||
Httputil *httputil.ReverseProxy
|
||||
}
|
||||
|
||||
func New(host string) *ReverseProxy {
|
||||
return &ReverseProxy{
|
||||
Host: host,
|
||||
Httputil: &httputil.ReverseProxy{
|
||||
Director: DefaultDirector(host),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type Data struct {
|
||||
Name string
|
||||
Value any
|
||||
}
|
||||
type DirectorFunc func(req *http.Request, data []Data) (*http.Request, []Data)
|
||||
|
||||
func GetData[T any](key string, data []Data) (res T) {
|
||||
for _, v := range data {
|
||||
if v.Name == key {
|
||||
res = v.Value.(T)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (rp *ReverseProxy) DefaultDirectorFunc(d []Data, fn DirectorFunc) func(*http.Request) {
|
||||
return func(r *http.Request) {
|
||||
host := ""
|
||||
req, data := fn(r, d)
|
||||
proxyData := GetData[conf.ReverseProxy]("proxy_data", data)
|
||||
host = proxyData.Host
|
||||
target, _ := url.Parse(host)
|
||||
targetQuery := target.RawQuery
|
||||
req.URL.Scheme = target.Scheme
|
||||
req.URL.Host = target.Host
|
||||
req.URL.Path, req.URL.RawPath = joinURLPath(target, req.URL)
|
||||
if targetQuery == "" || req.URL.RawQuery == "" {
|
||||
req.URL.RawQuery = targetQuery + req.URL.RawQuery
|
||||
} else {
|
||||
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
|
||||
}
|
||||
}
|
||||
}
|
||||
func DefaultDirector(host string) func(*http.Request) {
|
||||
return func(req *http.Request) {
|
||||
target, _ := url.Parse(host)
|
||||
targetQuery := target.RawQuery
|
||||
req.URL.Scheme = target.Scheme
|
||||
req.URL.Host = target.Host
|
||||
req.URL.Path, req.URL.RawPath = joinURLPath(target, req.URL)
|
||||
if targetQuery == "" || req.URL.RawQuery == "" {
|
||||
req.URL.RawQuery = targetQuery + req.URL.RawQuery
|
||||
} else {
|
||||
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
|
||||
}
|
||||
}
|
||||
}
|
||||
func joinURLPath(a, b *url.URL) (path, rawpath string) {
|
||||
if a.RawPath == "" && b.RawPath == "" {
|
||||
return SingleJoiningSlash(a.Path, b.Path), ""
|
||||
}
|
||||
// Same as singleJoiningSlash, but uses EscapedPath to determine
|
||||
// whether a slash should be added
|
||||
apath := a.EscapedPath()
|
||||
bpath := b.EscapedPath()
|
||||
|
||||
aslash := strings.HasSuffix(apath, "/")
|
||||
bslash := strings.HasPrefix(bpath, "/")
|
||||
|
||||
switch {
|
||||
case aslash && bslash:
|
||||
return a.Path + b.Path[1:], apath + bpath[1:]
|
||||
case !aslash && !bslash:
|
||||
return a.Path + "/" + b.Path, apath + "/" + bpath
|
||||
}
|
||||
return a.Path + b.Path, apath + bpath
|
||||
}
|
||||
|
||||
func SingleJoiningSlash(a, b string) string {
|
||||
aslash := strings.HasSuffix(a, "/")
|
||||
bslash := strings.HasPrefix(b, "/")
|
||||
switch {
|
||||
case aslash && bslash:
|
||||
return a + b[1:]
|
||||
case !aslash && !bslash:
|
||||
return a + "/" + b
|
||||
}
|
||||
return a + b
|
||||
}
|
||||
24
internal/router/router.go
Normal file
24
internal/router/router.go
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
package router
|
||||
|
||||
import "github.com/gorilla/mux"
|
||||
|
||||
func New() *MainRouter {
|
||||
r := &MainRouter{}
|
||||
r.SetMux()
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *MainRouter) SetMux() {
|
||||
r.Mux = mux.NewRouter()
|
||||
}
|
||||
|
||||
type MainRouter struct {
|
||||
Subrouters Subrouters
|
||||
Mux *mux.Router
|
||||
}
|
||||
type Subrouters map[string]SubRouter
|
||||
type SubRouter struct {
|
||||
Name string
|
||||
Group string
|
||||
*mux.Router
|
||||
}
|
||||
512
pkg/helper/helper.go
Normal file
512
pkg/helper/helper.go
Normal file
|
|
@ -0,0 +1,512 @@
|
|||
package helper
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/gookit/goutil/dump"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// const (
|
||||
// COLOR_Reset = "\033[0m"
|
||||
// COLOR_Red = "\033[31m"
|
||||
// COLOR_Green = "\033[32m"
|
||||
// COLOR_Yellow = "\033[33m"
|
||||
// COLOR_Blue = "\033[34m"
|
||||
// COLOR_Purple = "\033[35m"
|
||||
// COLOR_Cyan = "\033[36m"
|
||||
// COLOR_Gray = "\033[37m"
|
||||
// COLOR_White = "\033[97m" // Brighter white
|
||||
// )
|
||||
|
||||
// func Log(msg string) {
|
||||
// log.New(os.Stdout, msg, log.Ldate|log.Ltime)
|
||||
|
||||
// }
|
||||
// func Colorize(colorCode string, message string) string {
|
||||
// return colorCode + message + COLOR_Reset
|
||||
// }
|
||||
|
||||
type IsStruct struct{}
|
||||
|
||||
var (
|
||||
Is IsStruct
|
||||
h = New()
|
||||
)
|
||||
|
||||
type Helper struct {
|
||||
Convert Convert
|
||||
Struct Struct
|
||||
Screen Screen
|
||||
Error Error
|
||||
If If
|
||||
Log Log
|
||||
}
|
||||
type Convert struct {
|
||||
}
|
||||
type Struct struct {
|
||||
}
|
||||
type Screen struct {
|
||||
}
|
||||
type Error struct {
|
||||
Val error
|
||||
}
|
||||
type If struct {
|
||||
Error Error
|
||||
}
|
||||
type Log struct {
|
||||
Error Error
|
||||
}
|
||||
|
||||
func (h *Helper) P(val any) {
|
||||
dump.Println(val)
|
||||
}
|
||||
|
||||
// InsertStringValueIntoField inserts a string value into a specified field of a struct.
|
||||
//
|
||||
// structPtr must be a pointer to a struct.
|
||||
// fieldName is the name of the field (case-sensitive).
|
||||
// stringValue is the string value to be inserted.
|
||||
//
|
||||
// Returns an error if:
|
||||
// - structPtr is not a pointer to a struct.
|
||||
// - The fieldName is not found in the struct.
|
||||
// - The field is not settable (e.g., unexported).
|
||||
// - The field is not of type string.
|
||||
func New() *Helper {
|
||||
return &Helper{}
|
||||
}
|
||||
|
||||
func (e *Error) Log() {
|
||||
if e.Val != nil {
|
||||
log.Println(e.Val.Error())
|
||||
}
|
||||
}
|
||||
func (e *Error) In(err error) *Error {
|
||||
e.Val = err
|
||||
return e
|
||||
}
|
||||
func (conv *Struct) Insert(structPtr interface{}, param ...any) {
|
||||
// func (conv *Struct) Insert(structPtr interface{}, fieldName string, stringValue string, sep string) string {
|
||||
|
||||
rawFieldName := param[0].(string)
|
||||
fieldValue := param[1]
|
||||
sep := ""
|
||||
if len(param) > 2 {
|
||||
sep = param[2].(string)
|
||||
} else {
|
||||
sep = "_"
|
||||
}
|
||||
// if len(param[2].(string)) > 0 {
|
||||
// sep = param[2].(string)
|
||||
// }
|
||||
seperatorRune := []rune(sep)[0]
|
||||
field := CapitalizeAfterChar(rawFieldName, seperatorRune)
|
||||
field = CapitalizeFirstLetter(field)
|
||||
field = strings.ReplaceAll(field, sep, "")
|
||||
|
||||
err := InsertAnyValueIntoField(structPtr, field, fieldValue)
|
||||
h.Error.Val = err
|
||||
h.If.Error.Log()
|
||||
}
|
||||
func (conv *Struct) ToStructField(str string, seperator string) string {
|
||||
seperatorRune := []rune(seperator)[0]
|
||||
field := CapitalizeAfterChar(str, seperatorRune)
|
||||
field = CapitalizeFirstLetter(field)
|
||||
field = strings.ReplaceAll(field, seperator, "")
|
||||
return field
|
||||
}
|
||||
func InsertStringValueIntoField(structPtr interface{}, fieldName string, stringValue string) error {
|
||||
if structPtr == nil {
|
||||
return errors.New("structPtr cannot be nil")
|
||||
}
|
||||
|
||||
val := reflect.ValueOf(structPtr)
|
||||
if val.Kind() != reflect.Ptr {
|
||||
return errors.New("structPtr must be a pointer to a struct")
|
||||
}
|
||||
|
||||
elem := val.Elem()
|
||||
if elem.Kind() != reflect.Struct {
|
||||
return errors.New("structPtr must be a pointer to a struct")
|
||||
}
|
||||
|
||||
fieldVal := elem.FieldByName(fieldName)
|
||||
if !fieldVal.IsValid() {
|
||||
return fmt.Errorf("field '%s' not found in struct", fieldName)
|
||||
}
|
||||
|
||||
if !fieldVal.CanSet() {
|
||||
return fmt.Errorf("field '%s' is not settable (unexported or embedded without being exported)", fieldName)
|
||||
}
|
||||
|
||||
if fieldVal.Kind() != reflect.String {
|
||||
return fmt.Errorf("field '%s' is not a string type", fieldName)
|
||||
}
|
||||
|
||||
fieldVal.SetString(stringValue)
|
||||
return nil
|
||||
}
|
||||
func InsertAnyValueIntoField(structPtr interface{}, fieldName string, anyValue any) error {
|
||||
if structPtr == nil {
|
||||
return errors.New("structPtr cannot be nil")
|
||||
}
|
||||
|
||||
val := reflect.ValueOf(structPtr)
|
||||
if val.Kind() != reflect.Ptr {
|
||||
return errors.New("structPtr must be a pointer to a struct")
|
||||
}
|
||||
|
||||
elem := val.Elem()
|
||||
if elem.Kind() != reflect.Struct {
|
||||
return errors.New("structPtr must be a pointer to a struct")
|
||||
}
|
||||
|
||||
fieldVal := elem.FieldByName(fieldName)
|
||||
if !fieldVal.IsValid() {
|
||||
return fmt.Errorf("field '%s' not found in struct", fieldName)
|
||||
}
|
||||
|
||||
if !fieldVal.CanSet() {
|
||||
return fmt.Errorf("field '%s' is not settable (unexported or embedded without being exported)", fieldName)
|
||||
}
|
||||
|
||||
switch v := anyValue.(type) {
|
||||
case string:
|
||||
{
|
||||
if fieldVal.Kind() != reflect.String {
|
||||
return fmt.Errorf("field '%s' is not a string type", fieldName)
|
||||
}
|
||||
|
||||
fieldVal.SetString(v)
|
||||
return nil
|
||||
}
|
||||
case bool:
|
||||
{
|
||||
if fieldVal.Kind() != reflect.Bool {
|
||||
return fmt.Errorf("field '%s' is not a string type", fieldName)
|
||||
}
|
||||
|
||||
fieldVal.SetBool(v)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("value of field '%s' is unknown type", fieldName)
|
||||
}
|
||||
|
||||
// CapitalizeAfterChar capitalizes the first letter immediately following each occurrence of a specific character in a string.
|
||||
//
|
||||
// For example:
|
||||
// CapitalizeAfterChar("hello_world", '_') == "hello_World"
|
||||
// CapitalizeAfterChar("this-is-a-test", '-') == "this-Is-A-Test"
|
||||
// CapitalizeAfterChar(" leading spaces and_underscores", '_') == " leading spaces and_Underscores"
|
||||
func CapitalizeAfterChar(input string, char rune) string {
|
||||
var result strings.Builder
|
||||
capitalizeNext := false // Flag to indicate if the next letter should be capitalized
|
||||
|
||||
for _, r := range input {
|
||||
if capitalizeNext {
|
||||
result.WriteRune(unicode.ToUpper(r)) // Capitalize the current rune
|
||||
capitalizeNext = false // Reset the flag
|
||||
} else {
|
||||
result.WriteRune(r) // Write the rune as is
|
||||
}
|
||||
|
||||
if r == char {
|
||||
capitalizeNext = true // Set the flag to capitalize the next letter
|
||||
}
|
||||
}
|
||||
return result.String()
|
||||
}
|
||||
|
||||
func CapitalizeAfterCharMulti(input string, char rune) string {
|
||||
var result strings.Builder
|
||||
capitalizeNext := false // Flag to indicate if the next letter should be capitalized
|
||||
|
||||
for _, r := range input {
|
||||
if capitalizeNext {
|
||||
result.WriteRune(unicode.ToUpper(r)) // Capitalize the current rune
|
||||
capitalizeNext = false // Reset the flag
|
||||
} else {
|
||||
result.WriteRune(r) // Write the rune as is
|
||||
}
|
||||
|
||||
if r == char {
|
||||
capitalizeNext = true // Set the flag to capitalize the next letter
|
||||
}
|
||||
}
|
||||
return result.String()
|
||||
}
|
||||
|
||||
func CapitalizeFirstLetter(input string) string {
|
||||
if input == "" {
|
||||
return input // Return empty string if input is empty
|
||||
}
|
||||
|
||||
runes := []rune(input) // Convert string to rune slice for Unicode support
|
||||
firstRune := runes[0]
|
||||
|
||||
if !unicode.IsLetter(firstRune) {
|
||||
return input // Return original string if first char is not a letter
|
||||
}
|
||||
|
||||
capitalizedFirstRune := unicode.ToUpper(firstRune)
|
||||
runes[0] = capitalizedFirstRune
|
||||
|
||||
return string(runes) // Convert rune slice back to string
|
||||
}
|
||||
func MapIter[K comparable, V comparable](m map[K]V, fn func(K, V)) {
|
||||
for p, d := range m {
|
||||
fn(p, d)
|
||||
}
|
||||
}
|
||||
func (*Screen) Clear() {
|
||||
if runtime.GOOS == "windows" {
|
||||
cmd := exec.Command("cmd", "/c", "cls")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Run()
|
||||
} else {
|
||||
cmd := exec.Command("clear")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Run()
|
||||
}
|
||||
}
|
||||
|
||||
func RandStringByBits(nBits int) string {
|
||||
b := make([]byte, nBits/8)
|
||||
if _, err := io.ReadFull(rand.Reader, b); err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
return base64.RawURLEncoding.EncodeToString(b)
|
||||
}
|
||||
|
||||
func StructToMap(obj interface{}) map[string]any {
|
||||
|
||||
val := reflect.ValueOf(obj)
|
||||
typ := reflect.TypeOf(obj)
|
||||
result := make(map[string]any)
|
||||
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
field := typ.Field(i)
|
||||
result[field.Name] = val.Field(i).Interface()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func FetchGetJson(u string) (map[string]any, error) {
|
||||
resp, err := http.Get(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
respByes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := map[string]any{}
|
||||
json.Unmarshal(respByes, &data)
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func GetLastChar(str string) string {
|
||||
return str[len(str)-1:]
|
||||
}
|
||||
|
||||
func IsLastChar(str string, chr string) bool {
|
||||
if l := GetLastChar(str); l == chr {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
}
|
||||
|
||||
func IsLastCharDo(str string, chr string, f func(r bool)) bool {
|
||||
result := IsLastChar(str, chr)
|
||||
|
||||
func(bool) {
|
||||
f(result)
|
||||
}(result)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func IsFetchOK[V any](u string, funcName string, f func(string) (*http.Response, error)) *http.Response {
|
||||
// errCntr := 0
|
||||
errCntrTotal := 0
|
||||
var (
|
||||
a V
|
||||
resp *http.Response
|
||||
err error
|
||||
)
|
||||
for {
|
||||
resp, err = f(u)
|
||||
if err == nil && resp.StatusCode == 200 {
|
||||
// ClearScreen()
|
||||
break
|
||||
} else {
|
||||
// errCntr++
|
||||
// errCntrTotal++
|
||||
// if errCntr == 10 {
|
||||
// errCntr = 0
|
||||
// ClearScreen()
|
||||
// }
|
||||
// if errCntr == 1 {
|
||||
// ClearScreen()
|
||||
// }
|
||||
errCntrTotalStr := strconv.Itoa(errCntrTotal)
|
||||
pkg := reflect.TypeOf(a).PkgPath()
|
||||
dt := time.Now()
|
||||
dateTime := dt.Format("02-01-2006 15:04:05")
|
||||
fmt.Println("ERROR: " + dateTime + ">---------<" + errCntrTotalStr + ">")
|
||||
fmt.Println(" :")
|
||||
fmt.Println(" Package name: " + pkg)
|
||||
fmt.Println("Function name: " + funcName)
|
||||
fmt.Println(" URL: " + u)
|
||||
fmt.Println("Error message: " + err.Error())
|
||||
fmt.Println("--------------------------------------")
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
func IsSliceContains[T comparable](sliceVals []T, valueToSearch T) bool {
|
||||
for _, v := range sliceVals {
|
||||
if v == valueToSearch {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func AddDotBetween(k ...string) string {
|
||||
constructedKey := ""
|
||||
for i, key := range k {
|
||||
constructedKey += key
|
||||
if i < len(k)-1 {
|
||||
constructedKey += "."
|
||||
}
|
||||
}
|
||||
return constructedKey
|
||||
}
|
||||
|
||||
func RemoveSliceDuplicates(elements []string) []string {
|
||||
encountered := map[string]bool{}
|
||||
result := []string{}
|
||||
|
||||
for v := range elements {
|
||||
if encountered[elements[v]] {
|
||||
// Do not add duplicate.
|
||||
} else {
|
||||
// Record this element as an encountered element.
|
||||
encountered[elements[v]] = true
|
||||
// Append to result slice.
|
||||
result = append(result, elements[v])
|
||||
}
|
||||
}
|
||||
// Return the new slice.
|
||||
return result
|
||||
}
|
||||
|
||||
func IfLast[T any](counter int, someVar map[string]T, fn func()) {
|
||||
counter++
|
||||
if len(someVar) == counter {
|
||||
fn()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *IsStruct) Pointer(v any) bool {
|
||||
|
||||
valueOfP := reflect.ValueOf(v)
|
||||
if valueOfP.Kind() == reflect.Ptr {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func AppRunner(runApp func()) {
|
||||
wg := sync.WaitGroup{}
|
||||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
runApp()
|
||||
|
||||
<-ctx.Done()
|
||||
//do stuff after ending
|
||||
wg.Wait()
|
||||
fmt.Println("BYE BYE!")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func StartTestHTTPServer(port int) {
|
||||
p := strconv.Itoa(port)
|
||||
go func() {
|
||||
log.Println("Test server is running at http://" + GetIP() + ":" + p)
|
||||
r := mux.NewRouter()
|
||||
r.Path("/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, "/ OK")
|
||||
})
|
||||
r.Path("/test1").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, "/test1 OK")
|
||||
})
|
||||
r.Path("/test2").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, "/test2 OK")
|
||||
})
|
||||
err := http.ListenAndServe(":3000", r)
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func GetIP(prefix ...string) string {
|
||||
prfx := "192.168."
|
||||
if len(prefix) > 0 {
|
||||
prfx = prefix[0]
|
||||
}
|
||||
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
fmt.Println("Error getting interface addresses:", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
// Check if the address is an IP address
|
||||
if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
|
||||
|
||||
a := strings.HasPrefix(ipnet.IP.String(), prfx)
|
||||
c := ipnet.IP.To4() != nil
|
||||
if c && a { //check for ipv4
|
||||
prfx = ipnet.IP.String()
|
||||
break
|
||||
}
|
||||
// } else if ipnet.IP.To16() != nil { //check for ipv6
|
||||
// fmt.Println("Local IPv6 address:", ipnet.IP.String())
|
||||
// }
|
||||
}
|
||||
}
|
||||
return prfx
|
||||
}
|
||||
1
tmp/build-errors.log
Normal file
1
tmp/build-errors.log
Normal file
File diff suppressed because one or more lines are too long
BIN
tmp/main
Executable file
BIN
tmp/main
Executable file
Binary file not shown.
33
z.com.cert.pem
Normal file
33
z.com.cert.pem
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIFtzCCA5+gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwcTELMAkGA1UEBhMCSUwx
|
||||
DjAMBgNVBAgMBUhhaWZhMQ4wDAYDVQQHDAVIYWlmYTEKMAgGA1UECgwBWjEKMAgG
|
||||
A1UECwwBWjEOMAwGA1UEAwwFei5jb20xGjAYBgkqhkiG9w0BCQEWC2FkbWluQHou
|
||||
Y29tMB4XDTI1MDIxNjE0NTQ1NloXDTI2MDIyNjE0NTQ1NlowYTELMAkGA1UEBhMC
|
||||
SUwxDjAMBgNVBAgMBUhhaWZhMQowCAYDVQQKDAFaMQowCAYDVQQLDAFaMQ4wDAYD
|
||||
VQQDDAV6LmNvbTEaMBgGCSqGSIb3DQEJARYLYWRtaW5Aei5jb20wggEiMA0GCSqG
|
||||
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpdNQlUMd1VOeXLYx5bLct+CCEC1XQDboD
|
||||
DnhQcCOryNGYvOd+u/KyOIKie+TvMCzKSqtv66cy216r4xt1T0w/S5YGhthtM6UA
|
||||
KZroRRqjuyxt9n+F+u8Mq6wuAYOitWYFSwDlLj+XUM++REljekgYMPJFjRNeshdv
|
||||
ciAvmgSuba887lcfK9SnkV2GMGetqwtbSxBPPH2nsNl+1yPNrzJw1HatcZiyEF2e
|
||||
UeseR+yJ8IBqwzalB+GdlPOiy31eKArUYn6F3mWrPpTuJKf8/uAEx4tHkEdeAiaP
|
||||
lPH8/D2ACExi3bPQBZ0Mu4XlQujSG+vyW2UE6uiMAk+j4xEqCvTVAgMBAAGjggFn
|
||||
MIIBYzAJBgNVHRMEAjAAMBEGCWCGSAGG+EIBAQQEAwIGQDAzBglghkgBhvhCAQ0E
|
||||
JhYkT3BlblNTTCBHZW5lcmF0ZWQgU2VydmVyIENlcnRpZmljYXRlMB0GA1UdDgQW
|
||||
BBRoAJ51iApg+pFjY16jj6Nutnw5+jCBrgYDVR0jBIGmMIGjgBSHMJSKqW4qORmC
|
||||
/Wq8UImiAtuWjaF1pHMwcTELMAkGA1UEBhMCSUwxDjAMBgNVBAgMBUhhaWZhMQ4w
|
||||
DAYDVQQHDAVIYWlmYTEKMAgGA1UECgwBWjEKMAgGA1UECwwBWjEOMAwGA1UEAwwF
|
||||
ei5jb20xGjAYBgkqhkiG9w0BCQEWC2FkbWluQHouY29tghQD85U1CPpeLaY/YPkc
|
||||
xQBVyuU4+zAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwGQYD
|
||||
VR0RBBIwEIIFei5jb22CByouei5jb20wDQYJKoZIhvcNAQELBQADggIBAF/IKfK8
|
||||
dF6qAM0SMr+3K9fEQgJWDfHI2bLFnsFpzRHc5XUGnvp5sRCEXFDJIJSOwGQqv3rm
|
||||
ylcFVBE4lawtMC/NpMMRSz7/e/NdA/b5CFtCK2EjnM/KYE9EV06EebP0u8yRIppM
|
||||
Go71j7fAbncVmUwnhLcgIkpb+VKTfexFUIqSVgeXTFkIQa7ndP70W+AUt/5X+Rhy
|
||||
7c3rE6fklTabMJR8SxQKLz6KJzSJnRLH75H7CxmF2d7NP23HuEfk7OrTRQP+ASmD
|
||||
VJkzS6GtVQ/MEjVbj9ygABDVmk+0z2prJz1USyqniUhnznQZPOGz0F/M3VUiodsp
|
||||
rW3YOyPh6Ze8bHih4ivuNTsiXQKSiWWgIP8zEu664hwWtuXZAbVKP3XfLZ5X7gJ2
|
||||
Vqj9ulkIa+3VjSv+WA45QZVdBtSoOaNobRaKtjFnK5DWeW6t0e1+DG5PVZe1JTwH
|
||||
DUxfUEnXDBfsfttqHAHCamWo1dpWzJB9lnTjXHwQHkYYEimTLrPVhzwFV5yegqkP
|
||||
feMtsgbEsO+QqVSeqx3oy+W2J9tjBwAxrMg1TOMBSWRsaUvwtXwx1cf1bTzjhg9Y
|
||||
2+dzTmITfxh3tWMh0jYmO1C3PH2K8HKraAXdqrGDbxK94iYpgA5bk48s2H1YCDcF
|
||||
baYw2irjJC2cmsdGp3an0Mtb8sY4zLbGuRQl
|
||||
-----END CERTIFICATE-----
|
||||
28
z.com.key.pem
Normal file
28
z.com.key.pem
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCpdNQlUMd1VOeX
|
||||
LYx5bLct+CCEC1XQDboDDnhQcCOryNGYvOd+u/KyOIKie+TvMCzKSqtv66cy216r
|
||||
4xt1T0w/S5YGhthtM6UAKZroRRqjuyxt9n+F+u8Mq6wuAYOitWYFSwDlLj+XUM++
|
||||
REljekgYMPJFjRNeshdvciAvmgSuba887lcfK9SnkV2GMGetqwtbSxBPPH2nsNl+
|
||||
1yPNrzJw1HatcZiyEF2eUeseR+yJ8IBqwzalB+GdlPOiy31eKArUYn6F3mWrPpTu
|
||||
JKf8/uAEx4tHkEdeAiaPlPH8/D2ACExi3bPQBZ0Mu4XlQujSG+vyW2UE6uiMAk+j
|
||||
4xEqCvTVAgMBAAECggEALwfSvUyPHxxibk3g9+5ZZLPB6oPu3CCDKMgCUmjdLZc9
|
||||
vMNpCH6HXDlc4FW8czoOpFJXBGgF7pJ90vzkKQnKIqMKz2LrfFtiBwqFCMPtIdYX
|
||||
/aj1Oa0sXXrj/ZzD+QuZdgycAf90/L0b+zWenLJaggRLqUv/PT/2SyMEldGMTRCG
|
||||
c4+0qdIBhNVPjr6bIX6nOgnexFDFqvfLMy8LHq7LV20xqJDZiu3Xth1BBbCKId+a
|
||||
rngLU7oGSaIGSfhqLm02F3XPniMktrVkKc00aNzwAOBLLgiWpJzuwcyKCkDhAiBZ
|
||||
1rfniF6gW/PTWnZ4rscTiF4Dn8iKcr7r7LY+skEuXQKBgQDV9V8tBRnE/qUVqgjK
|
||||
j0Z7kwhTFWlCaLEqNzfXuV2C2QPYdF4B4nxIUILSMpkgtbDrW7OZBwMk4ac5FCvS
|
||||
4ok5LcisHs9MgBb4thyf5J+ZKTOM7Z0W+h1MgIs9Bful/PkPHyfNd8rFnhHjGIr9
|
||||
05Uqewu8Jwxz8K3hZs1/3fyVlwKBgQDKwOSNCaoWqDi92/vJphoDZOE0vlB3Ti24
|
||||
Rg57IlMJZh/l74Qu380E9tC6YidN18yAd4Z9xSBde6ymTdfc8SaID2aIlTZh1kUy
|
||||
TlWM+JZS5Nzwywb7dBJK/u6+Foe7HsYqUb8Sog/ne6ox0fFIh4thwO1Hh9uUv63L
|
||||
O80UFXaOcwKBgQDEe4jjtwNrPM4tjvBz1A9N/EBwzADV036e3gaSPM/7EX/Oj06l
|
||||
PHAVmJoKnhyxRSkrehL8PMxOWktOx49XImIR+FGIfuKvxhFSZSr0Suelp4iHqs3Q
|
||||
A/BUCNfVOmFWlXHCyUGsFo5H3Flgy3EYl+0sDcNBDjsJXcTQca/V9O24EQKBgBfy
|
||||
0swp8RI+Cn26hzIZUYdHGia9uAlvjYzvkXRP6Jj6nBfvw6A5xSCp+puZTmUucTRX
|
||||
aeZfK2R/YDRAi5fIUDHQB99oKIVD5uZ7RDWjgzYFXGeAw7Fd029ST2baiGu8xdFn
|
||||
2Hbd95zzCXZbAvH7OKZyQFSrom8eeOvBg4a0xk0rAoGBAMYguMmRtSXHNy5VYr02
|
||||
JnDUkEEo+qr9pb+z57OqzP1tDVQzjqovy1MQUrI9jLX78lky23P6Qi3mJATu73qo
|
||||
a14TIRGxPYzfYplhzWqi+LonohKmvRG+Gm6u82abesqgIjUuZmvyxQa7grMi90h8
|
||||
p5t8O+ki5NtPp+RAy5pDZg5Z
|
||||
-----END PRIVATE KEY-----
|
||||
Loading…
Reference in a new issue