mirror of
https://github.com/coder/coder.git
synced 2025-07-13 21:36:50 +00:00
feat: app sharing (now open source!) (#4378)
This commit is contained in:
@ -83,8 +83,8 @@ type OAuth2Configs struct {
|
||||
}
|
||||
|
||||
const (
|
||||
signedOutErrorMessage string = "You are signed out or your session has expired. Please sign in again to continue."
|
||||
internalErrorMessage string = "An internal error occurred. Please try again or contact the system administrator."
|
||||
SignedOutErrorMessage = "You are signed out or your session has expired. Please sign in again to continue."
|
||||
internalErrorMessage = "An internal error occurred. Please try again or contact the system administrator."
|
||||
)
|
||||
|
||||
type ExtractAPIKeyConfig struct {
|
||||
@ -119,21 +119,7 @@ func ExtractAPIKey(cfg ExtractAPIKeyConfig) func(http.Handler) http.Handler {
|
||||
// like workspace applications.
|
||||
write := func(code int, response codersdk.Response) {
|
||||
if cfg.RedirectToLogin {
|
||||
path := r.URL.Path
|
||||
if r.URL.RawQuery != "" {
|
||||
path += "?" + r.URL.RawQuery
|
||||
}
|
||||
|
||||
q := url.Values{}
|
||||
q.Add("message", response.Message)
|
||||
q.Add("redirect", path)
|
||||
|
||||
u := &url.URL{
|
||||
Path: "/login",
|
||||
RawQuery: q.Encode(),
|
||||
}
|
||||
|
||||
http.Redirect(rw, r, u.String(), http.StatusTemporaryRedirect)
|
||||
RedirectToLogin(rw, r, response.Message)
|
||||
return
|
||||
}
|
||||
|
||||
@ -157,7 +143,7 @@ func ExtractAPIKey(cfg ExtractAPIKeyConfig) func(http.Handler) http.Handler {
|
||||
token := apiTokenFromRequest(r)
|
||||
if token == "" {
|
||||
optionalWrite(http.StatusUnauthorized, codersdk.Response{
|
||||
Message: signedOutErrorMessage,
|
||||
Message: SignedOutErrorMessage,
|
||||
Detail: fmt.Sprintf("Cookie %q or query parameter must be provided.", codersdk.SessionTokenKey),
|
||||
})
|
||||
return
|
||||
@ -166,7 +152,7 @@ func ExtractAPIKey(cfg ExtractAPIKeyConfig) func(http.Handler) http.Handler {
|
||||
keyID, keySecret, err := SplitAPIToken(token)
|
||||
if err != nil {
|
||||
optionalWrite(http.StatusUnauthorized, codersdk.Response{
|
||||
Message: signedOutErrorMessage,
|
||||
Message: SignedOutErrorMessage,
|
||||
Detail: "Invalid API key format: " + err.Error(),
|
||||
})
|
||||
return
|
||||
@ -176,7 +162,7 @@ func ExtractAPIKey(cfg ExtractAPIKeyConfig) func(http.Handler) http.Handler {
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
optionalWrite(http.StatusUnauthorized, codersdk.Response{
|
||||
Message: signedOutErrorMessage,
|
||||
Message: SignedOutErrorMessage,
|
||||
Detail: "API key is invalid.",
|
||||
})
|
||||
return
|
||||
@ -192,7 +178,7 @@ func ExtractAPIKey(cfg ExtractAPIKeyConfig) func(http.Handler) http.Handler {
|
||||
hashedSecret := sha256.Sum256([]byte(keySecret))
|
||||
if subtle.ConstantTimeCompare(key.HashedSecret, hashedSecret[:]) != 1 {
|
||||
optionalWrite(http.StatusUnauthorized, codersdk.Response{
|
||||
Message: signedOutErrorMessage,
|
||||
Message: SignedOutErrorMessage,
|
||||
Detail: "API key secret is invalid.",
|
||||
})
|
||||
return
|
||||
@ -255,7 +241,7 @@ func ExtractAPIKey(cfg ExtractAPIKeyConfig) func(http.Handler) http.Handler {
|
||||
// Checking if the key is expired.
|
||||
if key.ExpiresAt.Before(now) {
|
||||
optionalWrite(http.StatusUnauthorized, codersdk.Response{
|
||||
Message: signedOutErrorMessage,
|
||||
Message: SignedOutErrorMessage,
|
||||
Detail: fmt.Sprintf("API key expired at %q.", key.ExpiresAt.String()),
|
||||
})
|
||||
return
|
||||
@ -422,3 +408,23 @@ func SplitAPIToken(token string) (id string, secret string, err error) {
|
||||
|
||||
return keyID, keySecret, nil
|
||||
}
|
||||
|
||||
// RedirectToLogin redirects the user to the login page with the `message` and
|
||||
// `redirect` query parameters set.
|
||||
func RedirectToLogin(rw http.ResponseWriter, r *http.Request, message string) {
|
||||
path := r.URL.Path
|
||||
if r.URL.RawQuery != "" {
|
||||
path += "?" + r.URL.RawQuery
|
||||
}
|
||||
|
||||
q := url.Values{}
|
||||
q.Add("message", message)
|
||||
q.Add("redirect", path)
|
||||
|
||||
u := &url.URL{
|
||||
Path: "/login",
|
||||
RawQuery: q.Encode(),
|
||||
}
|
||||
|
||||
http.Redirect(rw, r, u.String(), http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
@ -148,7 +148,7 @@ func TestOrganizationParam(t *testing.T) {
|
||||
DB: db,
|
||||
RedirectToLogin: false,
|
||||
}),
|
||||
httpmw.ExtractUserParam(db),
|
||||
httpmw.ExtractUserParam(db, false),
|
||||
httpmw.ExtractOrganizationParam(db),
|
||||
httpmw.ExtractOrganizationMemberParam(db),
|
||||
)
|
||||
@ -189,7 +189,7 @@ func TestOrganizationParam(t *testing.T) {
|
||||
RedirectToLogin: false,
|
||||
}),
|
||||
httpmw.ExtractOrganizationParam(db),
|
||||
httpmw.ExtractUserParam(db),
|
||||
httpmw.ExtractUserParam(db, false),
|
||||
httpmw.ExtractOrganizationMemberParam(db),
|
||||
)
|
||||
rtr.Get("/", func(rw http.ResponseWriter, r *http.Request) {
|
||||
|
@ -33,8 +33,11 @@ func UserParam(r *http.Request) database.User {
|
||||
return user
|
||||
}
|
||||
|
||||
// ExtractUserParam extracts a user from an ID/username in the {user} URL parameter.
|
||||
func ExtractUserParam(db database.Store) func(http.Handler) http.Handler {
|
||||
// ExtractUserParam extracts a user from an ID/username in the {user} URL
|
||||
// parameter.
|
||||
//
|
||||
//nolint:revive
|
||||
func ExtractUserParam(db database.Store, redirectToLoginOnMe bool) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
@ -53,7 +56,19 @@ func ExtractUserParam(db database.Store) func(http.Handler) http.Handler {
|
||||
}
|
||||
|
||||
if userQuery == "me" {
|
||||
user, err = db.GetUserByID(ctx, APIKey(r).UserID)
|
||||
apiKey, ok := APIKeyOptional(r)
|
||||
if !ok {
|
||||
if redirectToLoginOnMe {
|
||||
RedirectToLogin(rw, r, SignedOutErrorMessage)
|
||||
return
|
||||
}
|
||||
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Cannot use \"me\" without a valid session.",
|
||||
})
|
||||
return
|
||||
}
|
||||
user, err = db.GetUserByID(ctx, apiKey.UserID)
|
||||
if xerrors.Is(err, sql.ErrNoRows) {
|
||||
httpapi.ResourceNotFound(rw)
|
||||
return
|
||||
|
@ -63,7 +63,7 @@ func TestUserParam(t *testing.T) {
|
||||
r = returnedRequest
|
||||
})).ServeHTTP(rw, r)
|
||||
|
||||
httpmw.ExtractUserParam(db)(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
httpmw.ExtractUserParam(db, false)(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
})).ServeHTTP(rw, r)
|
||||
res := rw.Result()
|
||||
@ -85,7 +85,7 @@ func TestUserParam(t *testing.T) {
|
||||
routeContext := chi.NewRouteContext()
|
||||
routeContext.URLParams.Add("user", "ben")
|
||||
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, routeContext))
|
||||
httpmw.ExtractUserParam(db)(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
httpmw.ExtractUserParam(db, false)(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
})).ServeHTTP(rw, r)
|
||||
res := rw.Result()
|
||||
@ -107,7 +107,7 @@ func TestUserParam(t *testing.T) {
|
||||
routeContext := chi.NewRouteContext()
|
||||
routeContext.URLParams.Add("user", "me")
|
||||
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, routeContext))
|
||||
httpmw.ExtractUserParam(db)(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
httpmw.ExtractUserParam(db, false)(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
_ = httpmw.UserParam(r)
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
})).ServeHTTP(rw, r)
|
||||
|
@ -305,7 +305,7 @@ func TestWorkspaceAgentByNameParam(t *testing.T) {
|
||||
DB: db,
|
||||
RedirectToLogin: true,
|
||||
}),
|
||||
httpmw.ExtractUserParam(db),
|
||||
httpmw.ExtractUserParam(db, false),
|
||||
httpmw.ExtractWorkspaceAndAgentParam(db),
|
||||
)
|
||||
rtr.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
|
Reference in New Issue
Block a user