feat: add peanutbutter challenges
This commit is contained in:
parent
08897d82e3
commit
26039148eb
8
peanutbutter-challenges/peanutbutter-brand/README.md
Normal file
8
peanutbutter-challenges/peanutbutter-brand/README.md
Normal file
@ -0,0 +1,8 @@
|
||||
# peanutbutter brand
|
||||
## Text
|
||||
Welcome to the Peanut Butter Blog.
|
||||
Try to find the best Peanut Butter Brand.
|
||||
## Files
|
||||
site url
|
||||
## How to Deploy
|
||||
docker compose
|
21
peanutbutter-challenges/peanutbutter-brand/SOLUTION.md
Normal file
21
peanutbutter-challenges/peanutbutter-brand/SOLUTION.md
Normal file
@ -0,0 +1,21 @@
|
||||
## Difficulty
|
||||
Easy
|
||||
## Category
|
||||
Web
|
||||
## How To Solve
|
||||
This time, simply modifying the URL doesn't help.
|
||||
When you push the 'retrieve' button on the /profile page, you get a message telling you that you can only get the best brand when you like peanut butter. A quick look at the Chrome DevTools Network tab shows that this information is the result of a GET request to the server after the button click. Under the request header, you can see that a cookie named `token` is sent along. This is interesting. ![](devtools_brand_request.png)
|
||||
|
||||
When visiting the /profile page, a GET request for profile information is made to the server. The server responds with the information but also requires the browser to set a cookie. That cookie is a JSON Web Token (JWT) with the name `token` in this case. You can use the Chrome DevTools to inspect the cookie. ![](devtools_cookies.png) You can see that the cookie has the value `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiQ1RGIFBhcnRpY2lwYW50IiwibGlrZXNfcGVhbnV0X2J1dHRlciI6ZmFsc2V9._DG-nLXVTzNw_BoSQ240P6QNL9JbxRz6aWAgPFiXfVU`. You can simple decode this JWT, using an [online tool](https://jwt.io/#debugger-io?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiQ1RGIFBhcnRpY2lwYW50IiwibGlrZXNfcGVhbnV0X2J1dHRlciI6ZmFsc2V9._DG-nLXVTzNw_BoSQ240P6QNL9JbxRz6aWAgPFiXfVU).
|
||||
The obtained JSON is:
|
||||
```json
|
||||
{
|
||||
"role": "CTF Participant",
|
||||
"likes_peanut_butter": false
|
||||
}
|
||||
```
|
||||
In this tool, modify the JSON by setting `likes_peanut_butter` to `true` (this was the requirement for obtaining the 'best brand'). You now get a new JWT: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiQ1RGIFBhcnRpY2lwYW50IiwibGlrZXNfcGVhbnV0X2J1dHRlciI6dHJ1ZX0.Ndo_jZn8fFltuKiZK9lyVoXyLuiueaPLUmuC7_0Y8j8`.
|
||||
|
||||
In you Chrome DevTools, you can change the `token` cookie to the new JWT. Now, simply push the 'retrieve' button again to obtain the flag. Note that you should not refresh the page. That would result in a new GET request to obtain profile information and override the flag again to the old flag.
|
||||
## Flag
|
||||
`IGCTF{H3L4ES_PIND4K44SSSSSS}`
|
Binary file not shown.
After Width: | Height: | Size: 364 KiB |
BIN
peanutbutter-challenges/peanutbutter-brand/devtools_cookies.png
Normal file
BIN
peanutbutter-challenges/peanutbutter-brand/devtools_cookies.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 219 KiB |
@ -0,0 +1,7 @@
|
||||
# peanutbutter extra secret post
|
||||
## Text
|
||||
Welcome to the Peanut Butter Blog. The post with id `ebddb7db-b20a-4e8f-b9e9-954e1c07ab83` contains the flag. I hope you have access.
|
||||
## Files
|
||||
site url
|
||||
## How to Deploy
|
||||
docker compose
|
@ -0,0 +1,25 @@
|
||||
## Difficulty
|
||||
??
|
||||
## Category
|
||||
Web
|
||||
## How To Solve
|
||||
When you try to access the page `/posts/ebddb7db-b20a-4e8f-b9e9-954e1c07ab83`, you see that you have no access to the blog post. The network tab in the Chrome DevTools shows that a GET request is made to the server that responds with an array of blog posts. This is the same request as is made on the homepage to show the list of blog posts (except for the hidden one). The fact that all the blog posts are also fetched on this page is quite strange. In fact, before showing any blog post content, the client first fetches all blog posts and checks whether the visited blog post id is contained in the fetched blog posts list.
|
||||
|
||||
When visiting other blog posts, another request is made to the server after the blog post id was succesfully matched with the list of previously fetched blog posts. This other GET request obtaines the actual blog post content (`<server>/posts/<blogpost-id>`). Notice however that the server doesn't check the id on this this request, as it (stupidly) assumes that this already happened on the client. Simply executing this request using the given id should be sufficient to obtain the flag.
|
||||
|
||||
You can for example do this by right clicking the request, occuring in other blog posts, in the network tab of the DevTools. Then select copy as cURL, replace the other blog post id with the given blog post id, and perform the request, in your terminal for example.
|
||||
```bash
|
||||
> curl 'http://localhost:3000/posts/ebddb7db-b20a-4e8f-b9e9-954e1c07ab83' \
|
||||
-H 'Accept: */*' \
|
||||
-H 'Accept-Language: en-GB,en-US;q=0.9,en;q=0.8,nl;q=0.7' \
|
||||
-H 'Cache-Control: no-cache' \
|
||||
-H 'Connection: keep-alive' \
|
||||
-H 'DNT: 1' \
|
||||
-H 'Origin: <ORIGIN>' \
|
||||
-H 'Pragma: no-cache' \
|
||||
-H 'Referer: <REFERER>'
|
||||
|
||||
{"name":"The other, even more secret secret of peanut butter","id":"ebddb7db-b20a-4e8f-b9e9-954e1c07ab83","show":false,"list":false,"content":"IGCTF{Pe4nut_Butt3R_1s_n0t_made_froM_butTEr}"}%
|
||||
```
|
||||
## Flag
|
||||
`IGCTF{Pe4nut_Butt3R_1s_n0t_made_froM_butTEr}`
|
7
peanutbutter-challenges/peanutbutter-fruit/README.md
Normal file
7
peanutbutter-challenges/peanutbutter-fruit/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
# peanutbutter fruit
|
||||
## Text
|
||||
Welcome to the Peanut Butter Blog. As you can see, only admins can see the superior fruit that goes extremely well with peanut butter.
|
||||
## Files
|
||||
site url
|
||||
## How to Deploy
|
||||
docker compose
|
8
peanutbutter-challenges/peanutbutter-fruit/SOLUTION.md
Normal file
8
peanutbutter-challenges/peanutbutter-fruit/SOLUTION.md
Normal file
@ -0,0 +1,8 @@
|
||||
## Difficulty
|
||||
Easy
|
||||
## Category
|
||||
Web
|
||||
## How To Solve
|
||||
As you can see, only admins have access to the superior fruit. When you navigated from the home page to the profile page, you can see that the URL contains the word 'guest'. Simply replace that with the word 'admin' and voila.
|
||||
## Flag
|
||||
`IGCTF{Re4lly_any_fru1t_exc3pt_f0r_Mang03s}`
|
@ -0,0 +1,8 @@
|
||||
# peanutbutter secret post
|
||||
## Text
|
||||
Welcome to the Peanut Butter Blog.
|
||||
The website contains some information that the developer tried to hide. He failed.
|
||||
## Files
|
||||
site url
|
||||
## How to Deploy
|
||||
docker compose
|
10
peanutbutter-challenges/peanutbutter-secret-post/SOLUTION.md
Normal file
10
peanutbutter-challenges/peanutbutter-secret-post/SOLUTION.md
Normal file
@ -0,0 +1,10 @@
|
||||
## Difficulty
|
||||
??
|
||||
## Category
|
||||
Web
|
||||
## How To Solve
|
||||
When visiting the homepage, a GET request is made to the server to obtain the blog posts. Using the Chrome DevTools, you can inspect the response. ![](devtools_response.png)
|
||||
|
||||
Notice that this response corresponds to the blog posts visible on the homepage, with one difference. The homepage doesn't show the blog post with id `bee229bc-4147-4c34-9418-9572e9f0ee1b`. When clicking on other blog posts, you see that the URL for every blog post is structured as follows: `/posts/<id>`. Simply going to `/posts/bee229bc-4147-4c34-9418-9572e9f0ee1b`reveals the hidden blog post with the flag.
|
||||
## Flag
|
||||
`IGCTF{M4d3_fr0m_p3Anuts}`
|
Binary file not shown.
After Width: | Height: | Size: 329 KiB |
7
peanutbutter-challenges/src/backend/Dockerfile
Normal file
7
peanutbutter-challenges/src/backend/Dockerfile
Normal file
@ -0,0 +1,7 @@
|
||||
FROM node:18-alpine
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm i
|
||||
COPY . .
|
||||
EXPOSE 3000
|
||||
CMD ["npm", "start"]
|
31
peanutbutter-challenges/src/backend/app.ts
Normal file
31
peanutbutter-challenges/src/backend/app.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import express from 'express';
|
||||
import postsRouter from './routers/posts';
|
||||
import profileRouter from './routers/profile';
|
||||
import cors from 'cors';
|
||||
import cookieParser from 'cookie-parser';
|
||||
|
||||
const app = express();
|
||||
const port = 8000;
|
||||
|
||||
app.use(cookieParser());
|
||||
|
||||
const getBrand = (req, res) => {
|
||||
const token = req.cookies.token;
|
||||
if (!token) {
|
||||
return res.status(401).json({ message: 'hmm, something went wrong' });
|
||||
}
|
||||
const parsed = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
|
||||
if (parsed.likes_peanut_butter) {
|
||||
res.json({ brand: 'IGCTF{H3L4ES_PIND4K44SSSSSS}' });
|
||||
} else {
|
||||
res.json({ brand: 'you only get the best brand when you actually like peanut butter' });
|
||||
}
|
||||
}
|
||||
|
||||
app.get('/brand', getBrand);
|
||||
app.use("/profile", profileRouter);
|
||||
app.use("/posts", postsRouter);
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Server running on port ${port}`);
|
||||
});
|
3097
peanutbutter-challenges/src/backend/package-lock.json
generated
Normal file
3097
peanutbutter-challenges/src/backend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
29
peanutbutter-challenges/src/backend/package.json
Normal file
29
peanutbutter-challenges/src/backend/package.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "backend",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "app.ts",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"dev": "npx tsx app.ts",
|
||||
"start": "npx tsx app.ts"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/express": "^5.0.0",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"prettier": "^3.3.3",
|
||||
"tsx": "^4.19.1",
|
||||
"typescript": "^5.6.3",
|
||||
"typescript-eslint": "^8.11.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.21.1"
|
||||
}
|
||||
}
|
198
peanutbutter-challenges/src/backend/routers/posts.ts
Normal file
198
peanutbutter-challenges/src/backend/routers/posts.ts
Normal file
@ -0,0 +1,198 @@
|
||||
import { Router } from 'express';
|
||||
|
||||
const router = Router();
|
||||
const posts = [
|
||||
{
|
||||
name: "10 Surprising Uses for Peanut Butter That Don’t Involve Eating It",
|
||||
id: '0ae1ad6b-92c8-4902-905f-67987dcc47e5',
|
||||
show: true,
|
||||
content: `Peanut butter is a miracle spread in the eyes of its fans, but did you know its talents go way beyond the kitchen? We’re talking about peanut butter in ways you’d never expect: as an emergency household item, a craft supply, even a stress-relief tool. Let’s dive into ten surprising ways you can put your peanut butter to work (though, fair warning, the last one might lead to some sticky chaos).\n
|
||||
It turns out that peanut butter has a hidden talent for transforming scuffed-up shoes. Yes, a dollop of creamy peanut butter and a soft cloth can actually work wonders on leather shoes. After all, that natural oil and the tiny particles within peanut butter create an unexpectedly effective polish! Just rub a little peanut butter onto the scuff, give it a good buff, and your shoes will be almost good as new. The key here is a very small amount—too much, and you might find yourself wearing your breakfast to class.
|
||||
|
||||
Ever had a zipper stubbornly stuck at the worst possible moment? Peanut butter might just save the day. All you need is the tiniest bit of it on the zipper’s teeth; its oils act as a natural lubricant. Peanut butter even makes it onto the list of DIY hacks for emergency hair gel. In a pinch, it’s sticky and holds up better than you’d think. Granted, peanut butter hair gel might attract curious stares from passing dogs, but sometimes, getting that perfect hairstyle is worth it.
|
||||
|
||||
If you’ve got kids—or younger siblings to entertain—you’ve probably tried every craft material under the sun. Enter peanut butter: an edible, peanut-scented playdough. All you need to do is mix it with powdered sugar and some corn syrup, and voilà, hours of fun for the little ones. Just be prepared for things to get messy. Picture this: little peanut-buttery fingerprints on every surface in sight, tiny hands everywhere, and a minor crisis involving a peanut-butter-coated couch. It might not be the tidiest idea, but kids love it (until they inevitably smear it in their hair).
|
||||
|
||||
Another surprising trick is peanut butter as an adhesive remover. Got a price tag that just won’t peel off without leaving that annoying residue? Smear a little peanut butter over it, let it sit for a few minutes, and then wipe away—no more sticky residue. Peanut butter’s oil breaks down adhesives, turning a household annoyance into a chance to celebrate your peanutty problem-solver.
|
||||
|
||||
On to the DIY spa front: peanut butter face masks. While people are used to avocados, honey, and other natural ingredients for skincare, peanut butter can surprise you as a quick-and-easy face mask. Rich in healthy fats, it provides a smooth, moisturizing effect on the skin. But here’s the caveat: don’t leave it on too long, and be prepared for the smell to linger. Maybe keep this one as a solo spa day activity rather than a party trick with friends.
|
||||
|
||||
Need to free yourself from a pesky ring that won’t budge? Before you reach for the soap or ice, give peanut butter a try. Apply a small amount to your finger, and the ring should slide right off. The oils do the trick, and while it may sound messy, it’s actually quite effective in a pinch.
|
||||
|
||||
Here’s one for pet owners: distracting your dog during grooming sessions. Dab a bit of peanut butter on a spoon, and hold it out while you clip their nails or give them a bath. Peanut butter is the ultimate dog treat, so it keeps them preoccupied long enough to get through those tricky grooming moments. Just remember to clean up the trail of drool they’ll leave behind.
|
||||
|
||||
Perhaps the most bizarre use of all? Peanut butter stress-relief putty. During exam season, college students have been known to use peanut butter as a makeshift stress ball. Grab a jar, plunge your fingers in, and squeeze. The soft, pliable texture combined with the mild nutty aroma works surprisingly well to soothe frazzled nerves, and there’s something strangely satisfying about the squishy sensation. Just don’t forget to wash your hands—peanut-butter-covered notes probably won’t earn you any extra credit.
|
||||
|
||||
And finally, as a "hidden scent" deodorizer! Strange as it sounds, peanut butter can cover up smells—particularly for cutting boards and certain containers. Rub it onto your board, let it sit for a minute, then wash it off. Goodbye, garlic smell; hello, subtle peanut aroma.`
|
||||
},
|
||||
{
|
||||
name: "Peanut Butter: The Secret Ingredient in Ancient Egyptian Mummification?",
|
||||
id: '39db8bea-94e6-49cc-ad54-8762ac645889',
|
||||
show: true,
|
||||
content: `Peanut butter, beloved spread of breakfast tables worldwide, has seen its fair share of odd uses. But none could be stranger than the theory that surfaced in the niche corners of academia in 2024: the idea that ancient Egyptians might have used peanut butter as an ingredient in the mummification process. It started as a whisper among eccentric Egyptologists but quickly spiraled into an obsession that rattled the very core of historical scholarship.
|
||||
|
||||
The first hint of this bizarre theory came when Dr. Penelope Wensworth, a renowned but quirky historian with a penchant for conspiracy theories, published a paper in the Journal of Unlikely Archaeological Hypotheses. The paper, titled "Nutty Preservation: A Reevaluation of Egyptian Mummification Practices," proposed that early Egyptians experimented with various substances, including honey, oils, and even mashed legumes, in their quest for the perfect embalming formula.
|
||||
|
||||
The hypothesis might have been dismissed as a desperate attempt to get more readers if not for one pivotal discovery.
|
||||
|
||||
The Mysterious Hieroglyphics of Tomb KV65
|
||||
It all started with an excavation in the Valley of the Kings, where an international team uncovered an unfinished tomb labeled KV65. Among its faded murals were hieroglyphics that had long defied translation. Dr. Amir Hussein, an expert in Middle Kingdom art, noticed a particular set of glyphs that depicted a scribe holding what looked like an amphora next to a stylized representation of a jar. Inside the jar was an image that struck him as peculiar—a spiral, surrounded by dots.
|
||||
|
||||
“No way,” he muttered under his breath during the initial analysis. To the untrained eye, it could have been a container for oils or wine, common enough depictions in burial art. But Dr. Hussein had grown up in Cairo with a taste for fool-proof peanut butter sandwiches and recognized the swirled depiction instantly. It was unmistakable: a jar with the iconic texture of peanut butter.
|
||||
|
||||
The Lab Analysis
|
||||
Word spread fast. The Institute of Pseudo-Historical Studies received funding from a mix of amused and intrigued benefactors, eager to either debunk the theory or lean into the spectacle. Samples were taken from the preserved jars found in KV65 and sent to Dr. Harriet Mendez, a biochemical archaeologist specializing in ancient organic residues.
|
||||
|
||||
When Dr. Mendez conducted a comprehensive gas chromatography-mass spectrometry test, the results were shocking. Amidst the more expected compounds like myrrh and frankincense, traces of Arachis hypogaea—the groundnut plant, from which peanut butter is derived—were detected. The report ignited a frenzy of debate in academic circles. Could it be that the Egyptians, known for their elaborate embalming rituals, imported peanuts from as far as South America, or did they perhaps develop a similar leguminous plant on their own?
|
||||
|
||||
A Modern Peanut Butter Scandal
|
||||
Social media was ablaze. Peanut butter enthusiasts claimed that their favorite snack now had roots in a 3,000-year-old tradition. Twitter trended with hashtags like #PeanutButterPharaoh and #AnubisApproved. The peanut butter industry seized the opportunity to market their products as timeless, with labels boasting phrases like, “Embalmed with the Legacy of Kings” and “Taste What Pharaohs Preserved.”
|
||||
|
||||
Historians, however, were split. Dr. Mendez, exhausted from back-to-back interviews, released a statement warning against over-exaggeration. “There is still no conclusive evidence to suggest that peanut butter, as we know it, was used in full-scale embalming processes,” she insisted. But that didn’t stop the flow of peanut-themed conspiracy theories. Amateur archaeologists began claiming that other murals depicted Anubis, the god of mummification, handing jars of peanut butter to grateful souls in the afterlife, as if the spread had mystical properties.
|
||||
|
||||
The Deciphering of the “Nutty Papyrus”
|
||||
Just when the debate seemed to reach its peak, another bombshell dropped. Dr. Hussein’s colleague, linguist Emma Truong, uncovered a papyrus scroll buried deep in the archives of a forgotten museum in Luxor. It was old, damaged, but remarkably detailed in its references to “sticky golden paste” used to bind linen wrappings more effectively and “envelop the royal scent.” Emma and her team spent sleepless weeks translating the scroll, which described an experimental embalming process where this paste was mixed with myrrh and cinnamon to give bodies a fragrant, nutty preservation.
|
||||
|
||||
“The sticky properties of this paste not only helped with the wrapping but also served as a barrier against decay,” Emma explained to an audience of stunned colleagues at a conference. “And, dare I say it, the ancient Egyptians might have had the first recorded peanut butter recipe—or something very close to it.”
|
||||
|
||||
Historians, Squirrels, and Skeptics
|
||||
Of course, not everyone was convinced. Renowned Egyptologist Dr. Benjamin Clarke held a televised debate, dismissing the idea as a hoax or, at best, a mistranslation. “The Egyptians valued complex processes and wouldn’t have relied on something as trivial as peanut butter in their sacred rites,” he thundered.
|
||||
|
||||
But his argument faltered when a viral video from the archives showed a group of excavation workers chuckling at a particularly insistent squirrel sneaking peanut butter sandwiches at the dig site. “Isn’t it funny,” one worker remarked off-camera, “how even this little guy knows what’s valuable here?” The peanut butter had found its way back to its ancient stomping grounds, one sandwich at a time.
|
||||
|
||||
Legacy or Legend?
|
||||
The world still debates whether peanut butter played a significant role in mummification or if it was an obscure experiment that made its way into tomb art and burial texts. What’s certain is that the idea captured public imagination like never before. Some historians now joke about stocking jars in their research rooms, “just in case.” Peanut butter, in all its creamy and crunchy glory, transcended its breakfast-table origins and entered the hallowed halls of history—real or imagined—as the spread of the pharaohs.`
|
||||
},
|
||||
{
|
||||
name: "The Great Peanut Butter Heist of 2024: How One Squirrel Nearly Ruined Breakfast Time",
|
||||
id: 'c5723072-d8eb-4fff-afaa-527d84e80f1e',
|
||||
show: true,
|
||||
content: `The morning started just like any other at Dormitory 7B of VUB’s student housing. Sleepy-eyed students shuffled into the shared kitchen, cradling mugs of coffee like life support machines. The scent of burned toast mingled with the sharp tang of instant noodles as an eclectic breakfast symphony began to play out. Lucas, known around the dorm for his meticulous breakfast routine, stood by the counter, assembling the day’s pièce de résistance: a peanut butter and banana sandwich.
|
||||
|
||||
It was a special jar—a creamy, artisanal peanut butter that Lucas had splurged on during a weekend market trip. Imported from a small farm in Madagascar, it promised unparalleled nutty perfection with each bite. He’d hidden it behind a fortress of hummus and mystery sauces in the communal fridge, believing it was safe. Little did he know, something—or rather, someone—had other plans.
|
||||
|
||||
The security footage later reviewed by Lucas and his dorm mates would reveal everything. The day before the heist, an unusual visitor had scurried into the kitchen when one of the freshmen, nose buried in their phone, left the balcony door wide open. It was Theodore, an unusually plump and enterprising squirrel, notorious in the student blogosphere as The Phantom Forager. He had been sighted stealing everything from protein bars to blueberry muffins but had never been caught in the act.
|
||||
|
||||
Theodore’s eyes glistened as he surveyed his surroundings. There were easy marks—open packets of chips, a forgotten apple core—but Theodore was no common thief. His snout twitched as he caught a whiff of something extraordinary. He followed his nose to the fridge, pawed at the door's edge, and then with a well-practiced flick of his tail, slipped inside.
|
||||
|
||||
Monday morning arrived. Lucas hummed “Les filles du bord de mer” as he made his way to the fridge. He swung the door open and stopped mid-hum, his eyes narrowing in disbelief. The peanut butter jar was gone. A trail of tiny, sticky paw prints led from the fridge to the windowsill. His heart sank.
|
||||
|
||||
Panic set in as the implications unraveled. The peanut butter was not just a luxury; it was his secret weapon against grueling mornings packed with classes and CTF preparations. A few dorm mates, drawn by the commotion, gathered around as Lucas recounted the disappearance.
|
||||
|
||||
“Wait, are you saying The Phantom Forager struck again?” one of them whispered.
|
||||
|
||||
The crowd buzzed with excitement. The story of Theodore, the peanut-butter-loving squirrel, had reached legendary status. Lucas wasn’t just facing a missing snack; he was up against an icon.
|
||||
|
||||
Determined not to be bested, Lucas mobilized a search party. It included Theo, the computer science major with a knack for homemade surveillance systems, and Maria, a literature student who claimed to understand “the mind of a rogue.” Armed with peanut butter-scented traps, homemade squirrel repellent, and a borrowed drone, they set out to catch Theodore red-handed.
|
||||
|
||||
The operation spanned two full days. Frustration mounted as Theodore evaded capture, seemingly mocking them by moving the traps and leaving tiny claw-written notes that read, “Nice try!” Lucas felt the weight of defeat looming. His dream of tasting that smooth, rich, Madagascan peanut butter seemed destined for failure.
|
||||
|
||||
On the third night, the team decided to set a trap worthy of Theodore’s cunning. They borrowed a speaker from the communal lounge and played a recording of crinkling snack wrappers. From the bushes, Theodore emerged, his tail quivering in anticipation. He paused at the sight of a trail of crackers leading up to the prized jar of peanut butter, now sitting wide open on the balcony ledge.
|
||||
|
||||
He pounced. The second he touched the jar, a net dropped from above, wrapping him in a peanut-butter-scented embrace. Victory! Lucas rushed forward, eyes alight with triumph, but before he could celebrate, Theodore performed an acrobatic maneuver only a squirrel with a PhD in Escape could pull off. The net tore, and Theodore leaped off the ledge with the jar, parachuting down using what appeared to be an old plastic bag.
|
||||
|
||||
The dorm was left in stunned silence as the daring thief bounded across the quad, peanut butter prize clutched tightly. Lucas leaned on the balcony rail, watching as his snack, and a piece of his pride, disappeared into the campus greenery.
|
||||
|
||||
A few days later, a new post appeared on the student blog: “Theodore Strikes Again: The Story Behind the Great Peanut Butter Heist.” It featured a grainy photo of Theodore perched on a tree branch, licking his peanutty bounty. Lucas, while still a bit heartbroken, couldn't help but smile. He had become part of campus folklore, just another character in the legend of The Phantom Forager.
|
||||
|
||||
Rumor has it Lucas’s next CTF challenge involved finding a hidden cache of “Theodore’s treasure,” complete with paw-print clues and riddles only the cunning could solve.
|
||||
`
|
||||
},
|
||||
{
|
||||
name: "The Battle of Spreads: Peanut Butter vs. Avocado Toast – Which Will Rule the Breakfast Table?",
|
||||
id: '5deaa4f7-12a7-4676-8335-a6a873b3b1bb',
|
||||
show: true,
|
||||
content: `The breakfast table, once a harmonious space of cereal bowls and egg platters, had become a battlefield in the age-old debate: peanut butter or avocado toast? It was a conflict that divided dorm rooms, sparked debates at brunch spots, and even infiltrated family gatherings. Both sides had their champions and their ardent critics, and now, it had escalated to the most dramatic showdown imaginable—a courtroom trial, complete with gavel bangs and wacky witnesses.
|
||||
|
||||
The courtroom was a surreal scene. On one side stood Peanut Butter, represented by a sentient, smooth jar in a tiny suit, oozing confidence. Its label gleamed under the fluorescent lights, proclaiming "Packed with Protein!" On the other side was Avocado, looking a bit confused, holding a pit as though it were a gavel of its own. It wore a monocle, an attempt to appear wise and refined, though it was clearly unused to legal proceedings.
|
||||
|
||||
The honorable Judge Pancake presided over the case, his golden surface glistening with butter. He called for order as murmurs from the gallery—a mix of college students, nutritionists, and social media influencers—filled the room.
|
||||
|
||||
First to take the stand was Peanut Butter, supported by its entourage of animated almonds, peanuts, and health nuts. The jar puffed up with pride as its attorney, a sharp-talking slice of bread named Rye O’Toole, presented Exhibit A: “Nutritional Superiority.” A slide showed that peanut butter was not just a comfort food but a powerhouse, loaded with protein and healthy fats. Rye O’Toole pounded the podium and declared, “Who here hasn’t felt the post-workout glory of a peanut butter smoothie, hmm? Sustenance! Energy! A champion’s breakfast!” The nuts in the audience applauded, causing Judge Pancake to rap his gavel for silence.
|
||||
|
||||
Avocado’s defense was not to be underestimated. Represented by an earnest piece of sourdough toast, whose name tag read “Cris P. Crust,” it argued that avocado was the epitome of the modern breakfast. Cris P. Crust projected an image of a perfectly Instagrammed avocado toast, sprinkled with chili flakes and drizzled with olive oil. “Social prestige, your honor. Nutritional value with flair! A breakfast that whispers ‘sophistication’ with every bite,” Crust stated, adjusting its glasses with dramatic flourish.
|
||||
|
||||
The gallery buzzed. A young influencer with blue hair whispered to her friend, “Honestly, avocado toast is so yesterday, but it does get likes.” A protein-shake enthusiast countered with, “Peanut butter is the original king of gains.”
|
||||
|
||||
Things took a turn when celebrity endorsements were called in. First up was Banana, a longtime ally of Peanut Butter. Banana sauntered in with a confident swing, testifying to their collaborative dominance in breakfasts, smoothies, and even desserts. “Peanut butter is my ride-or-die,” Banana declared. “We’ve conquered snacks from toast to oatmeal.” Applause erupted once more, cut short by Judge Pancake’s stern glance.
|
||||
|
||||
Then, Avocado called its own surprise witness: Tomato, representing the controversial avocado-tomato toast hybrid that blurred the line between breakfast and lunch. Tomato’s testimony was passionate but polarizing. “We add zest, color, and antioxidants! We bring balance!” But someone in the back muttered, “That’s just bruschetta!” and Judge Pancake shook his head.
|
||||
|
||||
The case reached its climax with a set of meme submissions, presented as evidence. Peanut Butter’s memes were wholesome and nostalgic, full of childhood lunchboxes and relatable peanut butter jar struggles. Avocado’s memes, on the other hand, were trendy and cynical, often poking fun at their own overpriced reputation. One particularly viral meme that flashed on the screen read, “Can’t afford a house, but at least I have my avocado toast.” It earned a mix of laughter and sighs from the gallery.
|
||||
|
||||
In closing arguments, Rye O’Toole called for a return to basics. “Peanut butter is the spread of resilience. A spread that has seen world wars, economic depressions, and still sticks by your side—literally and figuratively.” Cris P. Crust countered with elegance: “Avocado embodies health, forward-thinking, and a touch of luxury. Why settle for simplicity when you can elevate your mornings?”
|
||||
|
||||
The jury, made up of a balanced mix of breakfast pastries and a bowl of skeptical granola, deliberated as the room held its breath. Judge Pancake’s expression remained neutral, though a rogue drip of syrup slid down his face, signaling that even he felt the tension.
|
||||
|
||||
After what seemed like an eternity, the verdict was announced. The jury ruled it a draw—both peanut butter and avocado toast were champions in their own right, representing different aspects of breakfast culture. A cheer rose from both sides, followed by a collective sigh of relief. Peanut Butter shook hands with Avocado, an act that squished them together briefly, creating an unexpected blend that would later become the trendiest new toast topping: Peanutocado Spread.
|
||||
|
||||
Thus, the battle of the spreads ended not in defeat but in a newfound harmony. Social media trended with #Peanutocado, and influencers were quick to adapt. The courtroom, now silent, had been a battleground for breakfast supremacy but had ultimately brought about a peace treaty fit for any table.
|
||||
`
|
||||
},
|
||||
{
|
||||
name: "Do you know the secret of peanut butter?",
|
||||
id: 'bee229bc-4147-4c34-9418-9572e9f0ee1b',
|
||||
show: false,
|
||||
content: "IGCTF{M4d3_fr0m_p3Anuts}"
|
||||
},
|
||||
{
|
||||
name: "The Conspiracy Behind Smooth vs. Crunchy: What Big Nut Doesn’t Want You to Know",
|
||||
id: '3eafb5c1-7bbd-4607-86f3-750672d98d75',
|
||||
show: true,
|
||||
content: `It started innocently enough, as debates often do. Friends arguing in kitchens, families divided at the breakfast table—smooth or crunchy, which was better? For years, it seemed like a harmless preference, a quirky conversation starter. But beneath the surface of this seemingly benign choice, there was a storm brewing that would shake the peanut butter industry to its core. Enter Big Nut, the shadowy organization pulling the strings.
|
||||
|
||||
Big Nut wasn’t just a nickname for the conglomerates dominating the peanut butter market; it was a clandestine network of decision-makers, ad agencies, and food scientists with one goal: control consumer behavior. They wanted everyone to think it was just a matter of taste. But in truth, they were waging a silent war over profits, and the battleground was spreadable.
|
||||
|
||||
The conspiracy unraveled thanks to a whistleblower named Charlie “Crunch” O’Malley, a third-generation peanut farmer from Georgia. Charlie had grown up surrounded by rows of legumes and jars of homemade peanut butter, never imagining he’d become the key to one of the most bizarre scandals in snack history. It started with a simple email from a supplier labeled “Confidential: Re. Textural Consistency Directives.” Intrigued, Charlie opened it and discovered a trove of memos outlining Big Nut’s covert experiments to manipulate public perception.
|
||||
|
||||
“They’re engineering the perfect bite, but it’s not about the customer,” Charlie revealed in a shaky interview, filmed in a dim barn with peanut plants rustling behind him. “It’s about making us dependent on their products, choosing what we think we prefer.” The documents outlined how flavor profiles, oil ratios, and even the subtle differences in peanut grind size were crafted not for nutrition or taste but for maximum sales.
|
||||
|
||||
The memos didn’t stop there. They detailed a media strategy designed to fuel debates. A series of carefully placed opinion pieces in lifestyle magazines would praise the “purity of smooth” one week, followed by op-eds extolling the “boldness of crunchy” the next. The cycle was engineered to keep consumers emotionally invested, never noticing that their choices were orchestrated.
|
||||
|
||||
The story took a turn when Dr. Nathaniel Jiferson, an eccentric food chemist and former Big Nut insider, decided he’d had enough. Dr. Jiferson, who wore a lab coat splattered with peanut oil stains and spoke with the fervor of a man who hadn’t seen daylight in weeks, emerged from his research bunker with a manifesto titled The Ground Truth. He claimed Big Nut was experimenting with additives to create an “invisible loyalty agent” that would trigger a preference for one type over the other, thus splitting consumer loyalty and doubling market engagement.
|
||||
|
||||
“They’ve perfected a blend of compounds that can only be described as neuro-nutty,” Jiferson said, waving a half-empty jar as if it were a piece of damning evidence. “One week, you’re a smooth loyalist; the next, you’re dipping spoons straight into the crunchy. It’s psychological warfare on your palate.”
|
||||
|
||||
The manifesto spread quickly through underground food forums, alarming casual snackers and peanut butter connoisseurs alike. The most outrageous claims involved celebrity endorsements: videos of pop stars casually enjoying peanut butter in sponsored content were alleged to contain subliminal audio frequencies—a peanut siren song that made viewers crave one type or the other.
|
||||
|
||||
As the revelations gained traction, PeanutCon, the largest annual convention of peanut enthusiasts, became a hotbed of tension. What was once a jovial affair of tastings and spread-ranking contests turned into a battleground. Smooth loyalists, dressed in silky, beige shirts emblazoned with “Smoothers for Life,” faced off against crunchy supporters clad in textured, hand-knitted sweaters marked with “Crunch On!” Signs read, “Down with Big Nut!” and “Freedom of Choice, Not Manipulation!”
|
||||
|
||||
Charlie O’Malley made an appearance, supported by a ragtag group of independent farmers with jars labeled with handmade tags that read “Honest Nut.” He gave a rousing speech, his voice hoarse from shouting over the uproar. “We’re here to take peanut butter back! We’ll grow, grind, and spread it the way our grandmothers did, without interference!”
|
||||
|
||||
The climax of PeanutCon came when Dr. Jiferson revealed a final twist: Big Nut’s real plan was to introduce a third category—Ultra-Smooth. “They want to end the debate by creating a new allegiance,” Jiferson declared, his eyes wild with both conviction and sleep deprivation. “Ultra-Smooth is smoother than smooth, engineered to glide so perfectly that you’ll forget crunchy ever existed.”
|
||||
|
||||
The crowd gasped, though a few attendees, cautiously curious, whispered, “I’d try it once.”
|
||||
|
||||
Meanwhile, Big Nut’s PR representatives issued a statement that they were merely pioneers of choice. “Consumers deserve options, and we provide them,” the statement read, the corporate jargon as smooth as their bestselling spread. They denied any tampering with consumer psychology, attributing any perceived loyalty shifts to natural market trends.
|
||||
|
||||
For a while, the controversy captivated the nation. The peanut butter aisle became a place of whispered debates and side-eyed glances, shoppers suspicious of what their choices said about them. Sales surged, ironically, as both sides stocked up to show allegiance.
|
||||
|
||||
In the end, the war didn’t end; it simply retreated into kitchen cabinets and breakfast nooks, where jars of smooth, crunchy, and maybe one day Ultra-Smooth sat side by side. People spread their choices onto bread, sometimes defiantly, other times thoughtfully. And though the shadows of Big Nut still lingered, one thing was certain: the battle had made everyone think twice before dipping a knife into their chosen jar.`
|
||||
}, {
|
||||
name: "The other, even more secret secret of peanut butter",
|
||||
id: 'ebddb7db-b20a-4e8f-b9e9-954e1c07ab83',
|
||||
show: false,
|
||||
list: false,
|
||||
content: `IGCTF{Pe4nut_Butt3R_1s_n0t_made_froM_butTEr}`
|
||||
}
|
||||
]
|
||||
const getPostTitles = (req, res) => {
|
||||
res.json(posts
|
||||
.filter(post => post.list !== false)
|
||||
.map(post => ({ name: post.name, id: post.id, show: post.show })));
|
||||
}
|
||||
|
||||
const getPost = (req, res) => {
|
||||
const postid = req.params.postid;
|
||||
const post = posts.find(post => post.id === postid);
|
||||
if (post) {
|
||||
res.json(post);
|
||||
} else {
|
||||
res.status(404).send('Post not found');
|
||||
}
|
||||
}
|
||||
|
||||
router.get('/', getPostTitles);
|
||||
router.get('/:postid', getPost);
|
||||
|
||||
export default router;
|
17
peanutbutter-challenges/src/backend/routers/profile.ts
Normal file
17
peanutbutter-challenges/src/backend/routers/profile.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { Router } from 'express';
|
||||
|
||||
const router = Router();
|
||||
|
||||
const getProfile = (req, res) => {
|
||||
const userName = req.params.username;
|
||||
res.cookie('token', "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiQ1RGIFBhcnRpY2lwYW50IiwibGlrZXNfcGVhbnV0X2J1dHRlciI6ZmFsc2V9._DG-nLXVTzNw_BoSQ240P6QNL9JbxRz6aWAgPFiXfVU");
|
||||
res.json({
|
||||
id: userName,
|
||||
flag: userName === 'admin' ? 'IGCTF{Re4lly_any_fru1t_exc3pt_f0r_Mang03s}' : 'only admins get to see the superior fruit',
|
||||
orders_amount: userName === 'admin' ? 0 : userName.length * 12,
|
||||
});
|
||||
}
|
||||
|
||||
router.get('/:username', getProfile);
|
||||
|
||||
export default router;
|
9
peanutbutter-challenges/src/docker-compose.yml
Normal file
9
peanutbutter-challenges/src/docker-compose.yml
Normal file
@ -0,0 +1,9 @@
|
||||
services:
|
||||
express-backend:
|
||||
build: ./backend
|
||||
network_mode: service:nextjs-frontend
|
||||
|
||||
nextjs-frontend:
|
||||
build: ./frontend
|
||||
ports:
|
||||
- "3000:3000"
|
7
peanutbutter-challenges/src/frontend/.dockerignore
Normal file
7
peanutbutter-challenges/src/frontend/.dockerignore
Normal file
@ -0,0 +1,7 @@
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
node_modules
|
||||
npm-debug.log
|
||||
README.md
|
||||
.next
|
||||
.git
|
3
peanutbutter-challenges/src/frontend/.eslintrc.json
Normal file
3
peanutbutter-challenges/src/frontend/.eslintrc.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": ["next/core-web-vitals", "next/typescript"]
|
||||
}
|
40
peanutbutter-challenges/src/frontend/.gitignore
vendored
Normal file
40
peanutbutter-challenges/src/frontend/.gitignore
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
66
peanutbutter-challenges/src/frontend/Dockerfile
Normal file
66
peanutbutter-challenges/src/frontend/Dockerfile
Normal file
@ -0,0 +1,66 @@
|
||||
# syntax=docker.io/docker/dockerfile:1
|
||||
|
||||
FROM node:18-alpine AS base
|
||||
|
||||
# Install dependencies only when needed
|
||||
FROM base AS deps
|
||||
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
||||
RUN apk add --no-cache libc6-compat
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies based on the preferred package manager
|
||||
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* ./
|
||||
RUN \
|
||||
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
|
||||
elif [ -f package-lock.json ]; then npm ci; \
|
||||
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \
|
||||
else echo "Lockfile not found." && exit 1; \
|
||||
fi
|
||||
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
# Next.js collects completely anonymous telemetry data about general usage.
|
||||
# Learn more here: https://nextjs.org/telemetry
|
||||
# Uncomment the following line in case you want to disable telemetry during the build.
|
||||
# ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
RUN \
|
||||
if [ -f yarn.lock ]; then yarn run build; \
|
||||
elif [ -f package-lock.json ]; then npm run build; \
|
||||
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \
|
||||
else echo "Lockfile not found." && exit 1; \
|
||||
fi
|
||||
|
||||
# Production image, copy all the files and run next
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
# Uncomment the following line in case you want to disable telemetry during runtime.
|
||||
# ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
COPY --from=builder /app/public ./public
|
||||
|
||||
# Automatically leverage output traces to reduce image size
|
||||
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENV PORT=3000
|
||||
|
||||
# server.js is created by next build from the standalone output
|
||||
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
CMD ["node", "server.js"]
|
36
peanutbutter-challenges/src/frontend/README.md
Normal file
36
peanutbutter-challenges/src/frontend/README.md
Normal file
@ -0,0 +1,36 @@
|
||||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
21
peanutbutter-challenges/src/frontend/app/globals.css
Normal file
21
peanutbutter-challenges/src/frontend/app/globals.css
Normal file
@ -0,0 +1,21 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--foreground);
|
||||
background: var(--background);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
28
peanutbutter-challenges/src/frontend/app/layout.tsx
Normal file
28
peanutbutter-challenges/src/frontend/app/layout.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import type { Metadata } from "next";
|
||||
import {Sour_Gummy} from 'next/font/google'
|
||||
import "./globals.css";
|
||||
|
||||
const sourGummy = Sour_Gummy({
|
||||
weight: "400",
|
||||
subsets: ["latin"],
|
||||
})
|
||||
export const metadata: Metadata = {
|
||||
title: "Peanut Butter",
|
||||
description: "Everything about Peanut Butter",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${sourGummy.className} antialiased w-screen h-screen`}
|
||||
>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
48
peanutbutter-challenges/src/frontend/app/page.tsx
Normal file
48
peanutbutter-challenges/src/frontend/app/page.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
"use client";
|
||||
import Link from "next/link";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function Home() {
|
||||
const peanutsImageUrl = "https://images.pexels.com/photos/209371/pexels-photo-209371.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2";
|
||||
const [blogPosts, setBlogPosts] = useState<{ id: string, name: string, show: boolean }[]>([]);
|
||||
useEffect(() => {
|
||||
const fetchPosts = async () => {
|
||||
const response = await fetch("/api/posts/");
|
||||
setBlogPosts(await response.json());
|
||||
// setBlogPosts([{ id: "1", name: "Hello world", show: true }]);
|
||||
console.log(blogPosts);
|
||||
};
|
||||
fetchPosts();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="w-full h-full pt-12 pl-16 flex flex-col items-start" style={{ backgroundImage: `url(${peanutsImageUrl})`, backgroundSize: 'cover' }}>
|
||||
<div className="flex items-center mb-7">
|
||||
<div className="text-7xl pr-6">🥜</div>
|
||||
<div className="flex flex-col">
|
||||
<h1 className="text-7xl">Peanut Butter</h1>
|
||||
<h2 className="text-3xl">For evolved people only</h2>
|
||||
</div>
|
||||
<div className="text-7xl pl-2">🥜</div>
|
||||
</div>
|
||||
<div className="flex space-x-20">
|
||||
<div>
|
||||
<h3 className="text-2xl mt-4 font-bold">Profile</h3>
|
||||
<Link href="/profile/guest" className="text-lg underline">My peanut butter profile</Link>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-2xl mt-4 font-bold">Blog posts</h3>
|
||||
{blogPosts.map((post) => (
|
||||
(<div key={post.id}>
|
||||
{post.show ? <div className="flex space-x-1 items-baseline">
|
||||
<p>🥜</p>
|
||||
<Link href={`/posts/${post.id}`} className="text-lg underline">{post.name}</Link>
|
||||
</div>
|
||||
: <></>}
|
||||
</div>)
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
"use client";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function PostPage({ params }: { params: Promise<{ postid: string }> }) {
|
||||
const [title, setTitle] = useState<string>("");
|
||||
const [content, setContent] = useState<string>("");
|
||||
useEffect(() => {
|
||||
const fetchPost = async () => {
|
||||
const allPostsResponse = await fetch("/api/posts");
|
||||
const allPosts = await allPostsResponse.json() as { id: string, name: string, show: boolean }[];
|
||||
const p = await params;
|
||||
if (allPosts.find(post => post.id === p.postid) === undefined) {
|
||||
setTitle("Post not found");
|
||||
setContent("This post does not exist, or you don't have access.");
|
||||
return;
|
||||
}
|
||||
const response = await fetch(`/api/posts/${p.postid}`);
|
||||
const post = await response.json();
|
||||
setTitle(post.name);
|
||||
setContent(post.content);
|
||||
};
|
||||
fetchPost();
|
||||
}, []);
|
||||
const peanutsImageUrl = "https://images.pexels.com/photos/6659880/pexels-photo-6659880.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2";
|
||||
|
||||
return (
|
||||
<div className="w-full h-full pt-12 pl-16 fixed overflow-y-scroll" style={{ background: `linear-gradient(rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.8)), url(${peanutsImageUrl})`, backgroundSize: 'cover', backgroundAttachment: "fixed" }}>
|
||||
<div className="absolute flex flex-col items-start pb-32">
|
||||
<h1 className="text-3xl font-bold mb-5 mr-5">{title}</h1>
|
||||
<p className="whitespace-pre-wrap w-3/5 text-lg">{content}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
"use client";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function ProfilePage({ params }: { params: Promise<{ username: string }> }) {
|
||||
|
||||
const [flag, setFlag] = useState<string>("");
|
||||
const [ordersAmount, setOrdersAmount] = useState<number>(0);
|
||||
const [brand, setBrand] = useState<string>("");
|
||||
const [username, setUsername] = useState<string>("");
|
||||
useEffect(() => {
|
||||
const fetchPost = async () => {
|
||||
const p = await params;
|
||||
setUsername(p.username);
|
||||
const response = await fetch(`/api/profile/${p.username}`, {
|
||||
credentials: 'include',
|
||||
});
|
||||
const post = await response.json();
|
||||
setFlag(post.flag);
|
||||
setOrdersAmount(post.orders_amount);
|
||||
};
|
||||
fetchPost();
|
||||
}, []);
|
||||
const getBrand = async () => {
|
||||
const response = await fetch("/api/brand", {
|
||||
credentials: 'include',
|
||||
})
|
||||
const responseJson = await response.json();
|
||||
setBrand(responseJson.brand);
|
||||
};
|
||||
const peanutsImageUrl = "https://images.pexels.com/photos/6659867/pexels-photo-6659867.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2";
|
||||
|
||||
return (
|
||||
<div className="w-full h-full pt-12 pl-16 fixed overflow-y-scroll" style={{ background: `linear-gradient(rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.8)), url(${peanutsImageUrl})`, backgroundSize: 'cover', backgroundAttachment: "fixed" }}>
|
||||
<div className="absolute flex flex-col items-start pb-32">
|
||||
<h1 className="text-5xl font-bold mb-5 mr-5">Your profile</h1>
|
||||
<div className="flex space-x-6">
|
||||
<div>
|
||||
<img src="/avatar.png" alt="avatar" width={300} height={300} className="rounded-xl" />
|
||||
</div>
|
||||
<div className="flex flex-col space-y-3">
|
||||
<div>
|
||||
<p className="text-xl">Your account</p>
|
||||
<p>{username}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xl">Your purchases</p>
|
||||
<p>{ordersAmount} orders</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xl">The best fruit</p>
|
||||
<p>{flag}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xl">The best brand</p>
|
||||
<div className="flex space-x-3">
|
||||
<a onClick={getBrand} className="text-orange-700 cursor-pointer bg-white rounded-xl px-3 py-0">Retrieve</a>
|
||||
<p>{brand}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
16
peanutbutter-challenges/src/frontend/next.config.ts
Normal file
16
peanutbutter-challenges/src/frontend/next.config.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
output: "standalone",
|
||||
rewrites: async () => {
|
||||
return [
|
||||
{
|
||||
source: "/api/:path*",
|
||||
destination: "http://localhost:8000/:path*",
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
5727
peanutbutter-challenges/src/frontend/package-lock.json
generated
Normal file
5727
peanutbutter-challenges/src/frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
peanutbutter-challenges/src/frontend/package.json
Normal file
26
peanutbutter-challenges/src/frontend/package.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "peanut-butter",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "19.0.0-rc-66855b96-20241106",
|
||||
"react-dom": "19.0.0-rc-66855b96-20241106",
|
||||
"next": "15.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "15.0.3"
|
||||
}
|
||||
}
|
8
peanutbutter-challenges/src/frontend/postcss.config.mjs
Normal file
8
peanutbutter-challenges/src/frontend/postcss.config.mjs
Normal file
@ -0,0 +1,8 @@
|
||||
/** @type {import('postcss-load-config').Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
BIN
peanutbutter-challenges/src/frontend/public/avatar.png
Normal file
BIN
peanutbutter-challenges/src/frontend/public/avatar.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 MiB |
BIN
peanutbutter-challenges/src/frontend/public/avatar.webp
Normal file
BIN
peanutbutter-challenges/src/frontend/public/avatar.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 274 KiB |
18
peanutbutter-challenges/src/frontend/tailwind.config.ts
Normal file
18
peanutbutter-challenges/src/frontend/tailwind.config.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import type { Config } from "tailwindcss";
|
||||
|
||||
export default {
|
||||
content: [
|
||||
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
background: "var(--background)",
|
||||
foreground: "var(--foreground)",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
} satisfies Config;
|
27
peanutbutter-challenges/src/frontend/tsconfig.json
Normal file
27
peanutbutter-challenges/src/frontend/tsconfig.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
Loading…
Reference in New Issue
Block a user