Skip to content
Merged
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
47 changes: 47 additions & 0 deletions api/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ https://upstash.com/docs/redis/sdks/ts/pipelining/auto-pipeline
import { hash as cryptoHash, createHmac, getRandomValues, randomUUID } from 'node:crypto';
import { Buffer } from "node:buffer";
import { Redis } from '@upstash/redis';
import { Octokit } from '@octokit/core';
import { createOrUpdateTextFile } from '@octokit/plugin-create-or-update-text-file';

const secret = process.env.SECRET;
const sigLen = parseInt(process.env.SIG_LEN);
Expand All @@ -31,6 +33,7 @@ const dbKeyPrefix = {
}
}
}

// Redis client for user database
const redisData = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL,
Expand All @@ -46,6 +49,10 @@ const redisRateLimit = new Redis({
enableAutoPipelining: true
})

// Setup Octokit for accessing the GitHub API
const MyOctokit = Octokit.plugin(createOrUpdateTextFile);
const octokit = new MyOctokit({ auth: process.env.GITHUB_PAT });

function hash(str){
return cryptoHash('md5', str, 'base64url').substring(0,hashLen);
// For small size str crypto.hash() is faster than crypto.createHash()
Expand Down Expand Up @@ -282,3 +289,43 @@ export async function OneSignalSendPush(app, externalID, data=null){
})
}).then((res) => res.json())
}

// Push JSON (object) to be stored at https://securelay.github.io/jsonbin/{id}/{publicKey}.json
// The function adds metadata using decoratePayload() above.
// Do not pass JSON in order to touch existing data (i.e. update its timestamp).
// Pass null as `json` and true as `remove` for removing the stored data.
// Returns true if data is updated or deleted, false otherwise.
// Ref: https://github.com/octokit/plugin-create-or-update-text-file.js/
export async function githubPushJSON(privateKey, json=null, remove=false){
const publicKey = genPublicKey(privateKey);
const path = id() + '/' + publicKey + '.json';
const touch = Boolean(!(json || remove));
let content, mode;

if (touch) {
// Just update timestamp in metadata when `touch` is true;
mode = 'touched';
content = ({exists, content}) => {
if (!exists) return null;
const json = JSON.parse(content);
json.time = Date.now();
return JSON.stringify(json);
}
} else if (json === null) {
mode = 'deleted';
content = null;
} else {
mode = 'updated';
content = JSON.stringify(decoratePayload(json));
}

const { updated, deleted } = await octokit.createOrUpdateTextFile({
owner: "securelay",
repo: "jsonbin",
path: path,
content: content,
message: mode + ' ' + path,
});

return updated || deleted;
}
37 changes: 28 additions & 9 deletions api/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import * as helper from './helper.js';
import Fastify from 'fastify';

const endpointID = helper.id();
const cdnUrlBase = `https://cdn.jsdelivr.net/gh/securelay/jsonbin@main/${endpointID}`;
const bodyLimit = parseInt(process.env.BODYLIMIT);
const fieldLimit = parseInt(process.env.FIELDLIMIT);
const webhookTimeout = parseInt(process.env.WEBHOOK_TIMEOUT);
Expand Down Expand Up @@ -42,7 +44,7 @@ fastify.get('/', (request, reply) => {
fastify.get('/id', (request, reply) => {
const app = request.query.app;
if (app == null) {
reply.send(helper.id());
reply.send(endpointID);
} else {
const OneSignalID = helper.OneSignalID(app);
if (OneSignalID) {
Expand Down Expand Up @@ -172,9 +174,15 @@ fastify.post('/private/:privateKey', async (request, reply) => {
const redirectOnErr = request.query.err;
try {
if (helper.validate(privateKey) !== 'private') throw 401;
await helper.privateProduce(privateKey, JSON.stringify(helper.decoratePayload(request.body)));
//await helper.privateProduce(privateKey, JSON.stringify(helper.decoratePayload(request.body)));
if (! await helper.githubPushJSON(privateKey, request.body)) throw 500;
if (redirectOnOk == null) {
reply.send({message: "Done", error: "Ok", statusCode: reply.statusCode});
reply.send({
message: "Done",
error: "Ok",
statusCode: reply.statusCode,
cdn: `${cdnUrlBase}/${helper.genPublicKey(privateKey)}.json`
});
} else {
reply.redirect(redirectOnOk, 303);
}
Expand All @@ -195,7 +203,8 @@ fastify.delete('/private/:privateKey', async (request, reply) => {
const { privateKey } = request.params;
try {
if (helper.validate(privateKey) !== 'private') throw 401;
await helper.privateDelete(privateKey);
//await helper.privateDelete(privateKey);
if (! await helper.githubPushJSON(privateKey, null, true)) throw 500;
reply.code(204);
} catch (err) {
if (err == 401) {
Expand All @@ -210,8 +219,14 @@ fastify.patch('/private/:privateKey', async (request, reply) => {
const { privateKey } = request.params;
try {
if (helper.validate(privateKey) !== 'private') throw 401;
await helper.privateRefresh(privateKey);
reply.send({message: "Done", error: "Ok", statusCode: reply.statusCode});
//await helper.privateRefresh(privateKey);
if (! await helper.githubPushJSON(privateKey)) throw 500;
reply.send({
message: "Done",
error: "Ok",
statusCode: reply.statusCode,
cdn: `${cdnUrlBase}/${helper.genPublicKey(privateKey)}.json`
});
} catch (err) {
if (err == 401) {
callUnauthorized(reply, 'Provided key is not Private');
Expand All @@ -225,9 +240,13 @@ fastify.get('/public/:publicKey', async (request, reply) => {
const { publicKey } = request.params;
try {
if (helper.validate(publicKey) !== 'public') throw 401;
const data = await helper.publicConsume(publicKey);
if (!data) throw 404;
reply.send(data);

//const data = await helper.publicConsume(publicKey);
//if (!data) throw 404;
//reply.send(data);

//reply.redirect(`https://securelay.github.io/jsonbin/${endpointID}/${publicKey}.json`, 301);
reply.redirect(`${cdnUrlBase}/${publicKey}.json`, 301);
} catch (err) {
if (err == 401) {
callUnauthorized(reply, 'Provided key is not Public');
Expand Down
1 change: 1 addition & 0 deletions api/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Run: node --env-file=.env test.js

import * as helper from './helper.js';

// process.exit(); // Use this to exit this script

console.log('Sending OneSignal Push for formonit app...OneSignal API returns:',
await helper.OneSignalSendPush('formonit', '6kI2oBt2dN', {"hello":"there"}));
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
"dependencies": {
"@fastify/cors": "^10.0.1",
"@fastify/formbody": "^8.0.1",
"@octokit/core": "^6.1.3",
"@octokit/plugin-create-or-update-text-file": "^5.1.0",
"@upstash/ratelimit": "^2.0.3",
"@upstash/redis": "^1.34.3",
"@vercel/edge": "^1.1.2",
Expand Down