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']}.
|
||||
*/
|
||||
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({
|
||||
settings: v.object({
|
||||
enabled: v.boolean(),
|
||||
totp: v.boolean(),
|
||||
}),
|
||||
}), response.json);
|
||||
}), response);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1524,36 +1537,66 @@ class PlApiClient {
|
||||
* Requires features{@link Features['manageMfa']}.
|
||||
*/
|
||||
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({
|
||||
key: v.string(),
|
||||
key: v.fallback(v.string(), ''),
|
||||
provisioning_uri: v.string(),
|
||||
}), response.json);
|
||||
}), response);
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features['manageMfa']}.
|
||||
*/
|
||||
confirmMfaSetup: async (method: 'totp', code: string, password: string) => {
|
||||
const response = await this.request(`/api/pleroma/accounts/mfa/confirm/${method}`, {
|
||||
method: 'POST',
|
||||
body: { code, password },
|
||||
});
|
||||
let response;
|
||||
|
||||
if (response.json?.error) throw response.json.error;
|
||||
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',
|
||||
body: { code, password },
|
||||
})).json;
|
||||
}
|
||||
|
||||
return response.json as {};
|
||||
if (response?.error) throw response.error;
|
||||
|
||||
return response as {};
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires features{@link Features['manageMfa']}.
|
||||
*/
|
||||
disableMfa: async (method: 'totp', password: string) => {
|
||||
const response = await this.request(`/api/pleroma/accounts/mfa/${method}`, {
|
||||
method: 'DELETE',
|
||||
body: { password },
|
||||
});
|
||||
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',
|
||||
body: { password },
|
||||
});
|
||||
}
|
||||
|
||||
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/backup_codes
|
||||
* @see GET /api/pleroma/accounts/mfa/setup/:method
|
||||
* @see POST /api/pleroma/accounts/mfa/confirm/:method
|
||||
* @see DELETE /api/pleroma/accounts/mfa/:method
|
||||
@ -995,6 +994,23 @@ const getFeatures = (instance: Instance) => {
|
||||
manageMfa: any([
|
||||
v.software === AKKOMA,
|
||||
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 './apps';
|
||||
export * from './chats';
|
||||
export type { PaginationParams } from './common';
|
||||
export * from './events';
|
||||
export * from './filtering';
|
||||
export * from './grouped-notifications';
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pl-api",
|
||||
"version": "1.0.0-rc.44",
|
||||
"version": "1.0.0-rc.45",
|
||||
"type": "module",
|
||||
"homepage": "https://github.com/mkljczk/pl-fe/tree/develop/packages/pl-api",
|
||||
"repository": {
|
||||
|
||||
@ -104,7 +104,7 @@
|
||||
"multiselect-react-dropdown": "^2.0.25",
|
||||
"mutative": "^1.1.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"pl-api": "^1.0.0-rc.44",
|
||||
"pl-api": "^1.0.0-rc.45",
|
||||
"postcss": "^8.4.49",
|
||||
"process": "^0.11.10",
|
||||
"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 { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
||||
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 EnableOtpForm from './mfa/enable-otp-form';
|
||||
@ -25,6 +26,7 @@ const messages = defineMessages({
|
||||
const MfaForm: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const features = useFeatures();
|
||||
const [displayOtpForm, setDisplayOtpForm] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
@ -44,8 +46,8 @@ const MfaForm: React.FC = () => {
|
||||
<DisableOtpForm />
|
||||
) : (
|
||||
<Stack space={4}>
|
||||
<EnableOtpForm displayOtpForm={displayOtpForm} handleSetupProceedClick={handleSetupProceedClick} />
|
||||
{displayOtpForm && <OtpConfirmForm />}
|
||||
{features.manageMfaBackupCodes && <EnableOtpForm displayOtpForm={displayOtpForm} handleSetupProceedClick={handleSetupProceedClick} />}
|
||||
{(displayOtpForm || !features.manageMfaBackupCodes) && <OtpConfirmForm />}
|
||||
</Stack>
|
||||
)}
|
||||
</Column>
|
||||
|
||||
@ -12,6 +12,7 @@ import Input from 'pl-fe/components/ui/input';
|
||||
import Stack from 'pl-fe/components/ui/stack';
|
||||
import Text from 'pl-fe/components/ui/text';
|
||||
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
||||
import { useFeatures } from 'pl-fe/hooks/use-features';
|
||||
import toast from 'pl-fe/toast';
|
||||
|
||||
const messages = defineMessages({
|
||||
@ -28,6 +29,7 @@ const OtpConfirmForm: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const history = useHistory();
|
||||
const dispatch = useAppDispatch();
|
||||
const features = useFeatures();
|
||||
|
||||
const [state, setState] = useState<{ password: string; isLoading: boolean; code: string; qrCodeURI: string; confirmKey: string }>({
|
||||
password: '',
|
||||
@ -101,20 +103,22 @@ const OtpConfirmForm: React.FC = () => {
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
labelText={intl.formatMessage(messages.passwordPlaceholder)}
|
||||
hintText={<FormattedMessage id='mfa.mfa_setup.password_hint' defaultMessage='Enter your current password to confirm your identity.' />}
|
||||
>
|
||||
<Input
|
||||
type='password'
|
||||
name='password'
|
||||
placeholder={intl.formatMessage(messages.passwordPlaceholder)}
|
||||
onChange={handleInputChange}
|
||||
disabled={state.isLoading}
|
||||
value={state.password}
|
||||
required
|
||||
/>
|
||||
</FormGroup>
|
||||
{features.manageMfaRequiresPassword && (
|
||||
<FormGroup
|
||||
labelText={intl.formatMessage(messages.passwordPlaceholder)}
|
||||
hintText={<FormattedMessage id='mfa.mfa_setup.password_hint' defaultMessage='Enter your current password to confirm your identity.' />}
|
||||
>
|
||||
<Input
|
||||
type='password'
|
||||
name='password'
|
||||
placeholder={intl.formatMessage(messages.passwordPlaceholder)}
|
||||
onChange={handleInputChange}
|
||||
disabled={state.isLoading}
|
||||
value={state.password}
|
||||
required
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
|
||||
<FormActions>
|
||||
<Button
|
||||
|
||||
@ -4,8 +4,7 @@ import { importEntities } from 'pl-fe/actions/importer';
|
||||
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
||||
import { useClient } from 'pl-fe/hooks/use-client';
|
||||
|
||||
import type { SearchParams } from 'pl-api';
|
||||
import type { PaginationParams } from 'pl-api/dist/params/common';
|
||||
import type { PaginationParams, SearchParams } from 'pl-api';
|
||||
|
||||
const useSearchAccounts = (
|
||||
query: string,
|
||||
|
||||
@ -6833,10 +6833,10 @@ pkg-dir@^4.1.0:
|
||||
dependencies:
|
||||
find-up "^4.0.0"
|
||||
|
||||
pl-api@^1.0.0-rc.44:
|
||||
version "1.0.0-rc.44"
|
||||
resolved "https://registry.yarnpkg.com/pl-api/-/pl-api-1.0.0-rc.44.tgz#e944ab2e27bd0756f5acc126972297715d2eb163"
|
||||
integrity sha512-HiXHfbrbh3TOS4KcFIyITxfJ9TXS4SxOdg9/eS2ntiV+bKcuRPpEbHv14AuL6E1kxsEOOZdbSRo3NvGY774org==
|
||||
pl-api@^1.0.0-rc.45:
|
||||
version "1.0.0-rc.45"
|
||||
resolved "https://registry.yarnpkg.com/pl-api/-/pl-api-1.0.0-rc.45.tgz#6c1986e850ab36ee1ba31248a2b90638bec06170"
|
||||
integrity sha512-NM5QZ9x9sjK3sOxThqO48926Lr+Uw/P911jaLl4CcKEQV+IuLYEdOheYdpeH2Ub5LErcUwxiiriv5Xepx+FEEA==
|
||||
dependencies:
|
||||
blurhash "^2.0.5"
|
||||
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 { importEntities } from 'pl-hooks/importer';
|
||||
|
||||
import type { SearchParams, Tag } from 'pl-api';
|
||||
import type { PaginationParams } from 'pl-api/dist/params/common';
|
||||
import type { PaginationParams, SearchParams, Tag } from 'pl-api';
|
||||
|
||||
const useSearchAccounts = (
|
||||
query: string,
|
||||
|
||||
Reference in New Issue
Block a user