2024-06-29 17:12:12 +00:00
|
|
|
// Identity. Store your memories and mental belongings
|
|
|
|
// Copyright (C) 2024 Sofía Aritz
|
|
|
|
//
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU Affero General Public License as published
|
|
|
|
// by the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU Affero General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
2024-06-16 18:56:41 +00:00
|
|
|
import { readFile } from "node:fs/promises";
|
2024-06-16 18:39:10 +00:00
|
|
|
import { createWriteStream, readFileSync, writeFileSync } from "node:fs";
|
|
|
|
import { createSign, generateKeyPairSync, randomUUID } from "node:crypto";
|
2024-06-15 19:58:23 +00:00
|
|
|
import Fastify from "fastify";
|
2024-06-16 18:56:41 +00:00
|
|
|
import multipart from "@fastify/multipart";
|
2024-06-16 10:53:12 +00:00
|
|
|
import { join } from "node:path";
|
2024-06-16 18:56:41 +00:00
|
|
|
import mime from "mime";
|
2024-06-16 18:39:10 +00:00
|
|
|
import { promisify } from "node:util";
|
|
|
|
import { pipeline } from "node:stream";
|
2024-06-27 21:50:14 +00:00
|
|
|
import cors from "@fastify/cors";
|
2024-06-30 16:46:35 +00:00
|
|
|
import { M2M_ALGORITHM, ASSETS_FOLDER, ASSET_API_LANDING_MESSAGE, IDENTITY_API_ENDPOINT } from "./consts.js";
|
2024-06-15 19:58:23 +00:00
|
|
|
|
2024-06-16 18:56:41 +00:00
|
|
|
const { private: M2M_PRIVATE_KEY, public: M2M_PUBLIC_KEY } = loadM2MKeys();
|
2024-06-16 10:53:12 +00:00
|
|
|
if (M2M_PRIVATE_KEY == null || M2M_PUBLIC_KEY == null) {
|
2024-06-16 18:56:41 +00:00
|
|
|
console.error("Couldn't load keys");
|
|
|
|
process.exit(1);
|
2024-06-16 10:53:12 +00:00
|
|
|
}
|
2024-06-16 10:18:19 +00:00
|
|
|
|
2024-06-29 17:12:12 +00:00
|
|
|
const app = new Fastify({
|
2024-06-15 19:58:23 +00:00
|
|
|
logger: true,
|
2024-06-16 18:56:41 +00:00
|
|
|
});
|
2024-06-15 19:58:23 +00:00
|
|
|
|
2024-06-29 17:12:12 +00:00
|
|
|
app.register(multipart);
|
|
|
|
app.register(cors, {
|
2024-06-27 21:50:14 +00:00
|
|
|
origin: true,
|
|
|
|
})
|
2024-06-16 18:39:10 +00:00
|
|
|
|
2024-06-29 17:12:12 +00:00
|
|
|
app.get("/", async () => {
|
2024-06-16 18:56:41 +00:00
|
|
|
return signString(ASSET_API_LANDING_MESSAGE);
|
|
|
|
});
|
2024-06-16 10:18:19 +00:00
|
|
|
|
2024-06-29 17:12:12 +00:00
|
|
|
app.get("/crypto/cert", async () => {
|
2024-06-16 18:56:41 +00:00
|
|
|
return M2M_PUBLIC_KEY;
|
|
|
|
});
|
2024-06-16 10:18:19 +00:00
|
|
|
|
2024-06-29 17:12:12 +00:00
|
|
|
app.get("/crypto/algo", () => {
|
2024-06-16 18:56:41 +00:00
|
|
|
return M2M_ALGORITHM;
|
|
|
|
});
|
2024-06-15 19:58:23 +00:00
|
|
|
|
2024-06-29 17:12:12 +00:00
|
|
|
app.put("/asset", {
|
2024-06-25 22:29:26 +00:00
|
|
|
async handler(request, reply) {
|
2024-06-30 16:04:54 +00:00
|
|
|
let { user, limits } = await userFromSessionKey(request.query.session_key);
|
|
|
|
if (user.assets.length >= limits.maxAssetCount) {
|
2024-06-25 22:29:26 +00:00
|
|
|
reply.code(403);
|
|
|
|
return "Max asset count reached. Contact support or upgrade your plan";
|
|
|
|
}
|
|
|
|
|
2024-06-16 18:56:41 +00:00
|
|
|
let file = await request.file();
|
2024-06-16 18:39:10 +00:00
|
|
|
|
2024-06-16 18:56:41 +00:00
|
|
|
let id = randomUUID().toString();
|
|
|
|
let extension = mime.getExtension(file.mimetype) || ".bin";
|
|
|
|
let full_id = `${id}.${extension}`;
|
2024-06-16 18:39:10 +00:00
|
|
|
|
2024-06-16 18:56:41 +00:00
|
|
|
let url = new URL(IDENTITY_API_ENDPOINT);
|
|
|
|
url.pathname = "/m2m/asset";
|
2024-06-16 18:39:10 +00:00
|
|
|
|
|
|
|
await fetch(url, {
|
|
|
|
method: "PUT",
|
|
|
|
body: signObject({
|
|
|
|
session_key: request.query.session_key,
|
|
|
|
asset_id: full_id,
|
2024-06-16 18:56:41 +00:00
|
|
|
}),
|
|
|
|
});
|
2024-06-16 10:53:12 +00:00
|
|
|
|
2024-06-26 11:39:06 +00:00
|
|
|
await promisify(pipeline)(file.file, createWriteStream(`.assets/${full_id}`));
|
|
|
|
|
|
|
|
return {
|
|
|
|
asset_id: full_id,
|
|
|
|
}
|
2024-06-16 10:53:12 +00:00
|
|
|
},
|
|
|
|
schema: {
|
|
|
|
query: {
|
|
|
|
type: "object",
|
|
|
|
properties: {
|
|
|
|
session_key: { type: "string" },
|
|
|
|
},
|
|
|
|
required: ["session_key"],
|
|
|
|
},
|
|
|
|
},
|
2024-06-16 18:56:41 +00:00
|
|
|
});
|
2024-06-16 10:53:12 +00:00
|
|
|
|
2024-06-29 17:12:12 +00:00
|
|
|
app.get("/asset", {
|
2024-06-16 10:18:19 +00:00
|
|
|
async handler(request, reply) {
|
2024-06-30 16:04:54 +00:00
|
|
|
let { user, limits } = await userFromSessionKey(request.query.session_key);
|
2024-06-16 10:53:12 +00:00
|
|
|
|
2024-06-25 22:29:26 +00:00
|
|
|
if ('statusCode' in user) {
|
|
|
|
reply.code(500);
|
|
|
|
return "Something failed, please try again later"
|
|
|
|
}
|
|
|
|
|
2024-06-16 10:53:12 +00:00
|
|
|
if (user.assets.includes(request.query.asset_id)) {
|
2024-06-16 18:56:41 +00:00
|
|
|
let path = join(ASSETS_FOLDER, request.query.asset_id);
|
2024-06-16 10:18:19 +00:00
|
|
|
|
2024-06-16 18:56:41 +00:00
|
|
|
reply.type(mime.getType(path));
|
|
|
|
reply.send(await readFile(path));
|
2024-06-16 10:53:12 +00:00
|
|
|
} else {
|
2024-06-25 22:29:26 +00:00
|
|
|
reply.code(401);
|
2024-06-16 18:56:41 +00:00
|
|
|
return "Not authorized";
|
2024-06-16 10:53:12 +00:00
|
|
|
}
|
2024-06-16 10:18:19 +00:00
|
|
|
},
|
|
|
|
schema: {
|
|
|
|
query: {
|
|
|
|
type: "object",
|
|
|
|
properties: {
|
2024-06-16 10:53:12 +00:00
|
|
|
asset_id: { type: "string" },
|
2024-06-16 10:18:19 +00:00
|
|
|
session_key: { type: "string" },
|
|
|
|
},
|
2024-06-16 10:53:12 +00:00
|
|
|
required: ["asset_id", "session_key"],
|
2024-06-16 10:18:19 +00:00
|
|
|
},
|
|
|
|
},
|
2024-06-16 18:56:41 +00:00
|
|
|
});
|
2024-06-16 10:18:19 +00:00
|
|
|
|
2024-06-29 17:12:12 +00:00
|
|
|
app.listen({ port: 3001 });
|
2024-06-16 10:18:19 +00:00
|
|
|
|
|
|
|
function loadM2MKeys() {
|
2024-06-16 18:39:10 +00:00
|
|
|
try {
|
|
|
|
return {
|
|
|
|
private: readFileSync("./.keys/m2m.pem").toString("ascii"),
|
|
|
|
public: readFileSync("./.keys/m2m.pub").toString("ascii"),
|
2024-06-16 18:56:41 +00:00
|
|
|
};
|
|
|
|
} catch {
|
|
|
|
console.warn("Generating M2M key pair!");
|
2024-06-16 18:39:10 +00:00
|
|
|
|
|
|
|
let { publicKey, privateKey } = generateKeyPairSync("rsa", {
|
|
|
|
modulusLength: 4096,
|
|
|
|
publicKeyEncoding: {
|
2024-06-16 18:56:41 +00:00
|
|
|
type: "spki",
|
|
|
|
format: "pem",
|
2024-06-16 18:39:10 +00:00
|
|
|
},
|
|
|
|
privateKeyEncoding: {
|
2024-06-16 18:56:41 +00:00
|
|
|
type: "pkcs8",
|
|
|
|
format: "pem",
|
2024-06-16 18:39:10 +00:00
|
|
|
},
|
2024-06-16 18:56:41 +00:00
|
|
|
});
|
2024-06-16 18:39:10 +00:00
|
|
|
|
2024-06-16 18:56:41 +00:00
|
|
|
writeFileSync("./.keys/m2m.pem", privateKey);
|
|
|
|
writeFileSync("./.keys/m2m.pub", publicKey);
|
2024-06-16 18:39:10 +00:00
|
|
|
|
2024-06-16 18:56:41 +00:00
|
|
|
return loadM2MKeys();
|
2024-06-16 10:18:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function signString(content) {
|
2024-06-16 18:56:41 +00:00
|
|
|
let sign = createSign(M2M_ALGORITHM);
|
|
|
|
sign.update(content);
|
|
|
|
return `-----BEGIN SIGNED MESSAGE-----\n\n${content}\n\n-----BEGIN SIGNATURE-----\n\n${sign.sign(M2M_PRIVATE_KEY, "base64")}\n-----END SIGNATURE-----`;
|
2024-06-16 10:53:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function signObject(content) {
|
2024-06-16 18:56:41 +00:00
|
|
|
return signString(JSON.stringify(content));
|
2024-06-16 10:53:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async function userFromSessionKey(session_key) {
|
2024-06-16 18:56:41 +00:00
|
|
|
let url = new URL(IDENTITY_API_ENDPOINT);
|
|
|
|
url.pathname = "/m2m/account";
|
2024-06-16 10:53:12 +00:00
|
|
|
|
|
|
|
let res1 = await fetch(url, {
|
|
|
|
method: "POST",
|
|
|
|
body: signObject({
|
2024-06-16 18:39:10 +00:00
|
|
|
session_key,
|
2024-06-16 18:56:41 +00:00
|
|
|
}),
|
|
|
|
});
|
2024-06-16 10:53:12 +00:00
|
|
|
|
2024-06-16 18:56:41 +00:00
|
|
|
return await res1.json();
|
|
|
|
}
|