mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-10-30 14:49:14 +00:00
122 lines
3.5 KiB
TypeScript
122 lines
3.5 KiB
TypeScript
import {cloneElement} from 'react';
|
|
import type {RouteComponentProps} from 'react-router';
|
|
|
|
import {addErrorMessage} from 'sentry/actionCreators/indicator';
|
|
import {fetchOrganizations} from 'sentry/actionCreators/organizations';
|
|
import DeprecatedAsyncComponent from 'sentry/components/deprecatedAsyncComponent';
|
|
import {t} from 'sentry/locale';
|
|
import type {Authenticator, OrganizationSummary, UserEmail} from 'sentry/types';
|
|
import {defined} from 'sentry/utils';
|
|
|
|
const ENDPOINT = '/users/me/authenticators/';
|
|
|
|
type Props = {
|
|
children: React.ReactElement;
|
|
} & RouteComponentProps<{authId: string}, {}> &
|
|
DeprecatedAsyncComponent['props'];
|
|
|
|
type State = {
|
|
emails: UserEmail[];
|
|
loadingOrganizations: boolean;
|
|
authenticators?: Authenticator[] | null;
|
|
organizations?: OrganizationSummary[];
|
|
} & DeprecatedAsyncComponent['state'];
|
|
|
|
class AccountSecurityWrapper extends DeprecatedAsyncComponent<Props, State> {
|
|
async fetchOrganizations() {
|
|
try {
|
|
this.setState({loadingOrganizations: true});
|
|
const organizations = await fetchOrganizations(this.api);
|
|
this.setState({organizations, loadingOrganizations: false});
|
|
} catch (e) {
|
|
this.setState({error: true, loadingOrganizations: false});
|
|
}
|
|
}
|
|
|
|
get shouldRenderLoading() {
|
|
return super.shouldRenderLoading || this.state.loadingOrganizations === true;
|
|
}
|
|
|
|
getEndpoints(): ReturnType<DeprecatedAsyncComponent['getEndpoints']> {
|
|
return [
|
|
['authenticators', ENDPOINT],
|
|
['emails', '/users/me/emails/'],
|
|
];
|
|
}
|
|
|
|
componentDidMount() {
|
|
super.componentDidMount();
|
|
this.fetchOrganizations();
|
|
}
|
|
|
|
reloadData() {
|
|
this.fetchOrganizations();
|
|
super.reloadData();
|
|
}
|
|
|
|
handleDisable = async (auth: Authenticator) => {
|
|
if (!auth || !auth.authId) {
|
|
return;
|
|
}
|
|
|
|
this.setState({loading: true});
|
|
|
|
try {
|
|
await this.api.requestPromise(`${ENDPOINT}${auth.authId}/`, {method: 'DELETE'});
|
|
this.reloadData();
|
|
} catch (_err) {
|
|
this.setState({loading: false});
|
|
addErrorMessage(t('Error disabling %s', auth.name));
|
|
}
|
|
};
|
|
|
|
handleRegenerateBackupCodes = async () => {
|
|
this.setState({loading: true});
|
|
|
|
try {
|
|
await this.api.requestPromise(`${ENDPOINT}${this.props.params.authId}/`, {
|
|
method: 'PUT',
|
|
});
|
|
this.reloadData();
|
|
} catch (_err) {
|
|
this.setState({loading: false});
|
|
addErrorMessage(t('Error regenerating backup codes'));
|
|
}
|
|
};
|
|
|
|
handleRefresh = () => {
|
|
this.reloadData();
|
|
};
|
|
|
|
renderBody() {
|
|
const {children} = this.props;
|
|
const {authenticators, organizations, emails} = this.state;
|
|
const enrolled =
|
|
authenticators?.filter(auth => auth.isEnrolled && !auth.isBackupInterface) || [];
|
|
const countEnrolled = enrolled.length;
|
|
const orgsRequire2fa = organizations?.filter(org => org.require2FA) || [];
|
|
const deleteDisabled = orgsRequire2fa.length > 0 && countEnrolled === 1;
|
|
const hasVerifiedEmail = !!emails?.find(({isVerified}) => isVerified);
|
|
|
|
// This happens when you switch between children views and the next child
|
|
// view is lazy loaded, it can potentially be `null` while the code split
|
|
// package is being fetched
|
|
if (!defined(children)) {
|
|
return null;
|
|
}
|
|
|
|
return cloneElement(this.props.children, {
|
|
onDisable: this.handleDisable,
|
|
onRegenerateBackupCodes: this.handleRegenerateBackupCodes,
|
|
authenticators,
|
|
deleteDisabled,
|
|
orgsRequire2fa,
|
|
countEnrolled,
|
|
hasVerifiedEmail,
|
|
handleRefresh: this.handleRefresh,
|
|
});
|
|
}
|
|
}
|
|
|
|
export default AccountSecurityWrapper;
|