- Reset TPIN: empty-body initiate -> OTP -> encrypted new TPIN via /tpin/reset/*, from a Services tile, search, and "Forgot your TPIN?" on the transfer's TPIN sheet (returns to the same transfer review) - Handle every documented code: SESSION_INVALID signs out, TPIN_NOT_SET offers Create TPIN, CBS customer/account/org refusals stop the flow, outages retry from the start, expired or spent OTP/token restarts, policy VALIDATION_ERROR stays on the new-TPIN step - Secrets live in screen state only and are dropped as each step ends - Countdowns use the envelope timestamp, kept aside by the interceptor, so a wrong device clock cannot skew OTP or token expiry - New TPINs refuse any three-digit run (1235, 5987), for create too - Dev API logger redacts the session token, OTPs, PINs, encrypted payloads and one-time tokens - Tests: request bodies, RSA-OAEP SHA-256 payload decrypted with Node, fresh nonce on key retry, server-clock deadlines, log redaction Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
49 lines
1.7 KiB
TypeScript
49 lines
1.7 KiB
TypeScript
import React from 'react';
|
|
import { StyleSheet } from 'react-native';
|
|
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
|
|
|
|
import { ScreenShell } from '../../../components';
|
|
import { useAppSelector } from '../../../app/hooks';
|
|
import { FeatureKey } from '../../../constants/featureKeys';
|
|
import { useFeature } from '../../../features';
|
|
import type { ServicesStackParamList } from '../../../navigation/types';
|
|
import { TpinResetFlow } from '../../payments/components/TpinResetFlow';
|
|
|
|
type Props = Readonly<NativeStackScreenProps<ServicesStackParamList, 'ResetTpin'>>;
|
|
|
|
/**
|
|
* Resets a forgotten transaction PIN, from the service desk.
|
|
*
|
|
* The signed-in session stands in for the MPIN that creating a TPIN asks for;
|
|
* the bank re-verifies the customer and an OTP to the registered mobile is the
|
|
* second factor.
|
|
*/
|
|
export default function ResetTpinScreen({ navigation }: Props) {
|
|
const session = useAppSelector((state) => state.auth.session);
|
|
const lastProfile = useAppSelector((state) => state.auth.lastProfile);
|
|
// Create TPIN is offered on TPIN_NOT_SET only where its route exists.
|
|
const canCreateTpin = useFeature(FeatureKey.SERVICES_TPIN_CHANGE);
|
|
|
|
return (
|
|
<ScreenShell
|
|
title="Reset TPIN"
|
|
subtitle="Set a new PIN for authorising fund transfers"
|
|
contentContainerStyle={styles.container}
|
|
>
|
|
<TpinResetFlow
|
|
mobileNumber={session?.mobileNumber ?? lastProfile?.mobileNumber}
|
|
onSuccess={() => navigation.goBack()}
|
|
onExit={() => navigation.goBack()}
|
|
onCreateTpin={canCreateTpin ? () => navigation.replace('SetTpin') : undefined}
|
|
/>
|
|
</ScreenShell>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
padding: 16,
|
|
flexGrow: 1,
|
|
},
|
|
});
|