Loading docker-compose.yml +1 −0 Original line number Diff line number Diff line Loading @@ -7,6 +7,7 @@ services: dockerfile: Dockerfile volumes: - ./react-frontend/my-react-app:/app - /app/node_modules ports: - "3000:3000" stdin_open: true Loading react-frontend/my-react-app/src/config.js +4 −4 Original line number Diff line number Diff line const config = { //backendBaseUrl: '/api', // For local development //frontendBaseUrl: 'http://localhost:3000/blog-demo-01', backendBaseUrl: 'https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-01/api', frontendBaseUrl: 'https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-01', backendBaseUrl: '/api', // For local development frontendBaseUrl: 'http://localhost:3000/blog-demo-01', // backendBaseUrl: 'https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-01/api', // frontendBaseUrl: 'https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-01', }; export default config; tests/api.http 0 → 100644 +65 −0 Original line number Diff line number Diff line ### 1) GET xsrf cookie from Tornado GET http://localhost:8888/api/xsrf-token > {% client.log("COOKIES:", response.cookies); %} > {% client.cookies.setAll(response.cookies); %} > {% client.log("AFTER SET:", client.cookies.getAll()); %} > {% client.global.set("xsrf", response.cookies._xsrf?.value); %} ### 2) Login POST http://localhost:8888/api/login Content-Type: application/json X-XSRFToken: {{xsrf}} { "email": "first-blogger@blog.com", "password": "blogger-01" } > {% client.cookies.setAll(response.cookies); %} > {% client.global.set("xsrf", response.cookies._xsrf.value); %} ### 1) Register new author POST http://localhost:8888/api/register Content-Type: application/json X-XSRFToken: {{xsrf}} { "email": "first-blogger@blog.com", "name": "First Blogger", "password": "blogger-01" } ### 3) Authenticated request → use the NEW xsrf token GET http://localhost:8888/api/get-user-role X-XSRFToken: {{xsrf}} ### Test cookies being sent automatically GET http://localhost:8888/api/get-user-role > {% console.log("Sent cookies:", request.cookies); %} ### 4) Create blog entry (requires correct xsrf + login) POST http://localhost:8888/api/create-blog-entry Content-Type: application/json X-XSRFToken: {{xsrf}} { "title": "My REST Client Post", "markdown": "## Hello World!" } tornado-backend/blog.py +38 −7 Original line number Diff line number Diff line Loading @@ -85,6 +85,7 @@ class Application(tornado.web.Application): (r"/api/blog-entries", BlogEntriesHandler), (r"/api/create-blog-entry", CreateBlogEntryHandler), (r"/api/update-blog-entry/([^/]+)", UpdateBlogEntriesHandler), (r"/api/xsrf-token", XSRFTokenHandler), ] settings = dict( blog_title="Tornado Blog", Loading @@ -92,6 +93,7 @@ class Application(tornado.web.Application): static_path=os.path.join(os.path.dirname(__file__), "static"), ui_modules={"Entry": EntryModule}, xsrf_cookies=True, xsrf_cookie_kwargs={"path": "/"}, cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__", login_url="/auth/login", debug=True, Loading Loading @@ -143,17 +145,36 @@ class BaseHandler(tornado.web.RequestHandler): raise ValueError("Expected 1 result, got %d" % len(results)) return results[0] # async def prepare(self): # # Ensure XSRF cookie is set with a correct path # if not self.get_secure_cookie("_xsrf"): # self.xsrf_token # This will set the _xsrf cookie # # get_current_user cannot be a coroutine, so set # # self.current_user in prepare instead. # # user_id = self.get_signed_cookie("blogdemo_user") # user_id = self.get_secure_cookie("blogdemo_user") # # user_id = self.get_cookie("blogdemo_user") # if user_id: # self.current_user = await self.queryone( # "SELECT * FROM authors WHERE id = %s", int(user_id) # ) async def prepare(self): # Ensure XSRF cookie is set with a correct path if not self.get_secure_cookie("_xsrf"): self.xsrf_token # This will set the _xsrf cookie # Log incoming cookies for every request logging.info(f"[PREPARE] Incoming Cookie Header: {self.request.headers.get('Cookie')}") logging.info(f"[PREPARE] Parsed _xsrf cookie: {self.get_cookie('_xsrf')}") logging.info(f"[PREPARE] X-XSRFToken header: {self.request.headers.get('X-XSRFToken')}") logging.info(f"SENT COOKIES FROM CLIENT: {self.request.headers.get('Cookie')}") # get_current_user cannot be a coroutine, so set # self.current_user in prepare instead. # user_id = self.get_signed_cookie("blogdemo_user") # Ensure XSRF cookie exists if not self.get_cookie("_xsrf"): _ = self.xsrf_token # force cookie creation # Load user from cookie if present user_id = self.get_secure_cookie("blogdemo_user") # user_id = self.get_cookie("blogdemo_user") if user_id: self.current_user = await self.queryone( "SELECT * FROM authors WHERE id = %s", int(user_id) Loading Loading @@ -183,6 +204,16 @@ class BaseHandler(tornado.web.RequestHandler): self.set_status(204) self.finish() class XSRFTokenHandler(BaseHandler): def check_xsrf_cookie(self): pass # allow this endpoint without XSRF check def get(self): # DO NOT call self.xsrf_token here again! token = self.get_cookie("_xsrf") logging.info(f"COOKIE: {token}") logging.info(f"HEADER: {token}") # same value now self.write({"_xsrf": token}) class HomeHandler(BaseHandler): async def get(self): Loading Loading
docker-compose.yml +1 −0 Original line number Diff line number Diff line Loading @@ -7,6 +7,7 @@ services: dockerfile: Dockerfile volumes: - ./react-frontend/my-react-app:/app - /app/node_modules ports: - "3000:3000" stdin_open: true Loading
react-frontend/my-react-app/src/config.js +4 −4 Original line number Diff line number Diff line const config = { //backendBaseUrl: '/api', // For local development //frontendBaseUrl: 'http://localhost:3000/blog-demo-01', backendBaseUrl: 'https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-01/api', frontendBaseUrl: 'https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-01', backendBaseUrl: '/api', // For local development frontendBaseUrl: 'http://localhost:3000/blog-demo-01', // backendBaseUrl: 'https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-01/api', // frontendBaseUrl: 'https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-01', }; export default config;
tests/api.http 0 → 100644 +65 −0 Original line number Diff line number Diff line ### 1) GET xsrf cookie from Tornado GET http://localhost:8888/api/xsrf-token > {% client.log("COOKIES:", response.cookies); %} > {% client.cookies.setAll(response.cookies); %} > {% client.log("AFTER SET:", client.cookies.getAll()); %} > {% client.global.set("xsrf", response.cookies._xsrf?.value); %} ### 2) Login POST http://localhost:8888/api/login Content-Type: application/json X-XSRFToken: {{xsrf}} { "email": "first-blogger@blog.com", "password": "blogger-01" } > {% client.cookies.setAll(response.cookies); %} > {% client.global.set("xsrf", response.cookies._xsrf.value); %} ### 1) Register new author POST http://localhost:8888/api/register Content-Type: application/json X-XSRFToken: {{xsrf}} { "email": "first-blogger@blog.com", "name": "First Blogger", "password": "blogger-01" } ### 3) Authenticated request → use the NEW xsrf token GET http://localhost:8888/api/get-user-role X-XSRFToken: {{xsrf}} ### Test cookies being sent automatically GET http://localhost:8888/api/get-user-role > {% console.log("Sent cookies:", request.cookies); %} ### 4) Create blog entry (requires correct xsrf + login) POST http://localhost:8888/api/create-blog-entry Content-Type: application/json X-XSRFToken: {{xsrf}} { "title": "My REST Client Post", "markdown": "## Hello World!" }
tornado-backend/blog.py +38 −7 Original line number Diff line number Diff line Loading @@ -85,6 +85,7 @@ class Application(tornado.web.Application): (r"/api/blog-entries", BlogEntriesHandler), (r"/api/create-blog-entry", CreateBlogEntryHandler), (r"/api/update-blog-entry/([^/]+)", UpdateBlogEntriesHandler), (r"/api/xsrf-token", XSRFTokenHandler), ] settings = dict( blog_title="Tornado Blog", Loading @@ -92,6 +93,7 @@ class Application(tornado.web.Application): static_path=os.path.join(os.path.dirname(__file__), "static"), ui_modules={"Entry": EntryModule}, xsrf_cookies=True, xsrf_cookie_kwargs={"path": "/"}, cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__", login_url="/auth/login", debug=True, Loading Loading @@ -143,17 +145,36 @@ class BaseHandler(tornado.web.RequestHandler): raise ValueError("Expected 1 result, got %d" % len(results)) return results[0] # async def prepare(self): # # Ensure XSRF cookie is set with a correct path # if not self.get_secure_cookie("_xsrf"): # self.xsrf_token # This will set the _xsrf cookie # # get_current_user cannot be a coroutine, so set # # self.current_user in prepare instead. # # user_id = self.get_signed_cookie("blogdemo_user") # user_id = self.get_secure_cookie("blogdemo_user") # # user_id = self.get_cookie("blogdemo_user") # if user_id: # self.current_user = await self.queryone( # "SELECT * FROM authors WHERE id = %s", int(user_id) # ) async def prepare(self): # Ensure XSRF cookie is set with a correct path if not self.get_secure_cookie("_xsrf"): self.xsrf_token # This will set the _xsrf cookie # Log incoming cookies for every request logging.info(f"[PREPARE] Incoming Cookie Header: {self.request.headers.get('Cookie')}") logging.info(f"[PREPARE] Parsed _xsrf cookie: {self.get_cookie('_xsrf')}") logging.info(f"[PREPARE] X-XSRFToken header: {self.request.headers.get('X-XSRFToken')}") logging.info(f"SENT COOKIES FROM CLIENT: {self.request.headers.get('Cookie')}") # get_current_user cannot be a coroutine, so set # self.current_user in prepare instead. # user_id = self.get_signed_cookie("blogdemo_user") # Ensure XSRF cookie exists if not self.get_cookie("_xsrf"): _ = self.xsrf_token # force cookie creation # Load user from cookie if present user_id = self.get_secure_cookie("blogdemo_user") # user_id = self.get_cookie("blogdemo_user") if user_id: self.current_user = await self.queryone( "SELECT * FROM authors WHERE id = %s", int(user_id) Loading Loading @@ -183,6 +204,16 @@ class BaseHandler(tornado.web.RequestHandler): self.set_status(204) self.finish() class XSRFTokenHandler(BaseHandler): def check_xsrf_cookie(self): pass # allow this endpoint without XSRF check def get(self): # DO NOT call self.xsrf_token here again! token = self.get_cookie("_xsrf") logging.info(f"COOKIE: {token}") logging.info(f"HEADER: {token}") # same value now self.write({"_xsrf": token}) class HomeHandler(BaseHandler): async def get(self): Loading