Go · 3095 bytes Raw Blame History
1 // SPDX-License-Identifier: AGPL-3.0-or-later
2
3 // Package auth contains shared auth primitives used across the auth
4 // surface (handlers, middleware, services). Reserved-name validation
5 // lives here because it's read by signup *and* by username-change in S10.
6 package auth
7
8 import "strings"
9
10 // reservedNames is the set of usernames that may NOT be claimed by users.
11 // It covers:
12 // - Every static top-level route shithub registers (so /login can never
13 // resolve to a user named "login").
14 // - GitHub-known reservations we choose to mirror for parity.
15 // - A buffer of likely-future routes we'd rather lock in now than fight
16 // to reclaim from a squatter later.
17 //
18 // MAINTENANCE: when adding a new top-level route in any sprint, add the
19 // leading path segment here. The audit test in
20 // internal/web/handlers/handlers_test.go enforces this by walking the
21 // registered chi router and checking every static segment.
22 var reservedNames = map[string]struct{}{
23 // shithub-registered routes (S02–S05+).
24 "static": {},
25 "healthz": {},
26 "readyz": {},
27 "metrics": {},
28 "signup": {},
29 "login": {},
30 "logout": {},
31 "password": {},
32 "verify-email": {},
33 "internal": {},
34 "settings": {},
35 "api": {},
36 "admin": {},
37 "new": {},
38 "explore": {},
39 "trending": {},
40 "marketplace": {},
41 "issues": {},
42 "pulls": {},
43 "notifications": {},
44 "watching": {},
45 "stars": {},
46 "orgs": {},
47 "organizations": {},
48 "search": {},
49 "about": {},
50 "contact": {},
51 "pricing": {},
52 "site": {},
53 "help": {},
54 "docs": {},
55 "blog": {},
56 "security": {},
57 "status": {},
58 "terms": {},
59 "privacy": {},
60 "cookies": {},
61 "oauth": {},
62 "sessions": {},
63 "saml": {},
64 "sso": {},
65 "webauthn": {},
66 "two-factor": {},
67 "recovery": {},
68 "avatar": {},
69 "avatars": {},
70 "raw": {},
71 "media": {},
72 "assets": {},
73 "favicon.ico": {},
74 "robots.txt": {},
75 "sitemap.xml": {},
76 "git": {},
77 "ssh": {},
78 "public": {},
79 "private": {},
80 "keys": {},
81 "tokens": {},
82 "shithub": {},
83 "shithubd": {},
84 "shithubbot": {},
85 // S29 — notification subscribe/unsubscribe per thread.
86 "threads": {},
87 // S30 — invitation accept/decline.
88 "invitations": {},
89 }
90
91 // IsReserved reports whether name is on the reserved list. The check is
92 // case-insensitive — usernames are stored as citext, so we normalize here
93 // to match.
94 func IsReserved(name string) bool {
95 _, ok := reservedNames[strings.ToLower(name)]
96 return ok
97 }
98
99 // ReservedNames returns a copy of the reserved set as a slice. Used by
100 // the route-audit test in handlers_test.go to confirm every registered
101 // top-level path segment is present here.
102 func ReservedNames() []string {
103 out := make([]string, 0, len(reservedNames))
104 for n := range reservedNames {
105 out = append(out, n)
106 }
107 return out
108 }
109