Improvements

This commit is contained in:
jason5ng32 committed 2026-07-17 15:11:28 +08:00
1 parent 2b20d9d41e
commit af7f5e1d64
6 files changed
+46 -4

No files matched your search

+2 -1
View File
@@ -22,7 +22,8 @@ function domainHasWhoisText(result) {
export default async (req, res) => {
const query = req.query.q;
if (!query) {
// typeof check also rejects the array form (?q=a&q=b) express produces.
if (!query || typeof query !== 'string') {
return res.status(400).json({ error: 'No address provided' });
}
if (!isValidIP(query) && !isValidDomain(query)) {
+12 -2
View File
@@ -13,6 +13,7 @@ import logger from '../common/logger.js';
// the DSN the SDK was configured with. Returns a URL or null. Exported for
// tests.
export const parseEnvelopeDsn = (rawBody) => {
if (!Buffer.isBuffer(rawBody)) return null;
try {
const text = rawBody.toString('utf8');
const newline = text.indexOf('\n');
@@ -43,6 +44,7 @@ const USER_ITEM_TYPES = new Set(['event', 'transaction', 'replay_event', 'feedba
// parse trouble returns the body unchanged: losing an IP is acceptable,
// losing the event is not. Exported for tests.
export const injectUserIp = (rawBody, ip) => {
if (!Buffer.isBuffer(rawBody)) return rawBody;
const NL = 0x0a;
try {
const headerEnd = rawBody.indexOf(NL);
@@ -103,7 +105,15 @@ export default async (req, res) => {
return res.status(404).json({ error: 'Tunnel not configured' });
}
const dsn = parseEnvelopeDsn(req.body);
// express.raw always yields a Buffer here, but guard anyway so a
// misconfigured parser (string / array body) can't reach the byte-level
// envelope walk below.
const rawBody = req.body;
if (!Buffer.isBuffer(rawBody)) {
return res.status(400).json({ error: 'Invalid envelope' });
}
const dsn = parseEnvelopeDsn(rawBody);
if (!dsn) {
return res.status(400).json({ error: 'Invalid envelope' });
}
@@ -113,7 +123,7 @@ export default async (req, res) => {
const visitorIp = visitorIpFrom(req.headers);
const body = visitorIp ? injectUserIp(req.body, visitorIp) : req.body;
const body = visitorIp ? injectUserIp(rawBody, visitorIp) : rawBody;
try {
const projectId = dsn.pathname.replace(/^\//, '');
+1
View File
@@ -80,6 +80,7 @@ export async function rdapDomain(domain, { timeoutMs = 5000 } = {}) {
// Numeric value of an IP for prefix math. Returns null on junk — callers
// validate first, but bootstrap CIDR bases also pass through here.
const ipToBigInt = (ip) => {
if (typeof ip !== 'string') return null;
if (!ip.includes(':')) {
const parts = ip.split('.');
if (parts.length !== 4) return null;
+7
View File
@@ -115,6 +115,13 @@ watch(() => store.allHasLoaded, (newVal) => {
if (newVal) getUserInfo();
});
// The unknown-auth-hint boot resolves sign-in AFTER allHasLoaded (background
// probe in main.js), so the watcher above already ran and skipped; retry when
// the signed-in state lands. getUserInfo self-guards against double fetches.
watch(isSignedIn, (signed) => {
if (signed && store.allHasLoaded) getUserInfo();
});
watch(() => triggerUserBenefits.value, (newVal) => {
if (newVal) openUserBenefits();
});
+6 -1
View File
@@ -22,7 +22,12 @@ const loadFirebaseAuth = () => {
]).then(([{ initializeApp }, authModule]) => ({
...authModule,
auth: authModule.getAuth(initializeApp(firebaseConfig)),
}));
})).catch((error) => {
// Don't memoize a transient chunk-load failure — the next auth
// action should retry the import instead of replaying the rejection.
authModulePromise = null;
throw error;
});
return authModulePromise;
};
+18
View File
@@ -37,6 +37,18 @@ test('parseEnvelopeDsn returns null for garbage input', () => {
assert.strictEqual(parseEnvelopeDsn(Buffer.from('{"no_dsn":1}\n{}')), null);
});
test('parseEnvelopeDsn returns null for non-Buffer input', () => {
assert.strictEqual(parseEnvelopeDsn(`{"dsn":"${OUR_DSN}"}\n{}`), null);
assert.strictEqual(parseEnvelopeDsn(undefined), null);
});
test('returns 400 when the body is not a Buffer', async () => {
process.env.VITE_SENTRY_DSN_FRONTEND = OUR_DSN;
const res = makeRes();
await tunnelHandler({ method: 'POST', body: `{"dsn":"${OUR_DSN}"}\n{}` }, res);
assert.strictEqual(res.statusCode, 400);
});
test('rejects non-POST methods with 405', async () => {
const res = makeRes();
await tunnelHandler({ method: 'GET' }, res);
@@ -146,6 +158,12 @@ test('injectUserIp leaves non-event items byte-identical', () => {
assert.deepStrictEqual(JSON.parse(items[1][1]).user, { ip_address: IP });
});
test('injectUserIp returns non-Buffer bodies unchanged', () => {
const str = '{"dsn":"x"}\n{"type":"event"}\n{}';
assert.strictEqual(injectUserIp(str, IP), str);
assert.strictEqual(injectUserIp(undefined, IP), undefined);
});
test('injectUserIp returns malformed bodies unchanged', () => {
const headerOnly = Buffer.from('{"dsn":"x"}');
assert.strictEqual(injectUserIp(headerOnly, IP), headerOnly);