fix(auth): harden pending oauth session consumption

This commit is contained in:
IanShaw027
2026-04-22 11:17:38 +08:00
parent 84628108fc
commit ca1f30a911
4 changed files with 146 additions and 38 deletions

View File

@@ -237,15 +237,40 @@ func (s *AuthPendingIdentityService) consumeSession(
}
now := time.Now().UTC()
updated, err := s.entClient.PendingAuthSession.UpdateOneID(session.ID).
update := s.entClient.PendingAuthSession.UpdateOneID(session.ID).
Where(
pendingauthsession.ConsumedAtIsNil(),
pendingauthsession.ExpiresAtGTE(now),
pendingauthsession.Or(
pendingauthsession.CompletionCodeExpiresAtIsNil(),
pendingauthsession.CompletionCodeExpiresAtGTE(now),
),
).
SetConsumedAt(now).
SetCompletionCodeHash("").
ClearCompletionCodeExpiresAt().
Save(ctx)
if err != nil {
ClearCompletionCodeExpiresAt()
if expectedBrowserSessionKey := strings.TrimSpace(session.BrowserSessionKey); expectedBrowserSessionKey != "" {
update = update.Where(pendingauthsession.BrowserSessionKeyEQ(expectedBrowserSessionKey))
}
updated, err := update.Save(ctx)
if err == nil {
return updated, nil
}
if !dbent.IsNotFound(err) {
return nil, err
}
return updated, nil
current, currentErr := s.entClient.PendingAuthSession.Get(ctx, session.ID)
if currentErr != nil {
if dbent.IsNotFound(currentErr) {
return nil, ErrPendingAuthSessionNotFound
}
return nil, currentErr
}
if err := validatePendingSessionState(current, browserSessionKey, expiredErr, consumedErr); err != nil {
return nil, err
}
return nil, consumedErr
}
func validatePendingSessionState(session *dbent.PendingAuthSession, browserSessionKey string, expiredErr error, consumedErr error) error {

View File

@@ -356,3 +356,69 @@ func TestAuthPendingIdentityService_ConsumeBrowserSession(t *testing.T) {
_, err = svc.ConsumeBrowserSession(ctx, session.SessionToken, "browser-session")
require.ErrorIs(t, err, ErrPendingAuthSessionConsumed)
}
func TestAuthPendingIdentityService_ConsumeBrowserSessionRejectsStaleLoadedSessionReplay(t *testing.T) {
svc, _ := newAuthPendingIdentityServiceTestClient(t)
ctx := context.Background()
session, err := svc.CreatePendingSession(ctx, CreatePendingAuthSessionInput{
Intent: "login",
Identity: PendingAuthIdentityKey{
ProviderType: "linuxdo",
ProviderKey: "linuxdo",
ProviderSubject: "stale-replay-subject",
},
BrowserSessionKey: "browser-session",
})
require.NoError(t, err)
loaded, err := svc.getBrowserSession(ctx, session.SessionToken)
require.NoError(t, err)
consumed, err := svc.consumeSession(ctx, loaded, "browser-session", ErrPendingAuthSessionExpired, ErrPendingAuthSessionConsumed)
require.NoError(t, err)
require.NotNil(t, consumed.ConsumedAt)
_, err = svc.consumeSession(ctx, loaded, "browser-session", ErrPendingAuthSessionExpired, ErrPendingAuthSessionConsumed)
require.ErrorIs(t, err, ErrPendingAuthSessionConsumed)
}
func TestAuthPendingIdentityService_ConsumeBrowserSessionScrubsLegacyCompletionTokens(t *testing.T) {
svc, client := newAuthPendingIdentityServiceTestClient(t)
ctx := context.Background()
session, err := svc.CreatePendingSession(ctx, CreatePendingAuthSessionInput{
Intent: "login",
Identity: PendingAuthIdentityKey{
ProviderType: "linuxdo",
ProviderKey: "linuxdo",
ProviderSubject: "legacy-token-subject",
},
BrowserSessionKey: "browser-session",
LocalFlowState: map[string]any{
"completion_response": map[string]any{
"access_token": "legacy-access-token",
"refresh_token": "legacy-refresh-token",
"expires_in": float64(3600),
"token_type": "Bearer",
"redirect": "/dashboard",
},
},
})
require.NoError(t, err)
consumed, err := svc.ConsumeBrowserSession(ctx, session.SessionToken, "browser-session")
require.NoError(t, err)
require.NotNil(t, consumed.ConsumedAt)
stored, err := client.PendingAuthSession.Get(ctx, session.ID)
require.NoError(t, err)
completion, ok := stored.LocalFlowState["completion_response"].(map[string]any)
require.True(t, ok)
require.NotContains(t, completion, "access_token")
require.NotContains(t, completion, "refresh_token")
require.NotContains(t, completion, "expires_in")
require.NotContains(t, completion, "token_type")
require.Equal(t, "/dashboard", completion["redirect"])
}