John Robinson
John Robinson

Reputation: 370

Flask-Security email confirmation (Flask-Mail) Fails to send without modifying flask_security/utils.py

I'm experimenting with Flask-Security and was having some trouble getting the confirmation email to send. I eventually fixed it by removing a line in flask_security/utils.py. I removed line 387, forcing flask-mail to use the app.config's mail sender.

386: msg = Message(subject,
387:               sender=_security.email_sender,
388:               recipients=[recipient])

Before removal, the code would fail in flask_mail.py, @ line 105 (inside the sanatize_address method), because the incoming addr was only a single string, not a tuple.

102: def sanitize_address(addr, encoding='utf-8'):
103:     if isinstance(addr, string_types):
104:         addr = parseaddr(force_text(addr))
105:     nm, addr = addr

I'd like to be able to run my code without having to modify flask_security/utils.py every time I install. Any suggestions? It's likely a step in my configuration that I'm missing, but I can't tell from flask-security's docs (they're a bit limited).

Thanks for the help, below is my example app.

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_security import Security, SQLAlchemyUserDatastore, \
    UserMixin, RoleMixin, login_required
import flask_security.utils
import flask_mail

# Create app
app = Flask(__name__)
app.config['DEBUG'] = True
app.config['SECRET_KEY'] = 'super-secret'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite'

# Flask-Security Config
app.config['SECURITY_PASSWORD_SALT'] = 'super_secret'
app.config['SECURITY_REGISTERABLE'] = True
app.config['SECURITY_TRACKABLE'] = True
app.config['SECURITY_CONFIRMABLE'] = True
app.config['SECURITY_RECOVERABLE'] = True

# Mail Config
app.config['MAIL_SERVER'] = 'smtp.googlemail.com'
app.config['MAIL_PORT'] = 465
app.config['MAIL_USE_TLS'] = False
app.config['MAIL_USE_SSL'] = True
app.config['MAIL_USERNAME'] = '[email protected]'
app.config['MAIL_PASSWORD'] = 'password'
app.config['MAIL_DEFAULT_SENDER'] = app.config['MAIL_USERNAME']

mail = flask_mail.Mail(app)

# Create database connection object
db = SQLAlchemy(app)

# Define models
roles_users = db.Table('roles_users',
        db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
        db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))

class Role(db.Model, RoleMixin):
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(80), unique=True)
    description = db.Column(db.String(255))

class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(255), unique=True)
    username = db.Column(db.String(255))
    password = db.Column(db.String(255))
    last_login_at = db.Column(db.DateTime())
    current_login_at = db.Column(db.DateTime())
    last_login_ip = db.Column(db.String(100))
    current_login_ip = db.Column(db.String(100))
    login_count = db.Column(db.Integer)
    active = db.Column(db.Boolean())
    confirmed_at = db.Column(db.DateTime())
    roles = db.relationship('Role', secondary='roles_users',
                        backref=db.backref('users', lazy='dynamic'))

# Setup Flask-Security
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)

# Create a user to test with
@app.before_first_request
def create_user():
    db.create_all()
    user_datastore.create_user(email='[email protected]', password=flask_security.utils.hash_password('password'))
    db.session.commit()

# Views
@app.route('/')
@login_required
def home():
    return('Here you go!')

if __name__ == '__main__':
    app.run()

Upvotes: 3

Views: 3596

Answers (1)

W. Cheng
W. Cheng

Reputation: 66

I also find this recently. After check source code, I find the reason. ps: I am a new python learner, so perhaps, the following is wrong.

In your code, app.config['MAIL_DEFAULT_SENDER'] = app.config['MAIL_USERNAME']. So, let's check source code to find how flask-security uses this configuration.

In flask-security/core.py :

41: _default_config = {
...
87:     'EMAIL_SENDER': LocalProxy(lambda: current_app.config.get(
88:         'MAIL_DEFAULT_SENDER', '[email protected]'
89:     )),

if not set SECURITY_EMAIL_SENDER configuration, flask-security uses a proxy of lambda: current_app.config.get('MAIL_DEFAULT_SENDER', '[email protected]') to set SECURITY_EMAIL_SENDER. Attention: SECURITY_EMAIL_SENDER is a LocalProxy instance at now.

386: msg = Message(subject,
387:               sender=_security.email_sender,
388:               recipients=[recipient])

_security.email_sender is a LocalProxy instance.

102: def sanitize_address(addr, encoding='utf-8'):
103:     if isinstance(addr, string_types):
104:         addr = parseaddr(force_text(addr))
105:     nm, addr = addr

addr is a LocalProxy instance. So isinstance(addr, string_types) is False, nm, addr = addr goes wrong.

So, set SECURITY_EMAIL_SENDER configuration to avoid this error if using flask-security.

Upvotes: 5

Related Questions