diff --git a/packages/pl-api/lib/client.ts b/packages/pl-api/lib/client.ts index 8725f5f7f..02241f574 100644 --- a/packages/pl-api/lib/client.ts +++ b/packages/pl-api/lib/client.ts @@ -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; diff --git a/packages/pl-api/lib/features.ts b/packages/pl-api/lib/features.ts index f1a9a889c..4af1eae9d 100644 --- a/packages/pl-api/lib/features.ts +++ b/packages/pl-api/lib/features.ts @@ -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, ]), /** diff --git a/packages/pl-api/lib/params/index.ts b/packages/pl-api/lib/params/index.ts index 08b7056b0..e7cb7d01e 100644 --- a/packages/pl-api/lib/params/index.ts +++ b/packages/pl-api/lib/params/index.ts @@ -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'; diff --git a/packages/pl-api/package.json b/packages/pl-api/package.json index abc69a19b..2bcbf3fa7 100644 --- a/packages/pl-api/package.json +++ b/packages/pl-api/package.json @@ -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": { diff --git a/packages/pl-fe/package.json b/packages/pl-fe/package.json index a7386dcf5..ce94a7e07 100644 --- a/packages/pl-fe/package.json +++ b/packages/pl-fe/package.json @@ -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", diff --git a/packages/pl-fe/src/features/security/mfa-form.tsx b/packages/pl-fe/src/features/security/mfa-form.tsx index 69b4323f1..803db100d 100644 --- a/packages/pl-fe/src/features/security/mfa-form.tsx +++ b/packages/pl-fe/src/features/security/mfa-form.tsx @@ -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(false); useEffect(() => { @@ -44,8 +46,8 @@ const MfaForm: React.FC = () => { ) : ( - - {displayOtpForm && } + {features.manageMfaBackupCodes && } + {(displayOtpForm || !features.manageMfaBackupCodes) && } )} diff --git a/packages/pl-fe/src/features/security/mfa/otp-confirm-form.tsx b/packages/pl-fe/src/features/security/mfa/otp-confirm-form.tsx index ec1fb4e77..a22dfa2c0 100644 --- a/packages/pl-fe/src/features/security/mfa/otp-confirm-form.tsx +++ b/packages/pl-fe/src/features/security/mfa/otp-confirm-form.tsx @@ -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 = () => { /> - } - > - - + {features.manageMfaRequiresPassword && ( + } + > + + + )}