feat(phone): Add `unconfirmed` to RecoveryPhoneManager

This commit is contained in:
Vijay Budhram 2024-11-13 11:56:41 -05:00
Родитель 13f7bc8202
Коммит d758f4939e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 9778545895B2532B
2 изменённых файлов: 118 добавлений и 1 удалений

Просмотреть файл

@ -11,6 +11,12 @@ describe('RecoveryPhoneManager', () => {
let recoveryPhoneManager: RecoveryPhoneManager;
let db: AccountDatabase;
const mockRedis = {
set: jest.fn(),
get: jest.fn(),
del: jest.fn(),
};
beforeAll(async () => {
db = await testAccountDatabaseSetup(['accounts', 'recoveryPhones']);
const moduleRef = await Test.createTestingModule({
@ -20,6 +26,10 @@ describe('RecoveryPhoneManager', () => {
provide: AccountDbProvider,
useValue: db,
},
{
provide: 'Redis',
useValue: mockRedis,
},
],
}).compile();
@ -76,4 +86,49 @@ describe('RecoveryPhoneManager', () => {
recoveryPhoneManager.registerPhoneNumber(uid.toString('hex'), phoneNumber)
).rejects.toThrow('Database error');
});
it('should store unconfirmed phone number data in Redis', async () => {
const mockPhone = RecoveryPhoneFactory();
const { uid, phoneNumber } = mockPhone;
const code = '123456';
const isSetup = true;
const lookupData = { foo: 'bar' };
await recoveryPhoneManager.storeUnconfirmed(
uid.toString('hex'),
code,
phoneNumber,
isSetup,
lookupData
);
const expectedData = JSON.stringify({
phoneNumber,
isSetup,
lookupData: JSON.stringify(lookupData),
});
const redisKey = `sms-attempt:${uid.toString('hex')}:${code}`;
expect(mockRedis.set).toHaveBeenCalledWith(
redisKey,
expectedData,
'EX',
600
);
});
it('should return null if no unconfirmed phone number data is found in Redis', async () => {
const mockPhone = RecoveryPhoneFactory();
const { uid } = mockPhone;
const code = '123456';
mockRedis.get.mockResolvedValue(null);
const result = await recoveryPhoneManager.getUnconfirmed(
uid.toString('hex'),
code
);
expect(result).toBeNull();
});
});

Просмотреть файл

@ -12,11 +12,17 @@ import {
RecoveryNumberAlreadyExistsError,
RecoveryNumberInvalidFormatError,
} from './recovery-phone.errors';
import { Redis } from 'ioredis';
const RECORD_EXPIRATION_SECONDS = 10 * 60;
@Injectable()
export class RecoveryPhoneManager {
private readonly redisPrefix = 'sms-attempt';
constructor(
@Inject(AccountDbProvider) private readonly db: AccountDatabase
@Inject(AccountDbProvider) private readonly db: AccountDatabase,
@Inject('Redis') private readonly redisClient: Redis
) {}
private isE164Format(phoneNumber: string) {
@ -57,4 +63,60 @@ export class RecoveryPhoneManager {
throw err;
}
}
/**
* Store phone number data and SMS code for a user.
*
* @param uid The user's unique identifier
* @param code The SMS code to associate with this UID
* @param phoneNumber The phone number to store
* @param isSetup Flag indicating if this SMS is to set up a number or verify an existing one
* @param lookupData Optional lookup data for the phone number
*/
async storeUnconfirmed(
uid: string,
code: string,
phoneNumber: string,
isSetup: boolean,
lookupData?: Record<string, any>
): Promise<void> {
const redisKey = `${this.redisPrefix}:${uid}:${code}`;
const data = {
phoneNumber,
isSetup,
lookupData: lookupData ? JSON.stringify(lookupData) : null,
};
await this.redisClient.set(
redisKey,
JSON.stringify(data),
'EX',
RECORD_EXPIRATION_SECONDS
);
}
/**
* Retrieve phone number data for a user using uid and sms code.
*
* @param uid The user's unique identifier
* @param code The SMS code associated with this user
* @returns The stored phone number data if found, or null if not found
*/
async getUnconfirmed(
uid: string,
code: string
): Promise<{
phoneNumber: string;
isSetup: boolean;
lookupData: Record<string, any> | null;
} | null> {
const redisKey = `${this.redisPrefix}:${uid}:${code}`;
const data = await this.redisClient.get(redisKey);
if (!data) {
return null;
}
return JSON.parse(data);
}
}