This is an automated email from the ASF dual-hosted git repository. LauraXia123 pushed a commit to branch issue-12588 in repository https://gitbox.apache.org/repos/asf/gravitino.git
commit 6a9b1bb89088518243711a483e4a29bfe9731449 Author: Qian Xia <[email protected]> AuthorDate: Tue Aug 25 14:18:27 2026 +0800 [#12588] improvement(web2-ui): Expose UI session timeout settings through server configuration --- conf/gravitino.conf.template | 14 ++++- .../web/src/lib/hooks/useAbsoluteSessionTimeout.js | 10 +--- web-v2/web/src/lib/hooks/useIdleTimeout.js | 8 +-- web-v2/web/src/lib/provider/IdleSessionProvider.js | 51 +++++++++--------- .../web/src/lib/provider/sessionTimeoutConfig.js | 62 ++++++++++++++++++++++ 5 files changed, 103 insertions(+), 42 deletions(-) diff --git a/conf/gravitino.conf.template b/conf/gravitino.conf.template index c9afcc025c..861dd8829d 100644 --- a/conf/gravitino.conf.template +++ b/conf/gravitino.conf.template @@ -43,6 +43,17 @@ gravitino.server.webserver.threadPoolWorkQueueSize = 100 gravitino.server.webserver.requestHeaderSize = 131072 # The response header size of the built-in web server gravitino.server.webserver.responseHeaderSize = 131072 +# UI inactivity timeout in milliseconds. The UI environment variable +# NEXT_PUBLIC_IDLE_TIMEOUT_MS is used when this config is not set. +# gravitino.server.webserver.sessionIdleTimeoutMs = 900000 +# Maximum UI session duration in milliseconds, regardless of user activity. The UI environment +# variable NEXT_PUBLIC_MAX_SESSION_DURATION_MS is used when this config is not set. +# gravitino.server.webserver.sessionMaxDurationMs = 18000000 +# UI warning countdown duration in milliseconds before the inactivity timeout. The UI environment +# variable NEXT_PUBLIC_IDLE_WARNING_LEAD_MS is used when this config is not set. +# gravitino.server.webserver.sessionIdleWarningLeadMs = 60000 +# multiple visibleConfigs are spitted by comma. +# gravitino.server.visibleConfigs=gravitino.server.webserver.sessionIdleTimeoutMs,gravitino.server.webserver.sessionMaxDurationMs,gravitino.server.webserver.sessionIdleWarningLeadMs # THE CONFIGURATION FOR Gravitino ENTITY STORE # The entity store to use, we only supports relational @@ -88,8 +99,7 @@ gravitino.fetchFile.blockUnsafeRemoteUri = true # THE CONFIGURATION FOR authorization # Whether Gravitino enable authorization or not gravitino.authorization.enable = false -# The admins of Gravitino service, multiple admins are spitted by comma. -gravitino.server.visibleConfigs=gravitino.authorization.serviceAdmins +# The admins of Gravitino service gravitino.authorization.serviceAdmins = anonymous # THE CONFIGURATION FOR AUXILIARY SERVICE diff --git a/web-v2/web/src/lib/hooks/useAbsoluteSessionTimeout.js b/web-v2/web/src/lib/hooks/useAbsoluteSessionTimeout.js index 58dab82730..41a6d84946 100644 --- a/web-v2/web/src/lib/hooks/useAbsoluteSessionTimeout.js +++ b/web-v2/web/src/lib/hooks/useAbsoluteSessionTimeout.js @@ -30,14 +30,8 @@ const SESSION_START_KEY = 'grtv-session-start' /** * Default maximum session duration: 5 hours in milliseconds. - * Overridable via NEXT_PUBLIC_MAX_SESSION_DURATION_MS environment variable. */ -const DEFAULT_MAX_SESSION_DURATION_MS = (() => { - const envVal = process.env.NEXT_PUBLIC_MAX_SESSION_DURATION_MS - const parsed = envVal ? Number(envVal) : NaN - - return Number.isFinite(parsed) && parsed > 0 ? parsed : 5 * 60 * 60 * 1000 -})() +const DEFAULT_MAX_SESSION_DURATION_MS = 5 * 60 * 60 * 1000 /** * Custom hook for absolute session duration enforcement. @@ -54,7 +48,7 @@ const DEFAULT_MAX_SESSION_DURATION_MS = (() => { * When true and no session start exists, the current time is recorded. * When false, the session start is cleared. * @param {number} [options.maxDurationMs] - Maximum session duration in milliseconds. - * Defaults to NEXT_PUBLIC_MAX_SESSION_DURATION_MS env var or 5 hours. + * Defaults to 5 hours. * @returns {{ isExpired: boolean, remainingMs: number, clearSession: () => void }} */ export function useAbsoluteSessionTimeout({ isAuthenticated, maxDurationMs = DEFAULT_MAX_SESSION_DURATION_MS } = {}) { diff --git a/web-v2/web/src/lib/hooks/useIdleTimeout.js b/web-v2/web/src/lib/hooks/useIdleTimeout.js index dc4246d748..56fb67a2a0 100644 --- a/web-v2/web/src/lib/hooks/useIdleTimeout.js +++ b/web-v2/web/src/lib/hooks/useIdleTimeout.js @@ -35,14 +35,8 @@ const DISABLED_SENTINEL = Number.MAX_SAFE_INTEGER /** * Default idle timeout: 15 minutes in milliseconds. - * Overridable via NEXT_PUBLIC_IDLE_TIMEOUT_MS environment variable. */ -const DEFAULT_IDLE_TIMEOUT_MS = (() => { - const envVal = process.env.NEXT_PUBLIC_IDLE_TIMEOUT_MS - const parsed = envVal ? Number(envVal) : NaN - - return Number.isFinite(parsed) && parsed > 0 ? parsed : 15 * 60 * 1000 -})() +const DEFAULT_IDLE_TIMEOUT_MS = 15 * 60 * 1000 /** * Throttle interval for activity events in milliseconds. diff --git a/web-v2/web/src/lib/provider/IdleSessionProvider.js b/web-v2/web/src/lib/provider/IdleSessionProvider.js index 0ffdcb8302..974909f2fe 100644 --- a/web-v2/web/src/lib/provider/IdleSessionProvider.js +++ b/web-v2/web/src/lib/provider/IdleSessionProvider.js @@ -28,28 +28,19 @@ import { useBroadcastChannel } from '@/lib/hooks/useBroadcastChannel' import { logoutAction } from '@/lib/store/auth' import IdleSessionContext from './IdleSessionContext' import IdleWarningModal from '@/components/IdleWarningModal' +import { resolveSessionTimeouts } from './sessionTimeoutConfig' /** * Default idle timeout: 15 minutes in milliseconds. - * Overridable via NEXT_PUBLIC_IDLE_TIMEOUT_MS environment variable. */ -const DEFAULT_IDLE_TIMEOUT_MS = (() => { - const envVal = process.env.NEXT_PUBLIC_IDLE_TIMEOUT_MS - const parsed = envVal ? Number(envVal) : NaN - - return Number.isFinite(parsed) && parsed > 0 ? parsed : 15 * 60 * 1000 -})() +const DEFAULT_IDLE_TIMEOUT_MS = 15 * 60 * 1000 /** * Default warning lead time: 60 seconds in milliseconds. - * Overridable via NEXT_PUBLIC_IDLE_WARNING_LEAD_MS environment variable. */ -const DEFAULT_WARNING_LEAD_MS = (() => { - const envVal = process.env.NEXT_PUBLIC_IDLE_WARNING_LEAD_MS - const parsed = envVal ? Number(envVal) : NaN +const DEFAULT_WARNING_LEAD_MS = 60 * 1000 - return Number.isFinite(parsed) && parsed > 0 ? parsed : 60 * 1000 -})() +const DEFAULT_MAX_SESSION_DURATION_MS = 5 * 60 * 60 * 1000 /** * Converts milliseconds to seconds, rounded up. @@ -72,17 +63,26 @@ function msToSeconds(ms) { * @param {React.ReactNode} props.children * @param {number} [props.idleTimeoutMs] - Idle timeout in milliseconds (default: 15 min) * @param {number} [props.warningLeadMs] - Warning lead time in milliseconds (default: 60s) + * @param {number} [props.maxSessionDurationMs] - Maximum session duration (default: 5 hours) */ export default function IdleSessionProvider({ children, idleTimeoutMs = DEFAULT_IDLE_TIMEOUT_MS, - warningLeadMs = DEFAULT_WARNING_LEAD_MS + warningLeadMs = DEFAULT_WARNING_LEAD_MS, + maxSessionDurationMs = DEFAULT_MAX_SESSION_DURATION_MS }) { const router = useRouter() const pathname = usePathname() const dispatch = useAppDispatch() const authType = useAppSelector(state => state.auth.authType) const authToken = useAppSelector(state => state.auth.authToken) + const systemConfig = useAppSelector(state => state.auth.systemConfig) + + const { + idleTimeoutMs: resolvedIdleTimeoutMs, + warningLeadMs: resolvedWarningLeadMs, + maxSessionDurationMs: resolvedMaxSessionDurationMs + } = resolveSessionTimeouts(systemConfig, { idleTimeoutMs, warningLeadMs, maxSessionDurationMs }) // Only enable idle timeout when the user is authenticated and not on login page. // Fall back to persisted token in localStorage to avoid prematurely treating @@ -98,7 +98,7 @@ export default function IdleSessionProvider({ const isLoginPage = pathname.endsWith('/login') const [state, setState] = useState('active') - const [warningCountdown, setWarningCountdown] = useState(msToSeconds(warningLeadMs)) + const [warningCountdown, setWarningCountdown] = useState(msToSeconds(resolvedWarningLeadMs)) const loggedOutRef = useRef(false) const wasAuthenticatedRef = useRef(isAuthenticated) @@ -107,7 +107,7 @@ export default function IdleSessionProvider({ resetActivity: resetIdleActivity, idleTimeRemaining } = useIdleTimeout({ - idleTimeoutMs: isAuthenticated ? idleTimeoutMs : Number.MAX_SAFE_INTEGER, + idleTimeoutMs: isAuthenticated ? resolvedIdleTimeoutMs : Number.MAX_SAFE_INTEGER, // Pause DOM activity detection during warning state so the modal stays // visible until the user explicitly clicks "Stay signed in" or "Sign out now" @@ -116,13 +116,14 @@ export default function IdleSessionProvider({ // Absolute session duration: forces logout after a fixed time regardless of activity const { isExpired: isAbsoluteExpired } = useAbsoluteSessionTimeout({ - isAuthenticated + isAuthenticated, + maxDurationMs: resolvedMaxSessionDurationMs }) const { sendMessage, onMessage } = useBroadcastChannel() // Track warning threshold: when idleTimeRemaining drops below warningLeadMs, show warning - const warningThreshold = warningLeadMs + const warningThreshold = resolvedWarningLeadMs // State machine transitions useEffect(() => { @@ -190,11 +191,11 @@ export default function IdleSessionProvider({ const handleStaySignedIn = useCallback(() => { setState('active') resetIdleActivity() - setWarningCountdown(msToSeconds(warningLeadMs)) + setWarningCountdown(msToSeconds(resolvedWarningLeadMs)) // Broadcast stay_signed_in to other tabs so they also dismiss warning and stay logged in sendMessage({ type: 'stay_signed_in', timestamp: Date.now() }) - }, [resetIdleActivity, warningLeadMs, sendMessage]) + }, [resetIdleActivity, resolvedWarningLeadMs, sendMessage]) // Cross-tab message handling (IST-REQ-005) useEffect(() => { @@ -204,14 +205,14 @@ export default function IdleSessionProvider({ resetIdleActivity() if (state === 'warning') { setState('active') - setWarningCountdown(msToSeconds(warningLeadMs)) + setWarningCountdown(msToSeconds(resolvedWarningLeadMs)) } } else if (message.type === 'stay_signed_in') { // Another tab clicked "Stay signed in" — dismiss warning and stay logged in resetIdleActivity() if (state === 'warning') { setState('active') - setWarningCountdown(msToSeconds(warningLeadMs)) + setWarningCountdown(msToSeconds(resolvedWarningLeadMs)) } } else if (message.type === 'logout') { // Another tab triggered logout — clear local auth state and redirect @@ -230,7 +231,7 @@ export default function IdleSessionProvider({ } } }) - }, [onMessage, resetIdleActivity, router, state, warningLeadMs, dispatch]) + }, [onMessage, resetIdleActivity, router, state, resolvedWarningLeadMs, dispatch]) // Broadcast activity to other tabs when this tab has activity (IST-REQ-005) // Only broadcast when idleTimeRemaining jumps UP (indicating a timer reset), @@ -275,7 +276,7 @@ export default function IdleSessionProvider({ loggedOutRef.current = false setState('active') resetIdleActivity() - setWarningCountdown(msToSeconds(warningLeadMs)) + setWarningCountdown(msToSeconds(resolvedWarningLeadMs)) } // Broadcast authenticated→unauthenticated transition (e.g., manual logout from user menu) @@ -285,7 +286,7 @@ export default function IdleSessionProvider({ } wasAuthenticatedRef.current = isAuthenticated - }, [isAuthenticated, resetIdleActivity, sendMessage, warningLeadMs]) + }, [isAuthenticated, resetIdleActivity, sendMessage, resolvedWarningLeadMs]) // Context value const contextValue = { diff --git a/web-v2/web/src/lib/provider/sessionTimeoutConfig.js b/web-v2/web/src/lib/provider/sessionTimeoutConfig.js new file mode 100644 index 0000000000..6a7d0947ab --- /dev/null +++ b/web-v2/web/src/lib/provider/sessionTimeoutConfig.js @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const SESSION_IDLE_TIMEOUT_KEY = 'gravitino.server.webserver.sessionIdleTimeoutMs' + +const SESSION_MAX_DURATION_KEY = 'gravitino.server.webserver.sessionMaxDurationMs' + +const SESSION_IDLE_WARNING_LEAD_KEY = 'gravitino.server.webserver.sessionIdleWarningLeadMs' + +function resolveSessionDuration(serverValue, envValue, defaultValue) { + const serverDuration = Number(serverValue) + if (Number.isFinite(serverDuration) && serverDuration > 0) { + return serverDuration + } + + const envDuration = Number(envValue) + + return Number.isFinite(envDuration) && envDuration > 0 ? envDuration : defaultValue +} + +/** + * Resolves all UI session timeout settings. + * + * @param {Object} systemConfig values returned by the /configs endpoint + * @param {Object} defaults built-in fallback values + * @returns {{idleTimeoutMs: number, warningLeadMs: number, maxSessionDurationMs: number}} + */ +export function resolveSessionTimeouts(systemConfig, defaults) { + return { + idleTimeoutMs: resolveSessionDuration( + systemConfig?.[SESSION_IDLE_TIMEOUT_KEY], + process.env.NEXT_PUBLIC_IDLE_TIMEOUT_MS, + defaults.idleTimeoutMs + ), + warningLeadMs: resolveSessionDuration( + systemConfig?.[SESSION_IDLE_WARNING_LEAD_KEY], + process.env.NEXT_PUBLIC_IDLE_WARNING_LEAD_MS, + defaults.warningLeadMs + ), + maxSessionDurationMs: resolveSessionDuration( + systemConfig?.[SESSION_MAX_DURATION_KEY], + process.env.NEXT_PUBLIC_MAX_SESSION_DURATION_MS, + defaults.maxSessionDurationMs + ) + } +}
