From 9619444da253c0def69142271d09b42ab691adb9 Mon Sep 17 00:00:00 2001 From: NamanMahor Date: Thu, 14 May 2026 11:57:18 +0530 Subject: [PATCH 1/3] updating ttl to 14days --- admin/server/auth/handlers.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/admin/server/auth/handlers.go b/admin/server/auth/handlers.go index 33d986031dc..72e90ae5dfa 100644 --- a/admin/server/auth/handlers.go +++ b/admin/server/auth/handlers.go @@ -341,8 +341,9 @@ func (a *Authenticator) authLoginCallback(w http.ResponseWriter, r *http.Request return } + ttl := 14 * 24 * time.Hour // Issue a new persistent auth token - authToken, err := a.admin.IssueUserAuthToken(r.Context(), user.ID, database.AuthClientIDRillWeb, "Browser session", nil, nil, false) + authToken, err := a.admin.IssueUserAuthToken(r.Context(), user.ID, database.AuthClientIDRillWeb, "Browser session", nil, &ttl, false) if err != nil { http.Error(w, fmt.Sprintf("failed to issue API token: %s", err), http.StatusInternalServerError) return @@ -405,7 +406,8 @@ func (a *Authenticator) authLoginCustomDomainCallback(w http.ResponseWriter, r * http.Error(w, err.Error(), http.StatusUnauthorized) return } - newAuthToken, err := a.admin.IssueUserAuthToken(r.Context(), validated.OwnerID(), database.AuthClientIDRillWeb, "Browser session", nil, nil, false) + ttl := 14 * 24 * time.Hour + newAuthToken, err := a.admin.IssueUserAuthToken(r.Context(), validated.OwnerID(), database.AuthClientIDRillWeb, "Browser session", nil, &ttl, false) if err != nil { http.Error(w, fmt.Sprintf("failed to issue API token: %s", err), http.StatusInternalServerError) return From 2bcc8fd32c8fd1767a0124dedcb10aeddb284422 Mon Sep 17 00:00:00 2001 From: NamanMahor Date: Fri, 15 May 2026 11:08:15 +0530 Subject: [PATCH 2/3] review comment --- admin/server/auth/handlers.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/admin/server/auth/handlers.go b/admin/server/auth/handlers.go index 72e90ae5dfa..16f0a0121b5 100644 --- a/admin/server/auth/handlers.go +++ b/admin/server/auth/handlers.go @@ -29,6 +29,7 @@ const ( cookieFieldRedirect = "redirect" cookieFieldCustomDomainFlow = "custom_domain_flow" cookieFieldAccessToken = "access_token" + browserSessionTTL = 14 * 24 * time.Hour ) // RegisterEndpoints adds HTTP endpoints for auth. @@ -341,8 +342,8 @@ func (a *Authenticator) authLoginCallback(w http.ResponseWriter, r *http.Request return } - ttl := 14 * 24 * time.Hour // Issue a new persistent auth token + ttl := browserSessionTTL authToken, err := a.admin.IssueUserAuthToken(r.Context(), user.ID, database.AuthClientIDRillWeb, "Browser session", nil, &ttl, false) if err != nil { http.Error(w, fmt.Sprintf("failed to issue API token: %s", err), http.StatusInternalServerError) @@ -406,7 +407,7 @@ func (a *Authenticator) authLoginCustomDomainCallback(w http.ResponseWriter, r * http.Error(w, err.Error(), http.StatusUnauthorized) return } - ttl := 14 * 24 * time.Hour + ttl := browserSessionTTL newAuthToken, err := a.admin.IssueUserAuthToken(r.Context(), validated.OwnerID(), database.AuthClientIDRillWeb, "Browser session", nil, &ttl, false) if err != nil { http.Error(w, fmt.Sprintf("failed to issue API token: %s", err), http.StatusInternalServerError) From f45138002fbf9ba81589896f1c668662a0a0f5c8 Mon Sep 17 00:00:00 2001 From: NamanMahor Date: Mon, 25 May 2026 10:47:11 +0530 Subject: [PATCH 3/3] Review comments --- admin/auth_token.go | 20 ++++++++++++++++++++ admin/database/database.go | 1 + admin/database/postgres/postgres.go | 8 ++++++++ admin/server/auth/handlers.go | 12 +++++++----- admin/server/auth/middleware.go | 13 +++++++++++-- 5 files changed, 47 insertions(+), 7 deletions(-) diff --git a/admin/auth_token.go b/admin/auth_token.go index e702eb09c5c..3d89081524e 100644 --- a/admin/auth_token.go +++ b/admin/auth_token.go @@ -226,6 +226,26 @@ func (s *Service) IssueMagicAuthToken(ctx context.Context, opts *IssueMagicAuthT return &magicAuthToken{model: dat, token: tkn}, nil } +// ExtendBrowserSessionAuthToken extends a Rill web browser session token when its +// remaining lifetime is at or below refreshThreshold. +func (s *Service) ExtendBrowserSessionAuthToken(ctx context.Context, authTok AuthToken, fullTTL, refreshThreshold time.Duration) error { + uat, ok := authTok.TokenModel().(*database.UserAuthToken) + if !ok { + return nil + } + if uat.AuthClientID == nil || *uat.AuthClientID != database.AuthClientIDRillWeb { + return nil + } + if uat.RepresentingUserID != nil || uat.Refresh { + return nil + } + if uat.ExpiresOn != nil && time.Until(*uat.ExpiresOn) > refreshThreshold { + return nil + } + newExpiresOn := time.Now().Add(fullTTL) + return s.DB.UpdateUserAuthTokenExpiresOn(ctx, uat.ID, newExpiresOn) +} + // RevokeAuthToken removes an auth token from persistent storage. func (s *Service) RevokeAuthToken(ctx context.Context, token string) error { parsed, err := authtoken.FromString(token) diff --git a/admin/database/database.go b/admin/database/database.go index aa7034aa0fb..f554c1f8d7f 100644 --- a/admin/database/database.go +++ b/admin/database/database.go @@ -161,6 +161,7 @@ type DB interface { FindUserAuthToken(ctx context.Context, id string) (*UserAuthToken, error) InsertUserAuthToken(ctx context.Context, opts *InsertUserAuthTokenOptions) (*UserAuthToken, error) UpdateUserAuthTokenUsedOn(ctx context.Context, ids []string) error + UpdateUserAuthTokenExpiresOn(ctx context.Context, id string, expiresOn time.Time) error DeleteUserAuthToken(ctx context.Context, id string) error DeleteAllUserAuthTokens(ctx context.Context, userID string) (int, error) DeleteUserAuthTokensByUserAndRepresentingUser(ctx context.Context, userID, representingUserID string) error diff --git a/admin/database/postgres/postgres.go b/admin/database/postgres/postgres.go index de57535790d..c6c7f6f64d2 100644 --- a/admin/database/postgres/postgres.go +++ b/admin/database/postgres/postgres.go @@ -1221,6 +1221,14 @@ func (c *connection) UpdateUserAuthTokenUsedOn(ctx context.Context, ids []string return nil } +func (c *connection) UpdateUserAuthTokenExpiresOn(ctx context.Context, id string, expiresOn time.Time) error { + _, err := c.getDB(ctx).ExecContext(ctx, "UPDATE user_auth_tokens SET expires_on=$2 WHERE id=$1", id, expiresOn) + if err != nil { + return parseErr("auth token", err) + } + return nil +} + func (c *connection) DeleteUserAuthToken(ctx context.Context, id string) error { res, err := c.getDB(ctx).ExecContext(ctx, "DELETE FROM user_auth_tokens WHERE id=$1", id) return checkDeleteRow("auth token", res, err) diff --git a/admin/server/auth/handlers.go b/admin/server/auth/handlers.go index 16f0a0121b5..7a30a83ab4a 100644 --- a/admin/server/auth/handlers.go +++ b/admin/server/auth/handlers.go @@ -29,7 +29,11 @@ const ( cookieFieldRedirect = "redirect" cookieFieldCustomDomainFlow = "custom_domain_flow" cookieFieldAccessToken = "access_token" - browserSessionTTL = 14 * 24 * time.Hour +) + +var ( + browserSessionTTL = 14 * 24 * time.Hour + browserSessionTTLRefreshThreshold = browserSessionTTL - 24*time.Hour ) // RegisterEndpoints adds HTTP endpoints for auth. @@ -343,8 +347,7 @@ func (a *Authenticator) authLoginCallback(w http.ResponseWriter, r *http.Request } // Issue a new persistent auth token - ttl := browserSessionTTL - authToken, err := a.admin.IssueUserAuthToken(r.Context(), user.ID, database.AuthClientIDRillWeb, "Browser session", nil, &ttl, false) + authToken, err := a.admin.IssueUserAuthToken(r.Context(), user.ID, database.AuthClientIDRillWeb, "Browser session", nil, &browserSessionTTL, false) if err != nil { http.Error(w, fmt.Sprintf("failed to issue API token: %s", err), http.StatusInternalServerError) return @@ -407,8 +410,7 @@ func (a *Authenticator) authLoginCustomDomainCallback(w http.ResponseWriter, r * http.Error(w, err.Error(), http.StatusUnauthorized) return } - ttl := browserSessionTTL - newAuthToken, err := a.admin.IssueUserAuthToken(r.Context(), validated.OwnerID(), database.AuthClientIDRillWeb, "Browser session", nil, &ttl, false) + newAuthToken, err := a.admin.IssueUserAuthToken(r.Context(), validated.OwnerID(), database.AuthClientIDRillWeb, "Browser session", nil, &browserSessionTTL, false) if err != nil { http.Error(w, fmt.Sprintf("failed to issue API token: %s", err), http.StatusInternalServerError) return diff --git a/admin/server/auth/middleware.go b/admin/server/auth/middleware.go index 92ca6296d3a..3ebf12be680 100644 --- a/admin/server/auth/middleware.go +++ b/admin/server/auth/middleware.go @@ -11,6 +11,7 @@ import ( "github.com/grpc-ecosystem/go-grpc-middleware/util/metautils" "github.com/rilldata/rill/runtime/pkg/observability" semconv "go.opentelemetry.io/otel/semconv/v1.21.0" + "go.uber.org/zap" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" @@ -91,8 +92,8 @@ func (a *Authenticator) HTTPMiddlewareLenient(next http.Handler) http.Handler { } // CookieRefreshMiddleware is a middleware that refreshes the auth cookie. -// This enables us to do rolling cookie refreshes so we can have a relatively short cookie max age. -// Note that it does not update the auth token encrypted inside the cookie. +// This enables us to do rolling cookie refreshes so we can have a relatively short cookie max age (browserSessionTTL). +// Once per day we refresh the auth token TTL if it's still valid. func (a *Authenticator) CookieRefreshMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { sess := a.cookies.Get(r, cookieName) @@ -102,6 +103,14 @@ func (a *Authenticator) CookieRefreshMiddleware(next http.Handler) http.Handler http.Error(w, err.Error(), http.StatusInternalServerError) return } + validatedToken, err := a.admin.ValidateAuthToken(r.Context(), authToken) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if err := a.admin.ExtendBrowserSessionAuthToken(r.Context(), validatedToken, browserSessionTTL, browserSessionTTLRefreshThreshold); err != nil { + a.logger.Info("failed to extend browser session auth token TTL", zap.Error(err), observability.ZapCtx(r.Context())) + } } next.ServeHTTP(w, r) })