Alex Tugarev 7ecc196baa
Sign in with SSO (#17055)
* [experiment] Add "Sign in with SSO" to Login

Reusing existing parts:
 * `/complete-auth` page of Dashbaord to forward results of authN flows running in a modal
 * Adding preliminary UI to the Login view: Org-slug and simple button.

* [gitpod-db] get team/org by slug

* [gitpod-db] fix OIDCClientConfig.OrganizationID field's type

* [oidc] consider returnTo URL

* [oidc] consider orgSlug param from start request

* [oidc] fix oauth2 clientId propagation

* [oidc] fix a flaky test

* [onboarding] skip for organizational accounts

* Move SSO Login UI into it's own component

* adjust validation a bit, add useCallbacks

* adding GetOIDCClientConfigByOrgSlug

* add table name

* removing commented out code

---------

Co-authored-by: Brad Harris <bmharris@gmail.com>
2023-03-29 15:49:39 +02:00

124 lines
3.4 KiB
Go

// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License.AGPL.txt in the project root for license information.
package oidc
import (
"net/http"
"net/url"
"time"
"github.com/gitpod-io/gitpod/common-go/log"
"github.com/go-chi/chi/v5"
)
func Router(s *Service) *chi.Mux {
router := chi.NewRouter()
router.Route("/start", func(r chi.Router) {
r.Get("/", s.getStartHandler())
})
router.Route("/callback", func(r chi.Router) {
r.Use(s.OAuth2Middleware)
r.Get("/", s.getCallbackHandler())
})
return router
}
const (
stateCookieName = "state"
nonceCookieName = "nonce"
)
func (s *Service) getStartHandler() http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
config, err := s.GetClientConfigFromStartRequest(r)
if err != nil {
log.Warn("client config not found: " + err.Error())
http.Error(rw, "config not found", http.StatusNotFound)
return
}
returnToURL := r.URL.Query().Get("returnTo")
if returnToURL == "" {
returnToURL = "/"
}
redirectURL := getCallbackURL(r.Host)
startParams, err := s.GetStartParams(config, redirectURL, returnToURL)
if err != nil {
http.Error(rw, "failed to start auth flow", http.StatusInternalServerError)
return
}
http.SetCookie(rw, newCallbackCookie(r, nonceCookieName, startParams.Nonce))
http.SetCookie(rw, newCallbackCookie(r, stateCookieName, startParams.State))
http.Redirect(rw, r, startParams.AuthCodeURL, http.StatusTemporaryRedirect)
}
}
func getCallbackURL(host string) string {
callbackURL := url.URL{Scheme: "https", Path: "/iam/oidc/callback", Host: host}
return callbackURL.String()
}
func newCallbackCookie(r *http.Request, name string, value string) *http.Cookie {
return &http.Cookie{
Name: name,
Value: value,
MaxAge: int(10 * time.Minute.Seconds()),
Secure: true,
SameSite: http.SameSiteLaxMode,
HttpOnly: true,
}
}
// The OIDC callback handler depends on the state produced in the OAuth2 middleware
func (s *Service) getCallbackHandler() http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
config, err := s.GetClientConfigFromCallbackRequest(r)
if err != nil {
log.Warn("client config not found: " + err.Error())
http.Error(rw, "config not found", http.StatusNotFound)
return
}
oauth2Result := GetOAuth2ResultFromContext(r.Context())
if oauth2Result == nil {
http.Error(rw, "OIDC precondition failure", http.StatusInternalServerError)
return
}
// nonce = number used once
nonceCookie, err := r.Cookie(nonceCookieName)
if err != nil {
http.Error(rw, "nonce not found", http.StatusBadRequest)
return
}
result, err := s.Authenticate(r.Context(), AuthenticateParams{
Config: config,
OAuth2Result: oauth2Result,
NonceCookieValue: nonceCookie.Value,
})
if err != nil {
log.Warn("OIDC authentication failed: " + err.Error())
http.Error(rw, "OIDC authentication failed", http.StatusInternalServerError)
return
}
log.WithField("id_token", result.IDToken).Trace("user verification was successful")
cookie, err := s.CreateSession(r.Context(), result, config.OrganizationID)
if err != nil {
log.Warn("Failed to create session: " + err.Error())
http.Error(rw, "Failed to create session", http.StatusInternalServerError)
return
}
http.SetCookie(rw, cookie)
http.Redirect(rw, r, oauth2Result.ReturnToURL, http.StatusTemporaryRedirect)
}
}