forked from coder/coder
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathprovider.go
More file actions
103 lines (89 loc) · 3.36 KB
/
provider.go
File metadata and controls
103 lines (89 loc) · 3.36 KB
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
package workspaceapps
import (
"context"
"net/http"
"net/url"
"time"
"cdr.dev/slog"
"github.com/coder/coder/v2/codersdk"
)
const (
// TODO(@deansheather): configurable expiry
DefaultTokenExpiry = time.Minute
// RedirectURIQueryParam is the query param for the app URL to be passed
// back to the API auth endpoint on the main access URL.
RedirectURIQueryParam = "redirect_uri"
)
type ResolveRequestOptions struct {
Logger slog.Logger
SignedTokenProvider SignedTokenProvider
DashboardURL *url.URL
PathAppBaseURL *url.URL
AppHostname string
AppRequest Request
// TODO: Replace these 2 fields with a "BrowserURL" field which is used for
// redirecting the user back to their initial request after authenticating.
// AppPath is the path under the app that was hit.
AppPath string
// AppQuery is the raw query of the request.
AppQuery string
}
func ResolveRequest(rw http.ResponseWriter, r *http.Request, opts ResolveRequestOptions) (*SignedToken, bool) {
appReq := opts.AppRequest.Normalize()
err := appReq.Validate()
if err != nil {
// This is a 500 since it's a coder server or proxy that's making this
// request struct based on details from the request. The values should
// already be validated before they are put into the struct.
WriteWorkspaceApp500(opts.Logger, opts.DashboardURL, rw, r, &appReq, err, "invalid app request")
return nil, false
}
token, ok := opts.SignedTokenProvider.FromRequest(r)
if ok && token.MatchesRequest(appReq) {
// The request has a valid signed app token and it matches the request.
return token, true
}
issueReq := IssueTokenRequest{
AppRequest: appReq,
PathAppBaseURL: opts.PathAppBaseURL.String(),
AppHostname: opts.AppHostname,
SessionToken: AppConnectSessionTokenFromRequest(r, appReq.AccessMethod),
AppPath: opts.AppPath,
AppQuery: opts.AppQuery,
}
token, tokenStr, ok := opts.SignedTokenProvider.Issue(r.Context(), rw, r, issueReq)
if !ok {
return nil, false
}
// Write the signed app token cookie.
//
// For path apps, this applies to only the path app base URL on the current
// domain, e.g.
// /@user/workspace[.agent]/apps/path-app/
//
// For subdomain apps, this applies to the entire subdomain, e.g.
// app--agent--workspace--user.apps.example.com
http.SetCookie(rw, &http.Cookie{
Name: codersdk.SignedAppTokenCookie,
Value: tokenStr,
Path: appReq.BasePath,
Expires: token.Expiry,
})
return token, true
}
// SignedTokenProvider provides signed workspace app tokens (aka. app tickets).
type SignedTokenProvider interface {
// FromRequest returns a parsed token from the request. If the request does
// not contain a signed app token or is is invalid (expired, invalid
// signature, etc.), it returns false.
FromRequest(r *http.Request) (*SignedToken, bool)
// Issue mints a new token for the given app request. It uses the long-lived
// session token in the HTTP request to authenticate and authorize the
// client for the given workspace app. The token is returned in struct and
// string form. The string form should be written as a cookie.
//
// If the request is invalid or the user is not authorized to access the
// app, false is returned. An error page is written to the response writer
// in this case.
Issue(ctx context.Context, rw http.ResponseWriter, r *http.Request, appReq IssueTokenRequest) (*SignedToken, string, bool)
}