Skip to content

Commit 3e30cdd

Browse files
committed
Handle ports as app names
1 parent 03c697d commit 3e30cdd

File tree

4 files changed

+92
-79
lines changed

4 files changed

+92
-79
lines changed

coderd/coderd.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,13 @@ func New(options *Options) *API {
131131
})
132132
},
133133
httpmw.Prometheus(options.PrometheusRegistry),
134-
api.handleSubdomain,
134+
// Handle all subdomain requests
135+
api.handleSubdomain(
136+
httpmw.RateLimitPerMinute(options.APIRateLimit),
137+
httpmw.ExtractAPIKey(options.Database, oauthConfigs, false),
138+
httpmw.ExtractUserParam(api.Database),
139+
httpmw.ExtractWorkspaceAndAgentParam(api.Database),
140+
),
135141
)
136142

137143
apps := func(r chi.Router) {

coderd/subdomain.go

Lines changed: 35 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
package coderd
22

33
import (
4-
"database/sql"
54
"fmt"
65
"net/http"
76
"regexp"
87
"strings"
98

10-
"github.com/coder/coder/coderd/httpapi"
11-
"github.com/coder/coder/codersdk"
9+
"github.com/coder/coder/coderd/httpmw"
1210

13-
"github.com/coder/coder/coderd/database"
11+
"github.com/go-chi/chi/v5"
1412

1513
"golang.org/x/xerrors"
1614
)
@@ -34,56 +32,42 @@ type Application struct {
3432
Domain string
3533
}
3634

37-
func (api *API) handleSubdomain(next http.Handler) http.Handler {
38-
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
39-
ctx := r.Context()
40-
app, err := ParseSubdomainAppURL(r)
41-
if err != nil {
42-
// Not a Dev URL, proceed as usual.
43-
// TODO: @emyrk we should probably catch invalid subdomains. Meaning
44-
// an invalid devurl should not route to the coderd.
45-
next.ServeHTTP(rw, r)
46-
return
47-
}
48-
49-
user, err := api.Database.GetUserByEmailOrUsername(ctx, database.GetUserByEmailOrUsernameParams{
50-
Username: app.Username,
51-
})
52-
if err != nil {
53-
if xerrors.Is(err, sql.ErrNoRows) {
54-
httpapi.ResourceNotFound(rw)
35+
func (api *API) handleSubdomain(middlewares ...func(http.Handler) http.Handler) func(http.Handler) http.Handler {
36+
return func(next http.Handler) http.Handler {
37+
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
38+
ctx := r.Context()
39+
app, err := ParseSubdomainAppURL(r)
40+
41+
if err != nil {
42+
// Not a Dev URL, proceed as usual.
43+
// TODO: @emyrk we should probably catch invalid subdomains. Meaning
44+
// an invalid devurl should not route to the coderd.
45+
next.ServeHTTP(rw, r)
5546
return
5647
}
57-
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
58-
Message: "Internal error fetching user.",
59-
Detail: err.Error(),
60-
})
61-
return
62-
}
63-
64-
workspace, err := api.Database.GetWorkspaceByOwnerIDAndName(ctx, database.GetWorkspaceByOwnerIDAndNameParams{
65-
OwnerID: user.ID,
66-
Name: app.WorkspaceName,
67-
})
68-
if err != nil {
69-
if xerrors.Is(err, sql.ErrNoRows) {
70-
httpapi.ResourceNotFound(rw)
71-
return
48+
49+
workspaceAgentKey := app.WorkspaceName
50+
if app.Agent != "" {
51+
workspaceAgentKey += "." + app.Agent
7252
}
73-
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
74-
Message: "Internal error fetching workspace.",
75-
Detail: err.Error(),
76-
})
77-
return
78-
}
79-
80-
api.proxyWorkspaceApplication(proxyApplication{
81-
Workspace: workspace,
82-
// TODO: Fetch workspace agent
83-
Agent: database.WorkspaceAgent{},
84-
AppName: app.AppName,
85-
}, rw, r)
86-
})
53+
chiCtx := chi.RouteContext(ctx)
54+
chiCtx.URLParams.Add("workspace_and_agent", workspaceAgentKey)
55+
chiCtx.URLParams.Add("user", app.Username)
56+
57+
// Use the passed in app middlewares before passing to the proxy app.
58+
mws := chi.Middlewares(middlewares)
59+
mws.Handler(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
60+
workspace := httpmw.WorkspaceParam(r)
61+
agent := httpmw.WorkspaceAgentParam(r)
62+
63+
api.proxyWorkspaceApplication(proxyApplication{
64+
Workspace: workspace,
65+
Agent: agent,
66+
AppName: app.AppName,
67+
}, rw, r)
68+
})).ServeHTTP(rw, r.WithContext(ctx))
69+
})
70+
}
8771
}
8872

8973
var (

coderd/subdomain_test.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func TestParseSubdomainAppURL(t *testing.T) {
4444
AppName: "app",
4545
WorkspaceName: "workspace",
4646
Agent: "",
47-
User: "user",
47+
Username: "user",
4848
Path: "",
4949
Domain: "coder.com",
5050
},
@@ -57,7 +57,7 @@ func TestParseSubdomainAppURL(t *testing.T) {
5757
AppName: "8080",
5858
WorkspaceName: "workspace",
5959
Agent: "",
60-
User: "user",
60+
Username: "user",
6161
Path: "",
6262
Domain: "coder.com",
6363
},
@@ -70,7 +70,7 @@ func TestParseSubdomainAppURL(t *testing.T) {
7070
AppName: "app",
7171
WorkspaceName: "workspace",
7272
Agent: "agent",
73-
User: "user",
73+
Username: "user",
7474
Path: "",
7575
Domain: "coder.com",
7676
},
@@ -83,7 +83,20 @@ func TestParseSubdomainAppURL(t *testing.T) {
8383
AppName: "8080",
8484
WorkspaceName: "workspace",
8585
Agent: "agent",
86-
User: "user",
86+
Username: "user",
87+
Path: "",
88+
Domain: "coder.com",
89+
},
90+
},
91+
{
92+
Name: "HyphenatedNames",
93+
URL: "https://admin-user--workspace-thing--agent-thing--app-name.coder.com",
94+
Expected: coderd.Application{
95+
AppURL: "",
96+
AppName: "app-name",
97+
WorkspaceName: "workspace-thing",
98+
Agent: "agent-thing",
99+
Username: "admin-user",
87100
Path: "",
88101
Domain: "coder.com",
89102
},

coderd/workspaceapps.go

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"net/http"
88
"net/http/httputil"
99
"net/url"
10+
"strconv"
1011
"strings"
1112

1213
"github.com/coder/coder/coderd/database"
@@ -51,34 +52,43 @@ func (api *API) proxyWorkspaceApplication(proxyApp proxyApplication, rw http.Res
5152
return
5253
}
5354

54-
app, err := api.Database.GetWorkspaceAppByAgentIDAndName(r.Context(), database.GetWorkspaceAppByAgentIDAndNameParams{
55-
AgentID: proxyApp.Agent.ID,
56-
Name: proxyApp.AppName,
57-
})
58-
if errors.Is(err, sql.ErrNoRows) {
59-
httpapi.Write(rw, http.StatusNotFound, codersdk.Response{
60-
Message: "Application not found.",
61-
})
62-
return
63-
}
64-
if err != nil {
65-
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
66-
Message: "Internal error fetching workspace application.",
67-
Detail: err.Error(),
68-
})
69-
return
70-
}
71-
if !app.Url.Valid {
72-
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
73-
Message: fmt.Sprintf("Application %s does not have a url.", app.Name),
55+
var internalURL string
56+
57+
num, err := strconv.Atoi(proxyApp.AppName)
58+
if err == nil && num <= 65535 {
59+
// TODO: @emyrk we should probably allow changing the schema?
60+
internalURL = "http://localhost:" + proxyApp.AppName
61+
} else {
62+
app, err := api.Database.GetWorkspaceAppByAgentIDAndName(r.Context(), database.GetWorkspaceAppByAgentIDAndNameParams{
63+
AgentID: proxyApp.Agent.ID,
64+
Name: proxyApp.AppName,
7465
})
75-
return
66+
if errors.Is(err, sql.ErrNoRows) {
67+
httpapi.Write(rw, http.StatusNotFound, codersdk.Response{
68+
Message: "Application not found.",
69+
})
70+
return
71+
}
72+
if err != nil {
73+
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
74+
Message: "Internal error fetching workspace application.",
75+
Detail: err.Error(),
76+
})
77+
return
78+
}
79+
if !app.Url.Valid {
80+
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
81+
Message: fmt.Sprintf("Application %s does not have a url.", app.Name),
82+
})
83+
return
84+
}
85+
internalURL = app.Url.String
7686
}
7787

78-
appURL, err := url.Parse(app.Url.String)
88+
appURL, err := url.Parse(internalURL)
7989
if err != nil {
8090
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
81-
Message: fmt.Sprintf("App url %q must be a valid url.", app.Url.String),
91+
Message: fmt.Sprintf("App url %q must be a valid url.", internalURL),
8292
Detail: err.Error(),
8393
})
8494
return

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy