miguelgrinberg / microblog

The microblogging application developed in my Flask Mega-Tutorial series. This version maps to the 2024 Edition of the tutorial.
http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world
MIT License
4.56k stars 1.65k forks source link

Hi miguel! Thankyou for your tutorial. #394

Closed Kishlay-notabot closed 5 months ago

Kishlay-notabot commented 5 months ago

I tried implementing a flask app on my own and im facing an error. Here's the code: app.py:

from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS
from flask_wtf import FlaskForm, CSRFProtect
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Email, EqualTo, ValidationError
import os

app = Flask(__name__)
csrf = CSRFProtect(app)
CORS(app)
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY')
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'
# app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)

# User model
class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    first_name = db.Column(db.String(50), nullable=False)
    last_name = db.Column(db.String(50), nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password = db.Column(db.String(120), nullable=False)
    ip = db.Column(db.String(45), nullable=False)
    created_at = db.Column(db.DateTime, default=db.func.current_timestamp())

# Create the database and the table
with app.app_context():
    db.create_all()

class SignupForm(FlaskForm):
    first_name = StringField('First Name', validators=[DataRequired()])
    last_name = StringField('Last Name', validators=[DataRequired()])
    email = StringField('Email', validators=[DataRequired(), Email()])
    password = PasswordField('Password', validators=[DataRequired()])
    confirm_password = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')])
    submit = SubmitField('Sign Up')

@app.route('/api/signup', methods=['POST'])
def signup():
    form = SignupForm(data=request.json)
    if form.validate():
        if User.query.filter_by(email=form.email.data).first():
            return jsonify({'message': 'Email already exists.', 'success': False}), 400

        user_ip = request.remote_addr

        new_user = User(
            first_name=form.first_name.data,
            last_name=form.last_name.data,
            email=form.email.data,
            password=form.password.data,
            ip=user_ip
        )
        db.session.add(new_user)
        db.session.commit()

        return jsonify({'message': 'User signed up successfully!', 'success': True}), 200
    else:
        app.logger.debug(f"Form errors: {form.errors}")
        return jsonify({'message': 'Form validation failed.', 'errors': form.errors, 'success': False}), 400

@app.route('/api/users', methods=['GET'])
def get_users():
    users = User.query.all()
    user_list = [{'first_name': user.first_name, 'last_name': user.last_name, 'email': user.email, 'created_at': user.created_at} for user in users]
    return jsonify(user_list)

if __name__ == '__main__':
    app.run(port=5000, debug=True)

script.js:

const signupForm = document.getElementById('signupForm');
if (signupForm) {
    signupForm.addEventListener('submit', async (e) => {
        e.preventDefault();

        const formData = new FormData(signupForm);
        const data = {};
        formData.forEach((value, key) => {
            data[key] = value;
        });

        console.log('Data being sent:', data); // Log the data being sent
        try {
            const response = await fetch('http://localhost:5000/api/signup', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify(data),
            });

            const result = await response.json();
            document.getElementById('message').textContent = result.message;
        } catch (error) {
            console.error('An error occurred:', error); // Add this line to log the error
            document.getElementById('message').textContent = 'An error occurred. Please try again.';
        }

    });
}

document.addEventListener('DOMContentLoaded', async () => {
    const userList = document.getElementById('userList');

    try {
        const response = await fetch('http://localhost:5000/api/users');
        const users = await response.json();

        users.forEach(user => {
            const row = document.createElement('tr');
            const emailCell = document.createElement('td');
            const dateCell = document.createElement('td');

            emailCell.textContent = user.email;
            dateCell.textContent = new Date(user.created_at).toLocaleString();

            row.appendChild(emailCell);
            row.appendChild(dateCell);
            userList.appendChild(row);
        });
    } catch (error) {
        console.error('Error fetching user data:', error);
    }
});

Earlier I didn't include csrf in my project, when i did, i faced a new error, and this wont work. the error is: script.js:24 An error occurred: SyntaxError: Unexpected token '<', "<!doctype "... is not valid JSON When i open the network tab and see the preview, i see an html render saying

Bad Request
The CSRF token is missing.

Why so? I'm a beginner and im confused.

miguelgrinberg commented 5 months ago

@Kishlay-notabot you have enabled CSRF protection for your Flask server, but then in the client you are not sending a CSRF token.

Kishlay-notabot commented 5 months ago

Thankyou for pointing that out. Do flask wtf forms require csrf tokens generally ?

miguelgrinberg commented 5 months ago

Yes, for forms it is a good practice to use CSRF protection. But you have enabled it for the entire server.

Kishlay-notabot commented 5 months ago

Oh okay, how do I limit csrf to just the scope of the form?

Kishlay-notabot commented 5 months ago

By creating a seperate forms.py file?

miguelgrinberg commented 5 months ago

Remove this:

csrf = CSRFProtect(app)
Kishlay-notabot commented 5 months ago

when i try to run the app after removing that, i encounter this error again: [2024-06-16 17:07:19,427] DEBUG in app: Form errors: {'csrf_token': ['The CSRF token is missing.']} and this was the main reason i started playing around with csrf. How do i bypass this then? And this pops up when i fill the form and click submit, There is an error 400 bad request on the console.

Kishlay-notabot commented 5 months ago

Ok so maybe the code I'm running is a mess, if you could just point out where is it going wrong, it would be very nice because I want to learn where did I actually go wrong. Maybe I'll not use flask-wtf for now, but I want to know where the mistake is exactly

miguelgrinberg commented 5 months ago

Forms are configured to require CSRF by default, you don't have to add anything to your application. How do you submit your forms?

Kishlay-notabot commented 5 months ago

thanks a lot for helping! ill check the resources out