Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 66 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,75 @@ pm2 start ecosystem.config.js --env production

## Protocol

### POST
### POST (Create new)

Example endpoint URL

```
https://json.openpatch.org/api/v2/post/
```

#### Binary payload
#### JSON payload

Example of `binary` payload
Example of JSON payload

```
1234567890
```json
{
"data": "1234567890",
"password": "mySecretPassword"
}
```

Note: The `password` field is optional. If provided, the stored data will be password-protected and can only be updated using the POST endpoint with the ID.

#### Response

```json
{
"id": "5633286537740288",
"data": "https://json.openpatch.org/api/v2/5633286537740288"
}
```

### POST (Update existing with password)

Example endpoint URL

```
https://json.openpatch.org/api/v2/post/5633286537740288
```

This endpoint allows you to update an existing JSON file by providing its ID. The file must have been created with a password, and you must provide the correct password to update it.

#### JSON payload

```json
{
"data": "new data",
"password": "mySecretPassword"
}
```

Note: The `password` field is required and must match the password stored in the existing file.

#### Response

Success (200):

```json
{
"id": "5633286537740288",
"data": "https://json.openpatch.org/api/v2/5633286537740288"
}
```

Error responses:

- 401 Unauthorized: Password is missing or incorrect
- 403 Forbidden: File is not password-protected
- 404 Not Found: File with the given ID does not exist
- 500 Internal Server Error: Could not update the data

### GET

Example endpoint URL
Expand All @@ -57,8 +101,23 @@ https://json.openpatch.org/api/v2/5633286537740288

#### Response

Example of binary response. If the id is found it will return the data. Otherwise 404.
Example of JSON response. If the id is found it will return the data. Otherwise 404.

For files without password:

```json
{
"data": "1234567890"
}
```
1234567890

For files with password:

```json
{
"data": "1234567890",
"password": true
}
```

Note: The `password` field is replaced with a boolean value (`true`) indicating the file is password-protected. The actual password value is never included in GET responses for security reasons.
70 changes: 68 additions & 2 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,24 @@ app.get("/", (req, res) => res.sendFile(`${process.cwd()}/index.html`));
app.get("/api/v2/:key", async (req, res) => {
try {
const key = req.params.key;

// Prevent path traversal attacks
if (key.includes("/") || key.includes("\\") || key.includes("..")) {
return res.status(400).json({ message: "Invalid key format." });
}

const filePath = path.join(FOLDER_NAME, key + ".json");
const content = await fs.readFile(filePath, "utf-8");
const data = JSON.parse(content);

// Replace password with boolean indicating if file is password-protected
if (data.password) {
data.password = true;
}

res.status(200);
res.setHeader("content-type", "application/octet-stream");
res.sendFile(path.join(FOLDER_NAME, key + ".json"));
res.setHeader("content-type", "application/json");
res.json(data);
} catch (error) {
console.error(error);
res.status(404).json({ message: "Could not find the file." });
Expand All @@ -45,6 +60,57 @@ app.post("/api/v2/post/", async (req, res) => {
}
});

app.post("/api/v2/post/:id", async (req, res) => {
try {
const id = req.params.id;

// Prevent path traversal attacks
if (id.includes("/") || id.includes("\\") || id.includes("..")) {
return res.status(400).json({ message: "Invalid id format." });
}

const filePath = path.join(FOLDER_NAME, id + ".json");
const newContent = req.body;

// Check if file exists
let existingContent;
try {
const fileContent = await fs.readFile(filePath, "utf-8");
existingContent = JSON.parse(fileContent);
} catch (error) {
return res.status(404).json({ message: "Could not find the file." });
}

// Check if password exists in the stored file
if (!existingContent.password) {
return res
.status(403)
.json({ message: "This file is not password protected." });
}

// Check if password was provided in request
if (!newContent.password) {
return res.status(401).json({ message: "Password is required." });
}

// Validate password
if (existingContent.password !== newContent.password) {
return res.status(401).json({ message: "Invalid password." });
}

// Password is valid, replace the file
await fs.writeFile(filePath, JSON.stringify(newContent));

res.status(200).json({
id,
data: `${LOCAL ? "http" : "https"}://${req.get("host")}/api/v2/${id}`,
});
} catch (error) {
console.error(error);
res.status(500).json({ message: "Could not update the data." });
}
});

async function start() {
await fs.mkdir(FOLDER_NAME).catch((error) => {
if (error.code !== "EEXIST") {
Expand Down
Loading