mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 21:45:31 +09:00
refactor(api): emit moderation events and apply account actions (#3031)
This commit is contained in:
1 parent
c488906131
commit
364084c819
385 files changed
+8994
-22221
No files matched your search
@@ -170,15 +170,6 @@ function isPhoneView(kind: RequiredActionViewKind): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function isPhoneInboundView(kind: RequiredActionViewKind): boolean {
|
||||
return (
|
||||
kind === 'phone-inbound-start' ||
|
||||
kind === 'phone-inbound-prepare' ||
|
||||
kind === 'phone-inbound-send' ||
|
||||
kind === 'phone-inbound-wait'
|
||||
);
|
||||
}
|
||||
|
||||
function getIntroDescription(flow: RequiredActionFlow | null, isEmailBounced: boolean): MessageDescriptor {
|
||||
if (isEmailBounced) return STEP_INTRO_BOUNCED_EMAIL_DESCRIPTION_DESCRIPTOR;
|
||||
if (flow?.mode === 'email_and_phone') return STEP_INTRO_EMAIL_AND_PHONE_DESCRIPTION_DESCRIPTOR;
|
||||
@@ -288,18 +279,6 @@ const RequiredActionModal: React.FC<{mock?: boolean}> = observer(({mock = false}
|
||||
return previous ? current.slice(0, -1) : current;
|
||||
});
|
||||
}, []);
|
||||
const goToPhoneCodeFromInbound = useCallback(() => {
|
||||
setCarouselDirection(1);
|
||||
setActionError(null);
|
||||
setHistory((current) => {
|
||||
const retainedHistory = current.filter((entry) => !isPhoneInboundView(entry.kind));
|
||||
if (retainedHistory.at(-1)?.kind === 'phone-number') {
|
||||
return retainedHistory;
|
||||
}
|
||||
return [...retainedHistory, {kind: 'phone-number'}];
|
||||
});
|
||||
setView({kind: 'phone-code'});
|
||||
}, []);
|
||||
const switchToPhonePath = useCallback(() => {
|
||||
setSelectedVerificationType('phone');
|
||||
}, []);
|
||||
@@ -462,7 +441,6 @@ const RequiredActionModal: React.FC<{mock?: boolean}> = observer(({mock = false}
|
||||
? {
|
||||
code: phoneVerification.screen.code,
|
||||
ourNumber: phoneVerification.screen.ourNumber,
|
||||
reason: phoneVerification.screen.reason,
|
||||
}
|
||||
: null;
|
||||
const renderView = (): React.ReactNode => {
|
||||
@@ -831,11 +809,9 @@ const RequiredActionModal: React.FC<{mock?: boolean}> = observer(({mock = false}
|
||||
/>
|
||||
<Button
|
||||
onClick={() =>
|
||||
void runPhoneAction(phoneVerification.onRefreshInboundChallenge, 'general').then((result) => {
|
||||
if (result === 'inbound-challenge') {
|
||||
void runPhoneAction(phoneVerification.onRefreshInboundChallenge, 'general').then((refreshed) => {
|
||||
if (refreshed) {
|
||||
goBackToFallback({kind: 'phone-inbound-send'});
|
||||
} else if (result === 'phone-code') {
|
||||
goToPhoneCodeFromInbound();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+6
-11
@@ -315,9 +315,9 @@ export const ESCAPE_CONFIRM_TITLE_WITH_GUILDS_DESCRIPTOR = msg({
|
||||
});
|
||||
export const ESCAPE_CONFIRM_OUTCOME_DESCRIPTOR = msg({
|
||||
message:
|
||||
'Your account goes back to normal right away and you will not need a phone number now. This is not a permanent exemption. Joining a large or public community can bring this check back.',
|
||||
'Your account goes back to normal right away and you will not need a phone number now. This is not a permanent exemption, and we can ask for this check again later.',
|
||||
comment:
|
||||
'First line of the set-aside confirmation in the required-action modal. Always shown. Says can rather than will, because whether the check returns depends on account age and instance settings the user cannot see.',
|
||||
'First line of the set-aside confirmation in the required-action modal. Always shown. Says can rather than will, because the check may or may not come back.',
|
||||
});
|
||||
export const ESCAPE_CONFIRM_LEAVE_DESCRIPTOR = msg({
|
||||
message: 'You will leave {guildNames}. Nothing you posted is deleted, and you can join again later.',
|
||||
@@ -420,8 +420,8 @@ export const PHONE_NUMBER_DESCRIPTION_DESCRIPTOR = msg({
|
||||
});
|
||||
export const PHONE_PRIVACY_DESCRIPTOR = msg({
|
||||
message:
|
||||
'Your number is not linked to your account. We keep only an encrypted marker, with no user ID, for at most {limit} verifications in about {duration} days.',
|
||||
comment: 'Required-action modal privacy note for phone verification. limit and duration are inserted by code.',
|
||||
'Your number is not linked to your account. We keep only an encrypted marker, with no user ID, for a limited time.',
|
||||
comment: 'Required-action modal privacy note for phone verification.',
|
||||
});
|
||||
export const ENTER_PHONE_CODE_TITLE_DESCRIPTOR = msg({
|
||||
message: 'Enter the SMS code',
|
||||
@@ -439,10 +439,6 @@ export const INBOUND_PHONE_START_DESCRIPTION_DESCRIPTOR = msg({
|
||||
message: 'For this check, you send us a text message instead of receiving one from us.',
|
||||
comment: 'Required-action modal body for starting an inbound phone verification challenge.',
|
||||
});
|
||||
export const INBOUND_PHONE_EXPENSIVE_REASON_DESCRIPTOR = msg({
|
||||
message: 'Sending an SMS to this number is expensive, so we need you to text us instead.',
|
||||
comment: 'Required-action modal explanation for an inbound phone verification challenge due to SMS cost.',
|
||||
});
|
||||
export const INBOUND_PHONE_DEFAULT_REASON_DESCRIPTOR = msg({
|
||||
message: 'This number needs text-in verification instead of an SMS from us.',
|
||||
comment: 'Required-action modal explanation for an inbound phone verification challenge.',
|
||||
@@ -570,13 +566,12 @@ export const PHONE_NOT_IN_SERVICE_DESCRIPTOR = msg({
|
||||
export const PHONE_NOT_MOBILE_DESCRIPTOR = msg({
|
||||
message:
|
||||
"This isn't a mobile number, so it can't receive our text. Try a mobile number, or contact support if you think that's wrong.",
|
||||
comment:
|
||||
'Required-action form error for landline, toll-free, premium, shared-cost, UAN, voicemail and pager lines. Do not name which line type the provider returned.',
|
||||
comment: 'Required-action form error when the number is not a mobile line. Do not name the line type.',
|
||||
});
|
||||
export const PHONE_NEEDS_REVIEW_DESCRIPTOR = msg({
|
||||
message: "We couldn't verify this number automatically. Contact support and a person will review your account.",
|
||||
comment:
|
||||
'Required-action form error for phone attempts blocked by a fraud signal. Deliberately non-specific. Never name the signal, the score or the threshold.',
|
||||
'Required-action form error when a phone attempt cannot be verified automatically and needs a support review. Keep it non-specific.',
|
||||
});
|
||||
export const CAPTCHA_REQUIRED_DESCRIPTOR = msg({
|
||||
message: 'A browser check is required before phone verification. Try again from the sign-in page or contact support.',
|
||||
|
||||
+10
-57
@@ -1,10 +1,6 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {
|
||||
MFA_CODE_DIGIT_COUNT,
|
||||
PHONE_VERIFICATION_LIMIT,
|
||||
PHONE_VERIFICATION_WINDOW_DAYS,
|
||||
} from '@app/features/app/config/I18nDisplayConstants';
|
||||
import {MFA_CODE_DIGIT_COUNT} from '@app/features/app/config/I18nDisplayConstants';
|
||||
import styles from '@app/features/auth/components/modals/RequiredActionModal.module.css';
|
||||
import {
|
||||
COUNTRY_DESCRIPTOR,
|
||||
@@ -14,7 +10,6 @@ import {
|
||||
INBOUND_PHONE_CODE_LABEL_DESCRIPTOR,
|
||||
INBOUND_PHONE_DEFAULT_REASON_DESCRIPTOR,
|
||||
INBOUND_PHONE_DESTINATION_LABEL_DESCRIPTOR,
|
||||
INBOUND_PHONE_EXPENSIVE_REASON_DESCRIPTOR,
|
||||
INBOUND_PHONE_PREPARE_DESCRIPTION_DESCRIPTOR,
|
||||
INBOUND_PHONE_PREPARE_TITLE_DESCRIPTOR,
|
||||
INBOUND_PHONE_SEND_DESCRIPTION_DESCRIPTOR,
|
||||
@@ -44,7 +39,6 @@ import {
|
||||
type CodeFormInputs,
|
||||
normalizeVerificationCode,
|
||||
type PhoneFormInputs,
|
||||
type PhoneInboundChallengeReason,
|
||||
type PhoneScreen,
|
||||
type SubmitCallback,
|
||||
} from '@app/features/auth/components/modals/required_action/RequiredActionTypes';
|
||||
@@ -65,7 +59,6 @@ import {
|
||||
getE164PhoneNumber,
|
||||
getPhoneNumberPlaceholder,
|
||||
} from '@app/media/data/CountryCodes';
|
||||
import type {I18n} from '@lingui/core';
|
||||
import {useLingui} from '@lingui/react/macro';
|
||||
import type React from 'react';
|
||||
import {useCallback, useEffect, useMemo, useState} from 'react';
|
||||
@@ -145,12 +138,6 @@ const renderCountryValue = (option: CountrySelectOption | null) => {
|
||||
);
|
||||
};
|
||||
|
||||
function getInboundReasonText(i18n: I18n, reason: PhoneInboundChallengeReason | null): string {
|
||||
return reason === 'expensive_destination'
|
||||
? i18n._(INBOUND_PHONE_EXPENSIVE_REASON_DESCRIPTOR)
|
||||
: i18n._(INBOUND_PHONE_DEFAULT_REASON_DESCRIPTOR);
|
||||
}
|
||||
|
||||
interface InboundPhoneStartStepProps {
|
||||
requiresInboundPhone: boolean;
|
||||
}
|
||||
@@ -179,7 +166,7 @@ export const InboundPhoneInstructionStep: React.FC<InboundPhoneInstructionStepPr
|
||||
<StepShell
|
||||
title={i18n._(INBOUND_PHONE_PREPARE_TITLE_DESCRIPTOR)}
|
||||
description={i18n._(INBOUND_PHONE_PREPARE_DESCRIPTION_DESCRIPTOR)}
|
||||
notice={getInboundReasonText(i18n, challenge.reason)}
|
||||
notice={i18n._(INBOUND_PHONE_DEFAULT_REASON_DESCRIPTOR)}
|
||||
data-flx="auth.required-action.required-action-phone.inbound-phone-instruction-step.step-shell"
|
||||
/>
|
||||
);
|
||||
@@ -249,10 +236,7 @@ export const PhoneNumberStep: React.FC<PhoneNumberFormProps> = ({
|
||||
<StepShell
|
||||
title={i18n._(PHONE_NUMBER_TITLE_DESCRIPTOR)}
|
||||
description={i18n._(PHONE_NUMBER_DESCRIPTION_DESCRIPTOR)}
|
||||
notice={i18n._(PHONE_PRIVACY_DESCRIPTOR, {
|
||||
limit: PHONE_VERIFICATION_LIMIT,
|
||||
duration: PHONE_VERIFICATION_WINDOW_DAYS,
|
||||
})}
|
||||
notice={i18n._(PHONE_PRIVACY_DESCRIPTOR)}
|
||||
data-flx="auth.required-action.required-action-phone.phone-number-step.step-shell"
|
||||
>
|
||||
<div
|
||||
@@ -346,7 +330,7 @@ export interface PhoneVerificationController {
|
||||
onCodeFormSubmit: SubmitCallback;
|
||||
onCountryChange: (country: CountryCode) => void;
|
||||
onPhoneInput: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
onRefreshInboundChallenge: () => Promise<'inbound-challenge' | 'phone-code' | null>;
|
||||
onRefreshInboundChallenge: () => Promise<boolean>;
|
||||
onBackToPhone: () => void;
|
||||
}
|
||||
|
||||
@@ -397,12 +381,7 @@ export const usePhoneVerification = ({
|
||||
setIsStartingInbound(true);
|
||||
try {
|
||||
const challenge = await UserCommands.startInboundPhoneChallenge();
|
||||
setScreen({
|
||||
kind: 'phone-inbound-challenge',
|
||||
code: challenge.challenge_code,
|
||||
ourNumber: challenge.our_number,
|
||||
reason: null,
|
||||
});
|
||||
setScreen({kind: 'phone-inbound-challenge', code: challenge.challenge_code, ourNumber: challenge.our_number});
|
||||
} finally {
|
||||
setIsStartingInbound(false);
|
||||
}
|
||||
@@ -423,7 +402,6 @@ export const usePhoneVerification = ({
|
||||
kind: 'phone-inbound-challenge',
|
||||
code: verification.challenge_code,
|
||||
ourNumber: verification.our_number,
|
||||
reason: verification.reason,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -445,42 +423,17 @@ export const usePhoneVerification = ({
|
||||
},
|
||||
[getCurrentE164PhoneNumber, returnToPhoneStepWithError, i18n],
|
||||
);
|
||||
const onRefreshInboundChallenge = useCallback(async (): Promise<'inbound-challenge' | 'phone-code' | null> => {
|
||||
if (screen.kind !== 'phone-inbound-challenge') return null;
|
||||
const currentReason = screen.reason;
|
||||
const onRefreshInboundChallenge = useCallback(async (): Promise<boolean> => {
|
||||
if (screen.kind !== 'phone-inbound-challenge') return false;
|
||||
setIsStartingInbound(true);
|
||||
try {
|
||||
if (currentReason === 'expensive_destination') {
|
||||
const e164Phone = getCurrentE164PhoneNumber();
|
||||
if (!e164Phone) {
|
||||
returnToPhoneStepWithError();
|
||||
return null;
|
||||
}
|
||||
const verification = await UserCommands.sendPhoneVerification(e164Phone);
|
||||
if (verification.channel === 'inbound_challenge') {
|
||||
setScreen({
|
||||
kind: 'phone-inbound-challenge',
|
||||
code: verification.challenge_code,
|
||||
ourNumber: verification.our_number,
|
||||
reason: verification.reason,
|
||||
});
|
||||
return 'inbound-challenge';
|
||||
}
|
||||
setScreen({kind: 'phone-code', recipient: e164Phone});
|
||||
return 'phone-code';
|
||||
}
|
||||
const challenge = await UserCommands.startInboundPhoneChallenge();
|
||||
setScreen({
|
||||
kind: 'phone-inbound-challenge',
|
||||
code: challenge.challenge_code,
|
||||
ourNumber: challenge.our_number,
|
||||
reason: null,
|
||||
});
|
||||
return 'inbound-challenge';
|
||||
setScreen({kind: 'phone-inbound-challenge', code: challenge.challenge_code, ourNumber: challenge.our_number});
|
||||
return true;
|
||||
} finally {
|
||||
setIsStartingInbound(false);
|
||||
}
|
||||
}, [getCurrentE164PhoneNumber, screen, returnToPhoneStepWithError]);
|
||||
}, [screen]);
|
||||
const {handleSubmit: onPhoneFormSubmit, isSubmitting: isPhoneSubmitting} = useRequiredActionFormSubmit(
|
||||
phoneForm,
|
||||
onStartInbound,
|
||||
|
||||
+1
-10
@@ -22,18 +22,9 @@ export interface VerificationTab {
|
||||
label: string;
|
||||
}
|
||||
|
||||
export type PhoneInboundChallengeReason =
|
||||
| 'voip'
|
||||
| 'canadian'
|
||||
| 'unknown_line_type'
|
||||
| 'expensive_destination'
|
||||
| 'account_forced'
|
||||
| 'behavioural_risk';
|
||||
|
||||
export interface ActiveInboundChallenge {
|
||||
code: string;
|
||||
ourNumber: string;
|
||||
reason: PhoneInboundChallengeReason | null;
|
||||
}
|
||||
|
||||
export type EmailScreen =
|
||||
@@ -46,7 +37,7 @@ export type PhoneScreen =
|
||||
| {kind: 'phone-number'}
|
||||
| {kind: 'phone-code'; recipient: string}
|
||||
| {kind: 'phone-inbound-start'}
|
||||
| {kind: 'phone-inbound-challenge'; code: string; ourNumber: string; reason: PhoneInboundChallengeReason | null};
|
||||
| {kind: 'phone-inbound-challenge'; code: string; ourNumber: string};
|
||||
|
||||
export const EMAIL_SCREEN_ORDER: ReadonlyArray<EmailScreen['kind']> = [
|
||||
'email-instructions',
|
||||
|
||||
Reference in new issue
Block a user