diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..3b31ef6 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,37 @@ +version: "3.7" + +services: + webapp: + build: + context: ./webapp + dockerfile: Dockerfile + ports: + - "3000:3000" + + migrate: + build: + context: ./server/prisma + dockerfile: Dockerfile + volumes: + - sqlite:/database/ + environment: + - DATABASE_URL=file:/database/db.sqlite + + server: + build: + context: ./server + dockerfile: Dockerfile + ports: + - "8080:8080" + volumes: + - sqlite:/database/ + environment: + - DATABASE_URL=file:/database/db.sqlite + - FRONTEND_URL=http://0.0.0.0:3000 + depends_on: + - migrate + +## By default this config uses default local driver, +## For custom volumes replace with volume driver configuration. +volumes: + sqlite: \ No newline at end of file diff --git a/server/.dockerignore b/server/.dockerignore new file mode 100644 index 0000000..af54d5f --- /dev/null +++ b/server/.dockerignore @@ -0,0 +1,3 @@ +node_modules +npm-debug.log +build \ No newline at end of file diff --git a/server/.gitignore b/server/.gitignore index 3c3629e..b7dab5e 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -1 +1,2 @@ node_modules +build \ No newline at end of file diff --git a/server/Dockerfile b/server/Dockerfile new file mode 100644 index 0000000..9c979d0 --- /dev/null +++ b/server/Dockerfile @@ -0,0 +1,28 @@ +FROM node:16-alpine AS BUILD_IMAGE + +# install dependencies +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm ci + +# Copy all local files into the image. +COPY . . +COPY .env.docker .env + +RUN npx prisma generate + +RUN npm run build + +# remove development dependencies +RUN npm prune --production + +FROM node:16-alpine + +WORKDIR /app +COPY --from=0 /app . +# COPY . . + +ENV PORT=8080 +EXPOSE 8080 + +CMD ["node", "./build/server.js"] \ No newline at end of file diff --git a/server/build/server.js b/server/build/server.js index 68a2da7..d461f0a 100644 --- a/server/build/server.js +++ b/server/build/server.js @@ -1,16 +1,99 @@ "use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); +require("dotenv/config"); const express_1 = __importDefault(require("express")); +const client_1 = require("@prisma/client"); +const util_1 = require("./util"); +const helmet_1 = __importDefault(require("helmet")); +const express_rate_limit_1 = __importDefault(require("express-rate-limit")); +// Initialize middleware clients +const prisma = new client_1.PrismaClient(); const app = (0, express_1.default)(); -const port = 8080; // default port to listen -// define a route handler for the default home page -app.get("/", (req, res) => { - res.send("Hello world!"); +app.use(express_1.default.json()); +app.use((0, helmet_1.default)({ + crossOriginResourcePolicy: { + policy: process.env.ENVIRONMENT == "dev" ? "cross-origin" : "same-origin", + }, +})); +// Apply rate limiting +const postLimiter = (0, express_rate_limit_1.default)({ + windowMs: parseInt(process.env.POST_LIMIT_WINDOW_SECONDS) * 1000, + max: parseInt(process.env.POST_LIMIT), + standardHeaders: true, + legacyHeaders: false, // Disable the `X-RateLimit-*` headers }); // start the Express server -app.listen(port, () => { - console.log(`server started at http://localhost:${port}!`); +app.listen(process.env.PORT, () => { + console.log(`server started at port ${process.env.PORT}`); }); +// Post new encrypted note +app.post("/note/", postLimiter, (req, res) => __awaiter(void 0, void 0, void 0, function* () { + try { + const note = req.body; + const savedNote = yield prisma.encryptedNote.create({ + data: Object.assign(Object.assign({}, note), { expire_time: (0, util_1.addDays)(new Date(), 30) }), + }); + console.log(`[POST] Saved note <${savedNote.id}> for <${req.ip}>`); + res.json({ + view_url: `${process.env.FRONTEND_URL}/note/${savedNote.id}`, + expire_time: savedNote.expire_time, + }); + } + catch (err) { + console.error(err.stack); + res.status(500).send("Something went wrong."); + } +})); +// Get encrypted note +app.get("/note/:id", (req, res) => __awaiter(void 0, void 0, void 0, function* () { + const note = yield prisma.encryptedNote.findUnique({ + where: { id: req.params.id }, + }); + if (note != null) { + res.send(note); + console.log(`[GET] Retrieved note <${note.id}> for <${req.ip}>`); + } + res.status(404).send(); +})); +// Default response for any other request +app.use((req, res, next) => { + res.status(500).send(); +}); +// // Error handling +// app.use((err, req, res, next) => { +// console.error(err.stack); +// res.status(500).send("Something broke!"); +// }); +// Clean up expired notes periodically +const interval = Math.max(parseInt(process.env.CLEANUP_INTERVAL_SECONDS) || 1, 1) * + 1000; +setInterval(() => __awaiter(void 0, void 0, void 0, function* () { + try { + console.log("[Cleanup] Cleaning up expired notes..."); + const deleted = yield prisma.encryptedNote.deleteMany({ + where: { + expire_time: { + lte: new Date(), + }, + }, + }); + console.log(`[Cleanup] Deleted ${deleted.count} expired notes.`); + } + catch (err) { + console.error(`[Cleanup] Error cleaning expired notes:`); + console.error(err); + } +}), interval); +//# sourceMappingURL=server.js.map \ No newline at end of file diff --git a/server/prisma/Dockerfile b/server/prisma/Dockerfile new file mode 100644 index 0000000..5afe675 --- /dev/null +++ b/server/prisma/Dockerfile @@ -0,0 +1,31 @@ +# FROM node:16-alpine AS BUILD_IMAGE + +# # install dependencies +# WORKDIR /app +# COPY package.json package-lock.json ./ +# RUN npm ci + +# # Copy all local files into the image. +# COPY . . +# COPY .env.docker .env + +# RUN npx prisma generate + +# # remove development dependencies +# RUN npm prune --production + +# CMD ["npx", "prisma migrate deploy + +FROM node:16-alpine +RUN echo $DATABASE_URL + +# Create app directory +WORKDIR /app + +# Install prisma for the migration +RUN npm install -g prisma --unsafe-perm + +# Copy schema and migration folder +ADD ./ ./prisma/ + +CMD [ "prisma", "migrate", "deploy"] \ No newline at end of file diff --git a/server/prisma/dev.db b/server/prisma/dev.db index f784a1d..49d5c64 100644 Binary files a/server/prisma/dev.db and b/server/prisma/dev.db differ diff --git a/server/prisma/dev.db-journal b/server/prisma/dev.db-journal new file mode 100644 index 0000000..37420b9 Binary files /dev/null and b/server/prisma/dev.db-journal differ diff --git a/server/prisma/migrations/20220701200456_devdev/migration.sql b/server/prisma/migrations/20220701200456_devdev/migration.sql new file mode 100644 index 0000000..254b395 --- /dev/null +++ b/server/prisma/migrations/20220701200456_devdev/migration.sql @@ -0,0 +1,14 @@ +-- RedefineTables +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_EncryptedNote" ( + "id" TEXT NOT NULL PRIMARY KEY, + "insert_time" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "expire_time" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "ciphertext" TEXT NOT NULL, + "hmac" TEXT NOT NULL +); +INSERT INTO "new_EncryptedNote" ("ciphertext", "hmac", "id", "insert_time") SELECT "ciphertext", "hmac", "id", "insert_time" FROM "EncryptedNote"; +DROP TABLE "EncryptedNote"; +ALTER TABLE "new_EncryptedNote" RENAME TO "EncryptedNote"; +PRAGMA foreign_key_check; +PRAGMA foreign_keys=ON; diff --git a/server/server.ts b/server/server.ts index a2111cb..4ea69a3 100644 --- a/server/server.ts +++ b/server/server.ts @@ -29,7 +29,7 @@ const postLimiter = rateLimit({ // start the Express server app.listen(process.env.PORT, () => { - console.log(`server started at http://localhost:${process.env.PORT}`); + console.log(`server started at port ${process.env.PORT}`); }); // Post new encrypted note @@ -55,6 +55,7 @@ app.get("/note/:id", async (req, res) => { }); if (note != null) { res.send(note); + console.log(`[GET] Retrieved note <${note.id}> for <${req.ip}>`); } res.status(404).send(); }); @@ -64,6 +65,12 @@ app.use((req, res, next) => { res.status(404).send(); }); +// // Error handling +// app.use((err, req, res, next) => { +// console.error(err.stack); +// res.status(500).send("Something broke!"); +// }); + // Clean up expired notes periodically const interval = Math.max(parseInt(process.env.CLEANUP_INTERVAL_SECONDS) || 1, 1) * diff --git a/server/tsconfig.json b/server/tsconfig.json index d859901..1dc9eb6 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "sourceMap": true, + "outDir": "./build", "lib": ["esnext"], "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, "module": "commonjs" /* Specify what module code is generated. */, diff --git a/webapp/Dockerfile b/webapp/Dockerfile index 9e24a15..b99ccf3 100644 --- a/webapp/Dockerfile +++ b/webapp/Dockerfile @@ -9,6 +9,9 @@ RUN npm ci # Copy all local files into the image. COPY . . +ENV VITE_BACKEND_URL="http://0.0.0.0:8080" +ENV VITE_BRANDING="Noteshare.space" + RUN npm run build # remove development dependencies