internal/policy/names.go
70 lines · 2360 bytes
1// Package policy holds access-control and naming rules.
2package policy
3
4import (
5 "fmt"
6 "regexp"
7)
8
9// reservedNames are forbidden as usernames and org names because they are, or
10// will be, top-level web routes (the UI serves /<owner>/<name>). Any change to
11// the httpd mux's top-level routes must be reflected here; the httpd package
12// asserts this in its tests.
13var reservedNames = map[string]bool{
14 "admin": true,
15 "api": true,
16 "archive": true,
17 "explore": true,
18 "favicon.svg": true,
19 "gitbay": true, // vanity go-import path on gitbay.org
20 "gitbay-bot": true, // authors dependency-update issues
21 "healthz": true,
22 "login": true,
23 "logout": true,
24 "new": true,
25 "notifications": true,
26 "privacy": true,
27 "raw": true,
28 "register": true,
29 "search": true,
30 "settings": true,
31 "static": true,
32}
33
34// namePat matches valid user, org, and repo names: lowercase alphanumerics,
35// dot, dash, underscore; must start with an alphanumeric. Dots are further
36// restricted by ValidateName to avoid "." / ".." and ".git" suffixes.
37var namePat = regexp.MustCompile(`^[a-z0-9][a-z0-9._-]{0,62}$`)
38
39// ValidateOwnerName checks a username or org name.
40func ValidateOwnerName(name string) error {
41 if err := ValidateName(name); err != nil {
42 return err
43 }
44 if reservedNames[name] {
45 return fmt.Errorf("name %q is reserved", name)
46 }
47 return nil
48}
49
50// ValidateName checks a repo name (reserved words are allowed for repos;
51// routes are namespaced under the owner).
52func ValidateName(name string) error {
53 if !namePat.MatchString(name) {
54 return fmt.Errorf("invalid name %q: lowercase letters, digits, '.', '-', '_' only; must start with a letter or digit; max 63 chars", name)
55 }
56 if name == "." || name == ".." {
57 return fmt.Errorf("invalid name %q", name)
58 }
59 if len(name) > 4 && name[len(name)-4:] == ".git" {
60 return fmt.Errorf("invalid name %q: must not end in .git", name)
61 }
62 if len(name) > 5 && name[len(name)-5:] == ".wiki" {
63 return fmt.Errorf("invalid name %q: .wiki names are reserved for wiki companion repositories", name)
64 }
65 return nil
66}
67
68// Reserved reports whether name is a reserved route word. Exported so the
69// httpd tests can assert route/reserved-list agreement.
70func Reserved(name string) bool { return reservedNames[name] }