andrewpietiladotcom/server.js

210 lines
No EOL
7.4 KiB
JavaScript

import express from "express";
const app = express();
import formidable from "formidable";
import fs from "node:fs";
import cookieParser from 'cookie-parser';
import path from 'node:path';
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
import cors from 'cors';
import bodyParser from "body-parser";
import MimeMessage from "./lib/MimeMessage.js";
import MimeHeader from "./lib/MimeHeader.js";
app.use(cors());
app.use(cookieParser());
let state = +fs.readFileSync("state.integer", "utf-8");
console.log(`State: ${state.toString()}`);
// app.use((req, res, next) => {
// console.log({url: req.url, headers: req.headers});
// req.on("data", (data) => {
// console.log(data.toString("utf8"));
// })
// next();
// })
app.post('/sendgrid/ingress', (req, res) => {
const form = formidable({});
form.parse(req, (err, fields, files) => {
var message = fields.email?.[0];
var messageObj = new MimeMessage(message);
var envelope = JSON.parse(fields.envelope?.[0]??"{}");
messageObj.prependHeader(new MimeHeader(`Received: from ${req.headers["x-forwarded-for"]} by andrewpietila.com with HTTPS-API id ${state} for ${envelope.to??"unknown"}; ${new Date().toISOString()}`));
messageObj.prependHeader(new MimeHeader(`X-Auth-Check: SPF ${fields.SPF?.[0]??"missing"}`));
messageObj.prependHeader(new MimeHeader(`X-Auth-Check: DKIM ${fields.dkim?.[0]??"missing"}`));
messageObj.prependHeader(new MimeHeader(`Return-Path: <${envelope.from??"unknown"}>`));
fs.writeFileSync(`messages/${new Date().toISOString()}.eml`, messageObj.toString());
fs.writeFileSync(`messages/${new Date().toISOString()}-headers.json`, JSON.stringify(req.headers, null, 4));
fs.writeFileSync(`messages/${new Date().toISOString()}.json`, JSON.stringify(fields, null, 4));
res.status(204);
res.end();
state++;
fs.writeFileSync("state.integer", state.toString(), {encoding: "utf-8"});
})
});
app.post('/mail/login', (req, res) => {
// TODO: Actual proper login stuffs.
res.cookie("authenticated", "true", {maxAge: 86_400_000});
res.redirect("/static/jmapjs");
})
// JMAP implementation.
app.all("/.well-known/jmap", (req, res) => {
res.redirect("/jmap/session");
});
// https://jmap.io/spec-core.html#the-jmap-session-resource
// TODO: Authenticated
app.get("/jmap/session", (req, res) => {
// TODO: HTTP Basic auth.
// if ( req.cookies.authenticated == "true" )
res.json({
"capabilities": {
"urn:ietf:params:jmap:core": {
"maxSizeUpload": 50000000,
"maxConcurrentUpload": 8,
"maxSizeRequest": 10000000,
"maxConcurrentRequests": 8,
"maxCallsInRequest": 32,
"maxObjectsInGet": 256,
"maxObjectsInSet": 128,
"collationAlgorithms": [
"i;ascii-numeric",
"i;ascii-casemap",
"i;unicode-casemap"
]
},
"urn:ietf:params:jmap:mail": {
maxMailboxesPerEmail: null,
maxMailboxDepth: null,
maxSizeMailboxName: 100,
maxSizeAttachmentsPerEmail: 50_000_000,
emailQuerySortOptions: [
"receivedAt"
],
mayCreateTopLevelMailbox: false
},
"urn:ietf:params:jmap:submission": {
maxDelayedSend: 0,
submissionExtensions: []
}
},
"accounts": {
"andrew@andrewpietila.com": {
"name": "andrew@andrewpietila.com",
isPersonal: true,
isReadOnly: false,
accountCapabilities: {}
}
},
"primaryAccounts": {
},
"username": "andrew@andrewpietila.com",
"apiUrl": "https://andrewpietila.com/api/jmap/api/",
"downloadUrl": "https://andrewpietila.com/api/jmap/download/{accountId}/{blobId}/{name}?accept={type}",
"uploadUrl": "https://andrewpietila.com/api/jmap/upload/{accountId}/",
"eventSourceUrl": "https://andrewpietila.com/api/jmap/eventsource/?types={types}&closeafter={closeafter}&ping={ping}",
"state": "75128aab4b1b"
});
// else {
// res.status(404);
// res.end();
// }
});
function isEmpty(obj) {
for (var prop in obj) {
if ( Object.prototype.hasOwnProperty.call(obj, prop) ) {
return false;
}
}
return true;
}
app.post("/api/jmap/api/", bodyParser.json(), (req, res) => {
const responses = req.body.methodCalls.map(element => {
if (element[0] === "Identity/get" && isEmpty(element[1]) ) {
return [ "Identity/get",
{
accountId: "andrew@andrewpietila.com",
state: state.toString(),
"list": [
{
"id": "andrew@andrewpietila.com",
"name": "Andrew Pietila",
"email": "andrew@andrewpietla.com",
"replyTo": null,
"bcc": null,
textSignature: "",
htmlSignature: "",
mayDelete: true
}
]
},
element[2]
]
} else if ( element[0] === "Mailbox/get" && isEmpty(element[1]) ) {
return ["Mailbox/get",
{
accountId: "andrew@andrewpietila.com",
state: state.toString(),
list: [{
id: "Inbox",
name: "Inbox",
parentId: null,
role: "inbox",
sortOrder: 0,
totalEmails: 0, // TODO: Index messages.
unreadEmails: 0, // TODO: Index messages.
totalThreads: 0, // TODO: Index messages.
unreadThreads: 0, // TODO: Index messages.
myRights: {
mayReadItems: true,
mayAddItems: true,
mayRemoveItems: true,
maySetSeen: true,
maySetKeyword: false,
mayCreateChild: false, // TODO: Implement child folders.
mayRename: false, // TODO: Implement renaming folders.
mayDelete: true,
maySubmit: true
},
isSubscribed: true // TODO: Handle subscriptions.
}],
},
element[2]
]
}
return [
"error",
{
type: "unknownMethod"
},
element[2]
]
});
res.json({"methodResponses": responses, sessionState: "1"});
})
app.use("/static", express.static(__dirname + "/static"));
app.listen(8080);