Services¶
Business logic and service layer implementations.
All services are located in src/services/ and follow dependency injection patterns. Services handle business logic, external integrations, and orchestrate operations across models.
Authentication Service¶
src.services.auth_service.AuthService ¶
Service for user authentication and management (orchestrator).
This service orchestrates authentication workflows by delegating to specialized services while maintaining a unified public API.
Workflows
- Registration: Create user → Delegate verification to VerificationService
- Verification: Delegate to VerificationService
- Login: Verify credentials → Generate tokens → Create refresh token
- Refresh: Validate refresh token → Generate new access token
- Password Reset: Delegate to PasswordResetService
Attributes:
| Name | Type | Description |
|---|---|---|
session |
Database session for async operations |
|
password_service |
Service for password operations (sync) |
|
jwt_service |
Service for JWT operations (sync) |
|
verification_service |
Service for email verification workflows |
|
password_reset_service |
Service for password reset workflows |
Source code in src/services/auth_service.py
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 | |
Functions¶
__init__ ¶
Initialize auth service with dependencies.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
AsyncSession
|
Async database session |
required |
cache
|
Optional[CacheBackend]
|
Cache backend for token blacklist (SOLID: Dependency Injection) |
None
|
Note
Development mode is automatically determined from settings.DEBUG. When DEBUG=True, emails are logged instead of sent. Cache is optional (defaults to singleton if not provided).
Source code in src/services/auth_service.py
register_user
async
¶
Register a new user with email verification.
Creates a new user account with hashed password and sends verification email. User account is inactive until verified.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
email
|
str
|
User's email address (unique) |
required |
password
|
str
|
Plain text password (will be hashed) |
required |
name
|
Optional[str]
|
User's full name (optional) |
None
|
Returns:
| Type | Description |
|---|---|
User
|
Created user instance (unverified) |
Raises:
| Type | Description |
|---|---|
HTTPException
|
If email already exists or password is weak |
Example
service = AuthService(session) user = await service.register_user( ... email="user@example.com", ... password="SecurePass123!", ... name="John Doe" ... ) user.is_verified False
Source code in src/services/auth_service.py
verify_email
async
¶
Verify user's email address using verification token.
Delegates to VerificationService for token validation and user activation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Email verification token string |
required |
Returns:
| Type | Description |
|---|---|
User
|
Verified user instance |
Raises:
| Type | Description |
|---|---|
HTTPException
|
If token is invalid or expired |
Example
service = AuthService(session) user = await service.verify_email("abc123def456") user.is_verified True
Source code in src/services/auth_service.py
login
async
¶
login(
email: str,
password: str,
ip_address: Optional[str] = None,
user_agent: Optional[str] = None,
) -> Tuple[str, str, User]
Authenticate user and generate JWT tokens with session tracking.
Verifies credentials and generates both access and refresh tokens. Refresh token is stored in database with session metadata (location, device info, fingerprint) for session management.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
email
|
str
|
User's email address |
required |
password
|
str
|
Plain text password |
required |
ip_address
|
Optional[str]
|
Client IP address (for geolocation) |
None
|
user_agent
|
Optional[str]
|
Client User-Agent header (for device fingerprinting) |
None
|
Returns:
| Type | Description |
|---|---|
Tuple[str, str, User]
|
Tuple of (access_token, refresh_token, user) |
Raises:
| Type | Description |
|---|---|
HTTPException
|
If credentials invalid or user not verified |
Example
service = AuthService(session) access, refresh, user = await service.login( ... email="user@example.com", ... password="SecurePass123!", ... ip_address="203.0.113.1", ... user_agent="Mozilla/5.0 ..." ... )
Source code in src/services/auth_service.py
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 | |
refresh_access_token
async
¶
refresh_access_token(
refresh_token: str,
ip_address: Optional[str] = None,
user_agent: Optional[str] = None,
) -> str
Generate new access token using opaque refresh token with session tracking.
Validates the opaque refresh token by hashing and database lookup. Updates last_used_at on the refresh token for session tracking. Generates JWT with jti claim linking to refresh token (session ID). This is Pattern A (industry standard): JWT access + opaque refresh.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
refresh_token
|
str
|
Opaque refresh token string (not JWT) |
required |
ip_address
|
Optional[str]
|
Client IP address (for activity tracking) |
None
|
user_agent
|
Optional[str]
|
Client User-Agent header (for fingerprint validation) |
None
|
Returns:
| Type | Description |
|---|---|
str
|
New JWT access token string |
Raises:
| Type | Description |
|---|---|
HTTPException
|
If refresh token invalid, expired, or revoked |
Example
service = AuthService(session) new_access_token = await service.refresh_access_token( ... refresh_token=refresh_token, ... ip_address="203.0.113.1", ... user_agent="Mozilla/5.0 ..." ... )
Source code in src/services/auth_service.py
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 | |
logout
async
¶
Logout user by revoking opaque refresh token.
Validates the opaque refresh token by hashing and database lookup, then marks it as revoked. Access tokens remain valid until expiration. This is Pattern A (industry standard): JWT access + opaque refresh.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
refresh_token
|
str
|
Opaque refresh token string (not JWT) |
required |
Raises:
| Type | Description |
|---|---|
HTTPException
|
If refresh token invalid |
Example
service = AuthService(session) await service.logout(refresh_token)
Source code in src/services/auth_service.py
request_password_reset
async
¶
Request password reset by sending reset email.
Delegates to PasswordResetService for token generation and email. Always succeeds to prevent email enumeration attacks.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
email
|
str
|
User's email address |
required |
Example
service = AuthService(session) await service.request_password_reset("user@example.com")
Source code in src/services/auth_service.py
reset_password
async
¶
Reset user password using reset token.
Delegates to PasswordResetService for token validation, password update, session revocation, and confirmation email.
Security: All active refresh tokens are revoked to ensure that any potentially compromised sessions are terminated. This forces users to log in again with the new password on all devices.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Password reset token string |
required |
new_password
|
str
|
New plain text password |
required |
Returns:
| Type | Description |
|---|---|
User
|
User instance with updated password |
Raises:
| Type | Description |
|---|---|
HTTPException
|
If token invalid/expired or password weak |
Example
service = AuthService(session) user = await service.reset_password( ... token="xyz789", ... new_password="NewSecurePass123!" ... )
Source code in src/services/auth_service.py
get_user_by_id
async
¶
Get user by ID.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_id
|
UUID
|
User's unique identifier |
required |
Returns:
| Type | Description |
|---|---|
Optional[User]
|
User instance or None if not found |
Example
service = AuthService(session) user = await service.get_user_by_id(user_id)
Source code in src/services/auth_service.py
get_user_by_email
async
¶
Get user by email address.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
email
|
str
|
User's email address |
required |
Returns:
| Type | Description |
|---|---|
Optional[User]
|
User instance or None if not found |
Example
service = AuthService(session) user = await service.get_user_by_email("user@example.com")
Source code in src/services/auth_service.py
update_user_profile
async
¶
Update user profile information.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_id
|
UUID
|
User's unique identifier |
required |
name
|
Optional[str]
|
New name (optional) |
None
|
Returns:
| Type | Description |
|---|---|
User
|
Updated user instance |
Raises:
| Type | Description |
|---|---|
HTTPException
|
If user not found |
Example
service = AuthService(session) user = await service.update_user_profile( ... user_id=user_id, ... name="Jane Doe" ... )
Source code in src/services/auth_service.py
change_password
async
¶
Change user password (requires current password).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_id
|
UUID
|
User's unique identifier |
required |
current_password
|
str
|
Current password for verification |
required |
new_password
|
str
|
New password to set |
required |
Returns:
| Type | Description |
|---|---|
User
|
User instance with updated password |
Raises:
| Type | Description |
|---|---|
HTTPException
|
If current password wrong or new password weak |
Example
service = AuthService(session) user = await service.change_password( ... user_id=user_id, ... current_password="OldPass123!", ... new_password="NewSecurePass123!" ... )
Source code in src/services/auth_service.py
558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 | |
Token Service¶
src.services.token_service.TokenService ¶
Service for managing OAuth tokens throughout their lifecycle.
This service provides a high-level interface for token operations, handling encryption, refresh logic, and audit logging automatically.
Source code in src/services/token_service.py
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 | |
Functions¶
__init__ ¶
Initialize the token service.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
AsyncSession
|
Database session for operations. |
required |
store_initial_tokens
async
¶
store_initial_tokens(
provider_id: UUID,
tokens: Dict[str, Any],
user_id: UUID,
request_info: Optional[Dict[str, str]] = None,
) -> ProviderToken
Store initial tokens after successful OAuth authentication.
This method is called after the OAuth callback to store the initial tokens received from the provider.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
provider_id
|
UUID
|
The provider instance ID. |
required |
tokens
|
Dict[str, Any]
|
Token response from provider containing: - access_token: The access token - refresh_token: Optional refresh token - expires_in: Token lifetime in seconds - id_token: Optional JWT ID token - scope: Optional granted scopes |
required |
user_id
|
UUID
|
User who authorized the connection. |
required |
request_info
|
Optional[Dict[str, str]]
|
Optional request metadata (IP, user agent). |
None
|
Returns:
| Type | Description |
|---|---|
ProviderToken
|
The created ProviderToken instance. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If provider or connection not found. |
Source code in src/services/token_service.py
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | |
get_valid_access_token
async
¶
Get a valid access token for a provider, refreshing if necessary.
This is the main method used by providers to get tokens. It handles: - Decryption of stored tokens - Checking expiration - Automatic refresh if expired or expiring soon - Audit logging
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
provider_id
|
UUID
|
The provider instance ID. |
required |
user_id
|
UUID
|
The user requesting the token. |
required |
Returns:
| Type | Description |
|---|---|
str
|
Valid decrypted access token. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If provider, connection, or token not found. |
Exception
|
If token refresh fails. |
Source code in src/services/token_service.py
refresh_token
async
¶
Refresh an expired or expiring access token.
Uses the refresh token to obtain a new access token from the provider. Handles token rotation if the provider sends a new refresh token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
provider_id
|
UUID
|
The provider instance ID. |
required |
user_id
|
UUID
|
The user requesting the refresh. |
required |
Returns:
| Type | Description |
|---|---|
ProviderToken
|
Updated ProviderToken instance. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no refresh token available. |
Exception
|
If refresh fails. |
Source code in src/services/token_service.py
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 | |
revoke_tokens
async
¶
revoke_tokens(
provider_id: UUID,
user_id: UUID,
request_info: Optional[Dict[str, str]] = None,
) -> None
Revoke tokens and disconnect a provider.
This removes the stored tokens and marks the connection as revoked. The provider instance itself is preserved for potential reconnection.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
provider_id
|
UUID
|
The provider instance ID. |
required |
user_id
|
UUID
|
The user revoking the connection. |
required |
request_info
|
Optional[Dict[str, str]]
|
Optional request metadata. |
None
|
Source code in src/services/token_service.py
get_token_info
async
¶
Get information about stored tokens without decrypting.
Useful for checking token status without exposing sensitive data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
provider_id
|
UUID
|
The provider instance ID. |
required |
Returns:
| Type | Description |
|---|---|
Optional[Dict[str, Any]]
|
Dictionary with token metadata, or None if no tokens. |
Source code in src/services/token_service.py
JWT Service¶
src.services.jwt_service.JWTService ¶
Service for JWT token operations.
This service provides JWT encoding and decoding functionality using python-jose. All methods are synchronous because JWT operations are pure CPU work with no I/O operations.
Token Types
- Access Token: Short-lived (30 min default), used for API authentication
- Refresh Token: Long-lived (30 days default), used to get new access tokens
Token Claims
- sub (subject): User ID
- email: User email address
- type: Token type (access/refresh)
- exp (expiration): Token expiration timestamp
- iat (issued at): Token creation timestamp
- jti (JWT ID): Unique token identifier (for refresh tokens)
Attributes:
| Name | Type | Description |
|---|---|---|
secret_key |
Secret key for signing tokens |
|
algorithm |
Signing algorithm (HS256) |
|
access_token_expire_minutes |
Access token TTL |
|
refresh_token_expire_days |
Refresh token TTL |
Source code in src/services/jwt_service.py
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 | |
Functions¶
__init__ ¶
Initialize JWT service with configuration from settings.
Source code in src/services/jwt_service.py
create_access_token ¶
create_access_token(
user_id: UUID,
email: str,
refresh_token_id: Optional[UUID] = None,
token_version: Optional[int] = None,
additional_claims: Optional[Dict[str, Any]] = None,
) -> str
Create a new access token for user authentication.
Access tokens are short-lived (default: 30 minutes) and used for authenticating API requests. They contain user identity information but should not contain sensitive data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_id
|
UUID
|
Unique user identifier |
required |
email
|
str
|
User's email address |
required |
refresh_token_id
|
Optional[UUID]
|
Optional refresh token UUID (adds jti claim for session management) |
None
|
token_version
|
Optional[int]
|
Optional user token version (for rotation validation) |
None
|
additional_claims
|
Optional[Dict[str, Any]]
|
Optional additional claims to include |
None
|
Returns:
| Type | Description |
|---|---|
str
|
Encoded JWT access token string |
Example
service = JWTService() from uuid import uuid4 user_id = uuid4() token = service.create_access_token(user_id, "user@example.com", token_version=1) len(token) > 0 True
Notes
- Adding refresh_token_id as jti enables current session detection
- Adding token_version enables rotation-based invalidation
- Existing tokens without jti/version still work (graceful degradation)
- jti = JWT ID (RFC 7519 standard claim)
Source code in src/services/jwt_service.py
create_refresh_token ¶
Create a new refresh token (DEPRECATED).
DEPRECATED: This method is deprecated. The application now uses opaque (random) refresh tokens instead of JWT refresh tokens. This follows industry standard Pattern A (JWT access + opaque refresh).
Refresh tokens should be generated using secrets.token_urlsafe() and stored as hashed values in the database. See AuthService._create_refresh_token().
This method is kept for backwards compatibility with existing tests only.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_id
|
UUID
|
Unique user identifier |
required |
jti
|
UUID
|
Unique token identifier |
required |
Returns:
| Type | Description |
|---|---|
str
|
Encoded JWT refresh token string (for testing only) |
Note
DO NOT use this for production authentication flows. Use AuthService._create_refresh_token() instead.
Source code in src/services/jwt_service.py
decode_token ¶
Decode and validate a JWT token.
This method decodes the token, verifies its signature, and checks that it hasn't expired. If validation fails, a JWTError exception is raised.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Encoded JWT token string |
required |
Returns:
| Type | Description |
|---|---|
Dict[str, Any]
|
Dictionary of token claims (sub, email, type, exp, etc.) |
Raises:
| Type | Description |
|---|---|
JWTError
|
If token is invalid, expired, or signature doesn't match |
Example
service = JWTService() from uuid import uuid4 user_id = uuid4() token = service.create_access_token(user_id, "user@example.com") claims = service.decode_token(token) claims["email"] 'user@example.com' claims["type"] 'access'
Source code in src/services/jwt_service.py
verify_token_type ¶
Decode token and verify it's of the expected type.
Use this to ensure tokens are being used correctly: - Access tokens for API authentication - Refresh tokens for token refresh
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Encoded JWT token string |
required |
expected_type
|
str
|
Expected token type ("access" or "refresh") |
required |
Returns:
| Type | Description |
|---|---|
Dict[str, Any]
|
Dictionary of token claims |
Raises:
| Type | Description |
|---|---|
JWTError
|
If token is invalid or wrong type |
Example
service = JWTService() from uuid import uuid4 user_id = uuid4() token = service.create_access_token(user_id, "user@example.com") claims = service.verify_token_type(token, "access") claims["type"] 'access'
service.verify_token_type(token, "refresh") Traceback (most recent call last): ... jose.exceptions.JWTError: Invalid token type: expected refresh, got access
Source code in src/services/jwt_service.py
get_user_id_from_token ¶
Extract user ID from a valid token.
Convenience method to get user ID without manually parsing claims.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Encoded JWT token string |
required |
Returns:
| Type | Description |
|---|---|
UUID
|
User ID as UUID |
Raises:
| Type | Description |
|---|---|
JWTError
|
If token is invalid or missing subject claim |
Example
service = JWTService() from uuid import uuid4 user_id = uuid4() token = service.create_access_token(user_id, "user@example.com") extracted_id = service.get_user_id_from_token(token) extracted_id == user_id True
Source code in src/services/jwt_service.py
get_token_jti ¶
Extract JWT ID (jti) from a refresh token.
The jti claim is used to track refresh tokens in the database and enable token revocation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Encoded refresh token string |
required |
Returns:
| Type | Description |
|---|---|
UUID
|
Token ID as UUID |
Raises:
| Type | Description |
|---|---|
JWTError
|
If token is invalid or missing jti claim |
Example
service = JWTService() from uuid import uuid4 user_id = uuid4() jti = uuid4() token = service.create_refresh_token(user_id, jti) extracted_jti = service.get_token_jti(token) extracted_jti == jti True
Source code in src/services/jwt_service.py
is_token_expired ¶
Check if a token is expired without raising an exception.
Useful for checking token status without catching exceptions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Encoded JWT token string |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if token is expired, False if still valid |
Note
If the token is malformed or has invalid signature, this will return True (treating invalid as expired).
Example
service = JWTService() from uuid import uuid4 user_id = uuid4() token = service.create_access_token(user_id, "user@example.com") service.is_token_expired(token) False
Source code in src/services/jwt_service.py
get_token_expiration ¶
Get the expiration time of a token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Encoded JWT token string |
required |
Returns:
| Type | Description |
|---|---|
Optional[datetime]
|
Expiration datetime, or None if token is invalid |
Example
service = JWTService() from uuid import uuid4 user_id = uuid4() token = service.create_access_token(user_id, "user@example.com") exp = service.get_token_expiration(token) exp is not None True
Source code in src/services/jwt_service.py
Password Service¶
src.services.password_service.PasswordService ¶
Service for password operations using bcrypt.
This service provides secure password hashing and verification using bcrypt with configurable rounds. All methods are synchronous because bcrypt is CPU-bound and passlib is a synchronous library.
Attributes:
| Name | Type | Description |
|---|---|---|
pwd_context |
Passlib CryptContext configured with bcrypt |
|
min_length |
Minimum password length (default: 8) |
|
require_uppercase |
Whether uppercase letter is required (default: True) |
|
require_lowercase |
Whether lowercase letter is required (default: True) |
|
require_digit |
Whether digit is required (default: True) |
|
require_special |
Whether special character is required (default: True) |
Source code in src/services/password_service.py
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 | |
Functions¶
__init__ ¶
Initialize password service with bcrypt configuration.
Bcrypt rounds are loaded from config (default: 12). Higher rounds = more secure but slower (exponential). - 10 rounds: ~100ms - 12 rounds: ~300ms (recommended) - 14 rounds: ~1200ms
Source code in src/services/password_service.py
hash_password ¶
Hash a password using bcrypt.
This operation is CPU-intensive (~300ms with 12 rounds) but synchronous. It can be called directly from async code without blocking the event loop improperly.
Note: Bcrypt has a 72-byte maximum password length. Passwords are automatically truncated to 72 bytes before hashing. This is safe because 72 bytes provides sufficient entropy.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
password
|
str
|
Plain text password to hash |
required |
Returns:
| Type | Description |
|---|---|
str
|
Hashed password string (includes salt and algorithm info) |
Example
service = PasswordService() hashed = service.hash_password("SecurePass123!") hashed.startswith("$2b$") True
Source code in src/services/password_service.py
verify_password ¶
Verify a password against its hash.
Uses constant-time comparison to prevent timing attacks.
Note: Bcrypt has a 72-byte maximum password length. Passwords are automatically truncated to 72 bytes before verification to match the hashing behavior.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
plain_password
|
str
|
Plain text password to verify |
required |
hashed_password
|
str
|
Hashed password to compare against |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if password matches, False otherwise |
Example
service = PasswordService() hashed = service.hash_password("SecurePass123!") service.verify_password("SecurePass123!", hashed) True service.verify_password("WrongPassword", hashed) False
Source code in src/services/password_service.py
validate_password_strength ¶
Validate password meets strength requirements.
Requirements (all must be met): - Minimum length (default: 8 characters) - At least one uppercase letter - At least one lowercase letter - At least one digit - At least one special character
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
password
|
str
|
Password to validate |
required |
Returns:
| Type | Description |
|---|---|
bool
|
Tuple of (is_valid: bool, error_message: str) |
str
|
If valid, error_message is empty string |
Tuple[bool, str]
|
If invalid, error_message describes the issue |
Example
service = PasswordService() valid, msg = service.validate_password_strength("weak") valid False msg 'Password must be at least 8 characters long'
valid, msg = service.validate_password_strength("SecurePass123!") valid True msg ''
Source code in src/services/password_service.py
needs_rehash ¶
Check if a password hash needs to be updated.
This happens when: - The bcrypt rounds configuration has changed - The hashing algorithm has been deprecated - The hash format is outdated
If this returns True, you should re-hash the password with the current settings after successful login.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hashed_password
|
str
|
Existing password hash to check |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if hash should be regenerated, False otherwise |
Example
service = PasswordService() hashed = service.hash_password("SecurePass123!") service.needs_rehash(hashed) False
Source code in src/services/password_service.py
generate_random_password ¶
Generate a cryptographically secure random password.
Useful for: - Temporary passwords during account creation - Password reset flows - Testing
The generated password will meet all strength requirements.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
length
|
int
|
Length of password to generate (default: 16) Must be >= min_length (8) |
16
|
Returns:
| Type | Description |
|---|---|
str
|
Randomly generated password meeting all requirements |
Raises:
| Type | Description |
|---|---|
ValueError
|
If length < min_length |
Example
service = PasswordService() password = service.generate_random_password(16) len(password) 16 service.validate_password_strength(password)[0] True
Source code in src/services/password_service.py
get_password_requirements_text ¶
Get human-readable password requirements.
Useful for displaying requirements to users during registration or password change flows.
Returns:
| Type | Description |
|---|---|
str
|
Formatted string describing password requirements |
Example
service = PasswordService() print(service.get_password_requirements_text()) Password must: - Be at least 8 characters long - Contain at least one uppercase letter - Contain at least one lowercase letter - Contain at least one digit - Contain at least one special character (!@#$%^&*()_+-=[]{}|;:,.<>?)
Source code in src/services/password_service.py
Email Service¶
src.services.email_service.EmailService ¶
Service for sending emails via AWS SES.
This service provides async email sending functionality using AWS SES. In development mode, emails can be logged instead of sent for testing.
Features
- HTML email support with plain text fallback
- Template-based emails for common scenarios
- Development mode (logs emails instead of sending)
- Error handling with graceful degradation
- AWS SES integration with proper credentials
Attributes:
| Name | Type | Description |
|---|---|---|
ses_client |
Boto3 SES client |
|
from_email |
Configured sender email address |
|
from_name |
Configured sender display name |
|
development_mode |
Whether to log emails instead of sending |
Source code in src/services/email_service.py
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 | |
Functions¶
__init__ ¶
Initialize email service with AWS SES configuration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
development_mode
|
bool
|
If True, log emails instead of sending. Useful for local development without AWS credentials. |
False
|
Source code in src/services/email_service.py
send_email
async
¶
Send an email via AWS SES or log it in development mode.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
to_email
|
str
|
Recipient email address |
required |
subject
|
str
|
Email subject line |
required |
html_body
|
str
|
HTML email body |
required |
text_body
|
Optional[str]
|
Plain text fallback (optional) |
None
|
Returns:
| Type | Description |
|---|---|
bool
|
True if email sent successfully (or logged in dev mode), False otherwise |
Example
service = EmailService() success = await service.send_email( ... to_email="user@example.com", ... subject="Welcome!", ... html_body="
Welcome to Dashtam!
", ... text_body="Welcome to Dashtam!" ... ) success True
Source code in src/services/email_service.py
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | |
send_verification_email
async
¶
send_verification_email(
to_email: str,
verification_token: str,
user_name: Optional[str] = None,
) -> bool
Send email verification email to user.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
to_email
|
str
|
User's email address |
required |
verification_token
|
str
|
Unique verification token |
required |
user_name
|
Optional[str]
|
User's name for personalization (optional) |
None
|
Returns:
| Type | Description |
|---|---|
bool
|
True if email sent successfully, False otherwise |
Example
service = EmailService() success = await service.send_verification_email( ... to_email="user@example.com", ... verification_token="abc123def456", ... user_name="John Doe" ... )
Source code in src/services/email_service.py
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 | |
send_password_reset_email
async
¶
send_password_reset_email(
to_email: str,
reset_token: str,
user_name: Optional[str] = None,
) -> bool
Send password reset email to user.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
to_email
|
str
|
User's email address |
required |
reset_token
|
str
|
Unique password reset token |
required |
user_name
|
Optional[str]
|
User's name for personalization (optional) |
None
|
Returns:
| Type | Description |
|---|---|
bool
|
True if email sent successfully, False otherwise |
Example
service = EmailService() success = await service.send_password_reset_email( ... to_email="user@example.com", ... reset_token="xyz789abc123", ... user_name="John Doe" ... )
Source code in src/services/email_service.py
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 | |
send_welcome_email
async
¶
Send welcome email to newly registered and verified user.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
to_email
|
str
|
User's email address |
required |
user_name
|
Optional[str]
|
User's name for personalization (optional) |
None
|
Returns:
| Type | Description |
|---|---|
bool
|
True if email sent successfully, False otherwise |
Example
service = EmailService() success = await service.send_welcome_email( ... to_email="user@example.com", ... user_name="John Doe" ... )
Source code in src/services/email_service.py
381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 | |
send_password_changed_notification
async
¶
Send notification email after password is successfully changed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
to_email
|
str
|
User's email address |
required |
user_name
|
Optional[str]
|
User's name for personalization (optional) |
None
|
Returns:
| Type | Description |
|---|---|
bool
|
True if email sent successfully, False otherwise |
Example
service = EmailService() success = await service.send_password_changed_notification( ... to_email="user@example.com", ... user_name="John Doe" ... )
Source code in src/services/email_service.py
476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 | |
Encryption Service¶
src.services.encryption.EncryptionService ¶
Service for encrypting and decrypting sensitive data.
This service provides a simple interface for encrypting strings (like OAuth tokens) before storing them in the database and decrypting them when needed.
The encryption key is derived from the application's SECRET_KEY or can be set via ENCRYPTION_KEY environment variable.
Source code in src/services/encryption.py
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 | |
Functions¶
__new__ ¶
Singleton pattern to ensure one encryption service instance.
__init__ ¶
encrypt ¶
Encrypt a string value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
plaintext
|
str
|
The string to encrypt. |
required |
Returns:
| Type | Description |
|---|---|
str
|
Base64-encoded encrypted string. |
Raises:
| Type | Description |
|---|---|
Exception
|
If encryption fails. |
Source code in src/services/encryption.py
decrypt ¶
Decrypt an encrypted string.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ciphertext
|
str
|
The encrypted string to decrypt. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The original plaintext string. |
Raises:
| Type | Description |
|---|---|
Exception
|
If decryption fails. |
Source code in src/services/encryption.py
encrypt_dict ¶
Encrypt all string values in a dictionary.
Useful for encrypting multiple tokens at once.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
dict
|
Dictionary with string values to encrypt. |
required |
Returns:
| Type | Description |
|---|---|
dict
|
Dictionary with encrypted values. |
Source code in src/services/encryption.py
decrypt_dict ¶
Decrypt all string values in a dictionary.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
dict
|
Dictionary with encrypted string values. |
required |
Returns:
| Type | Description |
|---|---|
dict
|
Dictionary with decrypted values. |
Source code in src/services/encryption.py
is_encrypted ¶
Check if a string appears to be encrypted.
This is a heuristic check based on Fernet token format.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
str
|
String to check. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the string appears to be encrypted. |
Source code in src/services/encryption.py
get_instance
classmethod
¶
Get the singleton instance of the encryption service.
Returns:
| Type | Description |
|---|---|
EncryptionService
|
The encryption service instance. |