210 lines
No EOL
7.4 KiB
JavaScript
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); |