Add support for a wildcard domain.

This commit is contained in:
Catherine
2025-09-15 07:16:42 +00:00
parent 11145f407e
commit abaf6d993b
5 changed files with 46 additions and 13 deletions

View File

@@ -53,7 +53,11 @@ $ curl http://127.0.0.1:3333/ -H 'Host: codeberg.page'
Authorization
-------------
DNS is used for authorization of content updates for custom domain names. Whenever a `PUT` or `POST` request is received at `hostname.tld` that has an `Authorization: Pages <token>` header, the TXT record(s) at `_git-pages-challenge.hostname.tld` are compared with `sha256("hostname.tld <token>")`. If there is a match then updates from any clone URLs are allowed.
DNS is used for authorization of content updates.
- If a `[wildcard]` configuration section is specified, and if the suffix of a hostname in a `POST` request is equal to `[wildcard].domain`, then the request is authorized when and only when the repository URL in the event body matches the repository URL computed from the configuration file. Otherwise the next rule is used.
- If a `PUT` or `POST` request is received at `<hostname>` with an `Authorization: Pages <token>` header, then the request is authorized when any of the the TXT records at `_git-pages-challenge.<hostname>` are equal to `SHA256("<hostname> <token>")`.
Architecture

View File

@@ -3,3 +3,8 @@ data-dir = "./data"
[listen]
protocol = "tcp"
address = ":3333"
[wildcard]
domain = "codeberg.page"
clone-url = "https://codeberg.org/%s/%s.git"
index-repo = "%s.codeberg.page"

View File

@@ -9,7 +9,7 @@ import (
"strings"
)
func getHost(r *http.Request) string {
func GetHost(r *http.Request) string {
// FIXME: handle IDNA
host, _, err := net.SplitHostPort(r.Host)
if err != nil {
@@ -19,8 +19,8 @@ func getHost(r *http.Request) string {
return host
}
func authorize(w http.ResponseWriter, r *http.Request) error {
host := getHost(r)
func Authorize(w http.ResponseWriter, r *http.Request) error {
host := GetHost(r)
authorization := r.Header.Get("Authorization")
if authorization == "" {

View File

@@ -12,6 +12,11 @@ type Config struct {
Protocol string `toml:"protocol"`
Address string `toml:"address"`
} `toml:"listen"`
Wildcard struct {
Domain string `toml:"domain"`
CloneURL string `toml:"clone-url"`
IndexRepo string `toml:"index-repo"`
} `toml:"wildcard"`
}
func readConfig(path string, config *Config) error {

View File

@@ -10,6 +10,7 @@ import (
"net/http"
"os"
"path/filepath"
"slices"
"strings"
"time"
@@ -20,7 +21,7 @@ import (
const fetchTimeout = 30 * time.Second
func getPage(w http.ResponseWriter, r *http.Request) error {
host := getHost(r)
host := GetHost(r)
// if the first directory of the path exists under `www/$host`, use it as the root,
// else use `www/$host/.index`
@@ -104,9 +105,9 @@ func getProjectName(w http.ResponseWriter, r *http.Request) (string, error) {
}
func putPage(w http.ResponseWriter, r *http.Request) error {
host := getHost(r)
host := GetHost(r)
err := authorize(w, r)
err := Authorize(w, r)
if err != nil {
return err
}
@@ -156,18 +157,28 @@ func putPage(w http.ResponseWriter, r *http.Request) error {
}
func postPage(w http.ResponseWriter, r *http.Request) error {
host := getHost(r)
err := authorize(w, r)
if err != nil {
return err
}
host := GetHost(r)
hostParts := strings.Split(host, ".")
projectName, err := getProjectName(w, r)
if err != nil {
return err
}
allowRepoURL := ""
if slices.Equal(hostParts[1:], strings.Split(config.Wildcard.Domain, ".")) {
userName := hostParts[0]
repoName := projectName
if repoName == ".index" {
repoName = fmt.Sprintf(config.Wildcard.IndexRepo, userName)
}
allowRepoURL = fmt.Sprintf(config.Wildcard.CloneURL, userName, repoName)
} else {
if err := Authorize(w, r); err != nil {
return err
}
}
if r.Header.Get("Content-Type") != "application/json" {
http.Error(w, "only JSON payload is allowed", http.StatusBadRequest)
return fmt.Errorf("invalid content type")
@@ -197,7 +208,15 @@ func postPage(w http.ResponseWriter, r *http.Request) error {
}
webRoot := fmt.Sprintf("%s/%s", host, projectName)
repoURL := event["repository"].(map[string]any)["clone_url"].(string)
if allowRepoURL != "" && repoURL != allowRepoURL {
http.Error(w,
fmt.Sprintf("wildcard domain requires repository to be %s", allowRepoURL),
http.StatusUnauthorized,
)
return fmt.Errorf("invalid clone URL")
}
result := FetchWithTimeout(webRoot, repoURL, "pages", fetchTimeout)
switch result.outcome {