- 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>
98 lines
3.5 KiB
TypeScript
98 lines
3.5 KiB
TypeScript
import {
|
|
TPIN_LENGTH,
|
|
containsSequentialRun,
|
|
describeTpinProblem,
|
|
isAcceptableNewTpin,
|
|
isValidTpinEntry,
|
|
} from './tpinValidation';
|
|
|
|
describe('isValidTpinEntry', () => {
|
|
it('accepts exactly four digits', () => {
|
|
expect(TPIN_LENGTH).toBe(4);
|
|
expect(isValidTpinEntry('2580')).toBe(true);
|
|
expect(isValidTpinEntry('258')).toBe(false);
|
|
expect(isValidTpinEntry('25801')).toBe(false);
|
|
expect(isValidTpinEntry('25a0')).toBe(false);
|
|
});
|
|
|
|
// On a transfer the server decides whether the digits are right, so an
|
|
// entry the create rules would refuse must still be typeable.
|
|
it('does not apply the choosing rules to an entry', () => {
|
|
expect(isValidTpinEntry('1234')).toBe(true);
|
|
expect(isValidTpinEntry('1111')).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('describeTpinProblem', () => {
|
|
it('says nothing about an empty field', () => {
|
|
expect(describeTpinProblem('')).toBeUndefined();
|
|
});
|
|
|
|
it('asks for four digits until there are four', () => {
|
|
expect(describeTpinProblem('25')).toBe('A TPIN is 4 digits');
|
|
expect(describeTpinProblem('2a')).toBe('Digits only');
|
|
});
|
|
|
|
it('refuses ascending and descending runs', () => {
|
|
expect(describeTpinProblem('1234')).toMatch(/run of digits/);
|
|
expect(describeTpinProblem('4321')).toMatch(/run of digits/);
|
|
expect(describeTpinProblem('6789')).toMatch(/run of digits/);
|
|
});
|
|
|
|
// The policy refuses "a sequence of three digits", so a run of three inside
|
|
// the four counts as much as one spanning all of them.
|
|
it('refuses a run of three anywhere in the TPIN', () => {
|
|
expect(describeTpinProblem('1235')).toMatch(/run of digits/);
|
|
expect(describeTpinProblem('5987')).toMatch(/run of digits/);
|
|
expect(describeTpinProblem('0124')).toMatch(/run of digits/);
|
|
expect(describeTpinProblem('7210')).toMatch(/run of digits/);
|
|
});
|
|
|
|
it('allows a TPIN with no run of three', () => {
|
|
expect(describeTpinProblem('1243')).toBeUndefined();
|
|
expect(describeTpinProblem('1357')).toBeUndefined();
|
|
// No wrap-around from 9 to 0.
|
|
expect(describeTpinProblem('8901')).toBeUndefined();
|
|
});
|
|
|
|
it('refuses the same digit repeated', () => {
|
|
expect(describeTpinProblem('1111')).toMatch(/same digit/);
|
|
});
|
|
|
|
it('refuses the last four digits of the registered mobile', () => {
|
|
expect(describeTpinProblem('0101', '9300000101')).toMatch(/last four digits/);
|
|
expect(describeTpinProblem('0101', '+91 93000 00101')).toMatch(/last four digits/);
|
|
expect(describeTpinProblem('0102', '9300000101')).toBeUndefined();
|
|
});
|
|
|
|
it('skips the mobile rule when the number is unknown', () => {
|
|
expect(describeTpinProblem('0101')).toBeUndefined();
|
|
expect(describeTpinProblem('0101', '')).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('isAcceptableNewTpin', () => {
|
|
it('accepts a TPIN that breaks no rule', () => {
|
|
expect(isAcceptableNewTpin('2580', '9300000101')).toBe(true);
|
|
});
|
|
|
|
it('refuses one that breaks any rule, or is incomplete', () => {
|
|
expect(isAcceptableNewTpin('258')).toBe(false);
|
|
expect(isAcceptableNewTpin('1234')).toBe(false);
|
|
expect(isAcceptableNewTpin('0101', '9300000101')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('containsSequentialRun', () => {
|
|
it('finds a run of three at the start, the end, or spanning the whole value', () => {
|
|
expect(containsSequentialRun('1239')).toBe(true);
|
|
expect(containsSequentialRun('9321')).toBe(true);
|
|
expect(containsSequentialRun('3456')).toBe(true);
|
|
});
|
|
|
|
it('does not count a run of two', () => {
|
|
expect(containsSequentialRun('1290')).toBe(false);
|
|
expect(containsSequentialRun('12')).toBe(false);
|
|
});
|
|
});
|