- ApiKeysController verlangt MANAGE_SETTINGS. Bisher konnte jeder
OIDC-Benutzer einen API-Key erzeugen, der im PermissionsGuard jede
Rechteprüfung umgeht (Rechteausweitung zum Admin).
- MailService setzt disableFileAccess/disableUrlAccess für jede Mail.
- send-email im Posteingang prüft den Body streng auf Strings/Zahlen.
Bisher liest nodemailer bei html: { path: '/proc/self/environ' }
beliebige Serverdateien ein und verschickt sie.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
83 lines
2.7 KiB
TypeScript
83 lines
2.7 KiB
TypeScript
import { Injectable, Logger } from '@nestjs/common';
|
|
import { ConfigService } from '@nestjs/config';
|
|
import * as nodemailer from 'nodemailer';
|
|
|
|
@Injectable()
|
|
export class MailService {
|
|
private readonly logger = new Logger(MailService.name);
|
|
private transporter: nodemailer.Transporter;
|
|
|
|
constructor(private readonly configService: ConfigService) {
|
|
this.transporter = nodemailer.createTransport({
|
|
host: this.configService.get<string>('SMTP_HOST', ''),
|
|
port: this.configService.get<number>('SMTP_PORT', 587),
|
|
secure: this.configService.get<string>('SMTP_SECURE', 'false') === 'true',
|
|
auth: {
|
|
user: this.configService.get<string>('SMTP_USER', ''),
|
|
pass: this.configService.get<string>('SMTP_PASS', ''),
|
|
},
|
|
});
|
|
}
|
|
|
|
async sendMail(options: {
|
|
to: string;
|
|
subject: string;
|
|
body: string;
|
|
html?: string;
|
|
attachments?: { filename: string; content: Buffer }[];
|
|
smtpOverride?: {
|
|
host: string;
|
|
port: number;
|
|
secure: boolean;
|
|
user: string;
|
|
pass: string;
|
|
from: string;
|
|
};
|
|
}): Promise<void> {
|
|
let transporter = this.transporter;
|
|
const globalFromEmail = this.configService.get<string>(
|
|
'SMTP_FROM',
|
|
'paperless@localhost',
|
|
);
|
|
const globalFromName = this.configService.get<string>('SMTP_FROM_NAME', '');
|
|
let from = globalFromName
|
|
? `"${globalFromName}" <${globalFromEmail}>`
|
|
: globalFromEmail;
|
|
|
|
if (options.smtpOverride) {
|
|
const o = options.smtpOverride;
|
|
transporter = nodemailer.createTransport({
|
|
host: o.host,
|
|
port: o.port,
|
|
secure: o.secure,
|
|
auth: { user: o.user, pass: o.pass },
|
|
});
|
|
from = o.from || from;
|
|
} else {
|
|
this.logger.debug(
|
|
`SMTP config — host: ${this.configService.get('SMTP_HOST')} port: ${this.configService.get('SMTP_PORT')} secure: ${this.configService.get('SMTP_SECURE')} user: ${this.configService.get('SMTP_USER')} from: ${from}`,
|
|
);
|
|
}
|
|
|
|
const info = await transporter.sendMail({
|
|
from,
|
|
to: options.to,
|
|
subject: options.subject,
|
|
text: options.body,
|
|
html: options.html,
|
|
attachments: options.attachments?.map((a) => ({
|
|
filename: a.filename,
|
|
content: a.content,
|
|
})),
|
|
// Inhalte kommen teils vom Aufrufer: nodemailer darf nie Dateien vom
|
|
// Server lesen oder URLs abrufen (`{ path }` / `{ href }`).
|
|
disableFileAccess: true,
|
|
disableUrlAccess: true,
|
|
});
|
|
|
|
this.logger.log(
|
|
`Mail gesendet an ${options.to}: "${options.subject}" — messageId: ${info.messageId} accepted: ${JSON.stringify(info.accepted)} rejected: ${JSON.stringify(info.rejected)} response: ${info.response}`,
|
|
);
|
|
}
|
|
}
|