mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 21:15:32 +09:00
fix(api)!: correct declared bounds and hide public bot mfa (#2485)
This commit is contained in:
1 parent
3be4171256
commit
bec34ea147
15 files changed
+222
-41
No files matched your search
@@ -1,11 +1,19 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {type UserAuthenticatorType, UserAuthenticatorTypes} from '@fluxer/constants/src/UserConstants';
|
||||
import {stripBannerForUser} from '../infrastructure/AssetEntitlementUtils';
|
||||
import type {Application} from '../models/Application';
|
||||
import type {User} from '../models/User';
|
||||
import {mapUserToPartialResponse} from '../user/UserMappers';
|
||||
import type {ApplicationBotResponse, ApplicationResponse} from './OAuth2Types';
|
||||
|
||||
function getActiveAuthenticatorTypes(user: User): Array<UserAuthenticatorType> {
|
||||
return Array.from(user.authenticatorTypes ?? []).filter(
|
||||
(type): type is UserAuthenticatorType =>
|
||||
type === UserAuthenticatorTypes.TOTP || type === UserAuthenticatorTypes.WEBAUTHN,
|
||||
);
|
||||
}
|
||||
|
||||
export function mapBotUserToResponse(
|
||||
user: User,
|
||||
opts?: {
|
||||
@@ -13,6 +21,7 @@ export function mapBotUserToResponse(
|
||||
},
|
||||
): ApplicationBotResponse {
|
||||
const partial = mapUserToPartialResponse(user);
|
||||
const authenticatorTypes = getActiveAuthenticatorTypes(user);
|
||||
return {
|
||||
id: partial.id,
|
||||
username: partial.username,
|
||||
@@ -21,8 +30,8 @@ export function mapBotUserToResponse(
|
||||
banner: stripBannerForUser(user),
|
||||
bio: user.bio ?? null,
|
||||
token: opts?.token,
|
||||
mfa_enabled: (user.authenticatorTypes?.size ?? 0) > 0,
|
||||
authenticator_types: user.authenticatorTypes ? Array.from(user.authenticatorTypes) : [],
|
||||
mfa_enabled: authenticatorTypes.length > 0,
|
||||
authenticator_types: authenticatorTypes,
|
||||
flags: partial.flags,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -47,7 +47,12 @@ import {verifyPassword} from '../utils/PasswordUtils';
|
||||
import type {ApplicationService} from './ApplicationService';
|
||||
import {ApplicationNotOwnedError} from './ApplicationService';
|
||||
import type {BotAuthService} from './BotAuthService';
|
||||
import {mapApplicationToResponse, mapBotTokenResetResponse, mapBotUserToResponse} from './OAuth2Mappers';
|
||||
import {
|
||||
mapApplicationToResponse,
|
||||
mapBotProfileToResponse,
|
||||
mapBotTokenResetResponse,
|
||||
mapBotUserToResponse,
|
||||
} from './OAuth2Mappers';
|
||||
import {filterOAuth2Scopes} from './OAuth2ScopeUtils';
|
||||
import {ACCESS_TOKEN_TTL_SECONDS, type OAuth2Service} from './OAuth2Service';
|
||||
import type {IApplicationRepository} from './repositories/IApplicationRepository';
|
||||
@@ -390,7 +395,7 @@ export class OAuth2RequestService {
|
||||
redirect_uris: Array.from(application.oauth2RedirectUris),
|
||||
scopes,
|
||||
bot_public: application.botIsPublic || isOwner,
|
||||
bot: botUser ? mapBotUserToResponse(botUser) : null,
|
||||
bot: botUser ? mapBotProfileToResponse(botUser) : null,
|
||||
current_user: requestingUser ? mapUserToPartialResponse(requestingUser) : null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {UserAuthenticatorTypes} from '@fluxer/constants/src/UserConstants';
|
||||
import type {ApplicationPublicResponse} from '@fluxer/schema/src/domains/oauth/OAuthSchemas';
|
||||
import {beforeEach, describe, expect, test} from 'vitest';
|
||||
import {createTestAccount} from '../../auth/tests/AuthTestUtils';
|
||||
import {createTestAccount, createTotpSecret, generateTotpCode} from '../../auth/tests/AuthTestUtils';
|
||||
import {createUserID} from '../../BrandedTypes';
|
||||
import {type ApiTestHarness, createApiTestHarness} from '../../test/ApiTestHarness';
|
||||
import {HTTP_STATUS} from '../../test/TestConstants';
|
||||
import {createBuilder, createBuilderWithoutAuth} from '../../test/TestRequestBuilder';
|
||||
import {UserRepository} from '../../user/repositories/UserRepository';
|
||||
import {createOAuth2Application, createUniqueApplicationName, getOAuth2Application} from './OAuth2TestUtils';
|
||||
|
||||
describe('OAuth2 Application Get', () => {
|
||||
@@ -52,4 +56,29 @@ describe('OAuth2 Application Get', () => {
|
||||
test('requires authentication', async () => {
|
||||
await createBuilderWithoutAuth(harness).get('/oauth2/applications/123').expect(HTTP_STATUS.UNAUTHORIZED).execute();
|
||||
});
|
||||
test('public bot representation omits the owner MFA mirror the owner view keeps', async () => {
|
||||
const account = await createTestAccount(harness);
|
||||
const createResult = await createOAuth2Application(harness, account.token, {
|
||||
name: createUniqueApplicationName(),
|
||||
});
|
||||
const secret = createTotpSecret();
|
||||
await createBuilder(harness, account.token)
|
||||
.post('/users/@me/mfa/totp/enable')
|
||||
.body({secret, code: generateTotpCode(secret), password: account.password})
|
||||
.expect(HTTP_STATUS.OK)
|
||||
.execute();
|
||||
const botUser = await new UserRepository().findUnique(createUserID(BigInt(createResult.botUserId)));
|
||||
expect(botUser?.authenticatorTypes.has(UserAuthenticatorTypes.TOTP)).toBe(true);
|
||||
const ownerView = await getOAuth2Application(harness, account.token, createResult.application.id);
|
||||
expect(ownerView.bot).toBeDefined();
|
||||
expect(ownerView.bot!.mfa_enabled).toBe(true);
|
||||
expect(ownerView.bot!.authenticator_types).toEqual([UserAuthenticatorTypes.TOTP]);
|
||||
const publicView = await createBuilderWithoutAuth<ApplicationPublicResponse>(harness)
|
||||
.get(`/oauth2/applications/${createResult.application.id}/public`)
|
||||
.expect(HTTP_STATUS.OK)
|
||||
.execute();
|
||||
expect(publicView.bot).toBeTruthy();
|
||||
expect('mfa_enabled' in publicView.bot!).toBe(false);
|
||||
expect('authenticator_types' in publicView.bot!).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -26113,7 +26113,7 @@
|
||||
"redirect_uris": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"maxItems": 20,
|
||||
"maxItems": 10,
|
||||
"description": "The registered redirect URIs for OAuth2"
|
||||
}
|
||||
},
|
||||
@@ -27745,7 +27745,6 @@
|
||||
"permission_overwrites": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ChannelOverwriteResponse"},
|
||||
"maxItems": 500,
|
||||
"description": "The permission overwrites for this channel"
|
||||
},
|
||||
"recipients": {
|
||||
@@ -28608,7 +28607,7 @@
|
||||
"properties": {"username": {"type": "string", "description": "The username of the recipient"}},
|
||||
"required": ["username"]
|
||||
},
|
||||
"maxItems": 49,
|
||||
"maxItems": 50,
|
||||
"description": "The recipients of the DM channel"
|
||||
}
|
||||
},
|
||||
@@ -30246,7 +30245,7 @@
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"maxItems": 100,
|
||||
"maxItems": 10,
|
||||
"description": "Autocomplete/suggestion tags for the sticker"
|
||||
},
|
||||
"animated": {"type": "boolean", "description": "Whether this sticker is animated"},
|
||||
@@ -31585,8 +31584,8 @@
|
||||
"roles": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/SnowflakeType"},
|
||||
"maxItems": 100,
|
||||
"description": "Array of role IDs to assign to the member (max 100)"
|
||||
"maxItems": 250,
|
||||
"description": "Array of role IDs to assign to the member (max 250)"
|
||||
},
|
||||
"avatar": {
|
||||
"anyOf": [{"$ref": "#/components/schemas/Base64ImageType"}, {"type": "null"}],
|
||||
@@ -31738,7 +31737,7 @@
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"maxItems": 100,
|
||||
"maxItems": 10,
|
||||
"description": "Autocomplete/suggestion tags for the sticker"
|
||||
},
|
||||
"animated": {"type": "boolean", "description": "Whether this sticker is animated"},
|
||||
@@ -32164,7 +32163,7 @@
|
||||
"redirect_uris": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"maxItems": 20,
|
||||
"maxItems": 10,
|
||||
"description": "The registered redirect URIs for OAuth2"
|
||||
},
|
||||
"bot_public": {"type": "boolean", "description": "Whether the bot can be invited by anyone"},
|
||||
@@ -32322,7 +32321,7 @@
|
||||
"redirect_uris": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"maxItems": 20,
|
||||
"maxItems": 10,
|
||||
"description": "The registered redirect URIs for OAuth2"
|
||||
},
|
||||
"scopes": {
|
||||
@@ -32357,13 +32356,6 @@
|
||||
"description": "The bio or description of the bot"
|
||||
},
|
||||
"token": {"type": "string", "description": "The bot token for authentication"},
|
||||
"mfa_enabled": {"type": "boolean", "description": "Whether the bot has MFA enabled"},
|
||||
"authenticator_types": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/AuthenticatorType"},
|
||||
"maxItems": 10,
|
||||
"description": "The types of authenticators enabled"
|
||||
},
|
||||
"flags": {"$ref": "#/components/schemas/BotFlags"}
|
||||
},
|
||||
"required": ["id", "username", "discriminator", "bio", "flags"],
|
||||
|
||||
Reference in new issue
Block a user