2023-04-21 03:21:46 +01:00
|
|
|
package servers
|
|
|
|
|
|
|
|
import (
|
2023-06-19 16:27:36 +01:00
|
|
|
"github.com/MrMelon54/mjwt"
|
|
|
|
"github.com/MrMelon54/mjwt/auth"
|
2023-04-21 03:21:46 +01:00
|
|
|
"github.com/MrMelon54/violet/utils"
|
|
|
|
"github.com/julienschmidt/httprouter"
|
|
|
|
"net/http"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
2023-04-21 15:49:01 +01:00
|
|
|
// NewApiServer creates and runs a http server containing all the API
|
|
|
|
// endpoints for the software
|
2023-04-21 03:21:46 +01:00
|
|
|
//
|
2023-04-21 15:49:01 +01:00
|
|
|
// `/compile` - reloads all domains, routes and redirects
|
2023-04-22 22:18:39 +01:00
|
|
|
func NewApiServer(conf *Conf, compileTarget utils.MultiCompilable) *http.Server {
|
2023-04-21 03:21:46 +01:00
|
|
|
r := httprouter.New()
|
|
|
|
|
2023-04-21 15:49:01 +01:00
|
|
|
// Endpoint for compile action
|
2023-04-21 03:21:46 +01:00
|
|
|
r.POST("/compile", func(rw http.ResponseWriter, req *http.Request, _ httprouter.Params) {
|
2023-06-04 22:28:48 +01:00
|
|
|
if !hasPerms(conf.Verify, req, "violet:compile") {
|
2023-04-21 03:21:46 +01:00
|
|
|
utils.RespondHttpStatus(rw, http.StatusForbidden)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-06-04 22:28:48 +01:00
|
|
|
// Trigger the compile action
|
|
|
|
compileTarget.Compile()
|
|
|
|
rw.WriteHeader(http.StatusAccepted)
|
|
|
|
})
|
|
|
|
|
2023-06-20 16:48:04 +01:00
|
|
|
// Endpoint for domains
|
|
|
|
r.PUT("/domain/:domain", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
|
|
|
|
if !hasPerms(conf.Verify, req, "violet:domains") {
|
|
|
|
utils.RespondHttpStatus(rw, http.StatusForbidden)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// add domain with active state
|
|
|
|
q := req.URL.Query()
|
|
|
|
conf.Domains.Put(params.ByName("domain"), q.Get("active") == "1")
|
2023-06-20 16:59:39 +01:00
|
|
|
conf.Domains.Compile()
|
2023-06-20 16:48:04 +01:00
|
|
|
})
|
|
|
|
r.DELETE("/domain/:domain", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
|
|
|
|
if !hasPerms(conf.Verify, req, "violet:domains") {
|
|
|
|
utils.RespondHttpStatus(rw, http.StatusForbidden)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// add domain with active state
|
|
|
|
q := req.URL.Query()
|
|
|
|
conf.Domains.Put(params.ByName("domain"), q.Get("active") == "1")
|
2023-06-20 16:59:39 +01:00
|
|
|
conf.Domains.Compile()
|
2023-06-20 16:48:04 +01:00
|
|
|
})
|
|
|
|
|
2023-06-04 22:28:48 +01:00
|
|
|
// Endpoint for acme-challenge
|
|
|
|
r.PUT("/acme-challenge/:domain/:key/:value", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
|
|
|
|
if !hasPerms(conf.Verify, req, "violet:acme-challenge") {
|
2023-04-21 03:21:46 +01:00
|
|
|
utils.RespondHttpStatus(rw, http.StatusForbidden)
|
|
|
|
return
|
|
|
|
}
|
2023-06-04 22:28:48 +01:00
|
|
|
domain := params.ByName("domain")
|
|
|
|
if !conf.Domains.IsValid(domain) {
|
|
|
|
utils.RespondVioletError(rw, http.StatusBadRequest, "Invalid ACME challenge domain")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
conf.Acme.Put(domain, params.ByName("key"), params.ByName("value"))
|
|
|
|
rw.WriteHeader(http.StatusAccepted)
|
|
|
|
})
|
|
|
|
r.DELETE("/acme-challenge/:domain/:key", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
|
|
|
|
if !hasPerms(conf.Verify, req, "violet:acme-challenge") {
|
2023-04-21 03:21:46 +01:00
|
|
|
utils.RespondHttpStatus(rw, http.StatusForbidden)
|
|
|
|
return
|
|
|
|
}
|
2023-06-04 22:28:48 +01:00
|
|
|
domain := params.ByName("domain")
|
|
|
|
if !conf.Domains.IsValid(domain) {
|
|
|
|
utils.RespondVioletError(rw, http.StatusBadRequest, "Invalid ACME challenge domain")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
conf.Acme.Delete(domain, params.ByName("key"))
|
2023-04-21 03:21:46 +01:00
|
|
|
rw.WriteHeader(http.StatusAccepted)
|
|
|
|
})
|
|
|
|
|
|
|
|
// Create and run http server
|
2023-06-04 22:28:48 +01:00
|
|
|
return &http.Server{
|
2023-04-24 01:35:23 +01:00
|
|
|
Addr: conf.ApiListen,
|
2023-04-21 03:21:46 +01:00
|
|
|
Handler: r,
|
|
|
|
ReadTimeout: time.Minute,
|
|
|
|
ReadHeaderTimeout: time.Minute,
|
|
|
|
WriteTimeout: time.Minute,
|
|
|
|
IdleTimeout: time.Minute,
|
|
|
|
MaxHeaderBytes: 2500,
|
|
|
|
}
|
2023-06-04 22:28:48 +01:00
|
|
|
}
|
|
|
|
|
2023-06-19 16:27:36 +01:00
|
|
|
func hasPerms(verify mjwt.Verifier, req *http.Request, perm string) bool {
|
2023-06-04 22:28:48 +01:00
|
|
|
// Get bearer token
|
|
|
|
bearer := utils.GetBearer(req)
|
|
|
|
if bearer == "" {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read claims from mjwt
|
|
|
|
_, b, err := mjwt.ExtractClaims[auth.AccessTokenClaims](verify, bearer)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Token must have perm
|
|
|
|
return b.Claims.Perms.Has(perm)
|
2023-04-21 03:21:46 +01:00
|
|
|
}
|