mirror of
https://codeberg.org/git-pages/git-pages.git
synced 2026-05-14 11:11:35 +00:00
Add support for a wildcard domain.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 == "" {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
37
src/serve.go
37
src/serve.go
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user