A pluggable framework for adding two-factor authentication to Django using one-time passwords.
—
One-time passwords delivered via email, useful for users who don't have smartphone access to authenticator apps or as a backup authentication method.
class EmailDevice(TimestampMixin, CooldownMixin, ThrottlingMixin, SideChannelDevice):
"""
Email-based OTP device with cooldown and throttling.
Fields:
- email: EmailField(blank=True) - Alternative email address
"""
email = models.EmailField(blank=True)
def generate_challenge(self, extra_context=None):
"""
Generate and email OTP token to user.
Parameters:
- extra_context: dict - Additional template context
Returns:
bool - True if challenge was generated and sent
"""
def get_subject(self) -> str:
"""Get email subject line."""
def send_mail(self, body, **kwargs):
"""
Send email with OTP token.
Parameters:
- body: str - Email body content
- **kwargs: Additional email parameters
"""
def get_cooldown_duration(self) -> int:
"""Return cooldown duration from settings."""
def get_throttle_factor(self) -> int:
"""Return throttle factor from settings."""class EmailDeviceAdmin(admin.ModelAdmin):
"""Admin interface for EmailDevice."""class OTPEmailSettings(Settings):
"""Email plugin settings with defaults."""from django_otp.plugins.otp_email.models import EmailDevice
# Create email device using user's email
device = EmailDevice.objects.create(
user=user,
name='Email OTP',
confirmed=True
)
# Create with alternative email
device = EmailDevice.objects.create(
user=user,
name='Backup Email',
email='backup@example.com', # Different from user.email
confirmed=True
)def send_email_otp(user):
"""Send OTP token via email."""
try:
device = EmailDevice.objects.get(user=user, confirmed=True)
# Check if generation is allowed (cooldown)
if device.generate_is_allowed() != True:
return False, "Please wait before requesting another code"
# Generate and send token
success = device.generate_challenge()
return success, "Code sent to email" if success else "Failed to send code"
except EmailDevice.DoesNotExist:
return False, "No email device configured"
def verify_email_otp(user, token):
"""Verify email OTP token."""
try:
device = EmailDevice.objects.get(user=user, confirmed=True)
if device.verify_token(token):
return True, "Token verified successfully"
else:
return False, "Invalid or expired token"
except EmailDevice.DoesNotExist:
return False, "No email device configured"# settings.py
# Email settings
OTP_EMAIL_SENDER = 'noreply@example.com'
OTP_EMAIL_SUBJECT = 'Your verification code'
# Token validity (default: 300 seconds)
OTP_EMAIL_TOKEN_VALIDITY = 600
# Cooldown duration (default: 30 seconds)
OTP_EMAIL_COOLDOWN_DURATION = 60
# Throttling factor (default: 1)
OTP_EMAIL_THROTTLE_FACTOR = 2
# Email templates
OTP_EMAIL_BODY_TEMPLATE = 'Your verification code is: {token}'
OTP_EMAIL_BODY_TEMPLATE_PATH = 'otp/email_body.txt'
OTP_EMAIL_BODY_HTML_TEMPLATE_PATH = 'otp/email_body.html'Your verification code is: {{ token }}
This code will expire in {{ valid_minutes }} minutes.
If you did not request this code, please ignore this email.<h2>Verification Code</h2>
<p>Your verification code is: <strong>{{ token }}</strong></p>
<p>This code will expire in {{ valid_minutes }} minutes.</p>
<p><small>If you did not request this code, please ignore this email.</small></p>Install with Tessl CLI
npx tessl i tessl/pypi-django-otp