Skip to content

Commit bb5747c

Browse files
committed
feat(bitbucket): enhance Bitbucket integration with improved validation and error handling
1 parent adf5a16 commit bb5747c

File tree

7 files changed

+263
-151
lines changed

7 files changed

+263
-151
lines changed

web-server/pages/api/integrations/bitbucket/scopes.ts

Lines changed: 63 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,81 @@ import { handleRequest } from '@/api-helpers/axios';
33
import * as yup from 'yup';
44

55
const payloadSchema = yup.object({
6-
username: yup.string().required('Username is required'),
7-
appPassword: yup.string().required('App password is required'),
8-
customDomain: yup.string().url('Custom domain must be a valid URL'),
6+
username: yup
7+
.string()
8+
.required('Username is required')
9+
.trim()
10+
.min(1, 'Username cannot be empty')
11+
.max(100, 'Username too long'),
12+
appPassword: yup
13+
.string()
14+
.required('App password is required')
15+
.min(1, 'App password cannot be empty')
16+
.max(500, 'App password too long')
917
});
1018

1119
const endpoint = new Endpoint(nullSchema);
1220

1321
endpoint.handle.POST(payloadSchema, async (req, res) => {
1422
try {
15-
const { username, appPassword, customDomain } = req.payload;
16-
const baseUrl = customDomain || 'https://api.bitbucket.org/2.0';
17-
const url = `${baseUrl}/user`;
23+
const { username, appPassword } = req.payload;
24+
const sanitizedUsername = username.replace(/[^\w.-]/g, '');
25+
26+
if (sanitizedUsername !== username) {
27+
return res.status(400).json({
28+
message: 'Invalid username format. Only alphanumeric characters, dots, and hyphens are allowed.'
29+
});
30+
}
31+
32+
const url = 'https://api.bitbucket.org/2.0/user';
33+
1834
const response = await handleRequest(url, {
1935
method: 'GET',
20-
auth: {
21-
username,
22-
password: appPassword,
36+
headers: {
37+
Authorization: `Basic ${Buffer.from(`${sanitizedUsername}:${appPassword}`).toString('base64')}`,
38+
'User-Agent': 'MiddlewareApp/1.0'
2339
},
24-
},true);
40+
timeout: 10000
41+
}, true);
2542

26-
res.status(200).json(response);
43+
if (!response.headers) {
44+
return res.status(400).json({
45+
message: 'Unable to retrieve permission information from BitBucket'
46+
});
47+
}
48+
49+
res.status(200).json({
50+
...response,
51+
headers: response.headers
52+
});
2753
} catch (error: any) {
28-
console.error('Error fetching Bitbucket user:', error.message);
29-
res.status(error.response?.status || 500).json({
30-
message: error.response?.data?.error?.message || 'Internal Server Error',
54+
console.error('Error fetching Bitbucket user:', {
55+
message: error.message,
56+
status: error.response?.status,
57+
hasCredentials: !!(req.payload?.username && req.payload?.appPassword)
3158
});
59+
60+
const status = error.response?.status || 500;
61+
let message = 'Internal Server Error';
62+
63+
switch (status) {
64+
case 401:
65+
message = 'Invalid BitBucket credentials';
66+
break;
67+
case 403:
68+
message = 'Access forbidden. Check your App Password permissions';
69+
break;
70+
case 404:
71+
message = 'BitBucket user not found';
72+
break;
73+
case 429:
74+
message = 'Rate limit exceeded. Please try again later';
75+
break;
76+
default:
77+
message = error.response?.data?.error?.message || message;
78+
}
79+
80+
res.status(status).json({ message });
3281
}
3382
});
3483

web-server/src/api-helpers/axios.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,10 @@ export const handleRequest = <T = any, B extends boolean = false>(
9191
internal({
9292
url,
9393
...params,
94-
headers: { 'Content-Type': 'application/json' }
94+
headers: {
95+
'Content-Type': 'application/json',
96+
...params.headers
97+
}
9598
})
9699
.then((r: any) => handleThen(r, includeHeaders))
97100
.catch(handleCatch);

web-server/src/content/Dashboards/BitbucketIntegrationCard.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ import {
147147
};
148148

149149
const IntegrationActionsButton: FC<{
150-
onClick: AnyFunction;
150+
onClick: () => void | Promise<void>;
151151
label: ReactNode;
152152
bgOpacity?: number;
153153
startIcon?: ReactNode;
@@ -245,4 +245,3 @@ import {
245245
</svg>
246246
);
247247
};
248-

0 commit comments

Comments
 (0)