Skip to content

Commit 0cb5741

Browse files
committed
refactor(mongodb): Improve verification code storage
- Added unique index on email field - Improved code handling and validation - Enhanced logging messages - Removed redundant cleanup method - Updated comments for clarity
1 parent 01a2e5e commit 0cb5741

File tree

1 file changed

+29
-11
lines changed

1 file changed

+29
-11
lines changed

lib/src/services/mongodb_verification_code_storage_service.dart

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ const String kVerificationCodesCollection = 'verification_codes';
1414
/// A MongoDB-backed implementation of [VerificationCodeStorageService].
1515
///
1616
/// Stores verification codes in a dedicated MongoDB collection with a TTL
17-
/// index on an `expiresAt` field for automatic cleanup.
17+
/// index on an `expiresAt` field for automatic cleanup. It uses a unique
18+
/// index on the `email` field to ensure data integrity.
1819
/// {@endtemplate}
1920
class MongoDbVerificationCodeStorageService
2021
implements VerificationCodeStorageService {
@@ -38,25 +39,32 @@ class MongoDbVerificationCodeStorageService
3839
DbCollection get _collection =>
3940
_connectionManager.db.collection(kVerificationCodesCollection);
4041

41-
/// Initializes the service by ensuring the TTL index exists.
42+
/// Initializes the service by ensuring required indexes exist.
4243
Future<void> _init() async {
4344
try {
44-
_log.info('Ensuring TTL index exists for verification codes...');
45+
_log.info('Ensuring indexes exist for verification codes...');
4546
final command = {
4647
'createIndexes': kVerificationCodesCollection,
4748
'indexes': [
49+
// TTL index for automatic document expiration
4850
{
4951
'key': {'expiresAt': 1},
5052
'name': 'expiresAt_ttl_index',
5153
'expireAfterSeconds': 0,
54+
},
55+
// Unique index to ensure only one code per email
56+
{
57+
'key': {'email': 1},
58+
'name': 'email_unique_index',
59+
'unique': true,
5260
}
5361
]
5462
};
5563
await _connectionManager.db.runCommand(command);
56-
_log.info('Verification codes TTL index is set up correctly.');
64+
_log.info('Verification codes indexes are set up correctly.');
5765
} catch (e, s) {
5866
_log.severe(
59-
'Failed to create TTL index for verification codes collection.',
67+
'Failed to create indexes for verification codes collection.',
6068
e,
6169
s,
6270
);
@@ -78,9 +86,14 @@ class MongoDbVerificationCodeStorageService
7886
final expiresAt = DateTime.now().add(codeExpiryDuration);
7987

8088
try {
89+
// Use updateOne with upsert: if a document for the email exists,
90+
// it's updated with a new code and expiry; otherwise, it's created.
8191
await _collection.updateOne(
82-
where.eq('_id', email),
83-
modify.set('code', code).set('expiresAt', expiresAt),
92+
where.eq('email', email),
93+
modify
94+
.set('code', code)
95+
.set('expiresAt', expiresAt)
96+
.setOnInsert('_id', ObjectId()),
8497
upsert: true,
8598
);
8699
_log.info(
@@ -96,14 +109,16 @@ class MongoDbVerificationCodeStorageService
96109
@override
97110
Future<bool> validateSignInCode(String email, String code) async {
98111
try {
99-
final entry = await _collection.findOne(where.id(email));
112+
final entry = await _collection.findOne(where.eq('email', email));
100113
if (entry == null) {
101114
return false; // No code found for this email
102115
}
103116

104117
final storedCode = entry['code'] as String?;
105118
final expiresAt = entry['expiresAt'] as DateTime?;
106119

120+
// The TTL index handles automatic deletion, but this check prevents
121+
// using a code in the brief window before it's deleted.
107122
if (storedCode != code ||
108123
expiresAt == null ||
109124
DateTime.now().isAfter(expiresAt)) {
@@ -120,7 +135,8 @@ class MongoDbVerificationCodeStorageService
120135
@override
121136
Future<void> clearSignInCode(String email) async {
122137
try {
123-
await _collection.deleteOne(where.id(email));
138+
// After successful validation, the code should be removed immediately.
139+
await _collection.deleteOne(where.eq('email', email));
124140
_log.info('Cleared sign-in code for $email');
125141
} catch (e) {
126142
_log.severe('Failed to clear sign-in code for $email: $e');
@@ -130,7 +146,8 @@ class MongoDbVerificationCodeStorageService
130146

131147
@override
132148
Future<void> cleanupExpiredCodes() async {
133-
// No-op, handled by TTL index.
149+
// This is a no-op because the TTL index on the MongoDB collection
150+
// handles the cleanup automatically on the server side.
134151
_log.finer(
135152
'cleanupExpiredCodes() called, but no action is needed due to TTL index.',
136153
);
@@ -139,7 +156,8 @@ class MongoDbVerificationCodeStorageService
139156

140157
@override
141158
void dispose() {
142-
// No-op, connection managed by AppDependencies.
159+
// This is a no-op because the underlying database connection is managed
160+
// by the injected MongoDbConnectionManager.
143161
_log.finer('dispose() called, no action needed.');
144162
}
145163
}

0 commit comments

Comments
 (0)