Add additional jmap API functionality.
This commit is contained in:
parent
9d4d264a09
commit
a2740f6359
8 changed files with 177 additions and 16 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
|||
sendgrid.env
|
||||
messages/
|
||||
node_modules/
|
||||
state.integer
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
[submodule "static/jmapjs"]
|
||||
path = static/jmapjs
|
||||
url = https://github.com/jmapio/jmap-demo-webmail
|
|
@ -1,4 +1,4 @@
|
|||
import MimeHeader from "./MimeHeader";
|
||||
import MimeHeader from "./MimeHeader.js";
|
||||
|
||||
class MimeMessage {
|
||||
constructor(message) {
|
||||
|
|
23
package-lock.json
generated
23
package-lock.json
generated
|
@ -8,6 +8,7 @@
|
|||
"@sendgrid/mail": "^8.1.4",
|
||||
"body-parser": "^1.20.3",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.21.2",
|
||||
"formidable": "^3.5.2"
|
||||
}
|
||||
|
@ -224,6 +225,19 @@
|
|||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cors": {
|
||||
"version": "2.8.5",
|
||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
||||
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"object-assign": "^4",
|
||||
"vary": "^1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
|
@ -702,6 +716,15 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.3",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz",
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
"@sendgrid/mail": "^8.1.4",
|
||||
"body-parser": "^1.20.3",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.21.2",
|
||||
"formidable": "^3.5.2"
|
||||
}
|
||||
|
|
151
server.js
151
server.js
|
@ -6,25 +6,54 @@ 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 = "";
|
||||
if ( fields.email ) {
|
||||
if ( typeof fields.email === "string") {
|
||||
message = fields.email;
|
||||
} else {
|
||||
message = fields.email.join("");
|
||||
}
|
||||
}
|
||||
var message = fields.email?.[0];
|
||||
|
||||
fs.writeFileSync(`messages/${new Date().toISOString()}.eml`, message);
|
||||
fs.writeFileSync(`messages/${new Date().toISOString()}.json`, JSON.stringify(fields));
|
||||
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"});
|
||||
})
|
||||
});
|
||||
|
||||
|
@ -43,7 +72,8 @@ app.all("/.well-known/jmap", (req, res) => {
|
|||
// https://jmap.io/spec-core.html#the-jmap-session-resource
|
||||
// TODO: Authenticated
|
||||
app.get("/jmap/session", (req, res) => {
|
||||
if ( req.cookies.authenticated == "true" )
|
||||
// TODO: HTTP Basic auth.
|
||||
// if ( req.cookies.authenticated == "true" )
|
||||
res.json({
|
||||
"capabilities": {
|
||||
"urn:ietf:params:jmap:core": {
|
||||
|
@ -60,6 +90,20 @@ app.get("/jmap/session", (req, res) => {
|
|||
"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": {
|
||||
|
@ -78,10 +122,89 @@ app.get("/jmap/session", (req, res) => {
|
|||
"eventSourceUrl": "https://andrewpietila.com/api/jmap/eventsource/?types={types}&closeafter={closeafter}&ping={ping}",
|
||||
"state": "75128aab4b1b"
|
||||
});
|
||||
else {
|
||||
res.status(404);
|
||||
res.end();
|
||||
// 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);
|
1
static/jmapjs
Submodule
1
static/jmapjs
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 2d59257506c634af1c2bf2a6325cc2aac59831b6
|
9
static/login.html
Normal file
9
static/login.html
Normal file
|
@ -0,0 +1,9 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en_US">
|
||||
<head>
|
||||
<title>Login</title>
|
||||
</head>
|
||||
<form action="/mail/login" enctype="multipart/form-data" method="post" >
|
||||
<input type="submit" value="Upload" />
|
||||
</form>
|
||||
</html>
|
Loading…
Add table
Reference in a new issue