[mirror] Scalable static site server for Git forges (like GitHub Pages)

[breaking-change] Only allow a single `[[wildcard]].index-repo`.

The git-pages webhook security model depends on there being
a 1:1 mapping between site URLs and repositories; being able to
specify multiple of them breaks this model, as anyone could switch
the published site from one to the other if both repositories exist.

+34 -46
+1 -1
conf/config.example.toml
··· 12 12 [[wildcard]] # non-default section 13 13 domain = "codeberg.page" 14 14 clone-url = "https://codeberg.org/<user>/<project>.git" 15 - index-repos = ["<user>.codeberg.page", "pages"] 15 + index-repo = "pages" 16 16 index-repo-branch = "main" 17 17 authorization = "forgejo" 18 18
+14 -19
src/auth.go
··· 265 265 } 266 266 267 267 if userName, found := pattern.Matches(host); found { 268 - repoURLs, branch := pattern.ApplyTemplate(userName, projectName) 269 - return &Authorization{repoURLs, branch}, nil 268 + repoURL, branch := pattern.ApplyTemplate(userName, projectName) 269 + return &Authorization{[]string{repoURL}, branch}, nil 270 270 } else { 271 271 return nil, AuthError{ 272 272 http.StatusUnauthorized, ··· 632 632 } 633 633 634 634 if userName, found := pattern.Matches(host); found { 635 - repoURLs, branch := pattern.ApplyTemplate(userName, projectName) 636 - for _, repoURL := range repoURLs { 637 - parsedRepoURL, err := url.Parse(repoURL) 638 - if err != nil { 639 - panic(err) // misconfiguration 640 - } 635 + repoURL, branch := pattern.ApplyTemplate(userName, projectName) 636 + parsedRepoURL, err := url.Parse(repoURL) 637 + if err != nil { 638 + panic(err) // misconfiguration 639 + } 641 640 642 - if err = checkGogsRepositoryPushPermission(parsedRepoURL, authorization); err != nil { 643 - errs = append(errs, err) 644 - continue 645 - } 641 + if err = checkGogsRepositoryPushPermission(parsedRepoURL, authorization); err != nil { 642 + errs = append(errs, err) 643 + continue 644 + } 646 645 647 - // This will actually be ignored by the caller of AuthorizeUpdateFromArchive, 648 - // but we return this information as it makes sense to do contextually here. 649 - return &Authorization{ 650 - []string{repoURL}, 651 - branch, 652 - }, nil 653 - } 646 + // This will actually be ignored by the caller of AuthorizeUpdateFromArchive, 647 + // but we return this information as it makes sense to do contextually here. 648 + return &Authorization{[]string{repoURL}, branch}, nil 654 649 } 655 650 } 656 651
+5 -5
src/config.go
··· 79 79 } 80 80 81 81 type WildcardConfig struct { 82 - Domain string `toml:"domain"` 83 - CloneURL string `toml:"clone-url"` // URL template, not an exact URL 84 - IndexRepos []string `toml:"index-repos" default:"[]"` 85 - IndexRepoBranch string `toml:"index-repo-branch" default:"pages"` 86 - Authorization string `toml:"authorization"` 82 + Domain string `toml:"domain"` 83 + CloneURL string `toml:"clone-url"` // URL template, not an exact URL 84 + IndexRepo string `toml:"index-repo" default:"pages"` 85 + IndexRepoBranch string `toml:"index-repo-branch" default:"pages"` 86 + Authorization string `toml:"authorization"` 87 87 } 88 88 89 89 type FallbackConfig struct {
+14 -21
src/wildcard.go
··· 11 11 type WildcardPattern struct { 12 12 Domain []string 13 13 CloneURL *fasttemplate.Template 14 - IndexRepos []*fasttemplate.Template 14 + IndexRepo *fasttemplate.Template 15 15 IndexBranch string 16 16 Authorization bool 17 17 } ··· 49 49 return subdomain, true 50 50 } 51 51 52 - func (pattern *WildcardPattern) ApplyTemplate(userName string, projectName string) ([]string, string) { 53 - var repoURLs []string 52 + func (pattern *WildcardPattern) ApplyTemplate(userName string, projectName string) (string, string) { 53 + var repoURL string 54 54 var branch string 55 55 repoURLTemplate := pattern.CloneURL 56 56 if projectName == ".index" { 57 - for _, indexRepoTemplate := range pattern.IndexRepos { 58 - indexRepo := indexRepoTemplate.ExecuteString(map[string]any{"user": userName}) 59 - repoURLs = append(repoURLs, repoURLTemplate.ExecuteString(map[string]any{ 60 - "user": userName, 61 - "project": indexRepo, 62 - })) 63 - } 57 + repoURL = repoURLTemplate.ExecuteString(map[string]any{ 58 + "user": userName, 59 + "project": pattern.IndexRepo.ExecuteString(map[string]any{"user": userName}), 60 + }) 64 61 branch = pattern.IndexBranch 65 62 } else { 66 - repoURLs = append(repoURLs, repoURLTemplate.ExecuteString(map[string]any{ 63 + repoURL = repoURLTemplate.ExecuteString(map[string]any{ 67 64 "user": userName, 68 65 "project": projectName, 69 - })) 66 + }) 70 67 branch = "pages" 71 68 } 72 - return repoURLs, branch 69 + return repoURL, branch 73 70 } 74 71 75 72 func TranslateWildcards(configs []WildcardConfig) ([]*WildcardPattern, error) { ··· 80 77 return nil, fmt.Errorf("wildcard pattern: clone URL: %w", err) 81 78 } 82 79 83 - var indexRepoTemplates []*fasttemplate.Template 84 80 var indexRepoBranch string = config.IndexRepoBranch 85 - for _, indexRepo := range config.IndexRepos { 86 - indexRepoTemplate, err := fasttemplate.NewTemplate(indexRepo, "<", ">") 87 - if err != nil { 88 - return nil, fmt.Errorf("wildcard pattern: index repo: %w", err) 89 - } 90 - indexRepoTemplates = append(indexRepoTemplates, indexRepoTemplate) 81 + indexRepoTemplate, err := fasttemplate.NewTemplate(config.IndexRepo, "<", ">") 82 + if err != nil { 83 + return nil, fmt.Errorf("wildcard pattern: index repo: %w", err) 91 84 } 92 85 93 86 authorization := false ··· 107 100 wildcardPatterns = append(wildcardPatterns, &WildcardPattern{ 108 101 Domain: strings.Split(config.Domain, "."), 109 102 CloneURL: cloneURLTemplate, 110 - IndexRepos: indexRepoTemplates, 103 + IndexRepo: indexRepoTemplate, 111 104 IndexBranch: indexRepoBranch, 112 105 Authorization: authorization, 113 106 })