refactor(quick-260921-m34): Aufgabe 3c - Randschicht beurteilt, drei Befunde gemeldet, 15 bleiben mit Urteil
httpntlm (exchange.provider, exchange-inbox.provider): NtlmOptions und
NtlmResponse beschreiben genau das, was uebergeben und gelesen wird. Die
ueberfluessige Zusicherung (httpntlm as any) faellt weg.
Graph-Rueckrufe (exchange.provider :157/:313): AuthProviderCallback aus dem
SDK selbst statt Handannotation - als import type, also ohne den dynamischen
Import zur Laufzeit zurueckzunehmen.
imapflow: streamToBuffer() nimmt Readable statt NodeJS.ReadableStream (alle
drei Aufrufer reichen client.download().content herein, imapflow deklariert
das als Readable) - damit traegt der Typ destroy() und die Zusicherung
faellt. node.parameters?.name war ebenfalls schon getypt.
nodemailer: ResolvedTransport.options wird SMTPTransport.Options; beide
Zweige bauen reine SMTP-Optionen, createTransport() nimmt sie ohne
Zusicherung.
node-forge: die vier let p7: any werden Captured<PkcsEnvelopedData |
PkcsSignedData> - der MITGELIEFERTE Typ. Die Lesestellen grenzen mit
'certificates' in p7 ein statt zuzusichern; verhaltensgleich, weil der
enveloped-Form das Feld fehlt und beide Schreibweisen dann die leere Liste
liefern. cert.siginfo war bereits getypt.
apps/web/src/test/setup.ts: expect.extend(matchers) traegt ohne Zusicherung
- geprueft im echten Typlauf (setup.ts liegt im include von
apps/web/tsconfig.json, mit einem absichtlichen Fehler nachgewiesen).
BEFUND 4 (D-03, gemeldet, NICHT repariert) imap.provider.ts:78 - der
Ausdruck (node as any).disposition?.parameters?.filename liest .parameters
von einer ZEICHENKETTE: imapflow deklariert disposition als string
(imap-flow.d.ts:448), die Parameter liegen in dispositionParameters (:450).
dispositionFilename ist damit zur Laufzeit immer ''. Folge: Outlook-Anhaenge,
die als application/octet-stream kommen, werden ueber den Dateinamen aus
Content-Disposition NICHT erkannt - nur ueber den aus Content-Type. Umbiegen
waere eine Verhaltensaenderung; die Zusicherung bleibt sichtbar stehen.
BEFUND 5 (D-03, gemeldet, NICHT repariert) imap.provider.ts:402 -
requireTLS kommt in imapflow 1.4.3 NIRGENDS vor, weder in ImapFlowOptions
noch im Laufzeitcode (beides durchsucht). Die Option wird still verworfen;
STARTTLS wird durch sie nicht erzwungen. Genau das } as any hat es
verdeckt. Bleibt stehen, damit der Befund in der Zaehlung sichtbar ist.
BEFUND 6 (D-03, gemeldet, Verhalten unveraendert) httpntlm liefert den
Rumpf als Zeichenkette, nicht als Buffer: httpreq setzt ihn nur bei
gesetzter Option binary auf Buffer (httpreq@1.1.1/lib/httpreq.js:391),
keiner der beiden Aufrufer setzt sie. Der Bestand rief unbesehen
.toString('utf-8') auf - das ging nur gut, weil String.toString() sein
Argument ignoriert. Die Testdoppel reichen dagegen wirklich Buffer herein.
NtlmResponse.body nennt jetzt beide Formen, die Fallunterscheidung liefert
fuer jede exakt dasselbe Ergebnis wie zuvor.
Urteil BLEIBT mit Begruendung im Code an allen 15 verbleibenden Stellen:
3x addCronJob (require-Umweg aus 07-04), 5x node-forge (EC-Zweig und
extensions: any[] sind in @types/node-forge nicht beschrieben, 2x null as
any wo die Typen die Bibliothek nachweislich falsch beschreiben), 2x
imap-Befunde oben, 2x tx: any plus 2x Gefolge (Aufgabe 1), 1x
disposition-Befund.
noExplicitAny in apps/api/src: 31 -> 15 (Ausgang 288, Schranke 45), apps/web
1 -> 0. type-check 4/4, lint 5/5 (0 error), apps/api 72/1143, apps/web
73/531, rls-access-inventory 30/30. noNonNullAssertion 56, as unknown as 33,
ts-expect-error/ts-ignore 0/0, Unterdrueckungsmarker 1. biome.json, alle
package.json und pnpm-lock.yaml unveraendert.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
This commit is contained in:
@@ -1,5 +1,18 @@
|
||||
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
||||
import * as forge from 'node-forge';
|
||||
|
||||
/**
|
||||
* Was `forge.pkcs7.messageFromPem()` bzw. `messageFromAsn1()` zurueckgeben —
|
||||
* der mitgelieferte Typ aus `@types/node-forge`, nicht ein eigener.
|
||||
*
|
||||
* Nur die signierte Form traegt `certificates`; die Lesestellen grenzen
|
||||
* deshalb mit `'certificates' in p7` ein. Das ist verhaltensgleich zum
|
||||
* bisherigen `p7.certificates ?? []`: bei einer enveloped-Nachricht fehlt
|
||||
* das Feld, und beide Schreibweisen liefern dann die leere Liste.
|
||||
*/
|
||||
type P7Message = forge.pkcs7.Captured<
|
||||
forge.pkcs7.PkcsEnvelopedData | forge.pkcs7.PkcsSignedData
|
||||
>;
|
||||
import type { UploadedFileLike } from '../auth/types/auth-user';
|
||||
|
||||
/**
|
||||
@@ -231,14 +244,16 @@ export class CertManagerService {
|
||||
.slice(0, 27)
|
||||
.toString('ascii')
|
||||
.includes('-----BEGIN');
|
||||
let p7: any;
|
||||
// siehe P7Message oben — mitgelieferter Typ, keine Behauptung.
|
||||
let p7: P7Message;
|
||||
if (isPemP7b) {
|
||||
p7 = forge.pkcs7.messageFromPem(file.buffer.toString('utf-8'));
|
||||
} else {
|
||||
const p7Asn1 = forge.asn1.fromDer(this.toForgeBuffer(file.buffer));
|
||||
p7 = forge.pkcs7.messageFromAsn1(p7Asn1);
|
||||
}
|
||||
const p7Certs: forge.pki.Certificate[] = p7.certificates ?? [];
|
||||
const p7Certs: forge.pki.Certificate[] =
|
||||
'certificates' in p7 ? p7.certificates : [];
|
||||
if (p7Certs.length === 0) {
|
||||
throw new Error('No certificate found in P7B/PKCS7');
|
||||
}
|
||||
@@ -270,6 +285,14 @@ export class CertManagerService {
|
||||
);
|
||||
|
||||
// Key type and size
|
||||
// BLEIBT als any, mit Begruendung (260921-m34, Aufgabe 3c, D-01/D-02):
|
||||
// @types/node-forge kennt nur `PublicKey = rsa.PublicKey | ed25519.Key`
|
||||
// (index.d.ts:232). Der EC-Zweig unten liest `curve` und
|
||||
// `params.curve.q.bitLength()` — Felder, die node-forge zur Laufzeit
|
||||
// liefert, die der mitgelieferte Typ aber GAR NICHT kennt. Eine
|
||||
// Umdeutung ueber zwei Stufen wuerde dieselbe Luecke verdecken und
|
||||
// zusaetzlich so aussehen, als sei sie geprueft. Ein ehrliches any mit
|
||||
// dieser Zeile ist hier das bessere Ergebnis.
|
||||
const pubKey = cert.publicKey as any;
|
||||
let keyType = 'RSA';
|
||||
let keyBits = 0;
|
||||
@@ -283,13 +306,24 @@ export class CertManagerService {
|
||||
}
|
||||
|
||||
// Subject Alternative Names
|
||||
//
|
||||
// BLEIBEN als any, mit Begruendung (260921-m34, Aufgabe 3c, D-01/D-02):
|
||||
// @types/node-forge deklariert `Certificate.extensions` als `any[]`
|
||||
// (index.d.ts:435) und sagt damit ueber den Inhalt einer Erweiterung
|
||||
// NICHTS aus. Jede Schnittstelle, die wir hier selbst fuer `altNames`
|
||||
// schrieben, waere unbelegt — der Compiler koennte sie an keiner
|
||||
// Stelle gegen etwas pruefen, sie saehe aber geprueft aus. Die drei
|
||||
// any-Stellen dieses Blocks bleiben deshalb sichtbar stehen, statt
|
||||
// gegen eine Behauptung getauscht zu werden.
|
||||
const sanExt = cert.extensions?.find((e: any) => e.name === 'subjectAltName');
|
||||
const san: string[] = ((sanExt as any)?.altNames ?? []).map((n: any) =>
|
||||
n.type === 2 ? (n.value as string) : `IP:${(n.ip ?? n.value) as string}`,
|
||||
);
|
||||
|
||||
// Signature algorithm — OID → human-readable name
|
||||
const sigOid = (cert.siginfo as any)?.algorithmOid ?? '';
|
||||
// @types/node-forge deklariert siginfo.algorithmOid als string — die
|
||||
// Zusicherung war ueberfluessig. Das ?. bleibt woertlich erhalten.
|
||||
const sigOid = cert.siginfo?.algorithmOid ?? '';
|
||||
const signatureAlgorithm = REVERSE_OIDS[sigOid] ?? sigOid;
|
||||
|
||||
// Fingerprints
|
||||
@@ -369,7 +403,9 @@ export class CertManagerService {
|
||||
.slice(0, 27)
|
||||
.toString('ascii')
|
||||
.includes('-----BEGIN');
|
||||
let p7: any;
|
||||
// Der mitgelieferte Typ traegt hier: messageFromPem/messageFromAsn1
|
||||
// liefern beide Captured<PkcsEnvelopedData | PkcsSignedData>.
|
||||
let p7: P7Message;
|
||||
if (isPemP7b) {
|
||||
// PEM-wrapped PKCS7 (e.g. -----BEGIN PKCS7-----)
|
||||
p7 = forge.pkcs7.messageFromPem(file.buffer.toString('utf-8'));
|
||||
@@ -378,7 +414,7 @@ export class CertManagerService {
|
||||
const p7Asn1 = forge.asn1.fromDer(this.toForgeBuffer(file.buffer));
|
||||
p7 = forge.pkcs7.messageFromAsn1(p7Asn1);
|
||||
}
|
||||
certs = (p7.certificates as forge.pki.Certificate[]) ?? [];
|
||||
certs = 'certificates' in p7 ? p7.certificates : [];
|
||||
if (certs.length === 0) {
|
||||
throw new Error('No certificates found in P7B/PKCS7 bundle');
|
||||
}
|
||||
@@ -505,14 +541,15 @@ export class CertManagerService {
|
||||
.slice(0, 27)
|
||||
.toString('ascii')
|
||||
.includes('-----BEGIN');
|
||||
let p7: any;
|
||||
// siehe P7Message oben — mitgelieferter Typ, keine Behauptung.
|
||||
let p7: P7Message;
|
||||
if (isPemP7b) {
|
||||
p7 = forge.pkcs7.messageFromPem(file.buffer.toString('utf-8'));
|
||||
} else {
|
||||
const p7Asn1 = forge.asn1.fromDer(this.toForgeBuffer(file.buffer));
|
||||
p7 = forge.pkcs7.messageFromAsn1(p7Asn1);
|
||||
}
|
||||
return (p7.certificates as forge.pki.Certificate[]) ?? [];
|
||||
return 'certificates' in p7 ? p7.certificates : [];
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
@@ -543,6 +580,12 @@ export class CertManagerService {
|
||||
// Open Question 1 resolution: toPkcs12Asn1(null, certs, password) works in node-forge 1.4.0
|
||||
// null as the private key produces a cert-only PKCS12 bundle (no key bag — cert bag only)
|
||||
const p12Asn1 = forge.pkcs12.toPkcs12Asn1(
|
||||
// BLEIBT (260921-m34, Aufgabe 3c): node-forge 1.4.0 nimmt hier einen
|
||||
// fehlenden Schluessel an und erzeugt ein reines
|
||||
// Zertifikatsbuendel; @types/node-forge schliesst null aus. Die
|
||||
// mitgelieferten Typen beschreiben die Bibliothek an dieser Stelle
|
||||
// also nachweislich falsch — ein erzwungener Typ waere eine
|
||||
// Behauptung ueber etwas, das nicht stimmt.
|
||||
null as any, // cert-only PFX — null key accepted by node-forge 1.4.0
|
||||
certs,
|
||||
password!,
|
||||
@@ -648,14 +691,16 @@ export class CertManagerService {
|
||||
.slice(0, 27)
|
||||
.toString('ascii')
|
||||
.includes('-----BEGIN');
|
||||
let p7: any;
|
||||
// siehe P7Message oben — mitgelieferter Typ, keine Behauptung.
|
||||
let p7: P7Message;
|
||||
if (isPemP7b) {
|
||||
p7 = forge.pkcs7.messageFromPem(file.buffer.toString('utf-8'));
|
||||
} else {
|
||||
const p7Asn1 = forge.asn1.fromDer(this.toForgeBuffer(file.buffer));
|
||||
p7 = forge.pkcs7.messageFromAsn1(p7Asn1);
|
||||
}
|
||||
const p7Certs: forge.pki.Certificate[] = p7.certificates ?? [];
|
||||
const p7Certs: forge.pki.Certificate[] =
|
||||
'certificates' in p7 ? p7.certificates : [];
|
||||
if (p7Certs.length === 0) {
|
||||
throw new Error('No certificate found in P7B/PKCS7');
|
||||
}
|
||||
@@ -698,6 +743,12 @@ export class CertManagerService {
|
||||
} else {
|
||||
// PFX — cert-only PKCS12 bundle (Open Question 1: null key works in node-forge 1.4.0)
|
||||
const p12Asn1 = forge.pkcs12.toPkcs12Asn1(
|
||||
// BLEIBT (260921-m34, Aufgabe 3c): node-forge 1.4.0 nimmt hier einen
|
||||
// fehlenden Schluessel an und erzeugt ein reines
|
||||
// Zertifikatsbuendel; @types/node-forge schliesst null aus. Die
|
||||
// mitgelieferten Typen beschreiben die Bibliothek an dieser Stelle
|
||||
// also nachweislich falsch — ein erzwungener Typ waere eine
|
||||
// Behauptung ueber etwas, das nicht stimmt.
|
||||
null as any, // cert-only PFX — null key accepted by node-forge 1.4.0
|
||||
[cert],
|
||||
password!,
|
||||
|
||||
Reference in New Issue
Block a user