From 5b3c33831f7570e4a9f018fa5bfc33729c6e70e5 Mon Sep 17 00:00:00 2001 From: Bryan Frimin Date: Wed, 15 Jul 2026 10:59:03 +0200 Subject: [PATCH] Remove session transfer authentication Drop the one-time session handoff flow now that trust center visitors authenticate through OAuth against the compliance portal. Signed-off-by: Bryan Frimin --- pkg/server/api/authn/session_transfer.go | 122 ------------------ pkg/server/api/authn/session_transfer_test.go | 79 ------------ .../api/trust/v1/session_transfer_handler.go | 98 -------------- 3 files changed, 299 deletions(-) delete mode 100644 pkg/server/api/authn/session_transfer.go delete mode 100644 pkg/server/api/authn/session_transfer_test.go delete mode 100644 pkg/server/api/trust/v1/session_transfer_handler.go diff --git a/pkg/server/api/authn/session_transfer.go b/pkg/server/api/authn/session_transfer.go deleted file mode 100644 index 35e6fb804..000000000 --- a/pkg/server/api/authn/session_transfer.go +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) 2026 Probo Inc . -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -package authn - -import ( - "crypto/hmac" - "crypto/sha256" - "encoding/base64" - "errors" - "fmt" - "strconv" - "strings" - "time" -) - -const sessionTransferTTL = 60 * time.Second - -var ( - ErrInvalidSessionTransferToken = errors.New("invalid session transfer token") - ErrSessionTransferTokenExpired = errors.New("session transfer token expired") -) - -// SignSessionTransfer creates a signed, time-limited token containing a -// session ID and the intended redirect URL. The token format is -// base64(sessionID:continueURL:timestamp).signature. -func SignSessionTransfer(sessionID string, continueURL string, secret string) (string, error) { - if secret == "" { - return "", fmt.Errorf("cannot sign session transfer token: secret is empty") - } - - payload := sessionID + ":" + continueURL + ":" + strconv.FormatInt(time.Now().Unix(), 10) - encoded := base64.RawURLEncoding.EncodeToString([]byte(payload)) - - mac := hmac.New(sha256.New, []byte(secret)) - mac.Write([]byte(encoded)) - sig := base64.RawURLEncoding.EncodeToString(mac.Sum(nil)) - - return encoded + "." + sig, nil -} - -// SessionTransferClaims holds the verified claims from a session transfer -// token. -type SessionTransferClaims struct { - SessionID string - ContinueURL string -} - -// VerifySessionTransfer verifies a session transfer token and returns -// the session ID and continue URL if the token is valid and not expired. -func VerifySessionTransfer(token string, secret string) (SessionTransferClaims, error) { - if secret == "" { - return SessionTransferClaims{}, fmt.Errorf("cannot verify session transfer token: secret is empty") - } - - parts := strings.SplitN(token, ".", 2) - if len(parts) != 2 { - return SessionTransferClaims{}, ErrInvalidSessionTransferToken - } - - encoded, sig := parts[0], parts[1] - - mac := hmac.New(sha256.New, []byte(secret)) - mac.Write([]byte(encoded)) - expectedSig := base64.RawURLEncoding.EncodeToString(mac.Sum(nil)) - - if !hmac.Equal([]byte(sig), []byte(expectedSig)) { - return SessionTransferClaims{}, ErrInvalidSessionTransferToken - } - - payload, err := base64.RawURLEncoding.DecodeString(encoded) - if err != nil { - return SessionTransferClaims{}, ErrInvalidSessionTransferToken - } - - // Payload format: sessionID:continueURL:timestamp - // Use LastIndex to find the timestamp separator (timestamp is always last). - idx := strings.LastIndex(string(payload), ":") - if idx < 0 { - return SessionTransferClaims{}, ErrInvalidSessionTransferToken - } - - tsStr := string(payload[idx+1:]) - rest := string(payload[:idx]) - - ts, err := strconv.ParseInt(tsStr, 10, 64) - if err != nil { - return SessionTransferClaims{}, ErrInvalidSessionTransferToken - } - - if time.Since(time.Unix(ts, 0)) > sessionTransferTTL { - return SessionTransferClaims{}, ErrSessionTransferTokenExpired - } - - // Split rest into sessionID and continueURL. - before, after, ok := strings.Cut(rest, ":") - if !ok { - return SessionTransferClaims{}, ErrInvalidSessionTransferToken - } - - return SessionTransferClaims{ - SessionID: before, - ContinueURL: after, - }, nil -} diff --git a/pkg/server/api/authn/session_transfer_test.go b/pkg/server/api/authn/session_transfer_test.go deleted file mode 100644 index b5a4a8339..000000000 --- a/pkg/server/api/authn/session_transfer_test.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) 2026 Probo Inc . -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -package authn - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestSignAndVerifySessionTransfer(t *testing.T) { - t.Parallel() - - secret := "test-secret-key" - sessionID := "ses_abc123" - continueURL := "https://custom.example.com/compliance" - - token, err := SignSessionTransfer(sessionID, continueURL, secret) - require.NoError(t, err) - require.NotEmpty(t, token) - - claims, err := VerifySessionTransfer(token, secret) - require.NoError(t, err) - assert.Equal(t, sessionID, claims.SessionID) - assert.Equal(t, continueURL, claims.ContinueURL) -} - -func TestVerifySessionTransfer_WrongSecret(t *testing.T) { - t.Parallel() - - token, err := SignSessionTransfer("ses_abc123", "https://example.com", "secret-a") - require.NoError(t, err) - - _, err = VerifySessionTransfer(token, "secret-b") - assert.ErrorIs(t, err, ErrInvalidSessionTransferToken) -} - -func TestVerifySessionTransfer_TamperedToken(t *testing.T) { - t.Parallel() - - token, err := SignSessionTransfer("ses_abc123", "https://example.com", "secret") - require.NoError(t, err) - - _, err = VerifySessionTransfer(token+"x", "secret") - assert.ErrorIs(t, err, ErrInvalidSessionTransferToken) -} - -func TestVerifySessionTransfer_MalformedToken(t *testing.T) { - t.Parallel() - - _, err := VerifySessionTransfer("not-a-valid-token", "secret") - assert.ErrorIs(t, err, ErrInvalidSessionTransferToken) -} - -func TestSignSessionTransfer_EmptySecret(t *testing.T) { - t.Parallel() - - _, err := SignSessionTransfer("ses_abc123", "https://example.com", "") - assert.Error(t, err) -} diff --git a/pkg/server/api/trust/v1/session_transfer_handler.go b/pkg/server/api/trust/v1/session_transfer_handler.go deleted file mode 100644 index d446fcde5..000000000 --- a/pkg/server/api/trust/v1/session_transfer_handler.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) 2026 Probo Inc . -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -package trust_v1 - -import ( - "errors" - "net/http" - - "go.gearno.de/kit/httpserver" - "go.gearno.de/kit/log" - "go.probo.inc/probo/pkg/gid" - "go.probo.inc/probo/pkg/iam" - "go.probo.inc/probo/pkg/saferedirect" - "go.probo.inc/probo/pkg/securecookie" - "go.probo.inc/probo/pkg/server/api/authn" -) - -type SessionTransferHandler struct { - iam *iam.Service - sessionCookie *authn.Cookie - cookieSecret string - safeRedirect *saferedirect.SafeRedirect - logger *log.Logger -} - -func NewSessionTransferHandler( - iamSvc *iam.Service, - cookieConfig securecookie.Config, - allowedHost saferedirect.AllowedHostFunc, - logger *log.Logger, -) *SessionTransferHandler { - return &SessionTransferHandler{ - iam: iamSvc, - sessionCookie: authn.NewCookie(&cookieConfig), - cookieSecret: cookieConfig.Secret, - safeRedirect: saferedirect.New(allowedHost), - logger: logger, - } -} - -func (h *SessionTransferHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - token := r.URL.Query().Get("token") - if token == "" { - httpserver.RenderError(w, http.StatusBadRequest, errors.New("missing token")) - return - } - - claims, err := authn.VerifySessionTransfer(token, h.cookieSecret) - if err != nil { - h.logger.WarnCtx(ctx, "invalid session transfer token", log.Error(err)) - httpserver.RenderError(w, http.StatusBadRequest, errors.New("invalid or expired token")) - - return - } - - continueURL := claims.ContinueURL - if continueURL == "" { - continueURL = "/" - } - - sessionID, err := gid.ParseGID(claims.SessionID) - if err != nil { - httpserver.RenderError(w, http.StatusBadRequest, errors.New("invalid token")) - return - } - - session, err := h.iam.SessionService.GetSession(ctx, sessionID) - if err != nil { - h.logger.ErrorCtx(ctx, "cannot get session for transfer", log.Error(err)) - httpserver.RenderError(w, http.StatusBadRequest, errors.New("invalid or expired token")) - - return - } - - h.sessionCookie.Set(w, session) - - h.safeRedirect.Redirect(w, r, continueURL, "/", http.StatusFound) -}