removed ConfigBasedAutoLogoutProvider

Signed-off-by: Manuel Scurti <manuel.scurti@agilelab.it>
This commit is contained in:
Manuel Scurti
2023-09-06 18:25:29 +02:00
parent 60a19896f0
commit 1bfb54bbaa
15 changed files with 216 additions and 239 deletions
-7
View File
@@ -49,13 +49,6 @@ export interface Config {
*/
useWorkerTimers?: boolean;
/**
* List of events that the autologout event listener will catch to detect if the user is active
* These events are standard DOM events.
* @visibility frontend
*/
events?: string[];
/**
* Enable/disable the automatic logout also on users that are logged in but with no Backstage tabs open.
* Default is true.
+1 -7
View File
@@ -110,11 +110,5 @@
"dist",
"config.d.ts"
],
"configSchema": "config.d.ts",
"jest": {
"resetMocks": false,
"setupFiles": [
"jest-localstorage-mock"
]
}
"configSchema": "config.d.ts"
}
@@ -14,13 +14,19 @@
* limitations under the License.
*/
import { identityApiRef, useApi } from '@backstage/core-plugin-api';
import React, { PropsWithChildren, useEffect, useMemo, useState } from 'react';
import {
ConfigApi,
configApiRef,
IdentityApi,
identityApiRef,
useApi,
} from '@backstage/core-plugin-api';
import React, { useEffect, useMemo, useState } from 'react';
import {
EventsType,
IdleTimerProvider,
IIdleTimer,
workerTimers,
useIdleTimer,
} from 'react-idle-timer';
import {
@@ -34,10 +40,10 @@ import { DefaultTimestampStore, TimestampStore } from './timestampStore';
export type AutoLogoutTrackableEvent = EventsType;
/** @public */
export type AutoLogoutProviderProps = {
export type AutoLogoutProps = {
/**
* Enable/disable the AutoLogoutMechanism.
* defaults to true
* defauls to true.
*/
enabled?: boolean;
/**
@@ -60,11 +66,6 @@ export type AutoLogoutProviderProps = {
* defaults to true.
*/
useWorkerTimers?: boolean;
/**
* List of DOM events that the AutoLogoutProvider will track to determine if the user is active or not.
* default list includes all the needed events for keyboard/pointers/mouse devices.
*/
events?: AutoLogoutTrackableEvent[];
/**
* Enable/disable the autologout for disconnected users.
* disconnected users are the ones that are logged in but have no Backstage tab open in their browsers.
@@ -74,6 +75,153 @@ export type AutoLogoutProviderProps = {
logoutIfDisconnected?: boolean;
};
type AutoLogoutInternalProps = Omit<Required<AutoLogoutProps>, 'enabled'> & {
events: AutoLogoutTrackableEvent[];
promptOpen: boolean;
setPromptOpen: (value: boolean) => void;
remainingTimeCountdown: number;
setRemainingTimeCountdown: (amount: number) => void;
identityApi: IdentityApi;
lastSeenOnlineStore: TimestampStore;
};
const ConditionalAutoLogout = ({
idleTimeoutMinutes,
events,
useWorkerTimers,
logoutIfDisconnected,
promptBeforeIdleSeconds,
promptOpen,
setPromptOpen,
remainingTimeCountdown,
setRemainingTimeCountdown,
identityApi,
lastSeenOnlineStore,
}: AutoLogoutInternalProps): JSX.Element => {
const promptBeforeIdleMillis = promptBeforeIdleSeconds * 1000;
const promptBeforeIdle = promptBeforeIdleMillis > 0 ? true : false;
const onPrompt = () => {
// onPrompt will be called `promptBeforeIdle` milliseconds before `timeout`.
// All events are disabled while the prompt is active.
// If the user wishes to stay active, call the `activate()` method.
// You can get the remaining prompt time with the `getRemainingTime()` method,
setPromptOpen(true);
setRemainingTimeCountdown(promptBeforeIdleMillis);
};
const onIdle = () => {
// onIdle will be called after the timeout is reached.
// Events will be rebound as long as `stopOnMount` is not set.
setPromptOpen(false);
setRemainingTimeCountdown(0);
identityApi.signOut();
};
const onActive = () => {
// onActive will only be called if `activate()` is called while `isPrompted()`
// is true. Here you will also want to close your modal and perform
// any active actions.
setPromptOpen(false);
setRemainingTimeCountdown(0);
};
const onAction = (
_event?: Event | undefined,
_idleTimer?: IIdleTimer | null,
) => {
// onAction will be called if any user event is detected. The list of events that triggers a user event detection is the list of configured events
// If any user event is detected we update the Last seen online in storage
lastSeenOnlineStore.save(new Date());
};
const timer = useIdleTimer({
timeout: idleTimeoutMinutes * 60 * 1000,
events: events,
crossTab: true,
name: 'autologout-timer',
timers: useWorkerTimers ? workerTimers : undefined,
onIdle: onIdle,
onActive: promptBeforeIdle ? onActive : undefined,
onAction: logoutIfDisconnected ? onAction : undefined,
onPrompt: promptBeforeIdle ? onPrompt : undefined,
promptBeforeIdle: promptBeforeIdle ? promptBeforeIdleMillis : undefined,
syncTimers: 1000,
});
return (
<>
{promptBeforeIdle && (
<StillTherePrompt
idleTimer={timer}
open={promptOpen}
setOpen={setPromptOpen}
remainingTime={remainingTimeCountdown}
setRemainingTime={setRemainingTimeCountdown}
promptTimeoutMillis={promptBeforeIdleMillis}
/>
)}
</>
);
};
const defaultConfig: Required<AutoLogoutProps> = {
enabled: true,
idleTimeoutMinutes: 0.5,
promptBeforeIdleSeconds: 10,
useWorkerTimers: true,
logoutIfDisconnected: true,
};
/**
* A list of DOM events that the activity tracker will use to determine if the user is active or not.
*/
const defaultTrackedEvents: AutoLogoutTrackableEvent[] = [
'mousemove',
'keydown',
'wheel',
'DOMMouseScroll',
'mousewheel',
'mousedown',
'touchstart',
'touchmove',
'MSPointerDown',
'MSPointerMove',
'visibilitychange',
];
/**
* Parses configuration for the AutoLogout. Properties configured in `app-config` take precedence over the props passed to the React component.
* If neither props nor config properties are found, a default value will be set accordingly.
*/
const parseConfig = (
configApi: ConfigApi,
props: AutoLogoutProps,
): Required<AutoLogoutProps> => {
return {
enabled:
configApi.getOptionalBoolean('auth.autologout.enabled') ??
props.enabled ??
defaultConfig.enabled,
idleTimeoutMinutes:
configApi.getOptionalNumber('auth.autologout.idleTimeoutMinutes') ??
props.idleTimeoutMinutes ??
defaultConfig.idleTimeoutMinutes,
promptBeforeIdleSeconds:
configApi.getOptionalNumber('auth.autologout.promptBeforeIdleSeconds') ??
props.promptBeforeIdleSeconds ??
defaultConfig.promptBeforeIdleSeconds,
useWorkerTimers:
configApi.getOptionalBoolean('auth.autologout.useWorkerTimers') ??
props.useWorkerTimers ??
defaultConfig.useWorkerTimers,
logoutIfDisconnected:
configApi.getOptionalBoolean('auth.autologout.logoutIfDisconnected') ??
props.logoutIfDisconnected ??
defaultConfig.logoutIfDisconnected,
};
};
/**
* The Autologout feature enables platform engineers to add a mechanism to log out users after a configurable amount of time of inactivity.
* When enabled, the mechanism will track user actions (mouse movement, mouse click, key pressing, taps, etc.) in order to determine if they are active or not.
@@ -81,28 +229,19 @@ export type AutoLogoutProviderProps = {
*
* @public
*/
export const AutoLogoutProvider = ({
children,
enabled = true,
idleTimeoutMinutes = 60,
promptBeforeIdleSeconds = 10,
useWorkerTimers = true,
events = [
'mousemove',
'keydown',
'wheel',
'DOMMouseScroll',
'mousewheel',
'mousedown',
'touchstart',
'touchmove',
'MSPointerDown',
'MSPointerMove',
'visibilitychange',
],
logoutIfDisconnected = true,
}: PropsWithChildren<AutoLogoutProviderProps>): JSX.Element => {
export const AutoLogout = (props: AutoLogoutProps): JSX.Element => {
const identityApi = useApi(identityApiRef);
const configApi = useApi(configApiRef);
const {
enabled,
idleTimeoutMinutes,
promptBeforeIdleSeconds,
logoutIfDisconnected,
useWorkerTimers,
}: AutoLogoutProps = useMemo(() => {
return parseConfig(configApi, props);
}, [configApi, props]);
useEffect(() => {
if (idleTimeoutMinutes < 0.5) {
@@ -142,70 +281,22 @@ export const AutoLogoutProvider = ({
});
if (!enabled) {
return <>{children}</>;
return <></>;
}
const promptBeforeIdleMillis = promptBeforeIdleSeconds * 1000;
const promptBeforeIdle = promptBeforeIdleMillis > 0 ? true : false;
const onPrompt = () => {
// onPrompt will be called `promptBeforeIdle` milliseconds before `timeout`.
// All events are disabled while the prompt is active.
// If the user wishes to stay active, call the `activate()` method.
// You can get the remaining prompt time with the `getRemainingTime()` method,
setPromptOpen(true);
setRemainingTimeCountdown(promptBeforeIdleMillis);
};
const onIdle = () => {
// onIdle will be called after the timeout is reached.
// Events will be rebound as long as `stopOnMount` is not set.
setPromptOpen(false);
setRemainingTimeCountdown(0);
identityApi.signOut();
};
const onActive = () => {
// onActive will only be called if `activate()` is called while `isPrompted()`
// is true. Here you will also want to close your modal and perform
// any active actions.
setPromptOpen(false);
setRemainingTimeCountdown(0);
};
const onAction = (
_event?: Event | undefined,
_idleTimer?: IIdleTimer | null,
) => {
// onAction will be called if any user event is detected. The list of events that triggers a user event detection is the list of configured events
// If any user event is detected we update the Last seen online in storage
lastSeenOnlineStore.save(new Date());
};
return (
<IdleTimerProvider
timeout={idleTimeoutMinutes * 60 * 1000}
events={events}
crossTab
name="autologout-timer"
timers={useWorkerTimers ? workerTimers : undefined}
onIdle={onIdle}
onActive={promptBeforeIdle ? onActive : undefined}
onAction={logoutIfDisconnected ? onAction : undefined}
onPrompt={promptBeforeIdle ? onPrompt : undefined}
promptBeforeIdle={promptBeforeIdle ? promptBeforeIdleMillis : undefined}
syncTimers={1000}
>
{promptBeforeIdle && (
<StillTherePrompt
open={promptOpen}
setOpen={setPromptOpen}
remainingTime={remainingTimeCountdown}
setRemainingTime={setRemainingTimeCountdown}
promptTimeoutMillis={promptBeforeIdleMillis}
/>
)}
{children}
</IdleTimerProvider>
<ConditionalAutoLogout
idleTimeoutMinutes={idleTimeoutMinutes}
promptBeforeIdleSeconds={promptBeforeIdleSeconds}
useWorkerTimers={useWorkerTimers}
events={defaultTrackedEvents}
logoutIfDisconnected={logoutIfDisconnected}
promptOpen={promptOpen}
setPromptOpen={setPromptOpen}
remainingTimeCountdown={remainingTimeCountdown}
setRemainingTimeCountdown={setRemainingTimeCountdown}
identityApi={identityApi}
lastSeenOnlineStore={lastSeenOnlineStore}
/>
);
};
@@ -18,10 +18,10 @@ import { createMocks } from 'react-idle-timer';
import { MessageChannel } from 'worker_threads';
import { ApiProvider } from '@backstage/core-app-api';
import { identityApiRef } from '@backstage/core-plugin-api';
import { renderWithEffects, TestApiRegistry } from '@backstage/test-utils';
import { TestApiRegistry } from '@backstage/test-utils';
import React from 'react';
import { AutoLogoutProvider } from './AutoLogoutProvider';
import { AutoLogout } from './AutoLogout';
import { cleanup, render } from '@testing-library/react';
// Mock the signOut function of identityApiRef
@@ -43,29 +43,15 @@ describe('AutoLogoutProvider', () => {
jest.clearAllMocks();
});
it('renders children even when enabled is false', async () => {
const { queryByText } = await renderWithEffects(
<ApiProvider apis={apis}>
<AutoLogoutProvider enabled={false}>
<div>Test Child</div>
</AutoLogoutProvider>
</ApiProvider>,
);
expect(queryByText('Test Child')).not.toBeNull();
expect(mockSignOut).not.toHaveBeenCalled();
});
it('should throw error if idleTimeoutMinutes is smaller than promptBeforeSeconds', async () => {
expect(() =>
render(
<ApiProvider apis={apis}>
<AutoLogoutProvider
<AutoLogout
enabled
idleTimeoutMinutes={0.5}
promptBeforeIdleSeconds={120}
>
<div>Test Child</div>
</AutoLogoutProvider>
/>
</ApiProvider>,
),
).toThrow();
@@ -75,9 +61,8 @@ describe('AutoLogoutProvider', () => {
expect(() =>
render(
<ApiProvider apis={apis}>
<AutoLogoutProvider idleTimeoutMinutes={0.49}>
<div>Test Child</div>
</AutoLogoutProvider>
<AutoLogout enabled idleTimeoutMinutes={0.49} />
<div>Test Child</div>
</ApiProvider>,
),
).toThrow();
@@ -21,9 +21,10 @@ import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';
import React, { useEffect } from 'react';
import { useIdleTimerContext } from 'react-idle-timer';
import { IIdleTimer } from 'react-idle-timer';
export interface StillTherePromptProps {
idleTimer: IIdleTimer;
promptTimeoutMillis: number;
remainingTime: number;
setRemainingTime: (amount: number) => void;
@@ -33,13 +34,13 @@ export interface StillTherePromptProps {
export const StillTherePrompt = (props: StillTherePromptProps) => {
const {
idleTimer,
setOpen,
open,
promptTimeoutMillis,
remainingTime,
setRemainingTime,
} = props;
const idleTimer = useIdleTimerContext();
useEffect(() => {
const interval = setInterval(() => {
@@ -63,7 +64,7 @@ export const StillTherePrompt = (props: StillTherePromptProps) => {
const seconds = timeTillPrompt > 1 ? 'seconds' : 'second';
return (
<Dialog open={open}>
<Dialog open={open} data-testid="inactivity-prompt-dialog">
<DialogTitle>Logging out due to inactivity</DialogTitle>
<DialogContent>
<DialogContentText>
@@ -13,5 +13,4 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './AutoLogoutProvider';
export * from './ConfigBasedAutoLogoutProvider';
export * from './AutoLogout';
@@ -1,66 +0,0 @@
/*
* Copyright 2023 The Backstage Authors
*
* Licensed 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.
*/
import { configApiRef, useApi } from '@backstage/core-plugin-api';
import { PropsWithChildren, useMemo } from 'react';
import {
AutoLogoutProvider,
AutoLogoutProviderProps,
AutoLogoutTrackableEvent,
} from './AutoLogoutProvider';
import React from 'react';
/** @public */
export type ConfigBasedAutoLogoutProviderProps = {};
/**
* A config based flavour of the AutoLogoutProvider.
* It allows to configure Autologout settings through `app-config`
*
* @public
*/
export const ConfigBasedAutoLogoutProvider = ({
children,
}: PropsWithChildren<ConfigBasedAutoLogoutProviderProps>): JSX.Element => {
const configApi = useApi(configApiRef);
const props: AutoLogoutProviderProps = useMemo(() => {
return {
enabled: configApi.getOptionalBoolean('auth.autologout.enabled'),
idleTimeoutMinutes: configApi.getOptionalNumber(
'auth.autologout.idleTimeoutMinutes',
),
promptBeforeIdleSeconds: configApi.getOptionalNumber(
'auth.autologout.promptBeforeIdleSeconds',
),
useWorkerTimers: configApi.getOptionalBoolean(
'auth.autologout.useWorkerTimers',
),
events: configApi
.getOptionalStringArray('auth.autologout.events')
?.map(event => event as AutoLogoutTrackableEvent),
logoutIfDisconnected: configApi.getOptionalBoolean(
'auth.autologout.logoutIfDisconnected',
),
};
}, [configApi]);
return (
<AutoLogoutProvider {...{ ...props, enabled: props.enabled ?? false }}>
{children}
</AutoLogoutProvider>
);
};
@@ -15,7 +15,7 @@
*/
export * from './AlertDisplay';
export * from './AutoLogoutProvider';
export * from './AutoLogout';
export * from './Avatar';
export * from './LinkButton';
export * from './CodeSnippet';
@@ -31,7 +31,7 @@ import { commonProvider } from './commonProvider';
import { guestProvider } from './guestProvider';
import { customProvider } from './customProvider';
import { IdentityApiSignOutProxy } from './IdentityApiSignOutProxy';
import { LAST_SEEN_ONLINE_STORAGE_KEY } from '../../components/AutoLogoutProvider/disconnectedUsers';
import { LAST_SEEN_ONLINE_STORAGE_KEY } from '../../components/AutoLogout/disconnectedUsers';
const PROVIDER_STORAGE_KEY = '@backstage/core:SignInPage:provider';
@@ -15,3 +15,4 @@
*/
import '@testing-library/jest-dom';
import 'jest-localstorage-mock';