-
Notifications
You must be signed in to change notification settings - Fork 0
Harden auth impl #25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Harden auth impl #25
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
cedbe20
chore(env): add JWT secret key requirement and update CORS origin
fulleni f0b2068
feat(config): add JWT secret key environment variable retrieval
fulleni 1639d16
refactor(auth): replace hardcoded secret key with environment variable
fulleni a1468cb
feat(auth): add MongoDB token blacklist service
fulleni 0d5e933
refactor(config): replace token blacklist service
fulleni 01a2e5e
feat(auth): Add MongoDB verification code storage
fulleni 0cb5741
refactor(mongodb): Improve verification code storage
fulleni 8e74597
refactor(services): Replace in-memory verification code storage
fulleni 1550783
refactor(services): remove unnecessary initialization
fulleni eb04f61
refactor(services): remove unnecessary init method
fulleni 8e52d81
feat(database): add indexes to verification codes and tokens
fulleni 48cbcfe
refactor(auth): replace print statements with logging
fulleni 76589a3
fix(authorization): replace print statements with logger
fulleni 7349220
fix(error_handler): use logger instead of print
fulleni 2ea1f83
refactor: remove unused auth & verification services
fulleni 74935bf
fix(auth): improve anonymous auth error handling
fulleni 981e366
fix(auth): improve error handling in delete-account
fulleni 205acd8
fix(auth): improve error handling in request-code handler
fulleni ee01c85
fix(auth): improve sign-out error logging
fulleni 170539f
fix: improve error logging in verify-code handler
fulleni 85a289b
fix(api): replace print statements with logger
fulleni 89e2417
feat(auth): configure JWT issuer and expiry
fulleni File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
feat(auth): Add MongoDB verification code storage
- Implements verification code storage using MongoDB. - Includes code generation and validation. - Uses TTL index for automatic cleanup. - Handles errors and logs relevant information. - Adds unit tests for the new service.
- Loading branch information
There are no files selected for viewing
145 changes: 145 additions & 0 deletions
lib/src/services/mongodb_verification_code_storage_service.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,145 @@ | ||
| import 'dart:async'; | ||
| import 'dart:math'; | ||
|
|
||
| import 'package:ht_api/src/services/verification_code_storage_service.dart'; | ||
| import 'package:ht_data_mongodb/ht_data_mongodb.dart'; | ||
| import 'package:ht_shared/ht_shared.dart'; | ||
| import 'package:logging/logging.dart'; | ||
| import 'package:mongo_dart/mongo_dart.dart'; | ||
|
|
||
| /// The name of the MongoDB collection for storing verification codes. | ||
| const String kVerificationCodesCollection = 'verification_codes'; | ||
|
|
||
| /// {@template mongodb_verification_code_storage_service} | ||
| /// A MongoDB-backed implementation of [VerificationCodeStorageService]. | ||
| /// | ||
| /// Stores verification codes in a dedicated MongoDB collection with a TTL | ||
| /// index on an `expiresAt` field for automatic cleanup. | ||
| /// {@endtemplate} | ||
| class MongoDbVerificationCodeStorageService | ||
| implements VerificationCodeStorageService { | ||
| /// {@macro mongodb_verification_code_storage_service} | ||
| MongoDbVerificationCodeStorageService({ | ||
| required MongoDbConnectionManager connectionManager, | ||
| required Logger log, | ||
| this.codeExpiryDuration = const Duration(minutes: 15), | ||
| }) : _connectionManager = connectionManager, | ||
| _log = log { | ||
| _init(); | ||
| } | ||
|
|
||
| final MongoDbConnectionManager _connectionManager; | ||
| final Logger _log; | ||
| final Random _random = Random(); | ||
|
|
||
| /// The duration for which generated codes are considered valid. | ||
| final Duration codeExpiryDuration; | ||
|
|
||
| DbCollection get _collection => | ||
| _connectionManager.db.collection(kVerificationCodesCollection); | ||
|
|
||
| /// Initializes the service by ensuring the TTL index exists. | ||
| Future<void> _init() async { | ||
| try { | ||
| _log.info('Ensuring TTL index exists for verification codes...'); | ||
| final command = { | ||
| 'createIndexes': kVerificationCodesCollection, | ||
| 'indexes': [ | ||
| { | ||
| 'key': {'expiresAt': 1}, | ||
| 'name': 'expiresAt_ttl_index', | ||
| 'expireAfterSeconds': 0, | ||
| } | ||
| ] | ||
| }; | ||
| await _connectionManager.db.runCommand(command); | ||
| _log.info('Verification codes TTL index is set up correctly.'); | ||
| } catch (e, s) { | ||
| _log.severe( | ||
| 'Failed to create TTL index for verification codes collection.', | ||
| e, | ||
| s, | ||
| ); | ||
| rethrow; | ||
| } | ||
| } | ||
|
|
||
| String _generateNumericCode({int length = 6}) { | ||
| final buffer = StringBuffer(); | ||
| for (var i = 0; i < length; i++) { | ||
| buffer.write(_random.nextInt(10).toString()); | ||
| } | ||
| return buffer.toString(); | ||
| } | ||
|
|
||
| @override | ||
| Future<String> generateAndStoreSignInCode(String email) async { | ||
| final code = _generateNumericCode(); | ||
| final expiresAt = DateTime.now().add(codeExpiryDuration); | ||
|
|
||
| try { | ||
| await _collection.updateOne( | ||
| where.eq('_id', email), | ||
| modify.set('code', code).set('expiresAt', expiresAt), | ||
| upsert: true, | ||
| ); | ||
| _log.info( | ||
| 'Stored sign-in code for $email (expires: $expiresAt)', | ||
| ); | ||
| return code; | ||
| } catch (e) { | ||
| _log.severe('Failed to store sign-in code for $email: $e'); | ||
| throw OperationFailedException('Failed to store sign-in code: $e'); | ||
| } | ||
fulleni marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| @override | ||
| Future<bool> validateSignInCode(String email, String code) async { | ||
| try { | ||
| final entry = await _collection.findOne(where.id(email)); | ||
| if (entry == null) { | ||
| return false; // No code found for this email | ||
| } | ||
|
|
||
| final storedCode = entry['code'] as String?; | ||
| final expiresAt = entry['expiresAt'] as DateTime?; | ||
|
|
||
| if (storedCode != code || | ||
| expiresAt == null || | ||
| DateTime.now().isAfter(expiresAt)) { | ||
| return false; // Code mismatch or expired | ||
| } | ||
|
|
||
| return true; | ||
| } catch (e) { | ||
| _log.severe('Error validating sign-in code for $email: $e'); | ||
| throw OperationFailedException('Failed to validate sign-in code: $e'); | ||
| } | ||
fulleni marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| @override | ||
| Future<void> clearSignInCode(String email) async { | ||
| try { | ||
| await _collection.deleteOne(where.id(email)); | ||
| _log.info('Cleared sign-in code for $email'); | ||
| } catch (e) { | ||
| _log.severe('Failed to clear sign-in code for $email: $e'); | ||
| throw OperationFailedException('Failed to clear sign-in code: $e'); | ||
| } | ||
fulleni marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| @override | ||
| Future<void> cleanupExpiredCodes() async { | ||
| // No-op, handled by TTL index. | ||
| _log.finer( | ||
| 'cleanupExpiredCodes() called, but no action is needed due to TTL index.', | ||
| ); | ||
| return Future.value(); | ||
| } | ||
|
|
||
| @override | ||
| void dispose() { | ||
| // No-op, connection managed by AppDependencies. | ||
| _log.finer('dispose() called, no action needed.'); | ||
| } | ||
| } | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.