fix: various fixes to sentry-reported errors and more

This commit is contained in:
Hampus Kraft
2026-02-18 15:38:51 +00:00
parent 302c0d2a0c
commit 0517a966a3
357 changed files with 25420 additions and 16281 deletions

View File

@@ -17,8 +17,9 @@
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import type {BlueskyCallbackResult} from '@fluxer/api/src/bluesky/IBlueskyOAuthService';
import type {BlueskyAuthorizeResult, BlueskyCallbackResult} from '@fluxer/api/src/bluesky/IBlueskyOAuthService';
import {Config} from '@fluxer/api/src/Config';
import {BlueskyOAuthAuthorizationFailedError} from '@fluxer/api/src/connection/errors/BlueskyOAuthAuthorizationFailedError';
import {BlueskyOAuthCallbackFailedError} from '@fluxer/api/src/connection/errors/BlueskyOAuthCallbackFailedError';
import {BlueskyOAuthNotEnabledError} from '@fluxer/api/src/connection/errors/BlueskyOAuthNotEnabledError';
import {BlueskyOAuthStateInvalidError} from '@fluxer/api/src/connection/errors/BlueskyOAuthStateInvalidError';
@@ -36,6 +37,15 @@ import {
BlueskyAuthorizeResponse,
} from '@fluxer/schema/src/domains/connection/BlueskyOAuthSchemas';
const BLUESKY_PROFILE_URL_RE = /^https?:\/\/bsky\.app\/profile\//i;
function normalizeBlueskyHandle(input: string): string {
let handle = input.trim();
handle = handle.replace(BLUESKY_PROFILE_URL_RE, '');
handle = handle.replace(/^@/, '');
return handle;
}
export function BlueskyOAuthController(app: HonoApp) {
app.get('/connections/bluesky/client-metadata.json', async (ctx) => {
const service = ctx.get('blueskyOAuthService');
@@ -73,8 +83,9 @@ export function BlueskyOAuthController(app: HonoApp) {
if (!service) {
throw new BlueskyOAuthNotEnabledError();
}
const {handle} = ctx.req.valid('json');
const {handle: rawHandle} = ctx.req.valid('json');
const userId = ctx.get('user').id;
const handle = normalizeBlueskyHandle(rawHandle);
const connectionService = ctx.get('connectionService');
const connections = await connectionService.getConnectionsForUser(userId);
@@ -86,7 +97,13 @@ export function BlueskyOAuthController(app: HonoApp) {
throw new ConnectionAlreadyExistsError();
}
const result = await service.authorize(handle, userId);
let result: BlueskyAuthorizeResult;
try {
result = await service.authorize(handle, userId);
} catch (error) {
Logger.error({error, handle}, 'Bluesky OAuth authorize failed');
throw new BlueskyOAuthAuthorizationFailedError();
}
return ctx.json({authorize_url: result.authorizeUrl});
},
);

View File

@@ -65,7 +65,7 @@ export class BlueskyOAuthService implements IBlueskyOAuthService {
clientMetadata: {
client_id: `${baseUrl}/connections/bluesky/client-metadata.json`,
client_name: config.client_name,
client_uri: config.client_uri || baseUrl,
client_uri: baseUrl,
logo_uri: config.logo_uri || undefined,
tos_uri: config.tos_uri || undefined,
policy_uri: config.policy_uri || undefined,

View File

@@ -20,6 +20,7 @@
import {createTestAccount} from '@fluxer/api/src/auth/tests/AuthTestUtils';
import {createUserID} from '@fluxer/api/src/BrandedTypes';
import {
createBlueskyConnectionViaOAuth,
createBlueskyDid,
createBlueskyHandle,
listConnections,
@@ -122,6 +123,69 @@ describe('Bluesky OAuth', () => {
const callArgs = harness.mockBlueskyOAuthService.authorizeSpy.mock.calls[0];
expect(callArgs[0]).toBe(handle);
});
it('normalises a bsky.app profile URL to a handle', async () => {
const account = await createTestAccount(harness);
await createBuilder(harness, account.token)
.post('/users/@me/connections/bluesky/authorize')
.body({handle: 'https://bsky.app/profile/alice.bsky.social'})
.expect(200)
.execute();
expect(harness.mockBlueskyOAuthService.authorizeSpy).toHaveBeenCalledTimes(1);
expect(harness.mockBlueskyOAuthService.authorizeSpy.mock.calls[0][0]).toBe('alice.bsky.social');
});
it('normalises an http bsky.app profile URL to a handle', async () => {
const account = await createTestAccount(harness);
await createBuilder(harness, account.token)
.post('/users/@me/connections/bluesky/authorize')
.body({handle: 'http://bsky.app/profile/someone.bsky.social'})
.expect(200)
.execute();
expect(harness.mockBlueskyOAuthService.authorizeSpy.mock.calls[0][0]).toBe('someone.bsky.social');
});
it('strips leading @ from handle', async () => {
const account = await createTestAccount(harness);
await createBuilder(harness, account.token)
.post('/users/@me/connections/bluesky/authorize')
.body({handle: '@alice.bsky.social'})
.expect(200)
.execute();
expect(harness.mockBlueskyOAuthService.authorizeSpy.mock.calls[0][0]).toBe('alice.bsky.social');
});
it('detects duplicate after normalising profile URL', async () => {
const account = await createTestAccount(harness);
const handle = createBlueskyHandle('testuser');
const did = createBlueskyDid('testuser');
const userId = createUserID(BigInt(account.userId));
await createBlueskyConnectionViaOAuth(harness, account.token, handle, did, userId);
await createBuilder(harness, account.token)
.post('/users/@me/connections/bluesky/authorize')
.body({handle: `https://bsky.app/profile/${handle}`})
.expect(HTTP_STATUS.CONFLICT, APIErrorCodes.CONNECTION_ALREADY_EXISTS)
.execute();
});
it('returns BLUESKY_OAUTH_AUTHORIZATION_FAILED when authorize throws', async () => {
const account = await createTestAccount(harness);
harness.mockBlueskyOAuthService.configure({shouldFailAuthorize: true});
await createBuilder(harness, account.token)
.post('/users/@me/connections/bluesky/authorize')
.body({handle: 'invalid-handle'})
.expect(HTTP_STATUS.BAD_REQUEST, APIErrorCodes.BLUESKY_OAUTH_AUTHORIZATION_FAILED)
.execute();
});
});
describe('GET /connections/bluesky/callback', () => {