Format go files with tab
ci/woodpecker/push/build Pipeline was successful
Details
ci/woodpecker/push/build Pipeline was successful
Details
This commit is contained in:
parent
3c3de0674e
commit
ad3fa1e246
|
@ -9,10 +9,10 @@ charset = utf-8
|
|||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
# CSS
|
||||
# GO
|
||||
[*.go]
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
indent_style = tab
|
||||
|
||||
# HTML
|
||||
[*.{htm,html}]
|
||||
|
|
|
@ -1,80 +1,80 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
quickCert "code.mrmelon54.com/melon/summer/cmd/azalea/quick-cert"
|
||||
"code.mrmelon54.com/melon/summer/cmd/azalea/servers"
|
||||
"code.mrmelon54.com/melon/summer/pkg/cli"
|
||||
"code.mrmelon54.com/melon/summer/pkg/proxy"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/certificate"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/renewal"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/web"
|
||||
"code.mrmelon54.com/melon/summer/pkg/utils"
|
||||
"errors"
|
||||
"github.com/mrmelon54/mjwt"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strconv"
|
||||
"strings"
|
||||
quickCert "code.mrmelon54.com/melon/summer/cmd/azalea/quick-cert"
|
||||
"code.mrmelon54.com/melon/summer/cmd/azalea/servers"
|
||||
"code.mrmelon54.com/melon/summer/pkg/cli"
|
||||
"code.mrmelon54.com/melon/summer/pkg/proxy"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/certificate"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/renewal"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/web"
|
||||
"code.mrmelon54.com/melon/summer/pkg/utils"
|
||||
"errors"
|
||||
"github.com/mrmelon54/mjwt"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var usedTables = []any{
|
||||
&web.Domain{},
|
||||
&web.ApiRoute{},
|
||||
&web.HttpHeader{},
|
||||
&web.HttpService{},
|
||||
&web.HttpRedirect{},
|
||||
&renewal.AcmeContent{},
|
||||
&certificate.Certificate{},
|
||||
&certificate.CertificateData{},
|
||||
&certificate.CertificateDomain{},
|
||||
&web.Domain{},
|
||||
&web.ApiRoute{},
|
||||
&web.HttpHeader{},
|
||||
&web.HttpService{},
|
||||
&web.HttpRedirect{},
|
||||
&renewal.AcmeContent{},
|
||||
&certificate.Certificate{},
|
||||
&certificate.CertificateData{},
|
||||
&certificate.CertificateDomain{},
|
||||
}
|
||||
|
||||
func main() {
|
||||
conf := cli.DecodeConfig[AzaleaConfig]("azalea")
|
||||
cli.Run("azalea", conf.Database, &Azalea{conf: conf})
|
||||
conf := cli.DecodeConfig[AzaleaConfig]("azalea")
|
||||
cli.Run("azalea", conf.Database, &Azalea{conf: conf})
|
||||
}
|
||||
|
||||
type Azalea struct {
|
||||
conf AzaleaConfig
|
||||
runner *cli.Runner
|
||||
reverseProxy *httputil.ReverseProxy
|
||||
httpServer *http.Server
|
||||
httpsServer *http.Server
|
||||
apiServer *http.Server
|
||||
verify mjwt.Provider
|
||||
conf AzaleaConfig
|
||||
runner *cli.Runner
|
||||
reverseProxy *httputil.ReverseProxy
|
||||
httpServer *http.Server
|
||||
httpsServer *http.Server
|
||||
apiServer *http.Server
|
||||
verify mjwt.Provider
|
||||
}
|
||||
|
||||
func (azalea *Azalea) Init(runner *cli.Runner) {
|
||||
azalea.runner = runner
|
||||
utils.Check("[Azalea.Init()] Failed to sync tables", runner.Database.Sync(usedTables...))
|
||||
quickCert.QuickCert(runner.Database)
|
||||
azalea.runner = runner
|
||||
utils.Check("[Azalea.Init()] Failed to sync tables", runner.Database.Sync(usedTables...))
|
||||
quickCert.QuickCert(runner.Database)
|
||||
|
||||
addrPort, err := splitPortCombo(azalea.conf.Listen.Https)
|
||||
utils.Check("[Azalea.Init()] Failed to parse HTTPs port", err)
|
||||
azalea.verify, err = mjwt.NewMJwtVerifierFromFile(azalea.conf.Auth.Public)
|
||||
utils.Check("[Azalea.Init()] Failed to load verifier public key", err)
|
||||
addrPort, err := splitPortCombo(azalea.conf.Listen.Https)
|
||||
utils.Check("[Azalea.Init()] Failed to parse HTTPs port", err)
|
||||
azalea.verify, err = mjwt.NewMJwtVerifierFromFile(azalea.conf.Auth.Public)
|
||||
utils.Check("[Azalea.Init()] Failed to load verifier public key", err)
|
||||
|
||||
azalea.reverseProxy = proxy.CreateHybridReverseProxy()
|
||||
azalea.httpServer = servers.NewHttpServer(azalea.conf.Listen.Http, addrPort, azalea.runner.Database)
|
||||
azalea.httpsServer = servers.NewHttpsServer(azalea.conf.Listen.Https, azalea.runner.Database, azalea.reverseProxy, azalea.conf.ApiDomain)
|
||||
azalea.apiServer = servers.NewApiServer(azalea.conf.Listen.Api, azalea.runner.Database, azalea.verify)
|
||||
azalea.reverseProxy = proxy.CreateHybridReverseProxy()
|
||||
azalea.httpServer = servers.NewHttpServer(azalea.conf.Listen.Http, addrPort, azalea.runner.Database)
|
||||
azalea.httpsServer = servers.NewHttpsServer(azalea.conf.Listen.Https, azalea.runner.Database, azalea.reverseProxy, azalea.conf.ApiDomain)
|
||||
azalea.apiServer = servers.NewApiServer(azalea.conf.Listen.Api, azalea.runner.Database, azalea.verify)
|
||||
}
|
||||
|
||||
func (azalea *Azalea) Destroy() {
|
||||
_ = azalea.httpServer.Close()
|
||||
_ = azalea.httpsServer.Close()
|
||||
_ = azalea.apiServer.Close()
|
||||
_ = azalea.httpServer.Close()
|
||||
_ = azalea.httpsServer.Close()
|
||||
_ = azalea.apiServer.Close()
|
||||
}
|
||||
|
||||
func splitPortCombo(a string) (uint16, error) {
|
||||
i := strings.LastIndexByte(a, ':')
|
||||
if i == -1 {
|
||||
return 0, errors.New("not an ip:port")
|
||||
}
|
||||
port := a[i+1:]
|
||||
parseInt, err := strconv.ParseUint(port, 10, 16)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint16(parseInt), err
|
||||
i := strings.LastIndexByte(a, ':')
|
||||
if i == -1 {
|
||||
return 0, errors.New("not an ip:port")
|
||||
}
|
||||
port := a[i+1:]
|
||||
parseInt, err := strconv.ParseUint(port, 10, 16)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint16(parseInt), err
|
||||
}
|
||||
|
|
|
@ -1,33 +1,33 @@
|
|||
package servers
|
||||
|
||||
import (
|
||||
"code.mrmelon54.com/melon/summer/pkg/api/crud"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/web"
|
||||
"code.mrmelon54.com/melon/summer/pkg/utils"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mrmelon54/mjwt"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
"xorm.io/xorm"
|
||||
"code.mrmelon54.com/melon/summer/pkg/api/crud"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/web"
|
||||
"code.mrmelon54.com/melon/summer/pkg/utils"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mrmelon54/mjwt"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func NewApiServer(listen string, db *xorm.Engine, verify mjwt.Provider) *http.Server {
|
||||
router := mux.NewRouter()
|
||||
crud.NewCrudHandler[web.HttpService](router, verify, crud.NewSinglePermProvider[web.HttpService]("manage:http-service", crud.NewDatabaseProvider[web.HttpService](db, "id")), "/http-services", "/http-service/{id}")
|
||||
crud.NewCrudHandler[web.HttpRedirect](router, verify, crud.NewSinglePermProvider[web.HttpRedirect]("manage:http-redirect", crud.NewDatabaseProvider[web.HttpRedirect](db, "id")), "/http-redirects", "/http-redirect/{id}")
|
||||
crud.NewCrudHandler[web.ApiRoute](router, verify, crud.NewSinglePermProvider[web.ApiRoute]("manage:api-route", crud.NewDatabaseProvider[web.ApiRoute](db, "id")), "/api-routes", "/api-route/{id}")
|
||||
router := mux.NewRouter()
|
||||
crud.NewCrudHandler[web.HttpService](router, verify, crud.NewSinglePermProvider[web.HttpService]("manage:http-service", crud.NewDatabaseProvider[web.HttpService](db, "id")), "/http-services", "/http-service/{id}")
|
||||
crud.NewCrudHandler[web.HttpRedirect](router, verify, crud.NewSinglePermProvider[web.HttpRedirect]("manage:http-redirect", crud.NewDatabaseProvider[web.HttpRedirect](db, "id")), "/http-redirects", "/http-redirect/{id}")
|
||||
crud.NewCrudHandler[web.ApiRoute](router, verify, crud.NewSinglePermProvider[web.ApiRoute]("manage:api-route", crud.NewDatabaseProvider[web.ApiRoute](db, "id")), "/api-routes", "/api-route/{id}")
|
||||
|
||||
s := &http.Server{
|
||||
Addr: listen,
|
||||
Handler: router,
|
||||
ReadTimeout: 1 * time.Minute,
|
||||
ReadHeaderTimeout: 1 * time.Minute,
|
||||
WriteTimeout: 1 * time.Minute,
|
||||
IdleTimeout: 1 * time.Minute,
|
||||
MaxHeaderBytes: 2500,
|
||||
}
|
||||
log.Printf("[API] Starting API server on: '%s'\n", s.Addr)
|
||||
go utils.RunBackgroundHttp("API", s)
|
||||
return s
|
||||
s := &http.Server{
|
||||
Addr: listen,
|
||||
Handler: router,
|
||||
ReadTimeout: 1 * time.Minute,
|
||||
ReadHeaderTimeout: 1 * time.Minute,
|
||||
WriteTimeout: 1 * time.Minute,
|
||||
IdleTimeout: 1 * time.Minute,
|
||||
MaxHeaderBytes: 2500,
|
||||
}
|
||||
log.Printf("[API] Starting API server on: '%s'\n", s.Addr)
|
||||
go utils.RunBackgroundHttp("API", s)
|
||||
return s
|
||||
}
|
||||
|
|
|
@ -1,250 +1,250 @@
|
|||
package servers
|
||||
|
||||
import (
|
||||
"code.mrmelon54.com/melon/summer/pkg/proxy"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/certificate"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/web"
|
||||
"code.mrmelon54.com/melon/summer/pkg/utils"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"github.com/gorilla/handlers"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mrmelon54/favicon"
|
||||
"github.com/sethvargo/go-limiter/httplimit"
|
||||
"github.com/sethvargo/go-limiter/memorystore"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"xorm.io/xorm"
|
||||
"code.mrmelon54.com/melon/summer/pkg/proxy"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/certificate"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/web"
|
||||
"code.mrmelon54.com/melon/summer/pkg/utils"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"github.com/gorilla/handlers"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mrmelon54/favicon"
|
||||
"github.com/sethvargo/go-limiter/httplimit"
|
||||
"github.com/sethvargo/go-limiter/memorystore"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
var apiRoutePattern = regexp.MustCompile("^/v(\\d+)/([a-zA-Z-_]+)/(.+)$")
|
||||
|
||||
func NewHttpsServer(listen string, db *xorm.Engine, reverseProxy *httputil.ReverseProxy, apiDomain string) *http.Server {
|
||||
router := mux.NewRouter()
|
||||
setupHttpsRouter(router, db, reverseProxy, apiDomain)
|
||||
router := mux.NewRouter()
|
||||
setupHttpsRouter(router, db, reverseProxy, apiDomain)
|
||||
|
||||
certCache := utils.NewCertCache()
|
||||
certCache := utils.NewCertCache()
|
||||
|
||||
s := &http.Server{
|
||||
Addr: listen,
|
||||
Handler: router,
|
||||
TLSConfig: &tls.Config{
|
||||
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
if !web.CheckDomainInDb(db, info.ServerName) {
|
||||
return nil, fmt.Errorf("invalid hostname used: '%s'", info.ServerName)
|
||||
}
|
||||
s := &http.Server{
|
||||
Addr: listen,
|
||||
Handler: router,
|
||||
TLSConfig: &tls.Config{
|
||||
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
if !web.CheckDomainInDb(db, info.ServerName) {
|
||||
return nil, fmt.Errorf("invalid hostname used: '%s'", info.ServerName)
|
||||
}
|
||||
|
||||
if certGrab, ok := certCache.Get(info.ServerName); ok {
|
||||
return certGrab, nil
|
||||
}
|
||||
if certGrab, ok := certCache.Get(info.ServerName); ok {
|
||||
return certGrab, nil
|
||||
}
|
||||
|
||||
// Just grab, save and output the certificate now
|
||||
cert, err := certificate.GetCertForDomain(db, info.ServerName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certLeaf, err := cert.GetCertificate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certCache.Put(info.ServerName, certLeaf)
|
||||
return certLeaf, nil
|
||||
},
|
||||
},
|
||||
ReadTimeout: 150 * time.Second,
|
||||
ReadHeaderTimeout: 150 * time.Second,
|
||||
WriteTimeout: 150 * time.Second,
|
||||
IdleTimeout: 150 * time.Second,
|
||||
MaxHeaderBytes: 4096000,
|
||||
}
|
||||
s.SetKeepAlivesEnabled(false)
|
||||
log.Printf("[HTTPS] Starting HTTPS server on: '%s'\n", s.Addr)
|
||||
go utils.RunBackgroundHttps("HTTPS", s)
|
||||
return s
|
||||
// Just grab, save and output the certificate now
|
||||
cert, err := certificate.GetCertForDomain(db, info.ServerName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certLeaf, err := cert.GetCertificate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certCache.Put(info.ServerName, certLeaf)
|
||||
return certLeaf, nil
|
||||
},
|
||||
},
|
||||
ReadTimeout: 150 * time.Second,
|
||||
ReadHeaderTimeout: 150 * time.Second,
|
||||
WriteTimeout: 150 * time.Second,
|
||||
IdleTimeout: 150 * time.Second,
|
||||
MaxHeaderBytes: 4096000,
|
||||
}
|
||||
s.SetKeepAlivesEnabled(false)
|
||||
log.Printf("[HTTPS] Starting HTTPS server on: '%s'\n", s.Addr)
|
||||
go utils.RunBackgroundHttps("HTTPS", s)
|
||||
return s
|
||||
}
|
||||
|
||||
func setupHttpsRouter(router *mux.Router, db *xorm.Engine, reverseProxy *httputil.ReverseProxy, apiDomain string) {
|
||||
faviconColor := favicon.NewColor()
|
||||
faviconColor := favicon.NewColor()
|
||||
|
||||
cors := handlers.CORS(
|
||||
handlers.AllowCredentials(),
|
||||
handlers.AllowedOrigins([]string{"*"}),
|
||||
handlers.AllowedHeaders([]string{"Content-Type", "Authorization"}),
|
||||
handlers.AllowedMethods([]string{
|
||||
http.MethodGet,
|
||||
http.MethodHead,
|
||||
http.MethodPost,
|
||||
http.MethodPut,
|
||||
http.MethodPatch,
|
||||
http.MethodDelete,
|
||||
http.MethodConnect,
|
||||
http.MethodOptions,
|
||||
http.MethodTrace,
|
||||
}),
|
||||
)
|
||||
cors := handlers.CORS(
|
||||
handlers.AllowCredentials(),
|
||||
handlers.AllowedOrigins([]string{"*"}),
|
||||
handlers.AllowedHeaders([]string{"Content-Type", "Authorization"}),
|
||||
handlers.AllowedMethods([]string{
|
||||
http.MethodGet,
|
||||
http.MethodHead,
|
||||
http.MethodPost,
|
||||
http.MethodPut,
|
||||
http.MethodPatch,
|
||||
http.MethodDelete,
|
||||
http.MethodConnect,
|
||||
http.MethodOptions,
|
||||
http.MethodTrace,
|
||||
}),
|
||||
)
|
||||
|
||||
router.Use(setupRateLimiter())
|
||||
router.HandleFunc("/favicon.{ext:(?:ico|png|svg)}", utils.CachedPage("1", func(rw http.ResponseWriter, req *http.Request) {
|
||||
if !web.CheckDomainInDb(db, req.Host) {
|
||||
http.Error(rw, http.StatusText(http.StatusTeapot), http.StatusTeapot)
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(req)
|
||||
ext := vars["ext"]
|
||||
h := rw.Header()
|
||||
router.Use(setupRateLimiter())
|
||||
router.HandleFunc("/favicon.{ext:(?:ico|png|svg)}", utils.CachedPage("1", func(rw http.ResponseWriter, req *http.Request) {
|
||||
if !web.CheckDomainInDb(db, req.Host) {
|
||||
http.Error(rw, http.StatusText(http.StatusTeapot), http.StatusTeapot)
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(req)
|
||||
ext := vars["ext"]
|
||||
h := rw.Header()
|
||||
|
||||
favSvg := favicon.NewSvg(req.Host, faviconColor)
|
||||
favSvg := favicon.NewSvg(req.Host, faviconColor)
|
||||
|
||||
var b []byte
|
||||
var err error
|
||||
var b []byte
|
||||
var err error
|
||||
|
||||
switch ext {
|
||||
case "svg":
|
||||
h.Set("Content-Type", "image/svg+xml")
|
||||
b, err = favSvg.ProduceSvg()
|
||||
case "png":
|
||||
h.Set("Content-Type", "image/png")
|
||||
b, err = favSvg.ProducePng()
|
||||
case "ico":
|
||||
h.Set("Content-Type", "image/x-icon")
|
||||
b, err = favSvg.ProduceIco()
|
||||
}
|
||||
switch ext {
|
||||
case "svg":
|
||||
h.Set("Content-Type", "image/svg+xml")
|
||||
b, err = favSvg.ProduceSvg()
|
||||
case "png":
|
||||
h.Set("Content-Type", "image/png")
|
||||
b, err = favSvg.ProducePng()
|
||||
case "ico":
|
||||
h.Set("Content-Type", "image/x-icon")
|
||||
b, err = favSvg.ProduceIco()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = rw.Write([]byte(http.StatusText(http.StatusInternalServerError)))
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = rw.Write([]byte(http.StatusText(http.StatusInternalServerError)))
|
||||
return
|
||||
}
|
||||
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
_, _ = rw.Write(b)
|
||||
}))
|
||||
router.PathPrefix("/").HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
if !web.CheckDomainInDb(db, req.Host) {
|
||||
http.Error(rw, http.StatusText(http.StatusTeapot), http.StatusTeapot)
|
||||
return
|
||||
}
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
_, _ = rw.Write(b)
|
||||
}))
|
||||
router.PathPrefix("/").HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
if !web.CheckDomainInDb(db, req.Host) {
|
||||
http.Error(rw, http.StatusText(http.StatusTeapot), http.StatusTeapot)
|
||||
return
|
||||
}
|
||||
|
||||
if hostDomain, hostPort, ok := utils.SplitDomainPort(req.Host, 443); ok {
|
||||
if hostDomain == apiDomain {
|
||||
cors(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
handleApiRoute(rw, req, db, reverseProxy)
|
||||
})).ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
if handleHttpService(rw, req, db, hostDomain, reverseProxy) {
|
||||
return
|
||||
}
|
||||
if handleHttpRedirect(rw, req, db, hostDomain, hostPort) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hostDomain, hostPort, ok := utils.SplitDomainPort(req.Host, 443); ok {
|
||||
if hostDomain == apiDomain {
|
||||
cors(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
handleApiRoute(rw, req, db, reverseProxy)
|
||||
})).ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
if handleHttpService(rw, req, db, hostDomain, reverseProxy) {
|
||||
return
|
||||
}
|
||||
if handleHttpRedirect(rw, req, db, hostDomain, hostPort) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("[HTTPS] Cannot find valid HTTP/WS service")
|
||||
unknownHttpService(rw)
|
||||
})
|
||||
log.Println("[HTTPS] Cannot find valid HTTP/WS service")
|
||||
unknownHttpService(rw)
|
||||
})
|
||||
}
|
||||
|
||||
func handleApiRoute(rw http.ResponseWriter, req *http.Request, db *xorm.Engine, reverseProxy *httputil.ReverseProxy) {
|
||||
sub := apiRoutePattern.FindStringSubmatch(req.URL.Path)
|
||||
sub := apiRoutePattern.FindStringSubmatch(req.URL.Path)
|
||||
|
||||
if len(sub) != 4 {
|
||||
rw.WriteHeader(http.StatusBadRequest)
|
||||
_, _ = rw.Write([]byte("Invalid API path\n"))
|
||||
return
|
||||
}
|
||||
if len(sub) != 4 {
|
||||
rw.WriteHeader(http.StatusBadRequest)
|
||||
_, _ = rw.Write([]byte("Invalid API path\n"))
|
||||
return
|
||||
}
|
||||
|
||||
pVersion, err := strconv.ParseUint(sub[1], 10, 64)
|
||||
if err != nil {
|
||||
rw.WriteHeader(http.StatusBadRequest)
|
||||
_, _ = rw.Write([]byte("Invalid API version\n"))
|
||||
return
|
||||
}
|
||||
pRoute := sub[2]
|
||||
pPath := sub[3]
|
||||
pVersion, err := strconv.ParseUint(sub[1], 10, 64)
|
||||
if err != nil {
|
||||
rw.WriteHeader(http.StatusBadRequest)
|
||||
_, _ = rw.Write([]byte("Invalid API version\n"))
|
||||
return
|
||||
}
|
||||
pRoute := sub[2]
|
||||
pPath := sub[3]
|
||||
|
||||
var connect web.ApiRoute
|
||||
ok, err := db.Where("route = ? and version = ? and enabled = 1", pRoute, pVersion).Limit(1).Get(&connect)
|
||||
if err != nil {
|
||||
log.Printf("[HTTPS] Failed to load api route with host address: %s\n", err)
|
||||
return
|
||||
}
|
||||
if ok {
|
||||
req = proxy.SetReverseProxyHost[proxy.ReverseProxyHeaderVar](req, connect, nil)
|
||||
req.Close = true
|
||||
req.RequestURI = ""
|
||||
req.URL.Path = "/" + pPath
|
||||
reverseProxy.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
var connect web.ApiRoute
|
||||
ok, err := db.Where("route = ? and version = ? and enabled = 1", pRoute, pVersion).Limit(1).Get(&connect)
|
||||
if err != nil {
|
||||
log.Printf("[HTTPS] Failed to load api route with host address: %s\n", err)
|
||||
return
|
||||
}
|
||||
if ok {
|
||||
req = proxy.SetReverseProxyHost[proxy.ReverseProxyHeaderVar](req, connect, nil)
|
||||
req.Close = true
|
||||
req.RequestURI = ""
|
||||
req.URL.Path = "/" + pPath
|
||||
reverseProxy.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
rw.WriteHeader(http.StatusBadRequest)
|
||||
_, _ = rw.Write([]byte("Invalid API route\n"))
|
||||
rw.WriteHeader(http.StatusBadRequest)
|
||||
_, _ = rw.Write([]byte("Invalid API route\n"))
|
||||
}
|
||||
|
||||
func handleHttpService(rw http.ResponseWriter, req *http.Request, db *xorm.Engine, hostDomain string, reverseProxy *httputil.ReverseProxy) bool {
|
||||
var connect web.HttpService
|
||||
ok, err := db.Where("address = ? and enabled = 1", hostDomain).Limit(1).Get(&connect)
|
||||
if err != nil {
|
||||
log.Printf("[HTTPS] Failed to load http service with host address: %s\n", err)
|
||||
return true
|
||||
}
|
||||
if ok {
|
||||
var updateHeader []web.HttpHeader
|
||||
err = db.Where("service_id = ?", connect.Id).Find(&updateHeader)
|
||||
if err != nil {
|
||||
log.Printf("[HTTPS] Failed to load http service override headers: %s\n", err)
|
||||
return true
|
||||
}
|
||||
var connect web.HttpService
|
||||
ok, err := db.Where("address = ? and enabled = 1", hostDomain).Limit(1).Get(&connect)
|
||||
if err != nil {
|
||||
log.Printf("[HTTPS] Failed to load http service with host address: %s\n", err)
|
||||
return true
|
||||
}
|
||||
if ok {
|
||||
var updateHeader []web.HttpHeader
|
||||
err = db.Where("service_id = ?", connect.Id).Find(&updateHeader)
|
||||
if err != nil {
|
||||
log.Printf("[HTTPS] Failed to load http service override headers: %s\n", err)
|
||||
return true
|
||||
}
|
||||
|
||||
req = proxy.SetReverseProxyHost[web.HttpHeader](req, connect, updateHeader)
|
||||
req.Close = true
|
||||
req.RequestURI = ""
|
||||
reverseProxy.ServeHTTP(rw, req)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
req = proxy.SetReverseProxyHost[web.HttpHeader](req, connect, updateHeader)
|
||||
req.Close = true
|
||||
req.RequestURI = ""
|
||||
reverseProxy.ServeHTTP(rw, req)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func handleHttpRedirect(rw http.ResponseWriter, req *http.Request, db *xorm.Engine, hostDomain string, hostPort uint16) bool {
|
||||
var redirects []web.HttpRedirect
|
||||
err := db.Where("address = ? and enabled = 1", hostDomain).Limit(1).Find(&redirects)
|
||||
if err != nil {
|
||||
log.Printf("Failed to load http redirect with host address: %s\n", err)
|
||||
return true
|
||||
}
|
||||
if len(redirects) == 1 {
|
||||
a := redirects[0].Target
|
||||
a = strings.ReplaceAll(a, "%domain%", hostDomain)
|
||||
a = strings.ReplaceAll(a, "%port%", fmt.Sprint(hostPort))
|
||||
a = strings.ReplaceAll(a, "%host%", req.Host)
|
||||
http.Redirect(rw, req, "https://"+a, http.StatusTemporaryRedirect)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
var redirects []web.HttpRedirect
|
||||
err := db.Where("address = ? and enabled = 1", hostDomain).Limit(1).Find(&redirects)
|
||||
if err != nil {
|
||||
log.Printf("Failed to load http redirect with host address: %s\n", err)
|
||||
return true
|
||||
}
|
||||
if len(redirects) == 1 {
|
||||
a := redirects[0].Target
|
||||
a = strings.ReplaceAll(a, "%domain%", hostDomain)
|
||||
a = strings.ReplaceAll(a, "%port%", fmt.Sprint(hostPort))
|
||||
a = strings.ReplaceAll(a, "%host%", req.Host)
|
||||
http.Redirect(rw, req, "https://"+a, http.StatusTemporaryRedirect)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func setupRateLimiter() mux.MiddlewareFunc {
|
||||
store, err := memorystore.New(&memorystore.Config{
|
||||
Tokens: 100,
|
||||
Interval: time.Minute,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
middleware, err := httplimit.NewMiddleware(store, httplimit.IPKeyFunc())
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
return middleware.Handle
|
||||
store, err := memorystore.New(&memorystore.Config{
|
||||
Tokens: 100,
|
||||
Interval: time.Minute,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
middleware, err := httplimit.NewMiddleware(store, httplimit.IPKeyFunc())
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
return middleware.Handle
|
||||
}
|
||||
|
||||
func unknownHttpService(rw http.ResponseWriter) {
|
||||
rw.WriteHeader(http.StatusTeapot)
|
||||
_, _ = rw.Write([]byte(http.StatusText(http.StatusTeapot) + "\n"))
|
||||
rw.WriteHeader(http.StatusTeapot)
|
||||
_, _ = rw.Write([]byte(http.StatusText(http.StatusTeapot) + "\n"))
|
||||
}
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"code.mrmelon54.com/melon/summer/pkg/api"
|
||||
"code.mrmelon54.com/melon/summer/pkg/api/crud"
|
||||
"code.mrmelon54.com/melon/summer/pkg/renewal"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mrmelon54/mjwt"
|
||||
"net/http"
|
||||
"xorm.io/xorm"
|
||||
"code.mrmelon54.com/melon/summer/pkg/api"
|
||||
"code.mrmelon54.com/melon/summer/pkg/api/crud"
|
||||
"code.mrmelon54.com/melon/summer/pkg/renewal"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mrmelon54/mjwt"
|
||||
"net/http"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type PermBaseConfig struct {
|
||||
Certificate string `yaml:"certificate"`
|
||||
CertificatePair string `yaml:"certificatePair"`
|
||||
Certificate string `yaml:"certificate"`
|
||||
CertificatePair string `yaml:"certificatePair"`
|
||||
}
|
||||
|
||||
func NewApiServer(listen string, db *xorm.Engine, renewal *renewal.Renewal, verify mjwt.Provider) *http.Server {
|
||||
router := mux.NewRouter()
|
||||
handleGetPublic(router, db, "/domain-certificate/{domain}")
|
||||
crud.NewCrudHandler[CertDbItem](router, verify, crud.NewSinglePermProvider[CertDbItem]("manage:certificate", NewCertDbProvider(db, renewal)), "/certificates", "/certificate/{id}")
|
||||
crud.NewCrudHandler[CertPrivate](router, verify, crud.NewSinglePermProvider[CertPrivate]("manage:certificate:admin", certPrivateProvider{db: db}), "/certificate-pairs", "/certificate-pair/{domain}")
|
||||
return api.RunApiServer(listen, router)
|
||||
router := mux.NewRouter()
|
||||
handleGetPublic(router, db, "/domain-certificate/{domain}")
|
||||
crud.NewCrudHandler[CertDbItem](router, verify, crud.NewSinglePermProvider[CertDbItem]("manage:certificate", NewCertDbProvider(db, renewal)), "/certificates", "/certificate/{id}")
|
||||
crud.NewCrudHandler[CertPrivate](router, verify, crud.NewSinglePermProvider[CertPrivate]("manage:certificate:admin", certPrivateProvider{db: db}), "/certificate-pairs", "/certificate-pair/{domain}")
|
||||
return api.RunApiServer(listen, router)
|
||||
}
|
||||
|
|
|
@ -1,57 +1,57 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"code.mrmelon54.com/melon/summer/cmd/buttercup/api"
|
||||
"code.mrmelon54.com/melon/summer/pkg/cli"
|
||||
"code.mrmelon54.com/melon/summer/pkg/renewal"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/certificate"
|
||||
renewal2 "code.mrmelon54.com/melon/summer/pkg/tables/renewal"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/web"
|
||||
"code.mrmelon54.com/melon/summer/pkg/utils"
|
||||
"github.com/mrmelon54/mjwt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"code.mrmelon54.com/melon/summer/cmd/buttercup/api"
|
||||
"code.mrmelon54.com/melon/summer/pkg/cli"
|
||||
"code.mrmelon54.com/melon/summer/pkg/renewal"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/certificate"
|
||||
renewal2 "code.mrmelon54.com/melon/summer/pkg/tables/renewal"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/web"
|
||||
"code.mrmelon54.com/melon/summer/pkg/utils"
|
||||
"github.com/mrmelon54/mjwt"
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var usedTables = []any{
|
||||
&web.Domain{},
|
||||
&renewal2.AcmeContent{},
|
||||
&certificate.Certificate{},
|
||||
&certificate.CertificateData{},
|
||||
&certificate.CertificateDomain{},
|
||||
&renewal2.LetsEncryptAccount{},
|
||||
&renewal2.NamesiloAccount{},
|
||||
&web.Domain{},
|
||||
&renewal2.AcmeContent{},
|
||||
&certificate.Certificate{},
|
||||
&certificate.CertificateData{},
|
||||
&certificate.CertificateDomain{},
|
||||
&renewal2.LetsEncryptAccount{},
|
||||
&renewal2.NamesiloAccount{},
|
||||
}
|
||||
|
||||
func main() {
|
||||
conf := cli.DecodeConfig[ButtercupConfig]("buttercup")
|
||||
cli.Run("buttercup", conf.Database, &Buttercup{conf: conf})
|
||||
conf := cli.DecodeConfig[ButtercupConfig]("buttercup")
|
||||
cli.Run("buttercup", conf.Database, &Buttercup{conf: conf})
|
||||
}
|
||||
|
||||
type Buttercup struct {
|
||||
conf ButtercupConfig
|
||||
runner *cli.Runner
|
||||
renewal *renewal.Renewal
|
||||
renewalWg *sync.WaitGroup
|
||||
apiServer *http.Server
|
||||
verify mjwt.Provider
|
||||
conf ButtercupConfig
|
||||
runner *cli.Runner
|
||||
renewal *renewal.Renewal
|
||||
renewalWg *sync.WaitGroup
|
||||
apiServer *http.Server
|
||||
verify mjwt.Provider
|
||||
}
|
||||
|
||||
func (buttercup *Buttercup) Init(runner *cli.Runner) {
|
||||
buttercup.runner = runner
|
||||
utils.Check("[Buttercup.Init()] Failed to sync tables", runner.Database.Sync(usedTables...))
|
||||
var err error
|
||||
buttercup.verify, err = mjwt.NewMJwtVerifierFromFile(buttercup.conf.Auth.Public)
|
||||
utils.Check("[Buttercup.Init()] Failed to load verifier public key", err)
|
||||
buttercup.runner = runner
|
||||
utils.Check("[Buttercup.Init()] Failed to sync tables", runner.Database.Sync(usedTables...))
|
||||
var err error
|
||||
buttercup.verify, err = mjwt.NewMJwtVerifierFromFile(buttercup.conf.Auth.Public)
|
||||
utils.Check("[Buttercup.Init()] Failed to load verifier public key", err)
|
||||
|
||||
buttercup.renewalWg = &sync.WaitGroup{}
|
||||
provider := renewal.NewCustomHTTPProvider(runner.Database)
|
||||
buttercup.renewal = renewal.NewRenewalService(buttercup.renewalWg, runner.Database, provider, buttercup.conf.Renewal)
|
||||
buttercup.apiServer = api.NewApiServer(buttercup.conf.Listen, runner.Database, buttercup.renewal, buttercup.verify)
|
||||
buttercup.renewalWg = &sync.WaitGroup{}
|
||||
provider := renewal.NewCustomHTTPProvider(runner.Database)
|
||||
buttercup.renewal = renewal.NewRenewalService(buttercup.renewalWg, runner.Database, provider, buttercup.conf.Renewal)
|
||||
buttercup.apiServer = api.NewApiServer(buttercup.conf.Listen, runner.Database, buttercup.renewal, buttercup.verify)
|
||||
}
|
||||
|
||||
func (buttercup *Buttercup) Destroy() {
|
||||
buttercup.renewal.Shutdown()
|
||||
buttercup.renewalWg.Wait()
|
||||
_ = buttercup.apiServer.Close()
|
||||
buttercup.renewal.Shutdown()
|
||||
buttercup.renewalWg.Wait()
|
||||
_ = buttercup.apiServer.Close()
|
||||
}
|
||||
|
|
|
@ -1,124 +1,124 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
quickUser "code.mrmelon54.com/melon/summer/cmd/marigold/quick-user"
|
||||
accountServer "code.mrmelon54.com/melon/summer/pkg/account-server"
|
||||
"code.mrmelon54.com/melon/summer/pkg/api"
|
||||
"code.mrmelon54.com/melon/summer/pkg/api/crud"
|
||||
"code.mrmelon54.com/melon/summer/pkg/cli"
|
||||
loginChecker "code.mrmelon54.com/melon/summer/pkg/login-checker"
|
||||
"code.mrmelon54.com/melon/summer/pkg/mailer"
|
||||
oauthServer "code.mrmelon54.com/melon/summer/pkg/oauth-server"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/oauth"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/user"
|
||||
"code.mrmelon54.com/melon/summer/pkg/utils"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"github.com/go-oauth2/oauth2/v4/manage"
|
||||
"github.com/go-oauth2/oauth2/v4/server"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mrmelon54/mjwt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
quickUser "code.mrmelon54.com/melon/summer/cmd/marigold/quick-user"
|
||||
accountServer "code.mrmelon54.com/melon/summer/pkg/account-server"
|
||||
"code.mrmelon54.com/melon/summer/pkg/api"
|
||||
"code.mrmelon54.com/melon/summer/pkg/api/crud"
|
||||
"code.mrmelon54.com/melon/summer/pkg/cli"
|
||||
loginChecker "code.mrmelon54.com/melon/summer/pkg/login-checker"
|
||||
"code.mrmelon54.com/melon/summer/pkg/mailer"
|
||||
oauthServer "code.mrmelon54.com/melon/summer/pkg/oauth-server"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/oauth"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/user"
|
||||
"code.mrmelon54.com/melon/summer/pkg/utils"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"github.com/go-oauth2/oauth2/v4/manage"
|
||||
"github.com/go-oauth2/oauth2/v4/server"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mrmelon54/mjwt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var usedTables = []any{
|
||||
&user.User{},
|
||||
&user.Perm{},
|
||||
&user.LinkUserPerm{},
|
||||
&oauth.AuthNamespace{},
|
||||
&oauth.Domain{},
|
||||
&oauth.App{},
|
||||
&user.User{},
|
||||
&user.Perm{},
|
||||
&user.LinkUserPerm{},
|
||||
&oauth.AuthNamespace{},
|
||||
&oauth.Domain{},
|
||||
&oauth.App{},
|
||||
}
|
||||
|
||||
func main() {
|
||||
conf := cli.DecodeConfig[MarigoldConfig]("marigold")
|
||||
cli.Run("marigold", conf.Database, &Marigold{conf: conf})
|
||||
conf := cli.DecodeConfig[MarigoldConfig]("marigold")
|
||||
cli.Run("marigold", conf.Database, &Marigold{conf: conf})
|
||||
}
|
||||
|
||||
type Marigold struct {
|
||||
conf MarigoldConfig
|
||||
runner *cli.Runner
|
||||
server *http.Server
|
||||
signer mjwt.Provider
|
||||
oauthManager *manage.Manager
|
||||
oauthSrv *server.Server
|
||||
checkLogin *loginChecker.LoginChecker
|
||||
auth *accountServer.AccountServer
|
||||
oauth *oauthServer.OAuthServer
|
||||
mailer *mailer.Mailer
|
||||
conf MarigoldConfig
|
||||
runner *cli.Runner
|
||||
server *http.Server
|
||||
signer mjwt.Provider
|
||||
oauthManager *manage.Manager
|
||||
oauthSrv *server.Server
|
||||
checkLogin *loginChecker.LoginChecker
|
||||
auth *accountServer.AccountServer
|
||||
oauth *oauthServer.OAuthServer
|
||||
mailer *mailer.Mailer
|
||||
}
|
||||
|
||||
func (marigold *Marigold) Init(runner *cli.Runner) {
|
||||
marigold.runner = runner
|
||||
utils.Check("[Marigold.Init()] Failed to sync tables", runner.Database.Sync(usedTables...))
|
||||
quickUser.QuickUser(runner.Database)
|
||||
marigold.runner = runner
|
||||
utils.Check("[Marigold.Init()] Failed to sync tables", runner.Database.Sync(usedTables...))
|
||||
quickUser.QuickUser(runner.Database)
|
||||
|
||||
var privKey *rsa.PrivateKey
|
||||
file, err := os.ReadFile(marigold.conf.Auth.Key)
|
||||
if os.IsNotExist(err) {
|
||||
privKey = generateNewKeys(marigold.conf.Auth)
|
||||
file = pem.EncodeToMemory(&pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(privKey),
|
||||
})
|
||||
} else {
|
||||
utils.Check("[Marigold.Init()] Failed to read token signing key", err)
|
||||
block, _ := pem.Decode(file)
|
||||
privKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
utils.Check("[Marigold.Init()] Failed to parse token signing key", err)
|
||||
}
|
||||
marigold.signer = mjwt.NewMJwtSigner(marigold.conf.Auth.Issuer, privKey)
|
||||
marigold.checkLogin = loginChecker.NewLoginChecker(runner.Database, marigold.signer)
|
||||
var privKey *rsa.PrivateKey
|
||||
file, err := os.ReadFile(marigold.conf.Auth.Key)
|
||||
if os.IsNotExist(err) {
|
||||
privKey = generateNewKeys(marigold.conf.Auth)
|
||||
file = pem.EncodeToMemory(&pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(privKey),
|
||||
})
|
||||
} else {
|
||||
utils.Check("[Marigold.Init()] Failed to read token signing key", err)
|
||||
block, _ := pem.Decode(file)
|
||||
privKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
utils.Check("[Marigold.Init()] Failed to parse token signing key", err)
|
||||
}
|
||||
marigold.signer = mjwt.NewMJwtSigner(marigold.conf.Auth.Issuer, privKey)
|
||||
marigold.checkLogin = loginChecker.NewLoginChecker(runner.Database, marigold.signer)
|
||||
|
||||
marigold.mailer, _ = mailer.NewMailer(marigold.conf.Smtp)
|
||||
marigold.mailer, _ = mailer.NewMailer(marigold.conf.Smtp)
|
||||
|
||||
router := mux.NewRouter()
|
||||
marigold.auth = accountServer.NewAccountServer(router.PathPrefix("/auth").Subrouter(), runner.Database, marigold.signer, marigold.mailer)
|
||||
marigold.oauth = oauthServer.NewOAuthServer(router.PathPrefix("/oauth").Subrouter(), runner.Database, marigold.signer, marigold.conf.Auth.AuthClient)
|
||||
crud.NewCrudHandler[user.User](router, marigold.signer, accountServer.NewUserProvider(runner.Database), "/users", "/user/{id}")
|
||||
crud.NewCrudHandler[oauth.App](router, marigold.signer, oauthServer.NewOAuthAppProvider(runner.Database), "/apps", "/app/{id}")
|
||||
router.HandleFunc("/scopes", func(rw http.ResponseWriter, req *http.Request) {
|
||||
scopes := strings.Split(req.URL.Query().Get("scope"), " ")
|
||||
foundScopes := make([]user.Perm, 0)
|
||||
err = runner.Database.Where("is_scope = ?", true).In("key", scopes).Find(&foundScopes)
|
||||
if api.GenericErrorMsg[user.Perm](rw, err, http.StatusInternalServerError, "Failed to find scopes in database") {
|
||||
return
|
||||
}
|
||||
_ = json.NewEncoder(rw).Encode(foundScopes)
|
||||
})
|
||||
router := mux.NewRouter()
|
||||
marigold.auth = accountServer.NewAccountServer(router.PathPrefix("/auth").Subrouter(), runner.Database, marigold.signer, marigold.mailer)
|
||||
marigold.oauth = oauthServer.NewOAuthServer(router.PathPrefix("/oauth").Subrouter(), runner.Database, marigold.signer, marigold.conf.Auth.AuthClient)
|
||||
crud.NewCrudHandler[user.User](router, marigold.signer, accountServer.NewUserProvider(runner.Database), "/users", "/user/{id}")
|
||||
crud.NewCrudHandler[oauth.App](router, marigold.signer, oauthServer.NewOAuthAppProvider(runner.Database), "/apps", "/app/{id}")
|
||||
router.HandleFunc("/scopes", func(rw http.ResponseWriter, req *http.Request) {
|
||||
scopes := strings.Split(req.URL.Query().Get("scope"), " ")
|
||||
foundScopes := make([]user.Perm, 0)
|
||||
err = runner.Database.Where("is_scope = ?", true).In("key", scopes).Find(&foundScopes)
|
||||
if api.GenericErrorMsg[user.Perm](rw, err, http.StatusInternalServerError, "Failed to find scopes in database") {
|
||||
return
|
||||
}
|
||||
_ = json.NewEncoder(rw).Encode(foundScopes)
|
||||
})
|
||||
|
||||
marigold.server = api.RunApiServer(marigold.conf.Listen, router)
|
||||
marigold.server = api.RunApiServer(marigold.conf.Listen, router)
|
||||
}
|
||||
|
||||
func (marigold *Marigold) Destroy() {
|
||||
_ = marigold.server.Close()
|
||||
_ = marigold.server.Close()
|
||||
}
|
||||
|
||||
func generateNewKeys(auth AuthConfig) *rsa.PrivateKey {
|
||||
// Generate key
|
||||
fmt.Println("[Marigold.Init()] Generating new RSA private key")
|
||||
key, err := rsa.GenerateKey(rand.New(rand.NewSource(time.Now().UnixNano())), 4096)
|
||||
utils.Check("[Marigold.Init()] Failed to generate new RSA private key", err)
|
||||
// Generate key
|
||||
fmt.Println("[Marigold.Init()] Generating new RSA private key")
|
||||
key, err := rsa.GenerateKey(rand.New(rand.NewSource(time.Now().UnixNano())), 4096)
|
||||
utils.Check("[Marigold.Init()] Failed to generate new RSA private key", err)
|
||||
|
||||
// Create key files
|
||||
createPriv, err := os.Create(auth.Key)
|
||||
utils.Check("[Marigold.Init()] Failed to open private key file for writing", err)
|
||||
createPub, err := os.Create(auth.Public)
|
||||
utils.Check("[Marigold.Init()] Failed to open public key file for writing", err)
|
||||
// Create key files
|
||||
createPriv, err := os.Create(auth.Key)
|
||||
utils.Check("[Marigold.Init()] Failed to open private key file for writing", err)
|
||||
createPub, err := os.Create(auth.Public)
|
||||
utils.Check("[Marigold.Init()] Failed to open public key file for writing", err)
|
||||
|
||||
// Encode and write keys
|
||||
keyBytes := x509.MarshalPKCS1PrivateKey(key)
|
||||
pubBytes := x509.MarshalPKCS1PublicKey(&key.PublicKey)
|
||||
err = pem.Encode(createPriv, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes})
|
||||
utils.Check("[Marigold.Init()] Failed to encode private key to file", err)
|
||||
err = pem.Encode(createPub, &pem.Block{Type: "RSA PUBLIC KEY", Bytes: pubBytes})
|
||||
utils.Check("[Marigold.Init()] Failed to encode public key to file", err)
|
||||
return key
|
||||
// Encode and write keys
|
||||
keyBytes := x509.MarshalPKCS1PrivateKey(key)
|
||||
pubBytes := x509.MarshalPKCS1PublicKey(&key.PublicKey)
|
||||
err = pem.Encode(createPriv, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes})
|
||||
utils.Check("[Marigold.Init()] Failed to encode private key to file", err)
|
||||
err = pem.Encode(createPub, &pem.Block{Type: "RSA PUBLIC KEY", Bytes: pubBytes})
|
||||
utils.Check("[Marigold.Init()] Failed to encode public key to file", err)
|
||||
return key
|
||||
}
|
||||
|
|
|
@ -1,54 +1,54 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"code.mrmelon54.com/melon/summer/pkg/api"
|
||||
"code.mrmelon54.com/melon/summer/pkg/api/crud"
|
||||
"code.mrmelon54.com/melon/summer/pkg/cli"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/web"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tcp"
|
||||
"code.mrmelon54.com/melon/summer/pkg/udp"
|
||||
"code.mrmelon54.com/melon/summer/pkg/utils"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mrmelon54/mjwt"
|
||||
"net/http"
|
||||
"code.mrmelon54.com/melon/summer/pkg/api"
|
||||
"code.mrmelon54.com/melon/summer/pkg/api/crud"
|
||||
"code.mrmelon54.com/melon/summer/pkg/cli"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/web"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tcp"
|
||||
"code.mrmelon54.com/melon/summer/pkg/udp"
|
||||
"code.mrmelon54.com/melon/summer/pkg/utils"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mrmelon54/mjwt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var usedTables = []any{
|
||||
&web.TcpRedirect{},
|
||||
&web.UdpRedirect{},
|
||||
&web.TcpRedirect{},
|
||||
&web.UdpRedirect{},
|
||||
}
|
||||
|
||||
func main() {
|
||||
conf := cli.DecodeConfig[RoseConfig]("rose")
|
||||
cli.Run("rose", conf.Database, &Rose{conf: conf})
|
||||
conf := cli.DecodeConfig[RoseConfig]("rose")
|
||||
cli.Run("rose", conf.Database, &Rose{conf: conf})
|
||||
}
|
||||
|
||||
type Rose struct {
|
||||
conf RoseConfig
|
||||
runner *cli.Runner
|
||||
server *http.Server
|
||||
tcpApi *tcp.Manager
|
||||
udpApi *udp.Manager
|
||||
verify mjwt.Provider
|
||||
conf RoseConfig
|
||||
runner *cli.Runner
|
||||
server *http.Server
|
||||
tcpApi *tcp.Manager
|
||||
udpApi *udp.Manager
|
||||
verify mjwt.Provider
|
||||
}
|
||||
|
||||
func (rose *Rose) Init(runner *cli.Runner) {
|
||||
rose.runner = runner
|
||||
utils.Check("[Rose.Init()] Failed to sync tables", runner.Database.Sync(usedTables...))
|
||||
var err error
|
||||
rose.verify, err = mjwt.NewMJwtVerifierFromFile(rose.conf.Auth.Public)
|
||||
utils.Check("[Rose.Init()] Failed to load verifier public key", err)
|
||||
rose.runner = runner
|
||||
utils.Check("[Rose.Init()] Failed to sync tables", runner.Database.Sync(usedTables...))
|
||||
var err error
|
||||
rose.verify, err = mjwt.NewMJwtVerifierFromFile(rose.conf.Auth.Public)
|
||||
utils.Check("[Rose.Init()] Failed to load verifier public key", err)
|
||||
|
||||
router := mux.NewRouter()
|
||||
rose.tcpApi = tcp.NewTcpManager(runner.Database)
|
||||
rose.udpApi = udp.NewUdpManager(runner.Database)
|
||||
crud.NewCrudHandler[web.TcpRedirect](router, rose.verify, crud.NewSinglePermProvider[web.TcpRedirect]("manager:tcp", crud.NewDatabaseProvider[web.TcpRedirect](runner.Database, "id")), "/tcp", "/tcp/{id}")
|
||||
crud.NewCrudHandler[web.UdpRedirect](router, rose.verify, crud.NewSinglePermProvider[web.UdpRedirect]("manager:udp", crud.NewDatabaseProvider[web.UdpRedirect](runner.Database, "id")), "/udp", "/udp/{id}")
|
||||
rose.server = api.RunApiServer(rose.conf.Listen, router)
|
||||
router := mux.NewRouter()
|
||||
rose.tcpApi = tcp.NewTcpManager(runner.Database)
|
||||
rose.udpApi = udp.NewUdpManager(runner.Database)
|
||||
crud.NewCrudHandler[web.TcpRedirect](router, rose.verify, crud.NewSinglePermProvider[web.TcpRedirect]("manager:tcp", crud.NewDatabaseProvider[web.TcpRedirect](runner.Database, "id")), "/tcp", "/tcp/{id}")
|
||||
crud.NewCrudHandler[web.UdpRedirect](router, rose.verify, crud.NewSinglePermProvider[web.UdpRedirect]("manager:udp", crud.NewDatabaseProvider[web.UdpRedirect](runner.Database, "id")), "/udp", "/udp/{id}")
|
||||
rose.server = api.RunApiServer(rose.conf.Listen, router)
|
||||
}
|
||||
|
||||
func (rose *Rose) Destroy() {
|
||||
_ = rose.server.Close()
|
||||
rose.tcpApi.Destroy()
|
||||
rose.udpApi.Destroy()
|
||||
_ = rose.server.Close()
|
||||
rose.tcpApi.Destroy()
|
||||
rose.udpApi.Destroy()
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,83 +1,83 @@
|
|||
package account_server
|
||||
|
||||
import (
|
||||
"code.mrmelon54.com/melon/summer/pkg/api/crud"
|
||||
"code.mrmelon54.com/melon/summer/pkg/claims/auth"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/user"
|
||||
"fmt"
|
||||
"time"
|
||||
"xorm.io/xorm"
|
||||
"code.mrmelon54.com/melon/summer/pkg/api/crud"
|
||||
"code.mrmelon54.com/melon/summer/pkg/claims/auth"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/user"
|
||||
"fmt"
|
||||
"time"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type userProvider struct {
|
||||
db *xorm.Engine
|
||||
dbCrud crud.CheckedProvider[user.User]
|
||||
db *xorm.Engine
|
||||
dbCrud crud.CheckedProvider[user.User]
|
||||
}
|
||||
|
||||
func NewUserProvider(db *xorm.Engine) crud.Provider[user.User] {
|
||||
return &userProvider{db, crud.NewDatabaseProvider[user.User](db, "id")}
|
||||
return &userProvider{db, crud.NewDatabaseProvider[user.User](db, "id")}
|
||||
}
|
||||
|
||||
func (u userProvider) Find(claims *auth.AccessTokenClaims) ([]user.User, error) {
|
||||
if !claims.Perms.Has("super:admin") {
|
||||
return nil, crud.ErrForbidden
|
||||
}
|
||||
return u.dbCrud.Find()
|
||||
if !claims.Perms.Has("super:admin") {
|
||||
return nil, crud.ErrForbidden
|
||||
}
|
||||
return u.dbCrud.Find()
|
||||
}
|
||||
|
||||
func (u userProvider) Add(_ *auth.AccessTokenClaims, _ user.User) (user.User, error) {
|
||||
// TODO: implement register instead of this
|
||||
return user.User{}, crud.ErrMethodNotAllowed
|
||||
// TODO: implement register instead of this
|
||||
return user.User{}, crud.ErrMethodNotAllowed
|
||||
}
|
||||
|
||||
func (u userProvider) Get(claims *auth.AccessTokenClaims, id string) (user.User, bool, error) {
|
||||
if claims == nil {
|
||||
return user.User{}, false, crud.ErrForbidden
|
||||
}
|
||||
myId := fmt.Sprint(claims.UserId)
|
||||
if id == "@me" || id == myId {
|
||||
return u.dbCrud.Get(myId)
|
||||
}
|
||||
if claims.Perms.Has("super:admin") {
|
||||
return u.dbCrud.Get(id)
|
||||
}
|
||||
get, ok, err := u.dbCrud.Get(id)
|
||||
if ok && err == nil {
|
||||
if user.ParseVisibility(get.Visibility) == user.ProfileVisibilityPublic {
|
||||
z := get
|
||||
z.Email = "melon@mrmelon54.com"
|
||||
z.Visibility = nil
|
||||
z.Gender = nil
|
||||
z.Birthday = time.UnixMilli(-1)
|
||||
z.LastOnline = time.UnixMilli(-1)
|
||||
z.EmailVerified = nil
|
||||
z.Enabled = nil
|
||||
return get, true, err
|
||||
}
|
||||
}
|
||||
return user.User{}, false, crud.ErrForbidden
|
||||
if claims == nil {
|
||||
return user.User{}, false, crud.ErrForbidden
|
||||
}
|
||||
myId := fmt.Sprint(claims.UserId)
|
||||
if id == "@me" || id == myId {
|
||||
return u.dbCrud.Get(myId)
|
||||
}
|
||||
if claims.Perms.Has("super:admin") {
|
||||
return u.dbCrud.Get(id)
|
||||
}
|
||||
get, ok, err := u.dbCrud.Get(id)
|
||||
if ok && err == nil {
|
||||
if user.ParseVisibility(get.Visibility) == user.ProfileVisibilityPublic {
|
||||
z := get
|
||||
z.Email = "melon@mrmelon54.com"
|
||||
z.Visibility = nil
|
||||
z.Gender = nil
|
||||
z.Birthday = time.UnixMilli(-1)
|
||||
z.LastOnline = time.UnixMilli(-1)
|
||||
z.EmailVerified = nil
|
||||
z.Enabled = nil
|
||||
return get, true, err
|
||||
}
|
||||
}
|
||||
return user.User{}, false, crud.ErrForbidden
|
||||
}
|
||||
|
||||
func (u userProvider) Put(claims *auth.AccessTokenClaims, id string, data user.User) error {
|
||||
if claims == nil {
|
||||
return crud.ErrForbidden
|
||||
}
|
||||
myId := fmt.Sprint(claims.UserId)
|
||||
if id == "@me" || id == myId {
|
||||
return u.dbCrud.Put(id, data.ClearForPut())
|
||||
}
|
||||
if claims.Perms.Has("super:admin") {
|
||||
return u.dbCrud.Put(id, data.ClearForAdminPut())
|
||||
}
|
||||
return crud.ErrForbidden
|
||||
if claims == nil {
|
||||
return crud.ErrForbidden
|
||||
}
|
||||
myId := fmt.Sprint(claims.UserId)
|
||||
if id == "@me" || id == myId {
|
||||
return u.dbCrud.Put(id, data.ClearForPut())
|
||||
}
|
||||
if claims.Perms.Has("super:admin") {
|
||||
return u.dbCrud.Put(id, data.ClearForAdminPut())
|
||||
}
|
||||
return crud.ErrForbidden
|
||||
}
|
||||
|
||||
func (u userProvider) Patch(_ *auth.AccessTokenClaims, _ string, _ user.User) error {
|
||||
// TODO: implement patch
|
||||
return crud.ErrMethodNotAllowed
|
||||
// TODO: implement patch
|
||||
return crud.ErrMethodNotAllowed
|
||||
}
|
||||
|
||||
func (u userProvider) Delete(_ *auth.AccessTokenClaims, _ string) error {
|
||||
// TODO: implement delete
|
||||
return crud.ErrMethodNotAllowed
|
||||
// TODO: implement delete
|
||||
return crud.ErrMethodNotAllowed
|
||||
}
|
||||
|
|
152
pkg/api/api.go
152
pkg/api/api.go
|
@ -1,93 +1,93 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"code.mrmelon54.com/melon/summer/pkg/utils"
|
||||
"code.mrmelon54.com/melon/summer/pkg/utils/debug"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/mux"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"log"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"time"
|
||||
"code.mrmelon54.com/melon/summer/pkg/utils"
|
||||
"code.mrmelon54.com/melon/summer/pkg/utils/debug"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/mux"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"log"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoError = errors.New("no error")
|
||||
ErrParamMissing = errors.New("parameter is missing")
|
||||
ErrParamWrongType = errors.New("parameter is of the wrong type")
|
||||
noLogErrors = []error{
|
||||
ErrNoError,
|
||||
ErrParamMissing,
|
||||
ErrParamWrongType,
|
||||
}
|
||||
// prevent logging these to stop clutter of the logs
|
||||
noLogProdErrors = []error{
|
||||
bcrypt.ErrMismatchedHashAndPassword,
|
||||
jwt.ErrTokenInvalidClaims,
|
||||
jwt.ErrTokenMalformed,
|
||||
jwt.ErrTokenExpired,
|
||||
}
|
||||
ErrNoError = errors.New("no error")
|
||||
ErrParamMissing = errors.New("parameter is missing")
|
||||
ErrParamWrongType = errors.New("parameter is of the wrong type")
|
||||
noLogErrors = []error{
|
||||
ErrNoError,
|
||||
ErrParamMissing,
|
||||
ErrParamWrongType,
|
||||
}
|
||||
// prevent logging these to stop clutter of the logs
|
||||
noLogProdErrors = []error{
|
||||
bcrypt.ErrMismatchedHashAndPassword,
|
||||
jwt.ErrTokenInvalidClaims,
|
||||
jwt.ErrTokenMalformed,
|
||||
jwt.ErrTokenExpired,
|
||||
}
|
||||
)
|
||||
|
||||
type logErrorOutput struct {
|
||||
Message string `json:"message"`
|
||||
LogId string `json:"log_id"`
|
||||
Message string `json:"message"`
|
||||
LogId string `json:"log_id"`
|
||||
}
|
||||
|
||||
func GenericErrorMsg[T any](rw http.ResponseWriter, err error, code int, msg string, args ...any) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
m := fmt.Sprintf(msg, args...)
|
||||
u := fmt.Sprintf("LOG_%s", uuid.New())
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
m := fmt.Sprintf(msg, args...)
|
||||
u := fmt.Sprintf("LOG_%s", uuid.New())
|
||||
|
||||
var noLog bool
|
||||
for _, i := range noLogErrors {
|
||||
if errors.Is(err, i) {
|
||||
noLog = true
|
||||
}
|
||||
}
|
||||
if !debug.IsDebug {
|
||||
for _, i := range noLogProdErrors {
|
||||
if errors.Is(err, i) {
|
||||
noLog = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !noLog {
|
||||
var pr string
|
||||
_, file, line, ok := runtime.Caller(1)
|
||||
if ok {
|
||||
pr = fmt.Sprintf(" (%s:%d)", file, line)
|
||||
}
|
||||
log.Printf("[GenericApi::%T] {%s}%s %s: %s\n", *new(T), u, pr, m, err)
|
||||
}
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
rw.WriteHeader(code)
|
||||
_ = json.NewEncoder(rw).Encode(logErrorOutput{
|
||||
Message: m,
|
||||
LogId: u,
|
||||
})
|
||||
return true
|
||||
var noLog bool
|
||||
for _, i := range noLogErrors {
|
||||
if errors.Is(err, i) {
|
||||
noLog = true
|
||||
}
|
||||
}
|
||||
if !debug.IsDebug {
|
||||
for _, i := range noLogProdErrors {
|
||||
if errors.Is(err, i) {
|
||||
noLog = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !noLog {
|
||||
var pr string
|
||||
_, file, line, ok := runtime.Caller(1)
|
||||
if ok {
|
||||
pr = fmt.Sprintf(" (%s:%d)", file, line)
|
||||
}
|
||||
log.Printf("[GenericApi::%T] {%s}%s %s: %s\n", *new(T), u, pr, m, err)
|
||||
}
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
rw.WriteHeader(code)
|
||||
_ = json.NewEncoder(rw).Encode(logErrorOutput{
|
||||
Message: m,
|
||||
LogId: u,
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
func RunApiServer(listen string, router *mux.Router) *http.Server {
|
||||
s := &http.Server{
|
||||
Addr: listen,
|
||||
Handler: router,
|
||||
ReadTimeout: 1 * time.Minute,
|
||||
ReadHeaderTimeout: 1 * time.Minute,
|
||||
WriteTimeout: 1 * time.Minute,
|
||||
IdleTimeout: 1 * time.Minute,
|
||||
MaxHeaderBytes: 2500,
|
||||
}
|
||||
log.Printf("[API] Starting API server on: '%s'\n", s.Addr)
|
||||
go utils.RunBackgroundHttp("API", s)
|
||||
return s
|
||||
s := &http.Server{
|
||||
Addr: listen,
|
||||
Handler: router,
|
||||
ReadTimeout: 1 * time.Minute,
|
||||
ReadHeaderTimeout: 1 * time.Minute,
|
||||
WriteTimeout: 1 * time.Minute,
|
||||
IdleTimeout: 1 * time.Minute,
|
||||
MaxHeaderBytes: 2500,
|
||||
}
|
||||
log.Printf("[API] Starting API server on: '%s'\n", s.Addr)
|
||||
go utils.RunBackgroundHttp("API", s)
|
||||
return s
|
||||
}
|
||||
|
|
|
@ -1,56 +1,56 @@
|
|||
package crud
|
||||
|
||||
import (
|
||||
"code.mrmelon54.com/melon/summer/pkg/claims/auth"
|
||||
"code.mrmelon54.com/melon/summer/pkg/claims/auth"
|
||||
)
|
||||
|
||||
type FunctionProvider[T any] struct {
|
||||
InnerFind fFind[T]
|
||||
InnerAdd fAdd[T]
|
||||
InnerGet fGet[T]
|
||||
InnerPut fPut[T]
|
||||
InnerPatch fPatch[T]
|
||||
InnerDelete fDelete
|
||||
InnerFind fFind[T]
|
||||
InnerAdd fAdd[T]
|
||||
InnerGet fGet[T]
|
||||
InnerPut fPut[T]
|
||||
InnerPatch fPatch[T]
|
||||
InnerDelete fDelete
|
||||
}
|
||||
|
||||
func (f FunctionProvider[T]) Find(claims *auth.AccessTokenClaims) ([]T, error) {
|
||||
if f.InnerFind == nil {
|
||||
return nil, ErrMethodNotAllowed
|
||||
}
|
||||
return f.InnerFind(claims)
|
||||
if f.InnerFind == nil {
|
||||
return nil, ErrMethodNotAllowed
|
||||
}
|
||||
return f.InnerFind(claims)
|
||||
}
|
||||
|
||||
func (f FunctionProvider[T]) Add(claims *auth.AccessTokenClaims, item T) (T, error) {
|
||||
if f.InnerAdd == nil {
|
||||
return *new(T), ErrMethodNotAllowed
|
||||
}
|
||||
return f.InnerAdd(claims, item)
|
||||
if f.InnerAdd == nil {
|
||||
return *new(T), ErrMethodNotAllowed
|
||||
}
|
||||
return f.InnerAdd(claims, item)
|
||||
}
|
||||
|
||||
func (f FunctionProvider[T]) Get(claims *auth.AccessTokenClaims, id string) (T, bool, error) {
|
||||
if f.InnerGet == nil {
|
||||
return *new(T), false, ErrMethodNotAllowed
|
||||
}
|
||||
return f.InnerGet(claims, id)
|
||||
if f.InnerGet == nil {
|
||||
return *new(T), false, ErrMethodNotAllowed
|
||||
}
|
||||
return f.InnerGet(claims, id)
|
||||
}
|
||||
|
||||
func (f FunctionProvider[T]) Put(claims *auth.AccessTokenClaims, id string, item T) error {
|
||||
if f.InnerPut == nil {
|
||||
return ErrMethodNotAllowed
|
||||
}
|
||||
return f.InnerPut(claims, id, item)
|
||||
if f.InnerPut == nil {
|
||||
return ErrMethodNotAllowed
|
||||
}
|
||||
return f.InnerPut(claims, id, item)
|
||||
}
|
||||
|
||||
func (f FunctionProvider[T]) Patch(claims *auth.AccessTokenClaims, id string, item T) error {
|
||||
if f.InnerPatch == nil {
|
||||
return ErrMethodNotAllowed
|
||||
}
|
||||
return f.InnerPatch(claims, id, item)
|
||||
if f.InnerPatch == nil {
|
||||
return ErrMethodNotAllowed
|
||||
}
|
||||
return f.InnerPatch(claims, id, item)
|
||||
}
|
||||
|
||||
func (f FunctionProvider[T]) Delete(claims *auth.AccessTokenClaims, id string) error {
|
||||
if f.InnerDelete == nil {
|
||||
return ErrMethodNotAllowed
|
||||
}
|
||||
return f.InnerDelete(claims, id)
|
||||
if f.InnerDelete == nil {
|
||||
return ErrMethodNotAllowed
|
||||
}
|
||||
return f.InnerDelete(claims, id)
|
||||
}
|
||||
|
|
|
@ -1,171 +1,171 @@
|
|||
package crud
|
||||
|
||||
import (
|
||||
"code.mrmelon54.com/melon/summer/pkg/api"
|
||||
"code.mrmelon54.com/melon/summer/pkg/claims/auth"
|
||||
"code.mrmelon54.com/melon/summer/pkg/utils"
|
||||
"encoding/json"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mrmelon54/mjwt"
|
||||
"net/http"
|
||||
"code.mrmelon54.com/melon/summer/pkg/api"
|
||||
"code.mrmelon54.com/melon/summer/pkg/claims/auth"
|
||||
"code.mrmelon54.com/melon/summer/pkg/utils"
|
||||
"encoding/json"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mrmelon54/mjwt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type crudHandler[T any] struct {
|
||||
provider Provider[T]
|
||||
verify mjwt.Provider
|
||||
provider Provider[T]
|
||||
verify mjwt.Provider
|
||||
}
|
||||
|
||||
type secureHandlerFunc func(rw http.ResponseWriter, req *http.Request, claims *auth.AccessTokenClaims)
|
||||
|
||||
func NewCrudHandler[T any](router *mux.Router, verify mjwt.Provider, provider Provider[T], findPath, itemPath string) {
|
||||
z := &crudHandler[T]{provider: provider, verify: verify}
|
||||
router.HandleFunc(findPath, z.claimExtractor(z.handleFind)).Methods(http.MethodGet)
|
||||
router.HandleFunc(findPath, z.claimExtractor(z.handleAdd)).Methods(http.MethodPost)
|
||||
router.HandleFunc(itemPath, z.claimExtractor(z.handleGet)).Methods(http.MethodGet)
|
||||
router.HandleFunc(itemPath, z.claimExtractor(z.handlePut)).Methods(http.MethodPut)
|
||||
router.HandleFunc(itemPath, z.claimExtractor(z.handlePatch)).Methods(http.MethodPatch)
|
||||
router.HandleFunc(itemPath, z.claimExtractor(z.handleDelete)).Methods(http.MethodDelete)
|
||||
z := &crudHandler[T]{provider: provider, verify: verify}
|
||||
router.HandleFunc(findPath, z.claimExtractor(z.handleFind)).Methods(http.MethodGet)
|
||||
router.HandleFunc(findPath, z.claimExtractor(z.handleAdd)).Methods(http.MethodPost)
|
||||
router.HandleFunc(itemPath, z.claimExtractor(z.handleGet)).Methods(http.MethodGet)
|
||||
router.HandleFunc(itemPath, z.claimExtractor(z.handlePut)).Methods(http.MethodPut)
|
||||
router.HandleFunc(itemPath, z.claimExtractor(z.handlePatch)).Methods(http.MethodPatch)
|
||||
router.HandleFunc(itemPath, z.claimExtractor(z.handleDelete)).Methods(http.MethodDelete)
|
||||
}
|
||||
|
||||
func (c *crudHandler[T]) claimExtractor(f secureHandlerFunc) func(rw http.ResponseWriter, req *http.Request) {
|
||||
return func(rw http.ResponseWriter, req *http.Request) {
|
||||
token := utils.GetBearerToken(req)
|
||||
if token == "" {
|
||||
f(rw, req, nil)
|
||||
return
|
||||
}
|
||||
return func(rw http.ResponseWriter, req *http.Request) {
|
||||
token := utils.GetBearerToken(req)
|
||||
if token == "" {
|
||||
f(rw, req, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Verify as mfa flow token
|
||||
_, b, err := mjwt.ExtractClaims[auth.AccessTokenClaims](c.verify, token)
|
||||
if err != nil {
|
||||
f(rw, req, nil)
|
||||
return
|
||||
}
|
||||
// Verify as mfa flow token
|
||||
_, b, err := mjwt.ExtractClaims[auth.AccessTokenClaims](c.verify, token)
|
||||
if err != nil {
|
||||
f(rw, req, nil)
|
||||
return
|
||||
}
|
||||
|
||||
f(rw, req, &b.Claims)
|
||||
}
|
||||
f(rw, req, &b.Claims)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *crudHandler[T]) handleFind(rw http.ResponseWriter, _ *http.Request, claims *auth.AccessTokenClaims) {
|
||||
a, err := c.provider.Find(claims)
|
||||
if HttpCatchError(rw, err) {
|
||||
return
|
||||
}
|
||||
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Find error") {
|
||||
return
|
||||
}
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
err = json.NewEncoder(rw).Encode(a)
|
||||
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Encode error") {
|
||||
return
|
||||
}
|
||||
a, err := c.provider.Find(claims)
|
||||
if HttpCatchError(rw, err) {
|
||||
return
|
||||
}
|
||||
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Find error") {
|
||||
return
|
||||
}
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
err = json.NewEncoder(rw).Encode(a)
|
||||
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Encode error") {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *crudHandler[T]) handleAdd(rw http.ResponseWriter, req *http.Request, claims *auth.AccessTokenClaims) {
|
||||
var t T
|
||||
err := json.NewDecoder(req.Body).Decode(&t)
|
||||
if api.GenericErrorMsg[T](rw, err, http.StatusBadRequest, "Decode error") {
|
||||
return
|
||||
}
|
||||
t, err = c.provider.Add(claims, t)
|
||||
if HttpCatchError(rw, err) {
|
||||
return
|
||||
}
|
||||
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Add error") {
|
||||
return
|
||||
}
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.WriteHeader(http.StatusCreated)
|
||||
err = json.NewEncoder(rw).Encode(&t)
|
||||
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Encode error") {
|
||||
return
|
||||
}
|
||||
var t T
|
||||
err := json.NewDecoder(req.Body).Decode(&t)
|
||||
if api.GenericErrorMsg[T](rw, err, http.StatusBadRequest, "Decode error") {
|
||||
return
|
||||
}
|
||||
t, err = c.provider.Add(claims, t)
|
||||
if HttpCatchError(rw, err) {
|
||||
return
|
||||
}
|
||||
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Add error") {
|
||||
return
|
||||
}
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.WriteHeader(http.StatusCreated)
|
||||
err = json.NewEncoder(rw).Encode(&t)
|
||||
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Encode error") {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *crudHandler[T]) handleGet(rw http.ResponseWriter, req *http.Request, claims *auth.AccessTokenClaims) {
|
||||
vars := mux.Vars(req)
|
||||
s, ok := vars["id"]
|
||||
if !ok {
|
||||
api.GenericErrorMsg[T](rw, api.ErrParamMissing, http.StatusBadRequest, "Parameter error")
|
||||
return
|
||||
}
|
||||
t, found, err := c.provider.Get(claims, s)
|
||||
if HttpCatchError(rw, err) {
|
||||
return
|
||||
}
|
||||
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Get error") {
|
||||
return
|
||||
}
|
||||
if !found {
|
||||
api.GenericErrorMsg[T](rw, err, http.StatusNotFound, "Not Found")
|
||||
return
|
||||
}
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
err = json.NewEncoder(rw).Encode(&t)
|
||||
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Encode error") {
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(req)
|
||||
s, ok := vars["id"]
|
||||
if !ok {
|
||||
api.GenericErrorMsg[T](rw, api.ErrParamMissing, http.StatusBadRequest, "Parameter error")
|
||||
return
|
||||
}
|
||||
t, found, err := c.provider.Get(claims, s)
|
||||
if HttpCatchError(rw, err) {
|
||||
return
|
||||
}
|
||||
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Get error") {
|
||||
return
|
||||
}
|
||||
if !found {
|
||||
api.GenericErrorMsg[T](rw, err, http.StatusNotFound, "Not Found")
|
||||
return
|
||||
}
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
err = json.NewEncoder(rw).Encode(&t)
|
||||
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Encode error") {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *crudHandler[T]) handlePut(rw http.ResponseWriter, req *http.Request, claims *auth.AccessTokenClaims) {
|
||||
vars := mux.Vars(req)
|
||||
s, ok := vars["id"]
|
||||
if !ok {
|
||||
api.GenericErrorMsg[T](rw, api.ErrParamMissing, http.StatusBadRequest, "Parameter error")
|
||||
return
|
||||
}
|
||||
var t T
|
||||
err := json.NewDecoder(req.Body).Decode(&t)
|
||||
if api.GenericErrorMsg[T](rw, err, http.StatusBadRequest, "Decode error") {
|
||||
return
|
||||
}
|
||||
err = c.provider.Put(claims, s, t)
|
||||
if HttpCatchError(rw, err) {
|
||||
return
|
||||
}
|
||||
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Put error") {
|
||||
return
|
||||
}
|
||||
rw.WriteHeader(http.StatusNoContent)
|
||||
vars := mux.Vars(req)
|
||||
s, ok := vars["id"]
|
||||
if !ok {
|
||||
api.GenericErrorMsg[T](rw, api.ErrParamMissing, http.StatusBadRequest, "Parameter error")
|
||||
return
|
||||
}
|
||||
var t T
|
||||
err := json.NewDecoder(req.Body).Decode(&t)
|
||||
if api.GenericErrorMsg[T](rw, err, http.StatusBadRequest, "Decode error") {
|
||||
return
|
||||
}
|
||||
err = c.provider.Put(claims, s, t)
|
||||
if HttpCatchError(rw, err) {
|
||||
return
|
||||
}
|
||||
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Put error") {
|
||||
return
|
||||
}
|
||||
rw.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (c *crudHandler[T]) handlePatch(rw http.ResponseWriter, req *http.Request, claims *auth.AccessTokenClaims) {
|
||||
vars := mux.Vars(req)
|
||||
s, ok := vars["id"]
|
||||
if !ok {
|
||||
api.GenericErrorMsg[T](rw, api.ErrParamMissing, http.StatusBadRequest, "Parameter error")
|
||||
return
|
||||
}
|
||||
var t T
|
||||
err := json.NewDecoder(req.Body).Decode(&t)
|
||||
if api.GenericErrorMsg[T](rw, err, http.StatusBadRequest, "Decode error") {
|
||||
return
|
||||
}
|
||||
err = c.provider.Patch(claims, s, t)
|
||||
if HttpCatchError(rw, err) {
|
||||
return
|
||||
}
|
||||
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Patch error") {
|
||||
return
|
||||
}
|
||||
rw.WriteHeader(http.StatusNoContent)
|
||||
vars := mux.Vars(req)
|
||||
s, ok := vars["id"]
|
||||
if !ok {
|
||||
api.GenericErrorMsg[T](rw, api.ErrParamMissing, http.StatusBadRequest, "Parameter error")
|
||||
return
|
||||
}
|
||||
var t T
|
||||
err := json.NewDecoder(req.Body).Decode(&t)
|
||||
if api.GenericErrorMsg[T](rw, err, http.StatusBadRequest, "Decode error") {
|
||||
return
|
||||
}
|
||||
err = c.provider.Patch(claims, s, t)
|
||||
if HttpCatchError(rw, err) {
|
||||
return
|
||||
}
|
||||
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Patch error") {
|
||||
return
|
||||
}
|
||||
rw.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (c *crudHandler[T]) handleDelete(rw http.ResponseWriter, req *http.Request, claims *auth.AccessTokenClaims) {
|
||||
vars := mux.Vars(req)
|
||||
s, ok := vars["id"]
|
||||
if !ok {
|
||||
api.GenericErrorMsg[T](rw, api.ErrParamMissing, http.StatusBadRequest, "Parameter error")
|
||||
return
|
||||
}
|
||||
err := c.provider.Delete(claims, s)
|
||||
if HttpCatchError(rw, err) {
|
||||
return
|
||||
}
|
||||
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Delete error") {
|
||||
return
|
||||
}
|
||||
rw.WriteHeader(http.StatusNoContent)
|
||||
vars := mux.Vars(req)
|
||||
s, ok := vars["id"]
|
||||
if !ok {
|
||||
api.GenericErrorMsg[T](rw, api.ErrParamMissing, http.StatusBadRequest, "Parameter error")
|
||||
return
|
||||
}
|
||||
err := c.provider.Delete(claims, s)
|
||||
if HttpCatchError(rw, err) {
|
||||
return
|
||||
}
|
||||
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Delete error") {
|
||||
return
|
||||
}
|
||||
rw.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
|
|
@ -3,21 +3,21 @@ package crud
|
|||
import "code.mrmelon54.com/melon/summer/pkg/claims/auth"
|
||||
|
||||
type Provider[T any] interface {
|
||||
Find(claims *auth.AccessTokenClaims) ([]T, error)
|
||||
Add(claims *auth.AccessTokenClaims, item T) (T, error)
|
||||
Get(claims *auth.AccessTokenClaims, id string) (T, bool, error)
|
||||
Put(claims *auth.AccessTokenClaims, id string, item T) error
|
||||
Patch(claims *auth.AccessTokenClaims, id string, item T) error
|
||||
Delete(claims *auth.AccessTokenClaims, id string) error
|
||||
Find(claims *auth.AccessTokenClaims) ([]T, error)
|
||||
Add(claims *auth.AccessTokenClaims, item T) (T, error)
|
||||
Get(claims *auth.AccessTokenClaims, id string) (T, bool, error)
|
||||
Put(claims *auth.AccessTokenClaims, id string, item T) error
|
||||
Patch(claims *auth.AccessTokenClaims, id string, item T) error
|
||||
Delete(claims *auth.AccessTokenClaims, id string) error
|
||||
}
|
||||
|
||||
type CheckedProvider[T any] interface {
|
||||
Find() ([]T, error)
|
||||
Add(item T) (T, error)
|
||||
Get(id string) (T, bool, error)
|
||||
Put(id string, item T) error
|
||||
Patch(id string, item T) error
|
||||
Delete(id string) error
|
||||
Find() ([]T, error)
|
||||
Add(item T) (T, error)
|
||||
Get(id string) (T, bool, error)
|
||||
Put(id string, item T) error
|
||||
Patch(id string, item T) error
|
||||
Delete(id string) error
|
||||
}
|
||||
|
||||
type fFind[T any] func(claims *auth.AccessTokenClaims) ([]T, error)
|
||||
|
|
|
@ -1,56 +1,56 @@
|
|||
package crud
|
||||
|
||||
import (
|
||||
"code.mrmelon54.com/melon/summer/pkg/claims/auth"
|
||||
"code.mrmelon54.com/melon/summer/pkg/claims/auth"
|
||||
)
|
||||
|
||||
type singlePermProvider[T any] struct {
|
||||
perm string
|
||||
provider CheckedProvider[T]
|
||||
perm string
|
||||
provider CheckedProvider[T]
|
||||
}
|
||||
|
||||
func NewSinglePermProvider[T any](perm string, provider CheckedProvider[T]) Provider[T] {
|
||||
return &singlePermProvider[T]{perm, provider}
|
||||
return &singlePermProvider[T]{perm, provider}
|
||||
}
|
||||
|
||||
func (s *singlePermProvider[T]) Find(claims *auth.AccessTokenClaims) ([]T, error) {
|
||||
if !claims.Perms.Has(s.perm) {
|
||||
return nil, ErrForbidden
|
||||
}
|
||||
return s.provider.Find()
|
||||
if !claims.Perms.Has(s.perm) {
|
||||
return nil, ErrForbidden
|
||||
}
|
||||
return s.provider.Find()
|
||||
}
|
||||
|
||||
func (s *singlePermProvider[T]) Add(claims *auth.AccessTokenClaims, item T) (T, error) {
|
||||
if !claims.Perms.Has(s.perm) {
|
||||
return *new(T), ErrForbidden
|
||||
}
|
||||
return s.provider.Add(item)
|
||||
if !claims.Perms.Has(s.perm) {
|
||||
return *new(T), ErrForbidden
|
||||
}
|
||||
return s.provider.Add(item)
|
||||
}
|
||||
|
||||
func (s *singlePermProvider[T]) Get(claims *auth.AccessTokenClaims, id string) (T, bool, error) {
|
||||
if !claims.Perms.Has(s.perm) {
|
||||
return *new(T), false, ErrForbidden
|
||||
}
|
||||
return s.provider.Get(id)
|
||||
if !claims.Perms.Has(s.perm) {
|
||||
return *new(T), false, ErrForbidden
|
||||
}
|
||||
return s.provider.Get(id)
|
||||
}
|
||||
|
||||
func (s *singlePermProvider[T]) Put(claims *auth.AccessTokenClaims, id string, item T) error {
|
||||
if !claims.Perms.Has(s.perm) {
|
||||
return ErrForbidden
|
||||
}
|
||||
return s.provider.Put(id, item)
|
||||
if !claims.Perms.Has(s.perm) {
|
||||
return ErrForbidden
|
||||
}
|
||||
return s.provider.Put(id, item)
|
||||
}
|
||||
|
||||
func (s *singlePermProvider[T]) Patch(claims *auth.AccessTokenClaims, id string, item T) error {
|
||||
if !claims.Perms.Has(s.perm) {
|
||||
return ErrForbidden
|
||||
}
|
||||
return s.provider.Patch(id, item)
|
||||
if !claims.Perms.Has(s.perm) {
|
||||
return ErrForbidden
|
||||
}
|
||||
return s.provider.Patch(id, item)
|
||||
}
|
||||
|
||||
func (s *singlePermProvider[T]) Delete(claims *auth.AccessTokenClaims, id string) error {
|
||||
if !claims.Perms.Has(s.perm) {
|
||||
return ErrForbidden
|
||||
}
|
||||
return s.provider.Delete(id)
|
||||
if !claims.Perms.Has(s.perm) {
|
||||
return ErrForbidden
|
||||
}
|
||||
return s.provider.Delete(id)
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"code.mrmelon54.com/melon/summer/pkg/claims"
|
||||
"github.com/mrmelon54/mjwt"
|
||||
"time"
|
||||
"code.mrmelon54.com/melon/summer/pkg/claims"
|
||||
"github.com/mrmelon54/mjwt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type AccessTokenClaims struct {
|
||||
UserId uint64 `json:"uid"`
|
||||
Perms *claims.PermStorage `json:"per"`
|
||||
UserId uint64 `json:"uid"`
|
||||
Perms *claims.PermStorage `json:"per"`
|
||||
}
|
||||
|
||||
func (a AccessTokenClaims) Valid() error { return nil }
|
||||
|
@ -16,8 +16,8 @@ func (a AccessTokenClaims) Valid() error { return nil }
|
|||
func (a AccessTokenClaims) Type() string { return "access-token" }
|
||||
|
||||
func CreateAccessToken(p mjwt.Provider, sub, id string, userId uint64, perms *claims.PermStorage) (string, error) {
|
||||
return p.GenerateJwt(sub, id, time.Minute*15, &AccessTokenClaims{
|
||||
UserId: userId,
|
||||
Perms: perms,
|
||||
})
|
||||
return p.GenerateJwt(sub, id, time.Minute*15, &AccessTokenClaims{
|
||||
UserId: userId,
|
||||
Perms: perms,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"github.com/mrmelon54/mjwt"
|
||||
"time"
|
||||
"github.com/mrmelon54/mjwt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RefreshTokenClaims struct {
|
||||
UserId uint64 `json:"uid"`
|
||||
UserId uint64 `json:"uid"`
|
||||
}
|
||||
|
||||
func (r RefreshTokenClaims) Valid() error { return nil }
|
||||
func (r RefreshTokenClaims) Type() string { return "refresh-token" }
|
||||
|
||||
func CreateRefreshToken(p mjwt.Provider, sub, id string, userId uint64) (string, error) {
|
||||
return p.GenerateJwt(sub, id, time.Hour*24*7, RefreshTokenClaims{
|
||||
UserId: userId,
|
||||
})
|
||||
return p.GenerateJwt(sub, id, time.Hour*24*7, RefreshTokenClaims{
|
||||
UserId: userId,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
package flow
|
||||
|
||||
import (
|
||||
"github.com/mrmelon54/mjwt"
|
||||
"time"
|
||||
"github.com/mrmelon54/mjwt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type LoginFlowClaims struct {
|
||||
LoginId uint64 `json:"lid"`
|
||||
NextState string `json:"nxs"`
|
||||
LoginId uint64 `json:"lid"`
|
||||
NextState string `json:"nxs"`
|
||||
}
|
||||
|
||||
func (l LoginFlowClaims) Valid() error { return nil }
|
||||
func (l LoginFlowClaims) Type() string { return "login-flow" }
|
||||
|
||||
func CreateLoginFlowToken(p mjwt.Provider, sub, id, nextState string, loginId uint64, dur time.Duration) (string, error) {
|
||||
return p.GenerateJwt(sub, id, dur, LoginFlowClaims{
|
||||
LoginId: loginId,
|
||||
NextState: nextState,
|
||||
})
|
||||
return p.GenerateJwt(sub, id, dur, LoginFlowClaims{
|
||||
LoginId: loginId,
|
||||
NextState: nextState,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
package flow
|
||||
|
||||
import (
|
||||
"github.com/mrmelon54/mjwt"
|
||||
"time"
|
||||
"github.com/mrmelon54/mjwt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MfaFlowClaims struct {
|
||||
LoginId uint64 `json:"lid"`
|
||||
LoginId uint64 `json:"lid"`
|
||||
}
|
||||
|
||||
func (m MfaFlowClaims) Valid() error { return nil }
|
||||
func (m MfaFlowClaims) Type() string { return "mfa-flow" }
|
||||
|
||||
func CreateMfaFlowToken(p mjwt.Provider, sub, id string, loginId uint64, dur time.Duration) (string, error) {
|
||||
return p.GenerateJwt(sub, id, dur, MfaFlowClaims{
|
||||
LoginId: loginId,
|
||||
})
|
||||
return p.GenerateJwt(sub, id, dur, MfaFlowClaims{
|
||||
LoginId: loginId,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
package flow
|
||||
|
||||
import (
|
||||
"code.mrmelon54.com/melon/summer/pkg/claims"
|
||||
"github.com/mrmelon54/mjwt"
|
||||
"time"
|
||||
"code.mrmelon54.com/melon/summer/pkg/claims"
|
||||
"github.com/mrmelon54/mjwt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type OAuthFlowClaims struct {
|
||||
LoginId uint64 `json:"lid"`
|
||||
Scopes *claims.PermStorage `json:"sco"`
|
||||
LoginId uint64 `json:"lid"`
|
||||
Scopes *claims.PermStorage `json:"sco"`
|
||||
}
|
||||
|
||||
func (o OAuthFlowClaims) Valid() error { return nil }
|
||||
|
@ -16,8 +16,8 @@ func (o OAuthFlowClaims) Valid() error { return nil }
|
|||
func (o OAuthFlowClaims) Type() string { return "oauth-flow" }
|
||||
|
||||
func CreateOAuthFlowToken(p mjwt.Provider, sub, id string, loginId uint64, scopes *claims.PermStorage, dur time.Duration) (string, error) {
|
||||
return p.GenerateJwt(sub, id, dur, OAuthFlowClaims{
|
||||
LoginId: loginId,
|
||||
Scopes: scopes,
|
||||
})
|
||||
return p.GenerateJwt(sub, id, dur, OAuthFlowClaims{
|
||||
LoginId: loginId,
|
||||
Scopes: scopes,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,85 +1,85 @@
|
|||
package claims
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"gopkg.in/yaml.v3"
|
||||
"sort"
|
||||
"encoding/json"
|
||||
"gopkg.in/yaml.v3"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type PermStorage struct {
|
||||
values map[string]struct{}
|
||||
values map[string]struct{}
|
||||
}
|
||||
|
||||
func NewPermStorage() *PermStorage {
|
||||
return new(PermStorage).setup()
|
||||
return new(PermStorage).setup()
|
||||
}
|
||||
|
||||
func (p *PermStorage) setup() *PermStorage {
|
||||
if p.values == nil {
|
||||
p.values = make(map[string]struct{})
|
||||
}
|
||||
return p
|
||||
if p.values == nil {
|
||||
p.values = make(map[string]struct{})
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PermStorage) Set(perm string) {
|
||||
p.values[perm] = struct{}{}
|
||||
p.values[perm] = struct{}{}
|
||||
}
|
||||
|
||||
func (p *PermStorage) Clear(perm string) {
|
||||
delete(p.values, perm)
|
||||
delete(p.values, perm)
|
||||
}
|
||||
|
||||
func (p *PermStorage) Has(perm string) bool {
|
||||
_, ok := p.values[perm]
|
||||
return ok
|
||||
_, ok := p.values[perm]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (p *PermStorage) OneOf(o *PermStorage) bool {
|
||||
for i := range o.values {
|
||||
if o.Has(i) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
for i := range o.values {
|
||||
if o.Has(i) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *PermStorage) dump() []string {
|
||||
var a []string
|
||||
for i := range p.values {
|
||||
a = append(a, i)
|
||||
}
|
||||
sort.Strings(a)
|
||||
return a
|
||||
var a []string
|
||||
for i := range p.values {
|
||||
a = append(a, i)
|
||||
}
|
||||
sort.Strings(a)
|
||||
return a
|
||||
}
|
||||
|
||||
func (p *PermStorage) prepare(a []string) {
|
||||
for _, i := range a {
|
||||
p.Set(i)
|
||||
}
|
||||
for _, i := range a {
|
||||
p.Set(i)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PermStorage) MarshalJSON() ([]byte, error) { return json.Marshal(p.dump()) }
|
||||
|
||||
func (p *PermStorage) UnmarshalJSON(bytes []byte) error {
|
||||
p.setup()
|
||||
var a []string
|
||||
err := json.Unmarshal(bytes, &a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.prepare(a)
|
||||
return nil
|
||||
p.setup()
|
||||
var a []string
|
||||
err := json.Unmarshal(bytes, &a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.prepare(a)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PermStorage) MarshalYAML() (interface{}, error) { return yaml.Marshal(p.dump()) }
|
||||
|
||||
func (p *PermStorage) UnmarshalYAML(value *yaml.Node) error {
|
||||
p.setup()
|
||||
var a []string
|
||||
err := value.Decode(&a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.prepare(a)
|
||||
return nil
|
||||
p.setup()
|
||||
var a []string
|
||||
err := value.Decode(&a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.prepare(a)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,75 +1,75 @@
|
|||
package login_checker
|
||||
|
||||
import (
|
||||
"code.mrmelon54.com/melon/summer/pkg/api"
|
||||
"code.mrmelon54.com/melon/summer/pkg/claims/auth"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/user"
|
||||
"code.mrmelon54.com/melon/summer/pkg/utils"
|
||||
"errors"
|
||||
"github.com/mrmelon54/mjwt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"xorm.io/xorm"
|
||||
"code.mrmelon54.com/melon/summer/pkg/api"
|
||||
"code.mrmelon54.com/melon/summer/pkg/claims/auth"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/user"
|
||||
"code.mrmelon54.com/melon/summer/pkg/utils"
|
||||
"errors"
|
||||
"github.com/mrmelon54/mjwt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type LoginChecker struct {
|
||||
db *xorm.Engine
|
||||
signer mjwt.Provider
|
||||
loginSite url.URL
|
||||
mfaLoginSite url.URL
|
||||
db *xorm.Engine
|
||||
signer mjwt.Provider
|
||||
loginSite url.URL
|
||||
mfaLoginSite url.URL
|
||||
}
|
||||
|
||||
func NewLoginChecker(db *xorm.Engine, signer mjwt.Provider) *LoginChecker {
|
||||
return &LoginChecker{
|
||||
db: db,
|
||||
signer: signer,
|
||||
loginSite: url.URL{Path: "/login"},
|
||||
mfaLoginSite: url.URL{Path: "/mfa"},
|
||||
}
|
||||
return &LoginChecker{
|
||||
db: db,
|
||||
signer: signer,
|
||||
loginSite: url.URL{Path: "/login"},
|
||||
mfaLoginSite: url.URL{Path: "/mfa"},
|
||||
}
|
||||
}
|
||||
|
||||
func (lc *LoginChecker) GetUserID(req *http.Request) (uint64, error) {
|
||||
token := utils.GetBearerToken(req)
|
||||
if token == "" {
|
||||
return 0, errors.New("access token missing")
|
||||
}
|
||||
token := utils.GetBearerToken(req)
|
||||
if token == "" {
|
||||
return 0, errors.New("access token missing")
|
||||
}
|
||||
|
||||
// Verify as access token
|
||||
_, b, err := mjwt.ExtractClaims[auth.AccessTokenClaims](lc.signer, token)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// Verify as access token
|
||||
_, b, err := mjwt.ExtractClaims[auth.AccessTokenClaims](lc.signer, token)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var u user.User
|
||||
get, err := lc.db.Where("id = ?", b.Claims.UserId).Get(&u)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if !utils.SBool(u.EmailVerified) {
|
||||
return 0, errors.New("requires email verification")
|
||||
}
|
||||
if !utils.SBool(u.MfaActive) {
|
||||
return 0, errors.New("requires MFA")
|
||||
}
|
||||
if get {
|
||||
return u.Id, nil
|
||||
}
|
||||
return 0, errors.New("failed login check")
|
||||
var u user.User
|
||||
get, err := lc.db.Where("id = ?", b.Claims.UserId).Get(&u)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if !utils.SBool(u.EmailVerified) {
|
||||
return 0, errors.New("requires email verification")
|
||||
}
|
||||
if !utils.SBool(u.MfaActive) {
|
||||
return 0, errors.New("requires MFA")
|
||||
}
|
||||
if get {
|
||||
return u.Id, nil
|
||||
}
|
||||
return 0, errors.New("failed login check")
|
||||
}
|
||||
|
||||
func (lc *LoginChecker) CheckRequired(rw http.ResponseWriter, req *http.Request, cbGood func(uint64)) {
|
||||
userId, err := lc.GetUserID(req)
|
||||
if api.GenericErrorMsg[LoginChecker](rw, err, http.StatusUnauthorized, "Token Not Valid") {
|
||||
return
|
||||
}
|
||||
cbGood(userId)
|
||||
userId, err := lc.GetUserID(req)
|
||||
if api.GenericErrorMsg[LoginChecker](rw, err, http.StatusUnauthorized, "Token Not Valid") {
|
||||
return
|
||||
}
|
||||
cbGood(userId)
|
||||
}
|
||||
|
||||
func (lc *LoginChecker) CheckOptional(rw http.ResponseWriter, req *http.Request, cbGood func(uint64), cbBad func(uint64)) {
|
||||
userId, err := lc.GetUserID(req)
|
||||
if err != nil {
|
||||
cbBad(0)
|
||||
return
|
||||
}
|
||||
cbGood(userId)
|
||||
userId, err := lc.GetUserID(req)
|
||||
if err != nil {
|
||||
cbBad(0)
|
||||
return
|
||||
}
|
||||
cbGood(userId)
|
||||
}
|
||||
|
|
|
@ -1,46 +1,46 @@
|
|||
package oauth_server
|
||||
|
||||
import (
|
||||
"code.mrmelon54.com/melon/summer/pkg/api/crud"
|
||||
"code.mrmelon54.com/melon/summer/pkg/claims/auth"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/oauth"
|
||||
"xorm.io/xorm"
|
||||
"code.mrmelon54.com/melon/summer/pkg/api/crud"
|
||||
"code.mrmelon54.com/melon/summer/pkg/claims/auth"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/oauth"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type oAuthAppProvider struct {
|
||||
db *xorm.Engine
|
||||
dbCrud crud.CheckedProvider[oauth.App]
|
||||
db *xorm.Engine
|
||||
dbCrud crud.CheckedProvider[oauth.App]
|
||||
}
|
||||
|
||||
func NewOAuthAppProvider(db *xorm.Engine) crud.Provider[oauth.App] {
|
||||
return &oAuthAppProvider{db, crud.NewDatabaseProvider[oauth.App](db, "app_id")}
|
||||
return &oAuthAppProvider{db, crud.NewDatabaseProvider[oauth.App](db, "app_id")}
|
||||
}
|
||||
|
||||
func (o oAuthAppProvider) Find(_ *auth.AccessTokenClaims) ([]oauth.App, error) {
|
||||
// TODO: implement this for users and admins
|
||||
return nil, crud.ErrMethodNotAllowed
|
||||
// TODO: implement this for users and admins
|
||||
return nil, crud.ErrMethodNotAllowed
|
||||
}
|
||||
|
||||
func (o oAuthAppProvider) Add(_ *auth.AccessTokenClaims, item oauth.App) (oauth.App, error) {
|
||||
return o.dbCrud.Add(item)
|
||||
return o.dbCrud.Add(item)
|
||||
}
|
||||
|
||||
func (o oAuthAppProvider) Get(_ *auth.AccessTokenClaims, id string) (oauth.App, bool, error) {
|
||||
var a oauth.App
|
||||
ok, err := o.db.Where("app_id = ? and enabled = ?", id, true).Get(&a)
|
||||
return a, ok, err
|
||||
var a oauth.App
|
||||
ok, err := o.db.Where("app_id = ? and enabled = ?", id, true).Get(&a)
|
||||
return a, ok, err
|
||||
}
|
||||
|
||||
func (o oAuthAppProvider) Put(claims *auth.AccessTokenClaims, id string, item oauth.App) error {
|
||||
_, err := o.db.Where("app_id = ? and user_id = ?", id, claims.UserId).Update(&item)
|
||||
return err
|
||||
_, err := o.db.Where("app_id = ? and user_id = ?", id, claims.UserId).Update(&item)
|
||||
return err
|
||||
}
|
||||
|
||||
func (o oAuthAppProvider) Patch(claims *auth.AccessTokenClaims, id string, item oauth.App) error {
|
||||
_, err := o.db.Where("app_id = ? and user_id = ?", id, claims.UserId).Update(&item)
|
||||
return err
|
||||
_, err := o.db.Where("app_id = ? and user_id = ?", id, claims.UserId).Update(&item)
|
||||
return err
|
||||
}
|
||||
|
||||
func (o oAuthAppProvider) Delete(_ *auth.AccessTokenClaims, _ string) error {
|
||||
return crud.ErrMethodNotAllowed
|
||||
return crud.ErrMethodNotAllowed
|
||||
}
|
||||
|
|
|
@ -1,218 +1,218 @@
|
|||
package oauth_server
|
||||
|
||||
import (
|
||||
"code.mrmelon54.com/melon/summer/pkg/api"
|
||||
"code.mrmelon54.com/melon/summer/pkg/claims"
|
||||
"code.mrmelon54.com/melon/summer/pkg/claims/auth"
|
||||
"code.mrmelon54.com/melon/summer/pkg/claims/flow"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/oauth"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/user"
|
||||
"code.mrmelon54.com/melon/summer/pkg/utils"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/go-oauth2/oauth2/v4/errors"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mrmelon54/mjwt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
"xorm.io/xorm"
|
||||
"code.mrmelon54.com/melon/summer/pkg/api"
|
||||
"code.mrmelon54.com/melon/summer/pkg/claims"
|
||||
"code.mrmelon54.com/melon/summer/pkg/claims/auth"
|
||||
"code.mrmelon54.com/melon/summer/pkg/claims/flow"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/oauth"
|
||||
"code.mrmelon54.com/melon/summer/pkg/tables/user"
|
||||
"code.mrmelon54.com/melon/summer/pkg/utils"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/go-oauth2/oauth2/v4/errors"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mrmelon54/mjwt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
var ErrInvalidClient = errors.New("invalid client")
|
||||
|
||||
type OAuthServer struct {
|
||||
signer mjwt.Provider
|
||||
router *mux.Router
|
||||
db *xorm.Engine
|
||||
authClient string
|
||||
signer mjwt.Provider
|
||||
router *mux.Router
|
||||
db *xorm.Engine
|
||||
authClient string
|
||||
}
|
||||
|
||||
type OAuthFlowInput struct {
|
||||
ResponseType string `json:"response_type"`
|
||||
ClientId uint64 `json:"client_id"`
|
||||
Scope *claims.PermStorage `json:"scope"`
|
||||
State string `json:"state"`
|
||||
RedirectUrl string `json:"redirect_url,omitempty"`
|
||||
ResponseType string `json:"response_type"`
|
||||
ClientId uint64 `json:"client_id"`
|
||||
Scope *claims.PermStorage `json:"scope"`
|
||||
State string `json:"state"`
|
||||
RedirectUrl string `json:"redirect_url,omitempty"`
|
||||
}
|
||||
|
||||
type OAuthFlowOutput struct {
|
||||
Code string `json:"code"`
|
||||
RedirectUrl string `json:"redirect_url,omitempty"`
|
||||
Code string `json:"code"`
|
||||
RedirectUrl string `json:"redirect_url,omitempty"`
|
||||
}
|
||||
|
||||
type OAuthTokenOutput struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn uint64 `json:"expires_in,omitempty"`
|
||||
RefreshToken string `json:"refresh_token,omitempty"`
|
||||
Scope *claims.PermStorage `json:"scope,omitempty"`
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn uint64 `json:"expires_in,omitempty"`
|
||||
RefreshToken string `json:"refresh_token,omitempty"`
|
||||
Scope *claims.PermStorage `json:"scope,omitempty"`
|
||||
}
|
||||
|
||||
func NewOAuthServer(router *mux.Router, db *xorm.Engine, signer mjwt.Provider, authClient string) *OAuthServer {
|
||||
s := &OAuthServer{
|
||||
signer: signer,
|
||||
router: router,
|
||||
db: db,
|
||||
authClient: authClient,
|
||||
}
|
||||
router.HandleFunc("/authorize", s.handleOAuthAuthorize).Methods(http.MethodPost, http.MethodGet)
|
||||
router.HandleFunc("/token", s.handleOAuthToken).Methods(http.MethodPost)
|
||||
return s
|
||||
s := &OAuthServer{
|
||||
signer: signer,
|
||||
router: router,
|
||||
db: db,
|
||||
authClient: authClient,
|
||||
}
|
||||
router.HandleFunc("/authorize", s.handleOAuthAuthorize).Methods(http.MethodPost, http.MethodGet)
|
||||
router.HandleFunc("/token", s.handleOAuthToken).Methods(http.MethodPost)
|
||||
return s
|
||||
}
|
||||
|
||||
func ClientAuthHandler(req *http.Request) (string, string, error) {
|
||||
cId, cSecret, err := ClientBasicHandler(req)
|
||||
if cId == "" && cSecret == "" {
|
||||
cId, cSecret, err = ClientFormHandler(req)
|
||||
}
|
||||
return cId, cSecret, err
|
||||
cId, cSecret, err := ClientBasicHandler(req)
|
||||
if cId == "" && cSecret == "" {
|
||||
cId, cSecret, err = ClientFormHandler(req)
|
||||
}
|
||||
return cId, cSecret, err
|
||||
}
|
||||
|
||||
// ClientFormHandler get client data from form
|
||||
func ClientFormHandler(r *http.Request) (string, string, error) {
|
||||
clientID := r.Form.Get("client_id")
|
||||
if clientID == "" {
|
||||
return "", "", ErrInvalidClient
|
||||
}
|
||||
clientSecret := r.Form.Get("client_secret")
|
||||
return clientID, clientSecret, nil
|
||||
clientID := r.Form.Get("client_id")
|
||||
if clientID == "" {
|
||||
return "", "", ErrInvalidClient
|
||||
}
|
||||
clientSecret := r.Form.Get("client_secret")
|
||||
return clientID, clientSecret, nil
|
||||
}
|
||||
|
||||
// ClientBasicHandler get client data from basic authorization
|
||||
func ClientBasicHandler(r *http.Request) (string, string, error) {
|
||||
username, password, ok := r.BasicAuth()
|
||||
if !ok {
|
||||
return "", "", ErrInvalidClient
|
||||
}
|
||||
return username, password, nil
|
||||
username, password, ok := r.BasicAuth()
|
||||
if !ok {
|
||||
return "", "", ErrInvalidClient
|
||||
}
|
||||
return username, password, nil
|
||||
}
|
||||
|
||||
func (o *OAuthServer) handleOAuthAuthorize(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.Method == http.MethodGet {
|
||||
appUrl, err := url.Parse(o.authClient)
|
||||
if err != nil {
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = rw.Write([]byte("Invalid application configuration\n"))
|
||||
return
|
||||
}
|
||||
appUrl.RawQuery = req.URL.RawQuery
|
||||
http.Redirect(rw, req, appUrl.String(), http.StatusFound)
|
||||
return
|
||||
}
|
||||
if req.Method == http.MethodGet {
|
||||
appUrl, err := url.Parse(o.authClient)
|
||||
if err != nil {
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = rw.Write([]byte("Invalid application configuration\n"))
|
||||
return
|
||||
}
|
||||
appUrl.RawQuery = req.URL.RawQuery
|
||||
http.Redirect(rw, req, appUrl.String(), http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
token := utils.GetBearerToken(req)
|
||||
if token == "" {
|
||||
api.GenericErrorMsg[OAuthServer](rw, api.ErrNoError, http.StatusInternalServerError, "Access token missing")
|
||||
return
|
||||
}
|
||||
token := utils.GetBearerToken(req)
|
||||
if token == "" {
|
||||
api.GenericErrorMsg[OAuthServer](rw, api.ErrNoError, http.StatusInternalServerError, "Access token missing")
|
||||
return
|
||||
}
|
||||
|
||||
var in OAuthFlowInput
|
||||
err := json.NewDecoder(req.Body).Decode(&in)
|
||||
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Failed to decode JSON input") {
|
||||
return
|
||||
}
|
||||
var in OAuthFlowInput
|
||||
err := json.NewDecoder(req.Body).Decode(&in)
|
||||
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Failed to decode JSON input") {
|
||||
return
|
||||
}
|
||||
|
||||
// Verify as access token
|
||||
_, b, err := mjwt.ExtractClaims[auth.AccessTokenClaims](o.signer, token)
|
||||
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusUnauthorized, "Token Not Valid") {
|
||||
return
|
||||
}
|
||||
// Verify as access token
|
||||
_, b, err := mjwt.ExtractClaims[auth.AccessTokenClaims](o.signer, token)
|
||||
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusUnauthorized, "Token Not Valid") {
|
||||
return
|
||||
}
|
||||
|
||||
if in.ResponseType == "code" {
|
||||
count, err := o.db.Where("app_id = ?", in.ClientId).Count(&oauth.App{})
|
||||
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Invalid Client") {
|
||||
return
|
||||
}
|
||||
if count != 1 {
|
||||
api.GenericErrorMsg[OAuthServer](rw, api.ErrNoError, http.StatusBadRequest, "Invalid Client")
|
||||
return
|
||||
}
|
||||
if in.ResponseType == "code" {
|
||||
count, err := o.db.Where("app_id = ?", in.ClientId).Count(&oauth.App{})
|
||||
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Invalid Client") {
|
||||
return
|
||||
}
|
||||
if count != 1 {
|
||||
api.GenericErrorMsg[OAuthServer](rw, api.ErrNoError, http.StatusBadRequest, "Invalid Client")
|
||||
return
|
||||
}
|
||||
|
||||
redirect, err := url.Parse(in.RedirectUrl)
|
||||
if api.GenericErrorMsg[OAuthServer](rw, err, 419, "Invalid Redirect URL") {
|
||||
return
|
||||
}
|
||||
appUrl := fmt.Sprintf("%s://%s", redirect.Scheme, redirect.Host)
|
||||
n, err := o.db.Where("app_id = ? and domain = ?", in.ClientId, appUrl).Count(&oauth.Domain{})
|
||||
if api.GenericErrorMsg[OAuthServer](rw, err, 419, "Invalid Redirect URL") {
|
||||
return
|
||||
}
|
||||
if n < 1 {
|
||||
api.GenericErrorMsg[OAuthServer](rw, api.ErrNoError, 419, "Invalid Redirect URL")
|
||||
return
|
||||
}
|
||||
redirect, err := url.Parse(in.RedirectUrl)
|
||||
if api.GenericErrorMsg[OAuthServer](rw, err, 419, "Invalid Redirect URL") {
|
||||
return
|
||||
}
|
||||
appUrl := fmt.Sprintf("%s://%s", redirect.Scheme, redirect.Host)
|
||||
n, err := o.db.Where("app_id = ? and domain = ?", in.ClientId, appUrl).Count(&oauth.Domain{})
|
||||
if api.GenericErrorMsg[OAuthServer](rw, err, 419, "Invalid Redirect URL") {
|
||||
return
|
||||
}
|
||||
if n < 1 {
|
||||
api.GenericErrorMsg[OAuthServer](rw, api.ErrNoError, 419, "Invalid Redirect URL")
|
||||
return
|
||||
}
|
||||
|
||||
flowToken, err := flow.CreateOAuthFlowToken(o.signer, fmt.Sprint(in.ClientId), uuid.NewString(), b.Claims.UserId, in.Scope, time.Minute*10)
|
||||
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Failed to sign token") {
|
||||
return
|
||||
}
|
||||
flowToken, err := flow.CreateOAuthFlowToken(o.signer, fmt.Sprint(in.ClientId), uuid.NewString(), b.Claims.UserId, in.Scope, time.Minute*10)
|
||||
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Failed to sign token") {
|
||||
return
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
rw.WriteHeader(http.StatusAccepted)
|
||||
err = json.NewEncoder(rw).Encode(OAuthFlowOutput{Code: flowToken})
|
||||
api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Failed to encode JSON output")
|
||||
} else {
|
||||
api.GenericErrorMsg[OAuthServer](rw, api.ErrNoError, http.StatusBadRequest, "Invalid response type")
|
||||
}
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
rw.WriteHeader(http.StatusAccepted)
|
||||
err = json.NewEncoder(rw).Encode(OAuthFlowOutput{Code: flowToken})
|
||||
api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Failed to encode JSON output")
|
||||
} else {
|
||||
api.GenericErrorMsg[OAuthServer](rw, api.ErrNoError, http.StatusBadRequest, "Invalid response type")
|
||||
}
|
||||
}
|
||||
|
||||
func (o *OAuthServer) handleOAuthToken(rw http.ResponseWriter, req *http.Request) {
|
||||
err := req.ParseForm()
|
||||
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Invalid POST request form") {
|
||||
return
|
||||
}
|
||||
err := req.ParseForm()
|
||||
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Invalid POST request form") {
|
||||
return
|
||||
}
|
||||
|
||||
cId, cSecret, err := ClientAuthHandler(req)
|
||||
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusUnauthorized, "Unauthorized") {
|
||||
return
|
||||
}
|
||||
count, err := o.db.Where("app_id = ? and secret = ?", cId, cSecret).Count(&oauth.App{})
|
||||
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Invalid Client") {
|
||||
return
|
||||
}
|
||||
if count != 1 {
|
||||
api.GenericErrorMsg[OAuthServer](rw, api.ErrNoError, http.StatusBadRequest, "Invalid Client")
|
||||
return
|
||||
}
|
||||
cId, cSecret, err := ClientAuthHandler(req)
|
||||
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusUnauthorized, "Unauthorized") {
|
||||
return
|
||||
}
|
||||
count, err := o.db.Where("app_id = ? and secret = ?", cId, cSecret).Count(&oauth.App{})
|
||||
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Invalid Client") {
|
||||
return
|
||||
}
|
||||
if count != 1 {
|
||||
api.GenericErrorMsg[OAuthServer](rw, api.ErrNoError, http.StatusBadRequest, "Invalid Client")
|
||||
return
|
||||
}
|
||||
|
||||
form := req.Form
|
||||
if form.Get("grant_type") == "authorization_code" {
|
||||
code := form.Get("code")
|
||||
form := req.Form
|
||||
if form.Get("grant_type") == "authorization_code" {
|
||||
code := form.Get("code")
|
||||
|
||||
// Verify as access token
|
||||
_, b, err := mjwt.ExtractClaims[flow.OAuthFlowClaims](o.signer, code)
|
||||
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusUnauthorized, "Token Not Valid") {
|
||||
return
|
||||
}
|
||||
// Verify as access token
|
||||
_, b, err := mjwt.ExtractClaims[flow.OAuthFlowClaims](o.signer, code)
|
||||
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusUnauthorized, "Token Not Valid") {
|
||||
return
|
||||
}
|
||||
|
||||
var u user.User
|
||||
ok, err := o.db.Where("id = ?", b.Claims.LoginId).Get(&u)
|
||||
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Invalid User") {
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Invalid User")
|
||||
return
|
||||
}
|
||||
var u user.User
|
||||
ok, err := o.db.Where("id = ?", b.Claims.LoginId).Get(&u)
|
||||
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Invalid User") {
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Invalid User")
|
||||
return
|
||||
}
|
||||
|
||||
token, err := auth.CreateAccessToken(o.signer, cId, fmt.Sprint(u.Id), u.Id, b.Claims.Scopes)
|
||||
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusInternalServerError, "Token Signing Error") {
|
||||
return
|
||||
}
|
||||
token, err := auth.CreateAccessToken(o.signer, cId, fmt.Sprint(u.Id), u.Id, b.Claims.Scopes)
|
||||
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusInternalServerError, "Token Signing Error") {
|
||||
return
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
rw.WriteHeader(http.StatusAccepted)
|
||||
err = json.NewEncoder(rw).Encode(OAuthTokenOutput{
|
||||
AccessToken: token,
|
||||
TokenType: "Bearer",
|
||||
ExpiresIn: uint64((time.Minute * 15).Seconds()),
|
||||
})
|
||||
api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Failed to encode JSON output")
|
||||
} else {
|
||||
api.GenericErrorMsg[OAuthServer](rw, api.ErrNoError, http.StatusBadRequest, "Invalid grant type")
|
||||
}
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
rw.WriteHeader(http.StatusAccepted)
|
||||
err = json.NewEncoder(rw).Encode(OAuthTokenOutput{
|
||||
AccessToken: token,
|
||||
TokenType: "Bearer",
|
||||
ExpiresIn: uint64((time.Minute * 15).Seconds()),
|
||||
})
|
||||
api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Failed to encode JSON output")
|
||||
} else {
|
||||
api.GenericErrorMsg[OAuthServer](rw, api.ErrNoError, http.StatusBadRequest, "Invalid grant type")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,175 +1,175 @@
|
|||
package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"golang.org/x/net/proxy"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"path"
|
||||
"sync"
|
||||
"time"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"golang.org/x/net/proxy"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"path"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type reverseProxyHostKey int
|
||||
|
||||
type ReverseProxyHostVar interface {
|
||||
TargetHost() string
|
||||
TargetPort() uint16
|
||||
TargetPath() string
|
||||
IsSecureMode() bool
|
||||
IsIgnoreCert() bool
|
||||
TargetHost() string
|
||||
TargetPort() uint16
|
||||
TargetPath() string
|
||||
IsSecureMode() bool
|
||||
IsIgnoreCert() bool
|
||||
}
|
||||
|
||||
type ReverseProxyHeaderVar interface {
|
||||
HeaderKey() string
|
||||
HeaderValue() string
|
||||
HeaderKey() string
|
||||
HeaderValue() string
|
||||
}
|
||||
|
||||
func SetReverseProxyHost[T ReverseProxyHeaderVar](req *http.Request, hf ReverseProxyHostVar, header []T) *http.Request {
|
||||
if header == nil {
|
||||
header = make([]T, 0)
|
||||
}
|
||||
ctx := req.Context()
|
||||
ctx2 := context.WithValue(ctx, reverseProxyHostKey(0), hf)
|
||||
ctx3 := context.WithValue(ctx2, reverseProxyHostKey(1), header)
|
||||
return req.WithContext(ctx3)
|
||||
if header == nil {
|
||||
header = make([]T, 0)
|
||||
}
|
||||
ctx := req.Context()
|
||||
ctx2 := context.WithValue(ctx, reverseProxyHostKey(0), hf)
|
||||
ctx3 := context.WithValue(ctx2, reverseProxyHostKey(1), header)
|
||||
return req.WithContext(ctx3)
|
||||
}
|
||||
|
||||
func CreateHybridReverseProxy() *httputil.ReverseProxy {
|
||||
return &httputil.ReverseProxy{
|
||||
Director: func(req *http.Request) {},
|
||||
Transport: NewHybridTransport(),
|
||||
ModifyResponse: func(rw *http.Response) error { return nil },
|
||||
ErrorHandler: func(rw http.ResponseWriter, req *http.Request, err error) {
|
||||
log.Printf("[ReverseProxy] Request: %#v\n -- Error: %s\n", req, err)
|
||||
rw.WriteHeader(http.StatusBadGateway)
|
||||
_, _ = rw.Write([]byte("502 Bad gateway\n"))
|
||||
},
|
||||
}
|
||||
return &httputil.ReverseProxy{
|
||||
Director: func(req *http.Request) {},
|
||||
Transport: NewHybridTransport(),
|
||||
ModifyResponse: func(rw *http.Response) error { return nil },
|
||||
ErrorHandler: func(rw http.ResponseWriter, req *http.Request, err error) {
|
||||
log.Printf("[ReverseProxy] Request: %#v\n -- Error: %s\n", req, err)
|
||||
rw.WriteHeader(http.StatusBadGateway)
|
||||
_, _ = rw.Write([]byte("502 Bad gateway\n"))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type HybridTransport struct {
|
||||
baseDialer *net.Dialer
|
||||
normalTransport http.RoundTripper
|
||||
insecureTransport http.RoundTripper
|
||||
socksSync *sync.RWMutex
|
||||
socksTransport map[string]http.RoundTripper
|
||||
baseDialer *net.Dialer
|
||||
normalTransport http.RoundTripper
|
||||
insecureTransport http.RoundTripper
|
||||
socksSync *sync.RWMutex
|
||||
socksTransport map[string]http.RoundTripper
|
||||
}
|
||||
|
||||
func NewHybridTransport() *HybridTransport {
|
||||
h := &HybridTransport{
|
||||
baseDialer: &net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
},
|
||||
socksSync: &sync.RWMutex{},
|
||||
socksTransport: make(map[string]http.RoundTripper),
|
||||
}
|
||||
h.normalTransport = &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: h.baseDialer.DialContext,
|
||||
ForceAttemptHTTP2: true,
|
||||
MaxIdleConns: 15,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
IdleConnTimeout: 30 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
ResponseHeaderTimeout: 10 * time.Second,
|
||||
}
|
||||
h.insecureTransport = &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: h.baseDialer.DialContext,
|
||||
ForceAttemptHTTP2: true,
|
||||
MaxIdleConns: 15,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
IdleConnTimeout: 30 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
ResponseHeaderTimeout: 10 * time.Second,
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
return h
|
||||
h := &HybridTransport{
|
||||
baseDialer: &net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
},
|
||||
socksSync: &sync.RWMutex{},
|
||||
socksTransport: make(map[string]http.RoundTripper),
|
||||
}
|
||||
h.normalTransport = &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: h.baseDialer.DialContext,
|
||||
ForceAttemptHTTP2: true,
|
||||
MaxIdleConns: 15,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
IdleConnTimeout: 30 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
ResponseHeaderTimeout: 10 * time.Second,
|
||||
}
|
||||
h.insecureTransport = &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: h.baseDialer.DialContext,
|
||||
ForceAttemptHTTP2: true,
|
||||
MaxIdleConns: 15,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
IdleConnTimeout: 30 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
ResponseHeaderTimeout: 10 * time.Second,
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *HybridTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
newHost := req.Context().Value(reverseProxyHostKey(0))
|
||||
hf, ok := newHost.(ReverseProxyHostVar)
|
||||
if !ok {
|
||||
return nil, errors.New("failed to detect reverse proxy configuration")
|
||||
}
|
||||
newHost := req.Context().Value(reverseProxyHostKey(0))
|
||||
hf, ok := newHost.(ReverseProxyHostVar)
|
||||
if !ok {
|
||||
return nil, errors.New("failed to detect reverse proxy configuration")
|
||||
}
|
||||
|
||||
newHead := req.Context().Value(reverseProxyHostKey(1))
|
||||
headers, ok := newHead.([]ReverseProxyHeaderVar)
|
||||
if !ok {
|
||||
return nil, errors.New("failed to detect reverse proxy header configuration")
|
||||
}
|
||||
newHead := req.Context().Value(reverseProxyHostKey(1))
|
||||
headers, ok := newHead.([]ReverseProxyHeaderVar)
|
||||
if !ok {
|
||||
return nil, errors.New("failed to detect reverse proxy header configuration")
|
||||
}
|
||||
|
||||
req.URL.Scheme = "http"
|
||||
if hf.IsSecureMode() {
|
||||
req.URL.Scheme = "https"
|
||||
}
|
||||
req.URL.Host = fmt.Sprintf("%s:%d", hf.TargetHost(), hf.TargetPort())
|
||||
if hf.TargetPath() != "" {
|
||||
req.URL.Path = "/" + path.Join(hf.TargetPath(), req.URL.Path)
|
||||
}
|
||||
req.URL.Scheme = "http"
|
||||
if hf.IsSecureMode() {
|
||||
req.URL.Scheme = "https"
|
||||
}
|
||||
req.URL.Host = fmt.Sprintf("%s:%d", hf.TargetHost(), hf.TargetPort())
|
||||
if hf.TargetPath() != "" {
|
||||
req.URL.Path = "/" + path.Join(hf.TargetPath(), req.URL.Path)
|
||||
}
|
||||
|
||||
// Do a round trip using existing transports
|
||||
var trip *http.Response
|
||||
var err error
|
||||
if hf.IsIgnoreCert() {
|
||||
trip, err = h.insecureTransport.RoundTrip(req)
|
||||
} else {
|
||||
trip, err = h.normalTransport.RoundTrip(req)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Do a round trip using existing transports
|
||||
var trip *http.Response
|
||||
var err error
|
||||
if hf.IsIgnoreCert() {
|
||||
trip, err = h.insecureTransport.RoundTrip(req)
|
||||
} else {
|
||||
trip, err = h.normalTransport.RoundTrip(req)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Override headers
|
||||
for _, i := range headers {
|
||||
trip.Header.Set(i.HeaderKey(), i.HeaderValue())
|
||||
}
|
||||
return trip, nil
|
||||
// Override headers
|
||||
for _, i := range headers {
|
||||
trip.Header.Set(i.HeaderKey(), i.HeaderValue())
|
||||
}
|
||||
return trip, nil
|
||||
}
|
||||
|
||||
func (h *HybridTransport) getSocksProxy(addr string, insecure bool) (http.RoundTripper, error) {
|
||||
if insecure {
|
||||
addr = "%i-" + addr
|
||||
}
|
||||
h.socksSync.RLock()
|
||||
s, ok := h.socksTransport[addr]
|
||||
h.socksSync.RUnlock()
|
||||
if ok {
|
||||
return s, nil
|
||||
}
|
||||
if insecure {
|
||||
addr = "%i-" + addr
|
||||
}
|
||||
h.socksSync.RLock()
|
||||
s, ok := h.socksTransport[addr]
|
||||
h.socksSync.RUnlock()
|
||||
if ok {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
dialer, err := proxy.SOCKS5("tcp", addr, nil, proxy.Direct)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot connect to the proxy: %s", err)
|
||||
}
|
||||
dialer, err := proxy.SOCKS5("tcp", addr, nil, proxy.Direct)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot connect to the proxy: %s", err)
|
||||
}
|
||||
|
||||
if f, ok := dialer.(proxy.ContextDialer); ok {
|
||||
t := &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: f.DialContext,
|
||||
ForceAttemptHTTP2: true,
|
||||
MaxIdleConns: 15,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
IdleConnTimeout: 30 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
ResponseHeaderTimeout: 10 * time.Second,
|
||||
DisableKeepAlives: true,
|
||||
}
|
||||
if insecure {
|
||||
t.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
}
|
||||
h.socksSync.Lock()
|
||||
h.socksTransport[addr] = t
|
||||
h.socksSync.Unlock()
|
||||
return t, nil
|
||||
}
|
||||
return nil, errors.New("cannot create socks5 dialer")
|
||||
if f, ok := dialer.(proxy.ContextDialer); ok {
|
||||
t := &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: f.DialContext,
|
||||
ForceAttemptHTTP2: true,
|
||||
MaxIdleConns: 15,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
IdleConnTimeout: 30 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
ResponseHeaderTimeout: 10 * time.Second,
|
||||
DisableKeepAlives: true,
|
||||
}
|
||||
if insecure {
|
||||
t.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
}
|
||||
h.socksSync.Lock()
|
||||
h.socksTransport[addr] = t
|
||||
h.socksSync.Unlock()
|
||||
return t, nil
|
||||
}
|
||||
return nil, errors.New("cannot create socks5 dialer")
|
||||
}
|
||||
|
|
|
@ -1,39 +1,39 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"code.mrmelon54.com/melon/summer/pkg/claims"
|
||||
"code.mrmelon54.com/melon/summer/pkg/utils"
|
||||
"code.mrmelon54.com/melon/summer/pkg/claims"
|
||||
"code.mrmelon54.com/melon/summer/pkg/utils"
|
||||
)
|
||||
|
||||
type Perm struct {
|
||||
Id uint64 `json:"id" xorm:"pk autoincr"`
|
||||
Key string `json:"key"`
|
||||
Name string `json:"name"`
|
||||
Desc string `json:"desc"`
|
||||
Visible *bool `json:"visible"`
|
||||
Enabled *bool `json:"enabled"`
|
||||
IsPerm *bool `json:"isPerm"`
|
||||
IsScope *bool `json:"isScope"`
|
||||
Id uint64 `json:"id" xorm:"pk autoincr"`
|
||||
Key string `json:"key"`
|
||||
Name string `json:"name"`
|
||||
Desc string `json:"desc"`
|
||||
Visible *bool `json:"visible"`
|
||||
Enabled *bool `json:"enabled"`
|
||||
IsPerm *bool `json:"isPerm"`
|
||||
IsScope *bool `json:"isScope"`
|
||||
}
|
||||
|
||||
func (p Perm) GetId() uint64 {
|
||||
return p.Id
|
||||
return p.Id
|
||||
}
|
||||
|
||||
func (p Perm) SetEnabled(v bool) Perm {
|
||||
p.Enabled = utils.PBool(v)
|
||||
return p
|
||||
p.Enabled = utils.PBool(v)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p Perm) ClearForNew() Perm {
|
||||
p.Id = 0
|
||||
return p
|
||||
p.Id = 0
|
||||
return p
|
||||
}
|
||||
|
||||
func CheckSpecificPerms(storage *claims.PermStorage, v []string) []bool {
|
||||
z := make([]bool, len(v))
|
||||
for i := range v {
|
||||
z[i] = storage.Has(v[i])
|
||||
}
|
||||
return z
|
||||
z := make([]bool, len(v))
|
||||
for i := range v {
|
||||
z[i] = storage.Has(v[i])
|
||||
}
|
||||
return z
|
||||
}
|
||||
|
|
|
@ -1,132 +1,132 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"code.mrmelon54.com/melon/summer/pkg/utils"
|
||||
"github.com/sec51/twofactor"
|
||||
"time"
|
||||
"code.mrmelon54.com/melon/summer/pkg/utils"
|
||||
"github.com/sec51/twofactor"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ProfileVisibility byte
|
||||
|
||||
const (
|
||||
ProfileVisibilityPrivate = ProfileVisibility(iota)
|
||||
ProfileVisibilityFriends
|
||||
ProfileVisibilityPublic
|
||||
ProfileVisibilityPrivate = ProfileVisibility(iota)
|
||||
ProfileVisibilityFriends
|
||||
ProfileVisibilityPublic
|
||||
)
|
||||
|
||||
func ParseVisibility(a *byte) ProfileVisibility {
|
||||
b := ProfileVisibility(utils.SByte(a))
|
||||
if b > ProfileVisibilityPublic {
|
||||
return ProfileVisibilityPrivate
|
||||
}
|
||||
return b
|
||||
b := ProfileVisibility(utils.SByte(a))
|
||||
if b > ProfileVisibilityPublic {
|
||||
return ProfileVisibilityPrivate
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (v ProfileVisibility) AsByte() *byte {
|
||||
return utils.PByte(byte(v))
|
||||
return utils.PByte(byte(v))
|
||||
}
|
||||
|
||||
type ProfilePronouns byte
|
||||
|
||||
const (
|
||||
ProfilePronounsUnknown = ProfilePronouns(iota)
|
||||
ProfilePronounsHeHim
|
||||
ProfilePronounsSheHer
|
||||
ProfilePronounsTheyThem
|
||||
ProfilePronounsUnknown = ProfilePronouns(iota)
|
||||
ProfilePronounsHeHim
|
||||
ProfilePronounsSheHer
|
||||
ProfilePronounsTheyThem
|
||||
)
|
||||
|
||||
func ParsePronouns(a *byte) ProfilePronouns {
|
||||
b := ProfilePronouns(utils.SByte(a))
|
||||
if b > ProfilePronounsTheyThem {
|
||||
return ProfilePronounsUnknown
|
||||
}
|
||||
return b
|
||||
b := ProfilePronouns(utils.SByte(a))
|
||||
if b > ProfilePronounsTheyThem {
|
||||
return ProfilePronounsUnknown
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (p ProfilePronouns) AsByte() *byte {
|
||||
return utils.PByte(byte(p))
|
||||
return utils.PByte(byte(p))
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Id uint64 `json:"id" xorm:"pk autoincr"`
|
||||
Email string `json:"email"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"-"`
|
||||
Visibility *byte `json:"visibility" xorm:"unsigned tinyint(3)"`
|
||||
Gender *string `json:"gender"`
|
||||
Pronouns *byte `json:"pronouns" xorm:"unsigned tinyint(3)"`
|
||||
Birthday time.Time `json:"birthday"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Created time.Time `json:"created"`
|
||||
LastOnline time.Time `json:"last_online"`
|
||||
Icon string `json:"icon"`
|
||||
MfaActive *bool `json:"-"`
|
||||
MfaBytes []byte `json:"-"`
|
||||
MfaObj *twofactor.Totp `json:"-" xorm:"-"`
|
||||
EmailVerified *bool `json:"email_verified"`
|
||||
EmailToken string `json:"-"`
|
||||
EnableRefresh *bool `json:"-"`
|
||||
Enabled *bool `json:"enabled"`
|
||||
Id uint64 `json:"id" xorm:"pk autoincr"`
|
||||
Email string `json:"email"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"-"`
|
||||
Visibility *byte `json:"visibility" xorm:"unsigned tinyint(3)"`
|
||||
Gender *string `json:"gender"`
|
||||
Pronouns *byte `json:"pronouns" xorm:"unsigned tinyint(3)"`
|
||||
Birthday time.Time `json:"birthday"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Created time.Time `json:"created"`
|
||||
LastOnline time.Time `json:"last_online"`
|
||||
Icon string `json:"icon"`
|
||||
MfaActive *bool `json:"-"`
|
||||
MfaBytes []byte `json:"-"`
|
||||
MfaObj *twofactor.Totp `json:"-" xorm:"-"`
|
||||
EmailVerified *bool `json:"email_verified"`
|
||||
EmailToken string `json:"-"`
|
||||
EnableRefresh *bool `json:"-"`
|
||||
Enabled *bool `json:"enabled"`
|
||||
}
|
||||
|
||||
func (u User) GetId() uint64 {
|
||||
return u.Id
|
||||
return u.Id
|
||||
}
|
||||
|
||||
func (u User) SetEnabled(v bool) User {
|
||||
u.Enabled = utils.PBool(v)
|
||||
return u
|
||||
u.Enabled = utils.PBool(v)
|
||||
return u
|
||||
}
|
||||
|
||||
func (u User) ClearForNew() User {
|
||||
u.Id = 0
|
||||
return u
|
||||
u.Id = 0
|
||||
return u
|
||||
}
|
||||
|
||||
func (u User) ClearForPut() User {
|
||||
return User{
|
||||
Id: 0,
|
||||
Email: "",
|
||||
Username: "",
|
||||
Password: "",
|
||||
Visibility: u.Visibility,
|
||||
Gender: u.Gender,
|
||||
Pronouns: u.Pronouns,
|
||||
Birthday: u.Birthday,
|
||||
DisplayName: u.DisplayName,
|
||||
Created: time.Time{},
|
||||
LastOnline: time.Time{},
|
||||
Icon: u.Icon,
|
||||
MfaActive: nil,
|
||||
MfaBytes: nil,
|
||||
MfaObj: nil,
|
||||
EmailVerified: nil,
|
||||
EmailToken: "",
|
||||
EnableRefresh: nil,
|
||||
Enabled: nil,
|
||||
}
|
||||
return User{
|
||||
Id: 0,
|
||||
Email: "",
|
||||
Username: "",
|
||||
Password: "",
|
||||
Visibility: u.Visibility,
|
||||
Gender: u.Gender,
|
||||
Pronouns: u.Pronouns,
|
||||
Birthday: u.Birthday,
|
||||
DisplayName: u.DisplayName,
|
||||
Created: time.Time{},
|
||||
LastOnline: time.Time{},
|
||||
Icon: u.Icon,
|
||||
MfaActive: nil,
|
||||
MfaBytes: nil,
|
||||
MfaObj: nil,
|
||||
EmailVerified: nil,
|
||||
EmailToken: "",
|
||||
EnableRefresh: nil,
|
||||
Enabled: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func (u User) ClearForAdminPut() User {
|
||||
return User{
|
||||
Id: 0,
|
||||
Email: "",
|
||||
Username: "",
|
||||
Password: "",
|
||||
Visibility: nil,
|
||||
Gender: nil,
|
||||
Pronouns: nil,
|
||||
Birthday: time.Time{},
|
||||
DisplayName: "",
|
||||
Created: time.Time{},
|
||||
LastOnline: time.Time{},
|
||||
Icon: "",
|
||||
MfaActive: nil,
|
||||
MfaBytes: nil,
|
||||
MfaObj: nil,
|
||||
EmailVerified: nil,
|
||||
EmailToken: "",
|
||||
EnableRefresh: u.EnableRefresh,
|
||||
Enabled: u.Enabled,
|
||||
}
|
||||
return User{
|
||||
Id: 0,
|
||||
Email: "",
|
||||
Username: "",
|
||||
Password: "",
|
||||
Visibility: nil,
|
||||
Gender: nil,
|
||||
Pronouns: nil,
|
||||
Birthday: time.Time{},
|
||||
DisplayName: "",
|
||||
Created: time.Time{},
|
||||
LastOnline: time.Time{},
|
||||
Icon: "",
|
||||
MfaActive: nil,
|
||||
MfaBytes: nil,
|
||||
MfaObj: nil,
|
||||
EmailVerified: nil,
|
||||
EmailToken: "",
|
||||
EnableRefresh: u.EnableRefresh,
|
||||
Enabled: u.Enabled,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,52 +3,52 @@ package web
|
|||
import "code.mrmelon54.com/melon/summer/pkg/utils"
|
||||
|
||||
type ApiRoute struct {
|
||||
Id uint64 `json:"id" xorm:"pk autoincr"`
|
||||
Route string `json:"route"`
|
||||
Version uint64 `json:"version"`
|
||||
DstHost string `json:"dst_host"`
|
||||
DstPort uint16 `json:"dst_port" xorm:"int(10) unsigned"`
|
||||
SecureMode *bool `json:"secure_mode,omitempty"`
|
||||
IgnoreCert *bool `json:"ignore_cert,omitempty"`
|
||||
Enabled *bool `json:"enabled"`
|
||||
Id uint64 `json:"id" xorm:"pk autoincr"`
|
||||
Route string `json:"route"`
|
||||
Version uint64 `json:"version"`
|
||||
DstHost string `json:"dst_host"`
|
||||
DstPort uint16 `json:"dst_port" xorm:"int(10) unsigned"`
|
||||
SecureMode *bool `json:"secure_mode,omitempty"`
|
||||
IgnoreCert *bool `json:"ignore_cert,omitempty"`
|
||||
Enabled *bool `json:"enabled"`
|
||||
}
|
||||
|
||||
func (a ApiRoute) GetId() uint64 {
|
||||
return a.Id
|
||||
return a.Id
|
||||
}
|
||||
|
||||
func (a ApiRoute) SetEnabled(v bool) ApiRoute {
|
||||
a.Enabled = utils.PBool(v)
|
||||
return a
|
||||
a.Enabled = utils.PBool(v)
|
||||
return a
|
||||
}
|
||||
|
||||
func (a ApiRoute) ClearForNew() ApiRoute {
|
||||
a.Id = 0
|
||||
return a
|
||||
a.Id = 0
|
||||
return a
|
||||
}
|
||||
|
||||
func (a ApiRoute) TargetHost() string {
|
||||
return a.DstHost
|
||||
return a.DstHost
|
||||
}
|
||||
|
||||
func (a ApiRoute) TargetPort() uint16 {
|
||||
return a.DstPort
|
||||
return a.DstPort
|
||||
}
|
||||
|
||||
func (a ApiRoute) TargetPath() string {
|
||||
return ""
|
||||
return ""
|
||||
}
|
||||
|
||||
func (a ApiRoute) IsSecureMode() bool {
|
||||
if a.SecureMode == nil {
|
||||
return false
|
||||
}
|
||||
return *a.SecureMode
|
||||
if a.SecureMode == nil {
|
||||
return false
|
||||
}
|
||||
return *a.SecureMode
|
||||
}
|
||||
|
||||
func (a ApiRoute) IsIgnoreCert() bool {
|
||||
if a.IgnoreCert == nil {
|
||||
return false
|
||||
}
|
||||
return *a.IgnoreCert
|
||||
if a.IgnoreCert == nil {
|
||||
return false
|
||||
}
|
||||
return *a.IgnoreCert
|
||||
}
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
package web
|
||||
|
||||
type HttpHeader struct {
|
||||
Id uint64 `json:"id" xorm:"pk autoincr"`
|
||||
ServiceId uint64 `json:"service_id"`
|
||||
Key string `json:"key"`
|
||||
Value *string `json:"value"`
|
||||
Id uint64 `json:"id" xorm:"pk autoincr"`
|
||||
ServiceId uint64 `json:"service_id"`
|
||||
Key string `json:"key"`
|
||||
Value *string `json:"value"`
|
||||
}
|
||||
|
||||
func (h HttpHeader) HeaderKey() string {
|
||||
return h.Key
|
||||
return h.Key
|
||||
}
|
||||
|
||||
func (h HttpHeader) HeaderValue() string {
|
||||
if h.Value == nil {
|
||||
return ""
|
||||
}
|
||||
return *h.Value
|
||||
if h.Value == nil {
|
||||
return ""
|
||||
}
|
||||
return *h.Value
|
||||
}
|
||||
|
|
|
@ -3,51 +3,51 @@ package web
|
|||
import "code.mrmelon54.com/melon/summer/pkg/utils"
|
||||
|
||||
type HttpService struct {
|
||||
Id uint64 `json:"id" xorm:"pk autoincr"`
|
||||
Address string `json:"address"`
|
||||
DstHost string `json:"dst_host"`
|
||||
DstPort uint16 `json:"dst_port" xorm:"int(10) unsigned"`
|
||||
SecureMode *bool `json:"secure_mode,omitempty"`
|
||||
IgnoreCert *bool `json:"ignore_cert,omitempty"`
|
||||
Enabled *bool `json:"enabled"`
|
||||
Id uint64 `json:"id" xorm:"pk autoincr"`
|
||||
Address string `json:"address"`
|
||||
DstHost string `json:"dst_host"`
|
||||
DstPort uint16 `json:"dst_port" xorm:"int(10) unsigned"`
|
||||
SecureMode *bool `json:"secure_mode,omitempty"`
|
||||
IgnoreCert *bool `json:"ignore_cert,omitempty"`
|
||||
Enabled *bool `json:"enabled"`
|
||||
}
|
||||
|
||||
func (h HttpService) GetId() uint64 {
|
||||
return h.Id
|
||||
return h.Id
|
||||
}
|
||||
|
||||
func (h HttpService) SetEnabled(v bool) HttpService {
|
||||
h.Enabled = utils.PBool(v)
|
||||
return h
|
||||
h.Enabled = utils.PBool(v)
|
||||
return h
|
||||
}
|
||||
|
||||
func (h HttpService) ClearForNew() HttpService {
|
||||
h.Id = 0
|
||||
return h
|
||||
h.Id = 0
|
||||
return h
|
||||
}
|
||||
|
||||
func (h HttpService) TargetHost() string {
|
||||
return h.DstHost
|
||||
return h.DstHost
|
||||
}
|
||||
|
||||
func (h HttpService) TargetPort() uint16 {
|
||||
return h.DstPort
|
||||
return h.DstPort
|
||||
}
|
||||
|
||||
func (h HttpService) TargetPath() string {
|
||||
return ""
|
||||
return ""
|
||||
}
|
||||
|
||||
func (h HttpService) IsSecureMode() bool {
|
||||
if h.SecureMode == nil {
|
||||
return false
|
||||
}
|
||||
return *h.SecureMode
|
||||
if h.SecureMode == nil {
|
||||
return false
|
||||
}
|
||||
return *h.SecureMode
|
||||
}
|
||||
|
||||
func (h HttpService) IsIgnoreCert() bool {
|
||||
if h.IgnoreCert == nil {
|
||||
return false
|
||||
}
|
||||
return *h.IgnoreCert
|
||||
if h.IgnoreCert == nil {
|
||||
return false
|
||||
}
|
||||
return *h.IgnoreCert
|
||||
}
|
||||
|
|
Reference in New Issue