fix: stop logging password reset secrets
This commit is contained in:
@@ -23,6 +23,8 @@ Deploy on one VPS with Docker Compose.
|
|||||||
- Keep secrets in server-side environment files or a secret manager.
|
- Keep secrets in server-side environment files or a secret manager.
|
||||||
- Back up PostgreSQL and object storage separately.
|
- Back up PostgreSQL and object storage separately.
|
||||||
- Prefer Telegram long polling to avoid an extra public webhook surface for the bot.
|
- Prefer Telegram long polling to avoid an extra public webhook surface for the bot.
|
||||||
|
- In non-production environments, set `EMAIL_PROVIDER=example` only when you explicitly want the built-in debug transport. It logs redacted email previews and must never emit live password-reset tokens.
|
||||||
|
- Do not rely on implicit email fallbacks. Unsupported providers now fail fast at startup so misconfigured deployments do not silently drop password-reset or billing mail.
|
||||||
|
|
||||||
## Upgrade strategy
|
## Upgrade strategy
|
||||||
- Build new images.
|
- Build new images.
|
||||||
|
|||||||
@@ -16,7 +16,9 @@
|
|||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc -p tsconfig.json",
|
"build": "tsc -p tsconfig.json",
|
||||||
"check": "tsc -p tsconfig.json --noEmit"
|
"check": "tsc -p tsconfig.json --noEmit",
|
||||||
|
"pretest": "pnpm build",
|
||||||
|
"test": "node --test dist/**/*.test.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nproxy/domain": "workspace:*"
|
"@nproxy/domain": "workspace:*"
|
||||||
|
|||||||
43
packages/providers/src/email.test.ts
Normal file
43
packages/providers/src/email.test.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import test from "node:test";
|
||||||
|
import { createEmailTransport } from "./email.js";
|
||||||
|
|
||||||
|
test("example email transport redacts password-reset tokens before logging", async () => {
|
||||||
|
const transport = createEmailTransport({
|
||||||
|
provider: "example",
|
||||||
|
from: "noreply@nproxy.test",
|
||||||
|
apiKey: "unused",
|
||||||
|
});
|
||||||
|
const logMessages: string[] = [];
|
||||||
|
const originalConsoleLog = console.log;
|
||||||
|
console.log = (message?: unknown) => {
|
||||||
|
logMessages.push(String(message ?? ""));
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await transport.send({
|
||||||
|
to: "user@example.com",
|
||||||
|
subject: "Reset your password",
|
||||||
|
text: "Reset link: https://app.nproxy.test/reset-password?token=secret-token-123",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
console.log = originalConsoleLog;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.equal(logMessages.length, 1);
|
||||||
|
assert.match(logMessages[0] ?? "", /"mode":"debug_redacted"/);
|
||||||
|
assert.match(logMessages[0] ?? "", /token=\[REDACTED\]/);
|
||||||
|
assert.doesNotMatch(logMessages[0] ?? "", /secret-token-123/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("unsupported email providers fail closed", () => {
|
||||||
|
assert.throws(
|
||||||
|
() =>
|
||||||
|
createEmailTransport({
|
||||||
|
provider: "smtp",
|
||||||
|
from: "noreply@nproxy.test",
|
||||||
|
apiKey: "unused",
|
||||||
|
}),
|
||||||
|
/Unsupported email provider: smtp/,
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -20,29 +20,20 @@ export function createEmailTransport(config: {
|
|||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
service: "email",
|
service: "email",
|
||||||
provider: config.provider,
|
provider: config.provider,
|
||||||
|
mode: "debug_redacted",
|
||||||
from: config.from,
|
from: config.from,
|
||||||
to: input.to,
|
to: input.to,
|
||||||
subject: input.subject,
|
subject: input.subject,
|
||||||
text: input.text,
|
textPreview: redactEmailText(input.text),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
throw new Error(`Unsupported email provider: ${config.provider}`);
|
||||||
async send(input) {
|
}
|
||||||
console.log(
|
|
||||||
JSON.stringify({
|
function redactEmailText(text: string): string {
|
||||||
service: "email",
|
return text.replace(/([?&]token=)[^&\s]+/gi, "$1[REDACTED]");
|
||||||
provider: config.provider,
|
|
||||||
mode: "noop_fallback",
|
|
||||||
from: config.from,
|
|
||||||
to: input.to,
|
|
||||||
subject: input.subject,
|
|
||||||
text: input.text,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user