Search code examples
pythonflasktokenflask-loginflask-security

Invalid Confirmation Token when trying to confirm user email manually with Flask-Security


Trying to figure out what is different between when I call send_confirmation_instructions and when it gets called with the built-in resend confirmation instructions form.

Here's the call in my new user view:

newuser = User(
            email = newform.email.data,
            first_name = newform.first_name.data,
            last_name = newform.last_name.data,
            business_name = newform.business_name.data,
            roles = [Role.query.filter_by(id=role_id).first() for role_id in newform.roles.data],
            active = True
            )

        if newform.req_conf.data:
            send_confirmation_instructions(newuser)
        else:
            newuser.confirmed_at = datetime.utcnow()

and here is the built-in call from the send confirmation view:

def send_confirmation():
    """View function which sends confirmation instructions."""

    form_class = _security.send_confirmation_form

    if request.json:
        form = form_class(MultiDict(request.json))
    else:
        form = form_class()

    if form.validate_on_submit():
        send_confirmation_instructions(form.user)
        if request.json is None:
            do_flash(*get_message('CONFIRMATION_REQUEST', email=form.user.email))

    if request.json:
        return _render_json(form)

    return _security.render_template(config_value('SEND_CONFIRMATION_TEMPLATE'),
                                     send_confirmation_form=form,
                                     **_ctx('send_confirmation'))

From what I can tell, all the magic happens with the function: send_confirmation_instructions(user)

and the only difference I can tell between how I call it and how Flask-Security calls it, is that I'm using a user instance, and the built-in function uses form.user.

Tracking down the form it uses, I cannot see where form.user is even assigned:

class SendConfirmationForm(Form, UserEmailFormMixin):

    submit = SubmitField(get_form_field_label('send_confirmation'))

    def __init__(self, *args, **kwargs):
        super(SendConfirmationForm, self).__init__(*args, **kwargs)
        if request.method == 'GET':
            self.email.data = request.args.get('email', None)

    def validate(self):
        if not super(SendConfirmationForm, self).validate():
            return False
        if self.user.confirmed_at is not None:
            self.email.errors.append(get_message('ALREADY_CONFIRMED')[0])
            return False
        return True

I'm quite lost now, and so I turn to ya'll for assistance. Any ideas what i'm doing wrong?


Solution

  • Decided to try committing to the database and then sending the confirmation, sure enough that worked. Not sure why it made a difference, because from what I can tell, none of the functions use or change anything in the db, but clearly I'm missing something.

    Changed code as follows and all is good now!

        if newform.req_conf.data:
            db.session.add(newuser)
            db.session.commit()
            send_confirmation_instructions(newuser)
        else:
            newuser.confirmed_at = datetime.utcnow()
            db.session.add(newuser)
            db.session.commit()