-
Notifications
You must be signed in to change notification settings - Fork 689
/
workspaceapps.go
143 lines (132 loc) · 4.42 KB
/
workspaceapps.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package coderd
import (
"fmt"
"net/http"
"net/url"
"time"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/httpapi"
"github.com/coder/coder/coderd/httpmw"
"github.com/coder/coder/coderd/rbac"
"github.com/coder/coder/coderd/workspaceapps"
"github.com/coder/coder/codersdk"
)
// @Summary Get applications host
// @ID get-applications-host
// @Security CoderSessionToken
// @Produce json
// @Tags Applications
// @Success 200 {object} codersdk.AppHostResponse
// @Router /applications/host [get]
func (api *API) appHost(rw http.ResponseWriter, r *http.Request) {
host := api.AppHostname
if host != "" && api.AccessURL.Port() != "" {
host += fmt.Sprintf(":%s", api.AccessURL.Port())
}
httpapi.Write(r.Context(), rw, http.StatusOK, codersdk.AppHostResponse{
Host: host,
})
}
// workspaceApplicationAuth is an endpoint on the main router that handles
// redirects from the subdomain handler.
//
// This endpoint is under /api so we don't return the friendly error page here.
// Any errors on this endpoint should be errors that are unlikely to happen
// in production unless the user messes with the URL.
//
// @Summary Redirect to URI with encrypted API key
// @ID redirect-to-uri-with-encrypted-api-key
// @Security CoderSessionToken
// @Tags Applications
// @Param redirect_uri query string false "Redirect destination"
// @Success 307
// @Router /applications/auth-redirect [get]
func (api *API) workspaceApplicationAuth(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if api.AppHostname == "" {
httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{
Message: "The server does not accept subdomain-based application requests.",
})
return
}
apiKey := httpmw.APIKey(r)
if !api.Authorize(r, rbac.ActionCreate, apiKey) {
httpapi.ResourceNotFound(rw)
return
}
// Get the redirect URI from the query parameters and parse it.
redirectURI := r.URL.Query().Get(workspaceapps.RedirectURIQueryParam)
if redirectURI == "" {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Missing redirect_uri query parameter.",
})
return
}
u, err := url.Parse(redirectURI)
if err != nil {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Invalid redirect_uri query parameter.",
Detail: err.Error(),
})
return
}
// Force the redirect URI to use the same scheme as the access URL for
// security purposes.
u.Scheme = api.AccessURL.Scheme
// Ensure that the redirect URI is a subdomain of api.Hostname and is a
// valid app subdomain.
subdomain, ok := httpapi.ExecuteHostnamePattern(api.AppHostnameRegex, u.Host)
if !ok {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "The redirect_uri query parameter must be a valid app subdomain.",
})
return
}
_, err = httpapi.ParseSubdomainAppURL(subdomain)
if err != nil {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "The redirect_uri query parameter must be a valid app subdomain.",
Detail: err.Error(),
})
return
}
// Create the application_connect-scoped API key with the same lifetime as
// the current session.
exp := apiKey.ExpiresAt
lifetimeSeconds := apiKey.LifetimeSeconds
if exp.IsZero() || time.Until(exp) > api.DeploymentValues.SessionDuration.Value() {
exp = database.Now().Add(api.DeploymentValues.SessionDuration.Value())
lifetimeSeconds = int64(api.DeploymentValues.SessionDuration.Value().Seconds())
}
cookie, _, err := api.createAPIKey(ctx, createAPIKeyParams{
UserID: apiKey.UserID,
LoginType: database.LoginTypePassword,
ExpiresAt: exp,
LifetimeSeconds: lifetimeSeconds,
Scope: database.APIKeyScopeApplicationConnect,
})
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Failed to create API key.",
Detail: err.Error(),
})
return
}
// Encrypt the API key.
encryptedAPIKey, err := api.AppSecurityKey.EncryptAPIKey(workspaceapps.EncryptedAPIKeyPayload{
APIKey: cookie.Value,
})
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Failed to encrypt API key.",
Detail: err.Error(),
})
return
}
// Redirect to the redirect URI with the encrypted API key in the query
// parameters.
q := u.Query()
q.Set(workspaceapps.SubdomainProxyAPIKeyParam, encryptedAPIKey)
u.RawQuery = q.Encode()
http.Redirect(rw, r, u.String(), http.StatusTemporaryRedirect)
}