Skip to content

Commit b0ac753

Browse files
authored
feat(server): log successful token exchange requests (#4780)
The authorization code flow emits an "login successful" info log in finalizeLogin with the resolved identity (user_id, username, email, groups). The token exchange flow (RFC 8693) never logged a corresponding success line, making CI/CD-style flows that rely on token exchange effectively invisible in operator logs even at debug level. Add an equivalent "token exchange successful" info log after TokenIdentity returns, mirroring the fields in finalizeLogin and adding client_id, subject_token_type and requested_token_type for the context specific to this grant. Signed-off-by: Helio Machado <0x2b3bfa0+git@googlemail.com>
1 parent f074c3d commit b0ac753

2 files changed

Lines changed: 63 additions & 0 deletions

File tree

server/handlers.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1856,6 +1856,18 @@ func (s *Server) handleTokenExchange(w http.ResponseWriter, r *http.Request, cli
18561856
return
18571857
}
18581858

1859+
email := identity.Email
1860+
if !identity.EmailVerified {
1861+
email += " (unverified)"
1862+
}
1863+
1864+
s.logger.InfoContext(ctx, "token exchange successful",
1865+
"connector_id", connID, "client_id", client.ID,
1866+
"user_id", identity.UserID,
1867+
"username", identity.Username, "preferred_username", identity.PreferredUsername,
1868+
"email", email, "groups", identity.Groups,
1869+
"subject_token_type", subjectTokenType, "requested_token_type", requestedTokenType)
1870+
18591871
claims := storage.Claims{
18601872
UserID: identity.UserID,
18611873
Username: identity.Username,

server/handlers_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/json"
77
"errors"
88
"fmt"
9+
"log/slog"
910
"net/http"
1011
"net/http/httptest"
1112
"net/url"
@@ -1741,6 +1742,56 @@ func TestHandleTokenExchange(t *testing.T) {
17411742
}
17421743
}
17431744

1745+
func TestHandleTokenExchangeLogsSuccess(t *testing.T) {
1746+
ctx := t.Context()
1747+
var logBuf bytes.Buffer
1748+
httpServer, s := newTestServer(t, func(c *Config) {
1749+
c.Logger = slog.New(slog.NewJSONHandler(&logBuf, &slog.HandlerOptions{Level: slog.LevelInfo}))
1750+
c.Storage.CreateClient(ctx, storage.Client{
1751+
ID: "client_1",
1752+
Secret: "secret_1",
1753+
})
1754+
})
1755+
defer httpServer.Close()
1756+
1757+
vals := make(url.Values)
1758+
vals.Set("grant_type", grantTypeTokenExchange)
1759+
vals.Set("connector_id", "mock")
1760+
vals.Set("scope", "openid")
1761+
vals.Set("requested_token_type", tokenTypeAccess)
1762+
vals.Set("subject_token_type", tokenTypeID)
1763+
vals.Set("subject_token", "foobar")
1764+
vals.Set("client_id", "client_1")
1765+
vals.Set("client_secret", "secret_1")
1766+
1767+
rr := httptest.NewRecorder()
1768+
req := httptest.NewRequest(http.MethodPost, httpServer.URL+"/token", strings.NewReader(vals.Encode()))
1769+
req.Header.Set("content-type", "application/x-www-form-urlencoded")
1770+
1771+
s.handleToken(rr, req)
1772+
require.Equal(t, http.StatusOK, rr.Code, rr.Body.String())
1773+
1774+
var found map[string]any
1775+
for _, line := range strings.Split(strings.TrimSpace(logBuf.String()), "\n") {
1776+
var entry map[string]any
1777+
require.NoError(t, json.Unmarshal([]byte(line), &entry))
1778+
if entry["msg"] == "token exchange successful" {
1779+
found = entry
1780+
break
1781+
}
1782+
}
1783+
require.NotNil(t, found, "expected \"token exchange successful\" log line, got: %s", logBuf.String())
1784+
require.Equal(t, "INFO", found["level"])
1785+
require.Equal(t, "mock", found["connector_id"])
1786+
require.Equal(t, "client_1", found["client_id"])
1787+
require.Equal(t, "0-385-28089-0", found["user_id"])
1788+
require.Equal(t, "Kilgore Trout", found["username"])
1789+
require.Equal(t, "kilgore@kilgore.trout", found["email"])
1790+
require.Equal(t, []any{"authors"}, found["groups"])
1791+
require.Equal(t, tokenTypeID, found["subject_token_type"])
1792+
require.Equal(t, tokenTypeAccess, found["requested_token_type"])
1793+
}
1794+
17441795
func TestHandleTokenExchangeConnectorGrantTypeRestriction(t *testing.T) {
17451796
ctx := t.Context()
17461797
httpServer, s := newTestServer(t, func(c *Config) {

0 commit comments

Comments
 (0)