tinyrelayfile
tinyrelay/internal/daemon/routing.go/

internal/daemon/routing.go raw
1package daemon
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "net"
8 "net/http"
9 "net/url"
10 "strings"
11
12 "github.com/FelineStateMachine/tinyrelay/internal/catalog"
13 "github.com/FelineStateMachine/tinyrelay/internal/sites"
14)
15
16// siteBase returns the tenant-scoped site hostname. A tenant named alice on
17// relay.example has sites at <site>.alice.relay.example, keeping identical
18// site labels on different tenants isolated.
19func (a *App) siteBase(meta catalog.Tenant) string {
20 base := "localhost"
21 if a.cfg.PublicURL != "" {
22 if u, err := url.Parse(a.cfg.PublicURL); err == nil && u.Hostname() != "" {
23 base = u.Hostname()
24 }
25 }
26 if ip := net.ParseIP(base); ip != nil {
27 return meta.Name + "." + base
28 }
29 return meta.Name + "." + strings.TrimSuffix(strings.ToLower(base), ".")
30}
31
32// resolveHostedSite resolves a site host without opening a tenant. The bool
33// distinguishes a relay host from a site host, while errors preserve catalog
34// failures for callers that should not fall back to the default tenant.
35func (a *App) resolveHostedSite(r *http.Request) (catalog.Tenant, string, bool, error) {
36 host := strings.ToLower(r.Host)
37 if h, _, err := net.SplitHostPort(host); err == nil {
38 host = h
39 }
40 if strings.HasPrefix(r.URL.Path, "/r/") {
41 parts := strings.Split(strings.TrimPrefix(r.URL.Path, "/r/"), "/")
42 if len(parts) > 0 && parts[0] != "" {
43 meta, err := a.catalog.GetByName(r.Context(), parts[0])
44 return meta, "", false, err
45 }
46 }
47 if mapping, err := a.catalog.ResolveHostMapping(r.Context(), host); err == nil {
48 meta, getErr := a.catalog.GetByID(r.Context(), mapping.TenantID)
49 if getErr != nil {
50 return catalog.Tenant{}, "", false, getErr
51 }
52 if mapping.Site == "" {
53 return meta, "", false, nil
54 }
55 if _, ok := sites.ParseSite(mapping.Site); !ok {
56 return catalog.Tenant{}, mapping.Site, true, errors.New("invalid mapped site label")
57 }
58 return meta, mapping.Site, true, nil
59 } else if !errors.Is(err, catalog.ErrHostMappingNotFound) {
60 return catalog.Tenant{}, "", false, err
61 }
62 base := strings.TrimSuffix(strings.ToLower(a.siteBaseHost()), ".")
63 suffix := "." + base
64 if !strings.HasSuffix(host, suffix) {
65 return catalog.Tenant{}, "", false, catalog.ErrNotFound
66 }
67 relative := strings.TrimSuffix(strings.TrimSuffix(host, suffix), ".")
68 parts := strings.Split(relative, ".")
69 if len(parts) == 1 {
70 if _, ok := sites.ParseSite(relative); !ok {
71 return catalog.Tenant{}, "", false, catalog.ErrNotFound
72 }
73 meta, err := a.catalog.GetByName(r.Context(), a.cfg.DefaultTenant)
74 return meta, relative, true, err
75 }
76 if len(parts) < 2 {
77 return catalog.Tenant{}, "", false, catalog.ErrNotFound
78 }
79 label := strings.Join(parts[:len(parts)-1], ".")
80 if _, ok := sites.ParseSite(label); !ok {
81 return catalog.Tenant{}, label, true, catalog.ErrNotFound
82 }
83 tenantName := parts[len(parts)-1]
84 meta, err := a.catalog.GetByName(r.Context(), tenantName)
85 if err != nil {
86 return catalog.Tenant{}, label, true, err
87 }
88 return meta, label, true, nil
89}
90
91func (a *App) siteBaseHost() string {
92 if a.cfg.PublicURL == "" {
93 return "localhost"
94 }
95 u, err := url.Parse(a.cfg.PublicURL)
96 if err != nil || u.Hostname() == "" {
97 return "localhost"
98 }
99 return u.Hostname()
100}
101
102// siteHostResolver is injected into a tenant's sites service. It only returns
103// labels assigned to that tenant, preventing a custom host or namespace host
104// from crossing tenant boundaries.
105func (t *Tenant) siteHostResolver(ctx context.Context, host string) (string, error) {
106 host = strings.ToLower(host)
107 if h, _, err := net.SplitHostPort(host); err == nil {
108 host = h
109 }
110 if mapping, err := t.app.catalog.ResolveHostMapping(ctx, host); err == nil {
111 if mapping.TenantID != t.meta.ID || mapping.Site == "" {
112 return "", catalog.ErrHostMappingNotFound
113 }
114 return mapping.Site, nil
115 } else if !errors.Is(err, catalog.ErrHostMappingNotFound) {
116 return "", err
117 }
118 base := "." + strings.ToLower(t.app.siteBase(t.meta))
119 if !strings.HasSuffix(host, base) {
120 return "", catalog.ErrNotFound
121 }
122 label := strings.TrimSuffix(strings.TrimSuffix(host, base), ".")
123 if _, ok := sites.ParseSite(label); !ok {
124 return "", catalog.ErrNotFound
125 }
126 return label, nil
127}
128
129func (a *App) hostedSiteURL(meta catalog.Tenant, label string) string {
130 if label == "" {
131 return ""
132 }
133 return fmt.Sprintf("https://%s/%s", a.siteBase(meta), label)
134}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs