api.npub.email/src/smtpServer.ts
2024-12-02 18:26:49 +01:00

101 lines
No EOL
3.9 KiB
TypeScript

import {SMTPServer} from "smtp-server";
import {deriveNsecForEmail, getNDK} from "./utils";
import {NDKEvent, NDKKind, NDKPrivateKeySigner} from "@nostr-dev-kit/ndk";
import {PrismaClient} from "@prisma/client";
import {logger} from "./utils/logs";
import {encryptEventForRecipient, parseEmail} from "@arx/utils";
export class NostrSmtpServer {
private server: SMTPServer;
constructor(db: PrismaClient, port: number) {
this.server = new SMTPServer({
authOptional: true,
logger: false,
onData: (stream, session, callback) => {
let mailData = '';
stream.on('data', (chunk: Buffer) => {
mailData += chunk.toString();
});
stream.on('end', async () => {
if (!session.envelope.mailFrom) {
logger.warn('Ignoring email without sender');
callback();
return;
}
try {
const parsedEmail = parseEmail(mailData);
for (let recipientEmail of session.envelope.rcptTo) {
const address = recipientEmail.address;
const parts = address.split('@');
if (parts[1] !== process.env.BASE_DOMAIN) {
logger.warn('Not sending email to', address, 'because it is not in the allowed domain');
continue;
}
const alias = parts[0];
const user = await db.alias.findUnique({
where: {
alias
},
include: {
user: true
}
});
if (!user) {
logger.warn('No user found for', alias, 'skipping');
continue;
}
const timeRemainingInSubscription = user.user.subscriptionDuration === null ? Infinity : (user.user.subscriptionDuration * 1000) - Date.now() + user.user.lastPayment.getTime();
if (timeRemainingInSubscription <= 0) {
logger.warn(`Subscription has expired for ${alias}`);
continue;
}
const recipient = user.npub;
const randomKeySinger = new NDKPrivateKeySigner(deriveNsecForEmail(
process.env.MASTER_NSEC!,
session.envelope.mailFrom?.address
));
const ndk = getNDK();
ndk.signer = randomKeySinger;
await ndk.connect();
const ndkUser = ndk.getUser({
npub: recipient
});
const randomKeyUser = await randomKeySinger.user();
const event = new NDKEvent();
event.kind = NDKKind.Article;
event.content = parsedEmail.body;
event.created_at = Math.floor(Date.now() / 1000);
event.pubkey = randomKeyUser.pubkey;
event.tags.push(['p', ndkUser.pubkey])
event.tags.push(['subject', parsedEmail.subject]);
event.tags.push(['email:localIP', session.localAddress]);
event.tags.push(['email:remoteIP', session.remoteAddress]);
event.tags.push(['email:isEmail', 'true']);
for (let to of session.envelope.rcptTo)
event.tags.push(['email:to', to.address]);
for (let header of Object.keys(parsedEmail.headers))
event.tags.push([`email:header:${header}`, parsedEmail.headers[header]]);
event.tags.push(['email:session', session.id]);
event.tags.push(['email:from', session.envelope.mailFrom?.address ?? '']);
await event.sign(randomKeySinger);
const encryptedEvent = await encryptEventForRecipient(ndk, event, ndkUser);
await encryptedEvent.publish();
}
} catch (e) {
logger.error(JSON.stringify(e));
} finally {
callback();
}
});
}
});
this.server.listen(port, '0.0.0.0');
logger.info(`SMTP Server running on port ${port}`);
}
}