pl-api: support 2fa configuration in gotosocial
Signed-off-by: Nicole Mikołajczyk <git@mkljczk.pl>
This commit is contained in:
@ -1499,14 +1499,27 @@ class PlApiClient {
|
|||||||
* Requires features{@link Features['manageMfa']}.
|
* Requires features{@link Features['manageMfa']}.
|
||||||
*/
|
*/
|
||||||
getMfaSettings: async () => {
|
getMfaSettings: async () => {
|
||||||
const response = await this.request('/api/pleroma/accounts/mfa');
|
let response;
|
||||||
|
|
||||||
|
switch (this.features.version.software) {
|
||||||
|
case GOTOSOCIAL:
|
||||||
|
response = await this.request('/api/v1/user').then(({ json }) => ({
|
||||||
|
settings: {
|
||||||
|
enabled: !!json?.two_factor_enabled_at,
|
||||||
|
method: 'totp',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
response = (await this.request('/api/pleroma/accounts/mfa')).json;
|
||||||
|
}
|
||||||
|
|
||||||
return v.parse(v.object({
|
return v.parse(v.object({
|
||||||
settings: v.object({
|
settings: v.object({
|
||||||
enabled: v.boolean(),
|
enabled: v.boolean(),
|
||||||
totp: v.boolean(),
|
totp: v.boolean(),
|
||||||
}),
|
}),
|
||||||
}), response.json);
|
}), response);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1524,36 +1537,66 @@ class PlApiClient {
|
|||||||
* Requires features{@link Features['manageMfa']}.
|
* Requires features{@link Features['manageMfa']}.
|
||||||
*/
|
*/
|
||||||
getMfaSetup: async (method: 'totp') => {
|
getMfaSetup: async (method: 'totp') => {
|
||||||
const response = await this.request(`/api/pleroma/accounts/mfa/setup/${method}`);
|
let response;
|
||||||
|
|
||||||
|
switch (this.features.version.software) {
|
||||||
|
case GOTOSOCIAL:
|
||||||
|
response = await this.request('/api/v1/user/2fa/qruri').then(({ data }) => ({
|
||||||
|
provisioning_uri: data,
|
||||||
|
key: new URL(data).searchParams.get('secret'),
|
||||||
|
}));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
response = (await this.request(`/api/pleroma/accounts/mfa/setup/${method}`)).json;
|
||||||
|
}
|
||||||
|
|
||||||
return v.parse(v.object({
|
return v.parse(v.object({
|
||||||
key: v.string(),
|
key: v.fallback(v.string(), ''),
|
||||||
provisioning_uri: v.string(),
|
provisioning_uri: v.string(),
|
||||||
}), response.json);
|
}), response);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requires features{@link Features['manageMfa']}.
|
* Requires features{@link Features['manageMfa']}.
|
||||||
*/
|
*/
|
||||||
confirmMfaSetup: async (method: 'totp', code: string, password: string) => {
|
confirmMfaSetup: async (method: 'totp', code: string, password: string) => {
|
||||||
const response = await this.request(`/api/pleroma/accounts/mfa/confirm/${method}`, {
|
let response;
|
||||||
|
|
||||||
|
switch (this.features.version.software) {
|
||||||
|
case GOTOSOCIAL:
|
||||||
|
response = await this.request('/api/v1/user/2fa/enable', { method: 'POST', body: { code } });
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
response = (await this.request(`/api/pleroma/accounts/mfa/confirm/${method}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: { code, password },
|
body: { code, password },
|
||||||
});
|
})).json;
|
||||||
|
}
|
||||||
|
|
||||||
if (response.json?.error) throw response.json.error;
|
if (response?.error) throw response.error;
|
||||||
|
|
||||||
return response.json as {};
|
return response as {};
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requires features{@link Features['manageMfa']}.
|
* Requires features{@link Features['manageMfa']}.
|
||||||
*/
|
*/
|
||||||
disableMfa: async (method: 'totp', password: string) => {
|
disableMfa: async (method: 'totp', password: string) => {
|
||||||
const response = await this.request(`/api/pleroma/accounts/mfa/${method}`, {
|
let response;
|
||||||
|
|
||||||
|
switch (this.features.version.software) {
|
||||||
|
case GOTOSOCIAL:
|
||||||
|
response = await this.request('/api/v1/user/2fa/disable', {
|
||||||
|
method: 'POST',
|
||||||
|
body: { password },
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
response = await this.request(`/api/pleroma/accounts/mfa/${method}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
body: { password },
|
body: { password },
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (response.json?.error) throw response.json.error;
|
if (response.json?.error) throw response.json.error;
|
||||||
|
|
||||||
|
|||||||
@ -987,7 +987,6 @@ const getFeatures = (instance: Instance) => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @see GET /api/pleroma/accounts/mfa
|
* @see GET /api/pleroma/accounts/mfa
|
||||||
* @see GET /api/pleroma/accounts/mfa/backup_codes
|
|
||||||
* @see GET /api/pleroma/accounts/mfa/setup/:method
|
* @see GET /api/pleroma/accounts/mfa/setup/:method
|
||||||
* @see POST /api/pleroma/accounts/mfa/confirm/:method
|
* @see POST /api/pleroma/accounts/mfa/confirm/:method
|
||||||
* @see DELETE /api/pleroma/accounts/mfa/:method
|
* @see DELETE /api/pleroma/accounts/mfa/:method
|
||||||
@ -995,6 +994,23 @@ const getFeatures = (instance: Instance) => {
|
|||||||
manageMfa: any([
|
manageMfa: any([
|
||||||
v.software === AKKOMA,
|
v.software === AKKOMA,
|
||||||
v.software === PLEROMA,
|
v.software === PLEROMA,
|
||||||
|
v.software === GOTOSOCIAL && gte(v.version, '0.19.0'),
|
||||||
|
]),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see GET /api/pleroma/accounts/mfa/backup_codes
|
||||||
|
*/
|
||||||
|
manageMfaBackupCodes: any([
|
||||||
|
v.software === AKKOMA,
|
||||||
|
v.software === PLEROMA,
|
||||||
|
]),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see POST /api/v1/user/2fa/enable
|
||||||
|
*/
|
||||||
|
manageMfaRequiresPassword: any([
|
||||||
|
v.software === AKKOMA,
|
||||||
|
v.software === PLEROMA,
|
||||||
]),
|
]),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -2,6 +2,7 @@ export * from './accounts';
|
|||||||
export * from './admin';
|
export * from './admin';
|
||||||
export * from './apps';
|
export * from './apps';
|
||||||
export * from './chats';
|
export * from './chats';
|
||||||
|
export type { PaginationParams } from './common';
|
||||||
export * from './events';
|
export * from './events';
|
||||||
export * from './filtering';
|
export * from './filtering';
|
||||||
export * from './grouped-notifications';
|
export * from './grouped-notifications';
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pl-api",
|
"name": "pl-api",
|
||||||
"version": "1.0.0-rc.44",
|
"version": "1.0.0-rc.45",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"homepage": "https://github.com/mkljczk/pl-fe/tree/develop/packages/pl-api",
|
"homepage": "https://github.com/mkljczk/pl-fe/tree/develop/packages/pl-api",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@ -104,7 +104,7 @@
|
|||||||
"multiselect-react-dropdown": "^2.0.25",
|
"multiselect-react-dropdown": "^2.0.25",
|
||||||
"mutative": "^1.1.0",
|
"mutative": "^1.1.0",
|
||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
"pl-api": "^1.0.0-rc.44",
|
"pl-api": "^1.0.0-rc.45",
|
||||||
"postcss": "^8.4.49",
|
"postcss": "^8.4.49",
|
||||||
"process": "^0.11.10",
|
"process": "^0.11.10",
|
||||||
"punycode": "^2.1.1",
|
"punycode": "^2.1.1",
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import Column from 'pl-fe/components/ui/column';
|
|||||||
import Stack from 'pl-fe/components/ui/stack';
|
import Stack from 'pl-fe/components/ui/stack';
|
||||||
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
||||||
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
|
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
|
||||||
|
import { useFeatures } from 'pl-fe/hooks/use-features';
|
||||||
|
|
||||||
import DisableOtpForm from './mfa/disable-otp-form';
|
import DisableOtpForm from './mfa/disable-otp-form';
|
||||||
import EnableOtpForm from './mfa/enable-otp-form';
|
import EnableOtpForm from './mfa/enable-otp-form';
|
||||||
@ -25,6 +26,7 @@ const messages = defineMessages({
|
|||||||
const MfaForm: React.FC = () => {
|
const MfaForm: React.FC = () => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const features = useFeatures();
|
||||||
const [displayOtpForm, setDisplayOtpForm] = useState<boolean>(false);
|
const [displayOtpForm, setDisplayOtpForm] = useState<boolean>(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -44,8 +46,8 @@ const MfaForm: React.FC = () => {
|
|||||||
<DisableOtpForm />
|
<DisableOtpForm />
|
||||||
) : (
|
) : (
|
||||||
<Stack space={4}>
|
<Stack space={4}>
|
||||||
<EnableOtpForm displayOtpForm={displayOtpForm} handleSetupProceedClick={handleSetupProceedClick} />
|
{features.manageMfaBackupCodes && <EnableOtpForm displayOtpForm={displayOtpForm} handleSetupProceedClick={handleSetupProceedClick} />}
|
||||||
{displayOtpForm && <OtpConfirmForm />}
|
{(displayOtpForm || !features.manageMfaBackupCodes) && <OtpConfirmForm />}
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
</Column>
|
</Column>
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import Input from 'pl-fe/components/ui/input';
|
|||||||
import Stack from 'pl-fe/components/ui/stack';
|
import Stack from 'pl-fe/components/ui/stack';
|
||||||
import Text from 'pl-fe/components/ui/text';
|
import Text from 'pl-fe/components/ui/text';
|
||||||
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
||||||
|
import { useFeatures } from 'pl-fe/hooks/use-features';
|
||||||
import toast from 'pl-fe/toast';
|
import toast from 'pl-fe/toast';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
@ -28,6 +29,7 @@ const OtpConfirmForm: React.FC = () => {
|
|||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const features = useFeatures();
|
||||||
|
|
||||||
const [state, setState] = useState<{ password: string; isLoading: boolean; code: string; qrCodeURI: string; confirmKey: string }>({
|
const [state, setState] = useState<{ password: string; isLoading: boolean; code: string; qrCodeURI: string; confirmKey: string }>({
|
||||||
password: '',
|
password: '',
|
||||||
@ -101,6 +103,7 @@ const OtpConfirmForm: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
|
{features.manageMfaRequiresPassword && (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
labelText={intl.formatMessage(messages.passwordPlaceholder)}
|
labelText={intl.formatMessage(messages.passwordPlaceholder)}
|
||||||
hintText={<FormattedMessage id='mfa.mfa_setup.password_hint' defaultMessage='Enter your current password to confirm your identity.' />}
|
hintText={<FormattedMessage id='mfa.mfa_setup.password_hint' defaultMessage='Enter your current password to confirm your identity.' />}
|
||||||
@ -115,6 +118,7 @@ const OtpConfirmForm: React.FC = () => {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
|
||||||
<FormActions>
|
<FormActions>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@ -4,8 +4,7 @@ import { importEntities } from 'pl-fe/actions/importer';
|
|||||||
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
||||||
import { useClient } from 'pl-fe/hooks/use-client';
|
import { useClient } from 'pl-fe/hooks/use-client';
|
||||||
|
|
||||||
import type { SearchParams } from 'pl-api';
|
import type { PaginationParams, SearchParams } from 'pl-api';
|
||||||
import type { PaginationParams } from 'pl-api/dist/params/common';
|
|
||||||
|
|
||||||
const useSearchAccounts = (
|
const useSearchAccounts = (
|
||||||
query: string,
|
query: string,
|
||||||
|
|||||||
@ -6833,10 +6833,10 @@ pkg-dir@^4.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
find-up "^4.0.0"
|
find-up "^4.0.0"
|
||||||
|
|
||||||
pl-api@^1.0.0-rc.44:
|
pl-api@^1.0.0-rc.45:
|
||||||
version "1.0.0-rc.44"
|
version "1.0.0-rc.45"
|
||||||
resolved "https://registry.yarnpkg.com/pl-api/-/pl-api-1.0.0-rc.44.tgz#e944ab2e27bd0756f5acc126972297715d2eb163"
|
resolved "https://registry.yarnpkg.com/pl-api/-/pl-api-1.0.0-rc.45.tgz#6c1986e850ab36ee1ba31248a2b90638bec06170"
|
||||||
integrity sha512-HiXHfbrbh3TOS4KcFIyITxfJ9TXS4SxOdg9/eS2ntiV+bKcuRPpEbHv14AuL6E1kxsEOOZdbSRo3NvGY774org==
|
integrity sha512-NM5QZ9x9sjK3sOxThqO48926Lr+Uw/P911jaLl4CcKEQV+IuLYEdOheYdpeH2Ub5LErcUwxiiriv5Xepx+FEEA==
|
||||||
dependencies:
|
dependencies:
|
||||||
blurhash "^2.0.5"
|
blurhash "^2.0.5"
|
||||||
http-link-header "^1.1.3"
|
http-link-header "^1.1.3"
|
||||||
|
|||||||
@ -4,8 +4,7 @@ import { usePlHooksApiClient } from 'pl-hooks/contexts/api-client';
|
|||||||
import { usePlHooksQueryClient } from 'pl-hooks/contexts/query-client';
|
import { usePlHooksQueryClient } from 'pl-hooks/contexts/query-client';
|
||||||
import { importEntities } from 'pl-hooks/importer';
|
import { importEntities } from 'pl-hooks/importer';
|
||||||
|
|
||||||
import type { SearchParams, Tag } from 'pl-api';
|
import type { PaginationParams, SearchParams, Tag } from 'pl-api';
|
||||||
import type { PaginationParams } from 'pl-api/dist/params/common';
|
|
||||||
|
|
||||||
const useSearchAccounts = (
|
const useSearchAccounts = (
|
||||||
query: string,
|
query: string,
|
||||||
|
|||||||
Reference in New Issue
Block a user