Restore session management
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
87
app/soapbox/features/auth_token_list/index.tsx
Normal file
87
app/soapbox/features/auth_token_list/index.tsx
Normal file
@ -0,0 +1,87 @@
|
||||
import React from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { defineMessages, FormattedDate, useIntl } from 'react-intl';
|
||||
|
||||
import { fetchOAuthTokens, revokeOAuthTokenById } from 'soapbox/actions/security';
|
||||
import { Button, Card, CardBody, CardHeader, CardTitle, Column, Spinner, Stack, Text } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||
import { Token } from 'soapbox/reducers/security';
|
||||
|
||||
const messages = defineMessages({
|
||||
header: { id: 'security.headers.tokens', defaultMessage: 'Sessions' },
|
||||
revoke: { id: 'security.tokens.revoke', defaultMessage: 'Revoke' },
|
||||
});
|
||||
|
||||
interface IAuthToken {
|
||||
token: Token,
|
||||
}
|
||||
|
||||
const AuthToken: React.FC<IAuthToken> = ({ token }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
|
||||
const handleRevoke = () => {
|
||||
dispatch(revokeOAuthTokenById(token.id));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='p-4 rounded-lg bg-gray-100 dark:bg-slate-700'>
|
||||
<Stack space={2}>
|
||||
<Stack>
|
||||
<Text size='md' weight='medium'>{token.app_name}</Text>
|
||||
<Text size='sm' theme='muted'>
|
||||
<FormattedDate
|
||||
value={new Date(token.valid_until)}
|
||||
hour12={false}
|
||||
year='numeric'
|
||||
month='short'
|
||||
day='2-digit'
|
||||
hour='2-digit'
|
||||
minute='2-digit'
|
||||
/>
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
<div className='flex justify-end'>
|
||||
<Button theme='primary' onClick={handleRevoke}>
|
||||
{intl.formatMessage(messages.revoke)}
|
||||
</Button>
|
||||
</div>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const AuthTokenList: React.FC = () =>{
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
const tokens = useAppSelector(state => state.security.get('tokens'));
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchOAuthTokens());
|
||||
}, []);
|
||||
|
||||
const body = tokens ? (
|
||||
<div className='grid grid-cols-1 sm:grid-cols-2 gap-4'>
|
||||
{tokens.map((token) => (
|
||||
<AuthToken key={token.id} token={token} />
|
||||
))}
|
||||
</div>
|
||||
) : <Spinner />;
|
||||
|
||||
return (
|
||||
<Column label={intl.formatMessage(messages.header)} transparent withHeader={false}>
|
||||
<Card variant='rounded'>
|
||||
<CardHeader backHref='/settings'>
|
||||
<CardTitle title={intl.formatMessage(messages.header)} />
|
||||
</CardHeader>
|
||||
|
||||
<CardBody>
|
||||
{body}
|
||||
</CardBody>
|
||||
</Card>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
export default AuthTokenList;
|
||||
@ -4,10 +4,11 @@ import { useDispatch } from 'react-redux';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import { fetchMfa } from 'soapbox/actions/mfa';
|
||||
import List, { ListItem } from 'soapbox/components/list';
|
||||
import { Button, Card, CardBody, CardHeader, CardTitle, Column } from 'soapbox/components/ui';
|
||||
import { useAppSelector, useOwnAccount } from 'soapbox/hooks';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
|
||||
import List, { ListItem } from '../../components/list';
|
||||
import { Button, Card, CardBody, CardHeader, CardTitle, Column } from '../../components/ui';
|
||||
import Preferences from '../preferences';
|
||||
|
||||
const messages = defineMessages({
|
||||
@ -19,6 +20,7 @@ const messages = defineMessages({
|
||||
changeEmail: { id: 'settings.change_email', defaultMessage: 'Change Email' },
|
||||
changePassword: { id: 'settings.change_password', defaultMessage: 'Change Password' },
|
||||
configureMfa: { id: 'settings.configure_mfa', defaultMessage: 'Configure MFA' },
|
||||
sessions: { id: 'settings.sessions', defaultMessage: 'Active sessions' },
|
||||
deleteAccount: { id: 'settings.delete_account', defaultMessage: 'Delete Account' },
|
||||
});
|
||||
|
||||
@ -29,11 +31,13 @@ const Settings = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
const mfa = useAppSelector((state) => state.security.get('mfa'));
|
||||
const features = useAppSelector((state) => getFeatures(state.instance));
|
||||
const account = useOwnAccount();
|
||||
|
||||
const navigateToChangeEmail = React.useCallback(() => history.push('/settings/email'), [history]);
|
||||
const navigateToChangePassword = React.useCallback(() => history.push('/settings/password'), [history]);
|
||||
const navigateToMfa = React.useCallback(() => history.push('/settings/mfa'), [history]);
|
||||
const navigateToSessions = React.useCallback(() => history.push('/settings/tokens'), [history]);
|
||||
const navigateToEditProfile = React.useCallback(() => history.push('/settings/profile'), [history]);
|
||||
|
||||
const isMfaEnabled = mfa.getIn(['settings', 'totp']);
|
||||
@ -74,6 +78,9 @@ const Settings = () => {
|
||||
intl.formatMessage({ id: 'mfa.enabled', defaultMessage: 'Enabled' }) :
|
||||
intl.formatMessage({ id: 'mfa.disabled', defaultMessage: 'Disabled' })}
|
||||
</ListItem>
|
||||
{features.sessionsAPI && (
|
||||
<ListItem label={intl.formatMessage(messages.sessions)} onClick={navigateToSessions} />
|
||||
)}
|
||||
</List>
|
||||
</CardBody>
|
||||
|
||||
|
||||
@ -43,8 +43,6 @@ const CompareHistoryModal: React.FC<ICompareHistoryModal> = ({ onClose, statusId
|
||||
|
||||
const poll = typeof version.poll !== 'string' && version.poll;
|
||||
|
||||
console.log(version.toJS());
|
||||
|
||||
return (
|
||||
<div className='flex flex-col py-2 first:pt-0 last:pb-0'>
|
||||
{version.spoiler_text?.length > 0 && (
|
||||
|
||||
@ -8,10 +8,19 @@ import { useDispatch } from 'react-redux';
|
||||
import { Switch, useHistory } from 'react-router-dom';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
|
||||
import { fetchFollowRequests } from 'soapbox/actions/accounts';
|
||||
import { fetchReports, fetchUsers, fetchConfig } from 'soapbox/actions/admin';
|
||||
import { fetchChats } from 'soapbox/actions/chats';
|
||||
import { uploadCompose, resetCompose } from 'soapbox/actions/compose';
|
||||
import { fetchCustomEmojis } from 'soapbox/actions/custom_emojis';
|
||||
import { fetchFilters } from 'soapbox/actions/filters';
|
||||
import { fetchMarker } from 'soapbox/actions/markers';
|
||||
import { openModal } from 'soapbox/actions/modals';
|
||||
import { expandNotifications } from 'soapbox/actions/notifications';
|
||||
import { register as registerPushNotifications } from 'soapbox/actions/push_notifications';
|
||||
import { fetchScheduledStatuses } from 'soapbox/actions/scheduled_statuses';
|
||||
import { connectUserStream } from 'soapbox/actions/streaming';
|
||||
import { expandHomeTimeline } from 'soapbox/actions/timelines';
|
||||
import Icon from 'soapbox/components/icon';
|
||||
import SidebarNavigation from 'soapbox/components/sidebar-navigation';
|
||||
import ThumbNavigation from 'soapbox/components/thumb_navigation';
|
||||
@ -29,16 +38,6 @@ import StatusPage from 'soapbox/pages/status_page';
|
||||
import { getAccessToken } from 'soapbox/utils/auth';
|
||||
import { getVapidKey } from 'soapbox/utils/auth';
|
||||
import { isStandalone } from 'soapbox/utils/state';
|
||||
|
||||
import { fetchFollowRequests } from '../../actions/accounts';
|
||||
import { fetchReports, fetchUsers, fetchConfig } from '../../actions/admin';
|
||||
import { uploadCompose, resetCompose } from '../../actions/compose';
|
||||
import { fetchFilters } from '../../actions/filters';
|
||||
import { openModal } from '../../actions/modals';
|
||||
import { expandNotifications } from '../../actions/notifications';
|
||||
import { fetchScheduledStatuses } from '../../actions/scheduled_statuses';
|
||||
import { connectUserStream } from '../../actions/streaming';
|
||||
import { expandHomeTimeline } from '../../actions/timelines';
|
||||
// import GroupSidebarPanel from '../groups/sidebar_panel';
|
||||
|
||||
import BackgroundShapes from './components/background_shapes';
|
||||
@ -115,12 +114,13 @@ import {
|
||||
SettingsStore,
|
||||
TestTimeline,
|
||||
LogoutPage,
|
||||
AuthTokenList,
|
||||
} from './util/async-components';
|
||||
import { WrappedRoute } from './util/react_router_helpers';
|
||||
|
||||
// Dummy import, to make sure that <Status /> ends up in the application bundle.
|
||||
// Without this it ends up in ~8 very commonly used bundles.
|
||||
import '../../components/status';
|
||||
import 'soapbox/components/status';
|
||||
|
||||
const isMobile = (width: number): boolean => width <= 1190;
|
||||
|
||||
@ -297,6 +297,7 @@ const SwitchingColumnsArea: React.FC = ({ children }) => {
|
||||
<WrappedRoute path='/settings/account' page={DefaultPage} component={DeleteAccount} content={children} />
|
||||
<WrappedRoute path='/settings/media_display' page={DefaultPage} component={MediaDisplay} content={children} />
|
||||
<WrappedRoute path='/settings/mfa' page={DefaultPage} component={MfaForm} exact />
|
||||
<WrappedRoute path='/settings/tokens' page={DefaultPage} component={AuthTokenList} content={children} />
|
||||
<WrappedRoute path='/settings' page={DefaultPage} component={Settings} content={children} />
|
||||
{/* <WrappedRoute path='/backups' page={DefaultPage} component={Backups} content={children} /> */}
|
||||
<WrappedRoute path='/soapbox/config' adminOnly page={DefaultPage} component={SoapboxConfig} content={children} />
|
||||
|
||||
@ -497,3 +497,7 @@ export function DatePicker() {
|
||||
export function CompareHistoryModal() {
|
||||
return import(/*webpackChunkName: "modals/compare_history_modal" */'../components/compare_history_modal');
|
||||
}
|
||||
|
||||
export function AuthTokenList() {
|
||||
return import(/* webpackChunkName: "features/auth_token_list" */'../../auth_token_list');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user