Ejemplo n.º 1
0
    def send_confirmation(addr, user_id):
        g.log = g.log.new(address=addr, user_id=user_id)
        g.log.info('Sending email confirmation for new address on account.')

        addr = addr.lower().strip()
        if not IS_VALID_EMAIL(addr):
            g.log.info('Failed. Invalid address.')
            raise ValueError(u'Cannot send confirmation. '
                             '{} is not a valid email.'.format(addr))

        message = u'email={email}&user_id={user_id}'.format(
            email=addr,
            user_id=user_id)
        digest = hmac.new(
            settings.NONCE_SECRET,
            message.encode('utf-8'),
            hashlib.sha256
        ).hexdigest()
        link = url_for('confirm-account-email',
                       digest=digest, email=addr, _external=True)
        res = send_email(
            to=addr,
            subject='Confirm email for your account at %s' % settings.SERVICE_NAME,
            text=render_template('email/confirm-account.txt', email=addr, link=link),
            html=render_template_string(TEMPLATES.get('confirm-account.html'), email=addr, link=link),
            sender=settings.ACCOUNT_SENDER
        )
        if not res[0]:
            g.log.info('Failed to send email.', reason=res[1], code=res[2])
            return False
        else:
            return True
Ejemplo n.º 2
0
def request_unconfirm_form(form_id):
    '''
    This endpoints triggers a confirmation email that directs users to the
    GET version of unconfirm_form.
    '''
    form = Form.query.get(form_id)

    unconfirm_url = url_for('unconfirm_form',
                            form_id=form.id,
                            digest=form.unconfirm_digest(),
                            _external=True)
    send_email(
        to=form.email,
        subject='Unsubscribe from form at {}'.format(form.host),
        html=render_template_string(
            TEMPLATES.get('unsubscribe-confirmation.html'),
            url=unconfirm_url,
            email=form.email,
            host=form.host),
        text=render_template('email/unsubscribe-confirmation.txt',
                             url=unconfirm_url,
                             email=form.email,
                             host=form.host),
        sender=settings.DEFAULT_SENDER,
    )

    return render_template(
        'info.html',
        title='Link sent to your address',
        text="We've sent an email to {} with a link to finish "
        "unsubscribing.".format(form.email)), 200
Ejemplo n.º 3
0
def request_unconfirm_form(form_id):
    '''
    This endpoints triggers a confirmation email that directs users to the
    GET version of unconfirm_form.
    '''
    form = Form.query.get(form_id)

    unconfirm_url = url_for(
        'unconfirm_form',
        form_id=form.id,
        digest=form.unconfirm_digest(),
        _external=True
    )
    send_email(
        to=form.email,
        subject='Unsubscribe from form at {}'.format(form.host),
        html=render_template_string(TEMPLATES.get('unsubscribe-confirmation.html'),
                                    url=unconfirm_url,
                                    email=form.email,
                                    host=form.host),
        text=render_template('email/unsubscribe-confirmation.txt',
            url=unconfirm_url,
            email=form.email,
            host=form.host),
        sender=settings.DEFAULT_SENDER,
    )

    return render_template('info.html',
        title='Link sent to your address',
        text="We've sent an email to {} with a link to finish "
             "unsubscribing.".format(form.email)), 200
Ejemplo n.º 4
0
def send_downgrade_email(customer_email):
    send_email(to=customer_email,
               subject='Successfully downgraded from {} {}'.format(
                   settings.SERVICE_NAME, settings.UPGRADED_PLAN_NAME),
               text=render_template('email/downgraded.txt'),
               html=render_template_string(TEMPLATES.get('downgraded.html')),
               sender=settings.DEFAULT_SENDER)
Ejemplo n.º 5
0
def send_downgrade_email(customer_email):
    send_email(
        to=customer_email,
        subject='Successfully downgraded from {} {}'.format(settings.SERVICE_NAME,
                                                            settings.UPGRADED_PLAN_NAME),
        text=render_template('email/downgraded.txt'),
        html=render_template_string(TEMPLATES.get('downgraded.html')),
        sender=settings.DEFAULT_SENDER
    )
Ejemplo n.º 6
0
def stripe_webhook():
    payload = request.data.decode('utf-8')
    g.log.info('Received webhook from Stripe')

    sig_header = request.headers.get('STRIPE_SIGNATURE')
    event = None

    try:
        if settings.TESTING:
            event = request.get_json()
        else:
            event = stripe.Webhook.construct_event(
                payload, sig_header, settings.STRIPE_WEBHOOK_SECRET)
        if event[
                'type'] == 'customer.subscription.deleted':  # User subscription has expired
            customer_id = event['data']['object']['customer']
            customer = stripe.Customer.retrieve(customer_id)
            if len(customer.subscriptions.data) == 0:
                user = User.query.filter_by(stripe_id=customer_id).first()
                user.plan = Plan.free
                DB.session.add(user)
                DB.session.commit()
                g.log.info('Downgraded user from webhook.', account=user.email)
                send_downgrade_email.delay(user.email)
        elif event['type'] == 'invoice.payment_failed':  # User payment failed
            customer_id = event['data']['object']['customer']
            customer = stripe.Customer.retrieve(customer_id)
            g.log.info('User payment failed', account=customer.email)
            send_email(
                to=customer.email,
                subject='[ACTION REQUIRED] Failed Payment for {} {}'.format(
                    settings.SERVICE_NAME, settings.UPGRADED_PLAN_NAME),
                text=render_template('email/payment-failed.txt'),
                html=render_template_string(
                    TEMPLATES.get('payment-failed.html')),
                sender=settings.DEFAULT_SENDER)
        return 'ok'
    except ValueError as e:
        g.log.error('Webhook failed for customer', json=event, error=e)
        return 'Failure, developer please check logs', 500
    except stripe.error.SignatureVerificationError as e:
        g.log.error('Webhook failed Stripe signature verification',
                    json=event,
                    error=e)
        return '', 400
    except Exception as e:
        g.log.error('Webhook failed for unknown reason', json=event, error=e)
        return '', 500
Ejemplo n.º 7
0
    def send_password_reset(self):
        g.log.info('Sending password reset.', account=self.email)

        digest = self.reset_password_digest()
        link = url_for('reset-password', digest=digest, email=self.email, _external=True)
        res = send_email(
            to=self.email,
            subject='Reset your %s password!' % settings.SERVICE_NAME,
            text=render_template('email/reset-password.txt', addr=self.email, link=link),
            html=render_template_string(TEMPLATES.get('reset-password.html'), add=self.email, link=link),
            sender=settings.ACCOUNT_SENDER
        )
        if not res[0]:
            g.log.info('Failed to send email.', reason=res[1], code=res[2])
            return False
        else:
            return True
Ejemplo n.º 8
0
        def render_content(ext):
            data, keys = None, None
            if store_data:
                if type(store_data) in (ImmutableMultiDict,
                                        ImmutableOrderedMultiDict):
                    data, _ = http_form_to_dict(store_data)
                    store_first_submission(nonce, data)
                else:
                    store_first_submission(nonce, store_data)

            params = dict(email=self.email,
                          host=self.host,
                          nonce_link=link,
                          keys=keys)
            if ext == 'html':
                return render_template_string(TEMPLATES.get('confirm.html'),
                                              **params)
            elif ext == 'txt':
                return render_template('email/confirm.txt', **params)
Ejemplo n.º 9
0
def stripe_webhook():
    payload = request.data.decode('utf-8')
    g.log.info('Received webhook from Stripe')

    sig_header = request.headers.get('STRIPE_SIGNATURE')
    event = None

    try:
        if settings.TESTING:
            event = request.get_json()
        else:
            event = stripe.Webhook.construct_event(
                payload, sig_header, settings.STRIPE_WEBHOOK_SECRET)
        if event['type'] == 'customer.subscription.deleted':  # User subscription has expired
            customer_id = event['data']['object']['customer']
            customer = stripe.Customer.retrieve(customer_id)
            if len(customer.subscriptions.data) == 0:
                user = User.query.filter_by(stripe_id=customer_id).first()
                user.upgraded = False
                DB.session.add(user)
                DB.session.commit()
                g.log.info('Downgraded user from webhook.', account=user.email)
                send_downgrade_email.delay(user.email)
        elif event['type'] == 'invoice.payment_failed':  # User payment failed
            customer_id = event['data']['object']['customer']
            customer = stripe.Customer.retrieve(customer_id)
            g.log.info('User payment failed', account=customer.email)
            send_email(to=customer.email,
                       subject='[ACTION REQUIRED] Failed Payment for {} {}'.format(settings.SERVICE_NAME,
                                                                                   settings.UPGRADED_PLAN_NAME),
                       text=render_template('email/payment-failed.txt'),
                       html=render_template_string(TEMPLATES.get('payment-failed.html')),
                       sender=settings.DEFAULT_SENDER)
        return 'ok'
    except ValueError as e:
        g.log.error('Webhook failed for customer', json=event, error=e)
        return 'Failure, developer please check logs', 500
    except stripe.error.SignatureVerificationError as e:
        g.log.error('Webhook failed Stripe signature verification', json=event, error=e)
        return '', 400
    except Exception as e:
        g.log.error('Webhook failed for unknown reason', json=event, error=e)
        return '', 500
Ejemplo n.º 10
0
        def render_content(ext):
            data, keys = None, None
            if store_data:
                if type(store_data) in (
                        ImmutableMultiDict, ImmutableOrderedMultiDict):
                    data, _ = http_form_to_dict(store_data)
                    store_first_submission(nonce, data)
                else:
                    store_first_submission(nonce, store_data)

            params = dict(
                email=self.email,
                host=self.host,
                nonce_link=link,
                keys=keys
            )
            if ext == 'html':
                return render_template_string(TEMPLATES.get('confirm.html'), **params)
            elif ext == 'txt':
                return render_template('email/confirm.txt', **params)
Ejemplo n.º 11
0
    def send(self, data, keys, referrer):
        '''
        Sends form to user's email.
        Assumes sender's email has been verified.
        '''

        subject = data.get('_subject') or \
            'New submission from %s' % referrer_to_path(referrer)
        reply_to = (data.get(
            '_replyto',
            data.get('email', data.get('Email'))
        ) or '').strip()
        cc = data.get('_cc', None)
        next = next_url(referrer, data.get('_next'))
        spam = data.get('_gotcha', None)
        format = data.get('_format', None)

        # turn cc emails into array
        if cc:
            cc = [email.strip() for email in cc.split(',')]

        # prevent submitting empty form
        if not any(data.values()):
            return {'code': Form.STATUS_EMAIL_EMPTY}

        # return a fake success for spam
        if spam:
            g.log.info('Submission rejected.', gotcha=spam)
            return {'code': Form.STATUS_EMAIL_SENT, 'next': next}

        # validate reply_to, if it is not a valid email address, reject
        if reply_to and not IS_VALID_EMAIL(reply_to):
            g.log.info('Submission rejected. Reply-To is invalid.',
                       reply_to=reply_to)
            return {
                'code': Form.STATUS_REPLYTO_ERROR,
                'address': reply_to,
                'referrer': referrer
            }

        # increase the monthly counter
        request_date = datetime.datetime.now()
        self.increase_monthly_counter(basedate=request_date)

        # increment the forms counter
        self.counter = Form.counter + 1

        # if submission storage is disabled and form is upgraded, don't store submission
        if self.disable_storage and self.upgraded:
            pass
        else:
            DB.session.add(self)

            # archive the form contents
            sub = Submission(self.id)
            sub.data = {key: data[key] for key in data if key not in KEYS_NOT_STORED}
            DB.session.add(sub)

            # commit changes
            DB.session.commit()

        # sometimes we'll delete all archived submissions over the limit
        if random.random() < settings.EXPENSIVELY_WIPE_SUBMISSIONS_FREQUENCY:
            records_to_keep = settings.ARCHIVED_SUBMISSIONS_LIMIT
            total_records = DB.session.query(func.count(Submission.id)) \
                .filter_by(form_id=self.id) \
                .scalar()

            if total_records > records_to_keep:
                newest = self.submissions.with_entities(Submission.id).limit(records_to_keep)
                DB.engine.execute(
                  delete(table('submissions')). \
                  where(Submission.form_id == self.id). \
                  where(~Submission.id.in_(newest))
                )

        # url to request_unconfirm_form page
        unconfirm = url_for('request_unconfirm_form', form_id=self.id, _external=True)

        # check if the forms are over the counter and the user is not upgraded
        overlimit = False
        monthly_counter = self.get_monthly_counter()
        monthly_limit = settings.MONTHLY_SUBMISSIONS_LIMIT \
                if self.id > settings.FORM_LIMIT_DECREASE_ACTIVATION_SEQUENCE \
                else settings.GRANDFATHER_MONTHLY_LIMIT

        if monthly_counter > monthly_limit and not self.upgraded:
            overlimit = True

        if monthly_counter == int(monthly_limit * 0.9) and not self.upgraded:
            # send email notification
            send_email(
                to=self.email,
                subject="Formspree Notice: Approaching submission limit.",
                text=render_template('email/90-percent-warning.txt',
                    unconfirm_url=unconfirm, limit=monthly_limit
                ),
                html=render_template_string(
                    TEMPLATES.get('90-percent-warning.html'),
                    unconfirm_url=unconfirm, limit=monthly_limit
                ),
                sender=settings.DEFAULT_SENDER
            )

        now = datetime.datetime.utcnow().strftime('%I:%M %p UTC - %d %B %Y')

        if not overlimit:
            g.log.info('Submitted.')
            text = render_template('email/form.txt',
                data=data, host=self.host, keys=keys, now=now,
                unconfirm_url=unconfirm)
            # check if the user wants a new or old version of the email
            if format == 'plain':
                html = render_template('email/plain_form.html',
                    data=data, host=self.host, keys=keys, now=now,
                    unconfirm_url=unconfirm)
            else:
                html = render_template_string(TEMPLATES.get('form.html'),
                    data=data, host=self.host, keys=keys, now=now,
                    unconfirm_url=unconfirm)
        else:
            g.log.info('Submission rejected. Form over quota.',
                monthly_counter=monthly_counter)
            # send an overlimit notification for the first x overlimit emails
            # after that, return an error so the user can know the website owner is not
            # going to read his message.
            if monthly_counter <= monthly_limit + settings.OVERLIMIT_NOTIFICATION_QUANTITY:
                subject = 'Formspree Notice: Your submission limit has been reached.'
                text = render_template('email/overlimit-notification.txt',
                    host=self.host, unconfirm_url=unconfirm, limit=monthly_limit)
                html = render_template_string(TEMPLATES.get('overlimit-notification.html'),
                    host=self.host, unconfirm_url=unconfirm, limit=monthly_limit)
            else:
                return {'code': Form.STATUS_OVERLIMIT}

        # if emails are disabled and form is upgraded, don't send email notification
        if self.disable_email and self.upgraded:
            return {'code': Form.STATUS_NO_EMAIL, 'next': next}
        else:
            result = send_email(
                to=self.email,
                subject=subject,
                text=text,
                html=html,
                sender=settings.DEFAULT_SENDER,
                reply_to=reply_to,
                cc=cc,
                headers={
                    'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click',
                    'List-Unsubscribe': '<' + url_for(
                        'unconfirm_form',
                        form_id=self.id,
                        digest=self.unconfirm_digest(),
                        _external=True
                    ) + '>'
                }
            )

            if not result[0]:
                g.log.warning('Failed to send email.',
                              reason=result[1], code=result[2])
                if result[1].startswith('Invalid replyto email address'):
                    return {
                        'code': Form.STATUS_REPLYTO_ERROR,
                        'address': reply_to,
                        'referrer': referrer
                    }

                return {
                    'code': Form.STATUS_EMAIL_FAILED,
                    'mailer-code': result[2],
                    'error-message': result[1]
                }

            return {'code': Form.STATUS_EMAIL_SENT, 'next': next}