🌱🏠 instant least-authority port-forwarding (with automatic HTTPS) for anyone, anywhere! We **really** don't want your TLS private keys, you can keep them 😃 https://greenhouse.server.garden/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

115 lines
3.7 KiB

package main
import (
"crypto/sha256"
"encoding/json"
"fmt"
"log"
"net/http"
"regexp"
"strings"
)
func AddAPIRoutesToFrontend(app *FrontendApp) {
handleWithAPIToken(app, "/api/tenant_info", func(responseWriter http.ResponseWriter, request *http.Request, user Session) {
//log.Println("GET /api/tenant_info")
tenantInfo, err := app.Backend.GetTenantInfo(user.TenantId)
if err != nil {
app.unhandledError(responseWriter, request, err)
return
}
tenantInfoBytes, err := json.Marshal(tenantInfo)
if err != nil {
app.unhandledError(responseWriter, request, err)
return
}
responseWriter.Header().Set("Content-Type", "application/json")
responseWriter.Write(tenantInfoBytes)
})
handleWithAPIToken(app, "/api/client_config", func(responseWriter http.ResponseWriter, request *http.Request, user Session) {
if request.Method != "POST" {
responseWriter.Header().Add("Allow", "POST")
http.Error(responseWriter, "405 Method Not Allowed, try POST", http.StatusMethodNotAllowed)
return
}
newNodeId := strings.ToLower(request.URL.Query().Get("serverName"))
if newNodeId == "" {
http.Error(responseWriter, "404 Not Found, a server name must be provided, like /api/client_config?serverName=my_server", http.StatusNotFound)
return
}
subdomainRegex := regexp.MustCompile("^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$")
if !subdomainRegex.MatchString(newNodeId) {
http.Error(responseWriter, "400 Bad Request, the server name must be a valid subdomain. It should only contain letters, numbers, and dashes", http.StatusBadRequest)
return
}
// used to use fmt.Sprintf("%s.%s", tenant.Subdomain, freeSubdomainDomain) as the greenhouseDomain
// tenant, err := app.Model.GetTenant(user.TenantId)
// if err != nil {
// app.unhandledError(responseWriter, request, err)
// return
// }
clientConfig, err := app.Backend.ThresholdProvisioning.GetClientConfig(
user.TenantId, greenhouseExternalDomain, newNodeId, user.APIToken,
)
if err != nil {
app.unhandledError(responseWriter, request, err)
return
}
clientConfigBytes, err := json.MarshalIndent(clientConfig, "", " ")
if err != nil {
app.unhandledError(responseWriter, request, err)
return
}
responseWriter.Header().Set("Content-Type", "application/json")
responseWriter.Write(clientConfigBytes)
})
}
func handleWithAPIToken(app *FrontendApp, path string, handler func(http.ResponseWriter, *http.Request, Session)) {
app.Router.HandleFunc(path, func(responseWriter http.ResponseWriter, request *http.Request) {
authorizationHeader := request.Header.Get("Authorization")
apiToken := strings.TrimPrefix(authorizationHeader, "Bearer ")
hasCorrectPrefix := strings.HasPrefix(authorizationHeader, "Bearer")
if !hasCorrectPrefix || len(apiToken) < 10 {
log.Printf("authorizationHeader has invalid format: length=%d, hasCorrectPrefix=%t", len(apiToken), hasCorrectPrefix)
http.Error(responseWriter, "Unauthorized", http.StatusUnauthorized)
return
}
//log.Printf("<%s>\n", apiToken)
hashedAPIToken := sha256.Sum256([]byte(apiToken))
//log.Printf("h<%s>\n", fmt.Sprintf("%x", hashedAPIToken[:]))
user, err := app.Model.GetUserByAPIToken(fmt.Sprintf("%x", hashedAPIToken[:]))
if err != nil {
log.Printf("GetUserByAPIToken returned error: %+v", err)
http.Error(responseWriter, "Unauthorized", http.StatusUnauthorized)
return
}
if user == nil || user.TenantId == 0 || !user.EmailVerified {
http.Error(responseWriter, "Unauthorized", http.StatusUnauthorized)
return
}
user.APIToken = apiToken
handler(responseWriter, request, *user)
})
}
func splitUrlPath(path string) []string {
split := strings.Split(path, "/")
toReturn := []string{}
for _, s := range split {
if s != "" {
toReturn = append(toReturn, s)
}
}
return toReturn
}