Skip to content
Snippets Groups Projects
handlers.go 37.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • Eric Chiang's avatar
    Eric Chiang committed
    		return
    	}
    
    
    	newToken := &internal.RefreshToken{
    		RefreshId: refresh.ID,
    		Token:     storage.NewID(),
    	}
    	rawNewToken, err := internal.Marshal(newToken)
    	if err != nil {
    		s.logger.Errorf("failed to marshal refresh token: %v", err)
    
    		s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
    
    Eric Chiang's avatar
    Eric Chiang committed
    		return
    	}
    
    	updater := func(old storage.RefreshToken) (storage.RefreshToken, error) {
    		if old.Token != refresh.Token {
    			return old, errors.New("refresh token claimed twice")
    		}
    		old.Token = newToken.Token
    		// Update the claims of the refresh token.
    		//
    		// UserID intentionally ignored for now.
    		old.Claims.Username = ident.Username
    		old.Claims.Email = ident.Email
    		old.Claims.EmailVerified = ident.EmailVerified
    		old.Claims.Groups = ident.Groups
    		old.ConnectorData = ident.ConnectorData
    
    		old.LastUsed = lastUsed
    
    
    	// Update LastUsed time stamp in refresh token reference object
    	// in offline session for the user.
    	if err := s.storage.UpdateOfflineSessions(refresh.Claims.UserID, refresh.ConnectorID, func(old storage.OfflineSessions) (storage.OfflineSessions, error) {
    		if old.Refresh[refresh.ClientID].ID != refresh.ID {
    			return old, errors.New("refresh token invalid")
    		}
    		old.Refresh[refresh.ClientID].LastUsed = lastUsed
    		return old, nil
    	}); err != nil {
    		s.logger.Errorf("failed to update offline session: %v", err)
    		s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
    		return
    	}
    
    	// Update refresh token in the storage.
    
    	if err := s.storage.UpdateRefreshToken(refresh.ID, updater); err != nil {
    		s.logger.Errorf("failed to update refresh token: %v", err)
    
    		s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
    
    Eric Chiang's avatar
    Eric Chiang committed
    		return
    	}
    
    	s.writeAccessToken(w, idToken, accessToken, rawNewToken, expiry)
    
    Eric Chiang's avatar
    Eric Chiang committed
    }
    
    
    func (s *Server) handleUserInfo(w http.ResponseWriter, r *http.Request) {
    	authorization := r.Header.Get("Authorization")
    	parts := strings.Fields(authorization)
    
    	if len(parts) != 2 || !strings.EqualFold(parts[0], "bearer") {
    		msg := "invalid authorization header"
    		w.Header().Set("WWW-Authenticate", fmt.Sprintf(`Bearer realm="dex", error="%s", error_description="%s"`, errInvalidRequest, msg))
    		s.tokenErrHelper(w, errInvalidRequest, msg, http.StatusBadRequest)
    		return
    	}
    
    	token := parts[1]
    
    	verified, err := s.verify(token)
    	if err != nil {
    		if err == errTokenExpired {
    			s.tokenErrHelper(w, errAccessDenied, err.Error(), http.StatusUnauthorized)
    			return
    		}
    		s.tokenErrHelper(w, errInvalidRequest, err.Error(), http.StatusBadRequest)
    		return
    	}
    
    	w.Header().Set("Content-Type", "application/json")
    	w.Write(verified)
    	}
    
    func (s *Server) verify(token string) ([]byte, error) {
    	keys, err := s.storage.GetKeys()
    	if err != nil {
    		return nil, fmt.Errorf("failed to get keys: %v", err)
    	}
    
    	if keys.SigningKey == nil {
    		return nil, fmt.Errorf("no private keys found")
    	}
    
    	object, err := jose.ParseSigned(token)
    	if err != nil {
    		return nil, fmt.Errorf("unable to parse signed message")
    	}
    
    	// Parse the message to check expiry, as it jose doesn't distinguish expiry error from others
    	parts := strings.Split(token, ".")
    	if len(parts) != 3 {
    		return nil, fmt.Errorf("square/go-jose: compact JWS format must have three parts")
    	}
    
    	payload, err := base64.RawURLEncoding.DecodeString(parts[1])
    	if err != nil {
    		return nil, err
    	}
    
    	// TODO: check other claims
    	var tokenInfo struct {
    		Expiry int64 `json:"exp"`
    	}
    
    	if err := json.Unmarshal(payload, &tokenInfo); err != nil {
    		return nil, err
    	}
    
    	if tokenInfo.Expiry < s.now().Unix() {
    		return nil, errTokenExpired
    	}
    
    	var allKeys []*jose.JSONWebKey
    
    	allKeys = append(allKeys, keys.SigningKeyPub)
    	for _, key := range keys.VerificationKeys {
    		allKeys = append(allKeys, key.PublicKey)
    	}
    
    	for _, pubKey := range allKeys {
    		verified, err := object.Verify(pubKey)
    		if err == nil {
    			return verified, nil
    		}
    	}
    	return nil, errors.New("unable to verify jwt")
    }
    
    
    func (s *Server) writeAccessToken(w http.ResponseWriter, idToken, accessToken, refreshToken string, expiry time.Time) {
    
    Eric Chiang's avatar
    Eric Chiang committed
    	// TODO(ericchiang): figure out an access token story and support the user info
    	// endpoint. For now use a random value so no one depends on the access_token
    	// holding a specific structure.
    	resp := struct {
    		AccessToken  string `json:"access_token"`
    		TokenType    string `json:"token_type"`
    		ExpiresIn    int    `json:"expires_in"`
    		RefreshToken string `json:"refresh_token,omitempty"`
    		IDToken      string `json:"id_token"`
    	}{
    
    		accessToken,
    
    Eric Chiang's avatar
    Eric Chiang committed
    		"bearer",
    
    		int(expiry.Sub(s.now()).Seconds()),
    
    Eric Chiang's avatar
    Eric Chiang committed
    		refreshToken,
    		idToken,
    	}
    	data, err := json.Marshal(resp)
    	if err != nil {
    
    		s.logger.Errorf("failed to marshal access token response: %v", err)
    		s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
    
    Eric Chiang's avatar
    Eric Chiang committed
    		return
    	}
    	w.Header().Set("Content-Type", "application/json")
    	w.Header().Set("Content-Length", strconv.Itoa(len(data)))
    	w.Write(data)
    }
    
    
    func (s *Server) renderError(w http.ResponseWriter, status int, description string) {
    
    	if err := s.templates.err(w, status, description); err != nil {
    
    		s.logger.Errorf("Server template error: %v", err)
    	}
    
    Eric Chiang's avatar
    Eric Chiang committed
    }
    
    
    func (s *Server) tokenErrHelper(w http.ResponseWriter, typ string, description string, statusCode int) {
    	if err := tokenErr(w, typ, description, statusCode); err != nil {
    
    		s.logger.Errorf("token error response: %v", err)
    
    
    // Check for username prompt override from connector. Defaults to "Username".
    func usernamePrompt(conn connector.PasswordConnector) string {
    	if attr := conn.Prompt(); attr != "" {
    		return attr
    	}
    	return "Username"
    }