chore(repo): remove dead code and useless tests (#3375)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-10 21:43:29 +02:00
1 parent 7e177a1c90
commit 1cd62eb661
1391 files changed
+783 -120415

No files matched your search

@@ -3,7 +3,7 @@
import {createTestAccount} from '@app/api/auth/tests/AuthTestUtils';
import {createOAuth2Application, createUniqueApplicationName} from '@app/api/oauth/tests/OAuth2TestUtils';
import {type ApiTestHarness, createApiTestHarness} from '@app/api/test/ApiTestHarness';
import {CAPTCHA_TEST_HEADER, issueSolvedCaptchaToken, useCheapCaptcha} from '@app/api/test/CaptchaTestUtils';
import {CAPTCHA_TEST_HEADER, useCheapCaptcha} from '@app/api/test/CaptchaTestUtils';
import {HTTP_STATUS} from '@app/api/test/TestConstants';
import {createBuilder} from '@app/api/test/TestRequestBuilder';
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
@@ -67,18 +67,6 @@ describe('OAuth2 Application Create', () => {
expect(botUsername?.toLowerCase()).not.toContain('fluxer');
expect(botUsername?.toLowerCase()).not.toContain('systemmessage');
});
test('creates application without optional fields', async () => {
const account = await createTestAccount(harness);
const appName = createUniqueApplicationName();
const result = await createOAuth2Application(harness, account.token, {
name: appName,
});
expect(result.application.id).toBeTruthy();
expect(result.application.name).toBe(appName);
expect(result.application.redirect_uris).toEqual([]);
expect(result.application.bot).toBeDefined();
expect(result.application.bot?.id).toBeTruthy();
});
test('requires captcha when creating a bot application', async () => {
const account = await createTestAccount(harness);
await useCheapCaptcha();
@@ -89,25 +77,6 @@ describe('OAuth2 Application Create', () => {
.expect(HTTP_STATUS.BAD_REQUEST, APIErrorCodes.CAPTCHA_REQUIRED)
.execute();
});
test('creates a bot application with a valid captcha token', async () => {
const account = await createTestAccount(harness);
await useCheapCaptcha();
await createBuilder(harness, account.token)
.post('/oauth2/applications')
.header(CAPTCHA_TEST_HEADER, 'true')
.header('x-captcha-token', await issueSolvedCaptchaToken(harness))
.body({name: createUniqueApplicationName()})
.expect(HTTP_STATUS.OK)
.execute();
});
test('rejects missing name', async () => {
const account = await createTestAccount(harness);
await createBuilder(harness, account.token)
.post('/oauth2/applications')
.body({})
.expect(HTTP_STATUS.BAD_REQUEST)
.execute();
});
test('rejects non-localhost http redirect URI hostnames', async () => {
const account = await createTestAccount(harness);
const json = await createBuilder<ValidationErrorResponse>(harness, account.token)
@@ -120,31 +89,4 @@ describe('OAuth2 Application Create', () => {
.execute();
expect(json.errors?.some((error) => error.path === 'redirect_uris.0')).toBe(true);
});
test('accepts https redirect URIs', async () => {
const account = await createTestAccount(harness);
const appName = createUniqueApplicationName();
const result = await createOAuth2Application(harness, account.token, {
name: appName,
redirect_uris: ['https://example.com/callback'],
});
expect(result.application.redirect_uris).toEqual(['https://example.com/callback']);
});
test('accepts localhost redirect URIs with http', async () => {
const account = await createTestAccount(harness);
const appName = createUniqueApplicationName();
const result = await createOAuth2Application(harness, account.token, {
name: appName,
redirect_uris: ['http://localhost:3000/callback'],
});
expect(result.application.redirect_uris).toEqual(['http://localhost:3000/callback']);
});
test('accepts IP address redirect URIs with http', async () => {
const account = await createTestAccount(harness);
const redirectURIs = ['http://192.168.1.42:3000/callback', 'http://[2001:db8::1]:3000/callback'];
const result = await createOAuth2Application(harness, account.token, {
name: createUniqueApplicationName(),
redirect_uris: redirectURIs,
});
expect(result.application.redirect_uris).toEqual(redirectURIs);
});
});
@@ -114,14 +114,6 @@ describe('OAuth2 Application Delete', () => {
.execute();
expect(replacementAuthorCount.count).toBe(1);
});
test('returns 404 for non-existent application', async () => {
const account = await createTestAccount(harness);
await createBuilder(harness, account.token)
.delete('/oauth2/applications/999999999999999999')
.body({password: account.password})
.expect(HTTP_STATUS.NOT_FOUND)
.execute();
});
test('enforces access control', async () => {
const owner = await createTestAccount(harness);
const otherUser = await createTestAccount(harness);
@@ -160,23 +152,4 @@ describe('OAuth2 Application Delete', () => {
.expect(HTTP_STATUS.BAD_REQUEST, 'INVALID_FORM_BODY')
.execute();
});
test('is idempotent', async () => {
const account = await createTestAccount(harness);
const createResult = await createOAuth2Application(harness, account.token, {
name: createUniqueApplicationName(),
});
await deleteOAuth2Application(harness, account.token, createResult.application.id, account.password);
await createBuilder(harness, account.token)
.delete(`/oauth2/applications/${createResult.application.id}`)
.body({password: account.password})
.expect(HTTP_STATUS.NOT_FOUND)
.execute();
});
test('requires authentication', async () => {
await createBuilderWithoutAuth(harness)
.delete('/oauth2/applications/123')
.body({password: 'test'})
.expect(HTTP_STATUS.UNAUTHORIZED)
.execute();
});
});
@@ -39,13 +39,6 @@ describe('OAuth2 Application Get', () => {
expect(application.bot?.token).toBeUndefined();
expect(application.client_secret).toBeUndefined();
});
test('returns 404 for non-existent application', async () => {
const account = await createTestAccount(harness);
await createBuilder(harness, account.token)
.get('/oauth2/applications/999999999999999999')
.expect(HTTP_STATUS.NOT_FOUND)
.execute();
});
test('enforces access control - user cannot access another users application', async () => {
const owner = await createTestAccount(harness);
const otherUser = await createTestAccount(harness);
@@ -57,9 +50,6 @@ describe('OAuth2 Application Get', () => {
.expect(HTTP_STATUS.FORBIDDEN)
.execute();
});
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, {
@@ -7,8 +7,6 @@ import {
listOAuth2Applications,
} from '@app/api/oauth/tests/OAuth2TestUtils';
import {type ApiTestHarness, createApiTestHarness} from '@app/api/test/ApiTestHarness';
import {HTTP_STATUS} from '@app/api/test/TestConstants';
import {createBuilder, createBuilderWithoutAuth} from '@app/api/test/TestRequestBuilder';
import {beforeEach, describe, expect, test} from 'vitest';
describe('OAuth2 Application List', () => {
@@ -16,11 +14,6 @@ describe('OAuth2 Application List', () => {
beforeEach(async () => {
harness = await createApiTestHarness();
});
test('returns empty list when no applications exist', async () => {
const account = await createTestAccount(harness);
const applications = await listOAuth2Applications(harness, account.token);
expect(applications).toEqual([]);
});
test('returns list response shape', async () => {
const account = await createTestAccount(harness);
const appName = createUniqueApplicationName();
@@ -56,22 +49,4 @@ describe('OAuth2 Application List', () => {
expect(owner2Apps.length).toBe(1);
expect(owner2Apps[0]?.id).toBe(app2.application.id);
});
test('supports alternative endpoint /users/@me/applications', async () => {
const account = await createTestAccount(harness);
await createOAuth2Application(harness, account.token, {
name: createUniqueApplicationName(),
});
const applications = await createBuilder<
Array<{
id: string;
}>
>(harness, account.token)
.get('/users/@me/applications')
.expect(HTTP_STATUS.OK)
.execute();
expect(applications.length).toBeGreaterThan(0);
});
test('requires authentication', async () => {
await createBuilderWithoutAuth(harness).get('/oauth2/applications/@me').expect(HTTP_STATUS.UNAUTHORIZED).execute();
});
});
@@ -1,14 +1,11 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {createTestAccount} from '@app/api/auth/tests/AuthTestUtils';
import {
createOAuth2Application,
createUniqueApplicationName,
updateOAuth2Application,
} from '@app/api/oauth/tests/OAuth2TestUtils';
import {createOAuth2Application, createUniqueApplicationName} from '@app/api/oauth/tests/OAuth2TestUtils';
import {type ApiTestHarness, createApiTestHarness} from '@app/api/test/ApiTestHarness';
import {HTTP_STATUS} from '@app/api/test/TestConstants';
import {createBuilder, createBuilderWithoutAuth} from '@app/api/test/TestRequestBuilder';
import {createBuilder} from '@app/api/test/TestRequestBuilder';
import type {ApplicationResponse} from '@fluxer/schema/src/domains/oauth/OAuthSchemas';
import {beforeEach, describe, expect, test} from 'vitest';
describe('OAuth2 Application Update', () => {
@@ -16,22 +13,6 @@ describe('OAuth2 Application Update', () => {
beforeEach(async () => {
harness = await createApiTestHarness();
});
test('updates application name', async () => {
const account = await createTestAccount(harness);
const originalName = createUniqueApplicationName();
const redirectURIs = ['https://example.com/callback'];
const createResult = await createOAuth2Application(harness, account.token, {
name: originalName,
redirect_uris: redirectURIs,
});
const newName = createUniqueApplicationName();
const updated = await updateOAuth2Application(harness, account.token, createResult.application.id, {
name: newName,
});
expect(updated.name).toBe(newName);
expect(updated.id).toBe(createResult.application.id);
expect(updated.redirect_uris).toEqual(redirectURIs);
});
test('supports partial updates', async () => {
const account = await createTestAccount(harness);
const originalName = createUniqueApplicationName();
@@ -41,84 +22,14 @@ describe('OAuth2 Application Update', () => {
redirect_uris: originalURIs,
});
const newName = createUniqueApplicationName();
const updated = await updateOAuth2Application(harness, account.token, createResult.application.id, {
name: newName,
});
const updated = await createBuilder<ApplicationResponse>(harness, account.token)
.patch(`/oauth2/applications/${createResult.application.id}`)
.body({name: newName})
.expect(HTTP_STATUS.OK)
.execute();
expect(updated.name).toBe(newName);
expect(updated.redirect_uris).toEqual(originalURIs);
});
test('updates redirect URIs', async () => {
const account = await createTestAccount(harness);
const createResult = await createOAuth2Application(harness, account.token, {
name: createUniqueApplicationName(),
redirect_uris: ['https://example.com/callback'],
});
const newURIs = ['https://example.com/new1', 'https://example.com/new2'];
const updated = await updateOAuth2Application(harness, account.token, createResult.application.id, {
redirect_uris: newURIs,
});
expect(updated.redirect_uris).toEqual(newURIs);
});
test('updates redirect URIs to IP addresses with http', async () => {
const account = await createTestAccount(harness);
const createResult = await createOAuth2Application(harness, account.token, {
name: createUniqueApplicationName(),
redirect_uris: ['https://example.com/callback'],
});
const newURIs = ['http://192.168.1.42:3000/callback', 'http://[2001:db8::1]:3000/callback'];
const updated = await updateOAuth2Application(harness, account.token, createResult.application.id, {
redirect_uris: newURIs,
});
expect(updated.redirect_uris).toEqual(newURIs);
});
test('updates bot_public flag', async () => {
const account = await createTestAccount(harness);
const createResult = await createOAuth2Application(harness, account.token, {
name: createUniqueApplicationName(),
bot_public: false,
});
const updated = await updateOAuth2Application(harness, account.token, createResult.application.id, {
bot_public: true,
});
expect(updated.bot_public).toBe(true);
});
test('updates multiple fields', async () => {
const account = await createTestAccount(harness);
const createResult = await createOAuth2Application(harness, account.token, {
name: createUniqueApplicationName(),
redirect_uris: ['https://example.com/callback'],
bot_public: false,
});
const newName = createUniqueApplicationName();
const newURIs = ['https://example.com/updated'];
const updated = await updateOAuth2Application(harness, account.token, createResult.application.id, {
name: newName,
redirect_uris: newURIs,
bot_public: true,
});
expect(updated.name).toBe(newName);
expect(updated.redirect_uris).toEqual(newURIs);
expect(updated.bot_public).toBe(true);
});
test('keeps bot user after update', async () => {
const account = await createTestAccount(harness);
const createResult = await createOAuth2Application(harness, account.token, {
name: createUniqueApplicationName(),
});
const updated = await updateOAuth2Application(harness, account.token, createResult.application.id, {
name: createUniqueApplicationName(),
});
expect(updated.bot).toBeDefined();
expect(updated.bot?.id).toBe(createResult.application.bot?.id);
});
test('returns 404 for non-existent application', async () => {
const account = await createTestAccount(harness);
await createBuilder(harness, account.token)
.patch('/oauth2/applications/999999999999999999')
.body({name: createUniqueApplicationName()})
.expect(HTTP_STATUS.NOT_FOUND)
.execute();
});
test('enforces access control', async () => {
const owner = await createTestAccount(harness);
const otherUser = await createTestAccount(harness);
@@ -131,11 +42,4 @@ describe('OAuth2 Application Update', () => {
.expect(HTTP_STATUS.FORBIDDEN)
.execute();
});
test('requires authentication', async () => {
await createBuilderWithoutAuth(harness)
.patch('/oauth2/applications/123')
.body({name: createUniqueApplicationName()})
.expect(HTTP_STATUS.UNAUTHORIZED)
.execute();
});
});
@@ -150,11 +150,4 @@ describe('OAuth2 authorizations deauthorize', () => {
const afterAuthz = await listOAuth2Authorizations(harness, endUser.token);
expect(afterAuthz).toHaveLength(0);
});
it('verifies that deauthorizing a non-existent application returns an appropriate error', async () => {
const user = await createTestAccount(harness);
await createBuilder(harness, user.token)
.delete('/oauth2/@me/authorizations/123456789012345678')
.expect(HTTP_STATUS.NOT_FOUND, 'UNKNOWN_APPLICATION')
.execute();
});
});
@@ -1,120 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {createTestAccount} from '@app/api/auth/tests/AuthTestUtils';
import {
authorizeOAuth2,
createOAuth2Application,
exchangeOAuth2AuthorizationCode,
listOAuth2Authorizations,
} from '@app/api/oauth/tests/OAuthTestUtils';
import {type ApiTestHarness, createApiTestHarness} from '@app/api/test/ApiTestHarness';
import {afterAll, beforeAll, beforeEach, describe, expect, it} from 'vitest';
describe('OAuth2 authorizations list', () => {
let harness: ApiTestHarness;
beforeAll(async () => {
harness = await createApiTestHarness();
});
beforeEach(async () => {
await harness.reset();
});
afterAll(async () => {
await harness?.shutdown();
});
it('verifies that a user with no authorized apps receives an empty list', async () => {
const user = await createTestAccount(harness);
const authorizations = await listOAuth2Authorizations(harness, user.token);
expect(authorizations).toHaveLength(0);
});
it('verifies that after a user authorizes an OAuth2 application, it appears in their authorizations list', async () => {
const appOwner = await createTestAccount(harness);
const endUser = await createTestAccount(harness);
const redirectURI = 'https://example.com/authz/callback';
const app = await createOAuth2Application(harness, appOwner, {
name: 'Auth List Test',
redirect_uris: [redirectURI],
});
const {code: authCode} = await authorizeOAuth2(harness, endUser.token, {
client_id: app.id,
redirect_uri: redirectURI,
scope: 'identify email',
});
await exchangeOAuth2AuthorizationCode(harness, {
client_id: app.id,
client_secret: app.client_secret,
code: authCode,
redirect_uri: redirectURI,
});
const authorizations = await listOAuth2Authorizations(harness, endUser.token);
expect(authorizations).toHaveLength(1);
expect(authorizations[0].application.id).toBe(app.id);
const scopes = authorizations[0].scopes;
expect(scopes).toContain('identify');
expect(scopes).toContain('email');
expect(scopes).not.toContain('bot');
expect(authorizations[0].authorized_at).toBeTruthy();
});
it('verifies that multiple authorized applications are correctly listed', async () => {
const appOwner = await createTestAccount(harness);
const endUser = await createTestAccount(harness);
const redirectURI = 'https://example.com/authz/multi';
const app1 = await createOAuth2Application(harness, appOwner, {
name: 'Multi Test App 1',
redirect_uris: [redirectURI],
});
const {code: code1} = await authorizeOAuth2(harness, endUser.token, {
client_id: app1.id,
redirect_uri: redirectURI,
scope: 'identify',
});
await exchangeOAuth2AuthorizationCode(harness, {
client_id: app1.id,
client_secret: app1.client_secret,
code: code1,
redirect_uri: redirectURI,
});
const app2 = await createOAuth2Application(harness, appOwner, {
name: 'Multi Test App 2',
redirect_uris: [redirectURI],
});
const {code: code2} = await authorizeOAuth2(harness, endUser.token, {
client_id: app2.id,
redirect_uri: redirectURI,
scope: 'identify email',
});
await exchangeOAuth2AuthorizationCode(harness, {
client_id: app2.id,
client_secret: app2.client_secret,
code: code2,
redirect_uri: redirectURI,
});
const authorizations = await listOAuth2Authorizations(harness, endUser.token);
expect(authorizations).toHaveLength(2);
const appIds = authorizations.map((a) => a.application.id);
expect(appIds).toContain(app1.id);
expect(appIds).toContain(app2.id);
});
it('verifies that bot-only authorizations (scope = "bot" only) do not appear in the authorizations list', async () => {
const appOwner = await createTestAccount(harness);
const endUser = await createTestAccount(harness);
const redirectURI = 'https://example.com/authz/bot';
const app = await createOAuth2Application(harness, appOwner, {
name: 'Bot Only Test',
redirect_uris: [redirectURI],
});
const {code: authCode} = await authorizeOAuth2(harness, endUser.token, {
client_id: app.id,
redirect_uri: redirectURI,
scope: 'identify',
});
await exchangeOAuth2AuthorizationCode(harness, {
client_id: app.id,
client_secret: app.client_secret,
code: authCode,
redirect_uri: redirectURI,
});
const authorizations = await listOAuth2Authorizations(harness, endUser.token);
expect(authorizations).toHaveLength(1);
expect(authorizations[0].scopes).not.toContain('bot');
});
});
@@ -1,15 +1,11 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {createTestAccount} from '@app/api/auth/tests/AuthTestUtils';
import {
authorizeOAuth2,
createOAuth2Application,
exchangeOAuth2AuthorizationCode,
} from '@app/api/oauth/tests/OAuthTestUtils';
import {createOAuth2Application} from '@app/api/oauth/tests/OAuthTestUtils';
import {type ApiTestHarness, createApiTestHarness} from '@app/api/test/ApiTestHarness';
import {HTTP_STATUS} from '@app/api/test/TestConstants';
import {createBuilder} from '@app/api/test/TestRequestBuilder';
import {afterAll, beforeAll, beforeEach, describe, expect, it} from 'vitest';
import {afterAll, beforeAll, beforeEach, describe, it} from 'vitest';
describe('OAuth2 authorize redirect URI validation', () => {
let harness: ApiTestHarness;
@@ -22,54 +18,6 @@ describe('OAuth2 authorize redirect URI validation', () => {
afterAll(async () => {
await harness?.shutdown();
});
it('verifies that localhost URIs work correctly for development purposes', async () => {
const appOwner = await createTestAccount(harness);
const endUser = await createTestAccount(harness);
const localhostURI = 'http://localhost:8080/oauth/callback';
const app = await createOAuth2Application(harness, appOwner, {
name: 'Localhost URI',
redirect_uris: [localhostURI],
});
const {code: authCode} = await authorizeOAuth2(harness, endUser.token, {
client_id: app.id,
redirect_uri: localhostURI,
scope: 'identify',
state: 'localhost-state',
});
const tokenResp = await exchangeOAuth2AuthorizationCode(harness, {
client_id: app.id,
client_secret: app.client_secret,
code: authCode,
redirect_uri: localhostURI,
});
expect(tokenResp.access_token).toBeTruthy();
});
it('verifies that an application can have multiple registered redirect URIs and use any of them', async () => {
const appOwner = await createTestAccount(harness);
const endUser = await createTestAccount(harness);
const uri1 = 'https://app.example.com/callback';
const uri2 = 'https://staging.example.com/callback';
const uri3 = 'https://localhost:3000/callback';
const app = await createOAuth2Application(harness, appOwner, {
name: 'Multiple URIs',
redirect_uris: [uri1, uri2, uri3],
});
const uris = [uri1, uri2, uri3];
for (const uri of uris) {
const {code: authCode} = await authorizeOAuth2(harness, endUser.token, {
client_id: app.id,
redirect_uri: uri,
scope: 'identify',
});
const tokenResp = await exchangeOAuth2AuthorizationCode(harness, {
client_id: app.id,
client_secret: app.client_secret,
code: authCode,
redirect_uri: uri,
});
expect(tokenResp.access_token).toBeTruthy();
}
});
it('verifies that redirect URIs must match exactly (no partial matches)', async () => {
const appOwner = await createTestAccount(harness);
const endUser = await createTestAccount(harness);
@@ -45,53 +45,4 @@ describe('OAuth2 authorize state parameter', () => {
});
expect(tokenResp.access_token).toBeTruthy();
});
it('verifies behavior when state is omitted', async () => {
const appOwner = await createTestAccount(harness);
const endUser = await createTestAccount(harness);
const redirectURI = 'https://example.com/state/empty';
const app = await createOAuth2Application(harness, appOwner, {
name: 'Empty State',
redirect_uris: [redirectURI],
});
const {code: authCode, state: returnedState} = await authorizeOAuth2(harness, endUser.token, {
client_id: app.id,
redirect_uri: redirectURI,
scope: 'identify',
});
expect(authCode).toBeTruthy();
expect(returnedState).toBeTruthy();
const tokenResp = await exchangeOAuth2AuthorizationCode(harness, {
client_id: app.id,
client_secret: app.client_secret,
code: authCode,
redirect_uri: redirectURI,
});
expect(tokenResp.access_token).toBeTruthy();
});
it('verifies that state parameters with special characters are preserved correctly', async () => {
const appOwner = await createTestAccount(harness);
const endUser = await createTestAccount(harness);
const redirectURI = 'https://example.com/state/special';
const app = await createOAuth2Application(harness, appOwner, {
name: 'Special State',
redirect_uris: [redirectURI],
});
const testCases = [
{state: 'state-with-dashes-123', name: 'with dashes'},
{state: 'state_with_underscores_456', name: 'with underscores'},
{state: 'state.with.periods.789', name: 'with periods'},
{state: 'state=with=equals', name: 'with equals'},
{state: 'state%20with%20spaces', name: 'with encoded chars'},
{state: 'c3RhdGUtYmFzZTY0LWxpa2U=', name: 'base64-like'},
];
for (const tc of testCases) {
const {state: returnedState} = await authorizeOAuth2(harness, endUser.token, {
client_id: app.id,
redirect_uri: redirectURI,
scope: 'identify',
state: tc.state,
});
expect(returnedState).toBe(tc.state);
}
});
});
@@ -29,27 +29,6 @@ describe('OAuth2 Bot Guild Add', () => {
afterEach(async () => {
await harness?.shutdown();
});
test('should add bot to guild with proper role creation', async () => {
const owner = await createTestAccount(harness);
const guild = await createGuild(harness, owner.token, 'Bot Test Guild');
const app = await createOAuth2Application(harness, owner.token, {
name: createUniqueApplicationName(),
redirect_uris: ['https://example.com/callback'],
bot_public: true,
});
await createBuilder(harness, owner.token)
.post('/oauth2/authorize/consent')
.body({
client_id: app.application.id,
scope: 'bot',
guild_id: guild.id,
permissions: Permissions.SEND_MESSAGES.toString(),
})
.expect(HTTP_STATUS.OK)
.execute();
const botMember = await getMember(harness, owner.token, guild.id, app.botUserId);
expect(botMember.user?.id).toBe(app.botUserId);
});
test('should add bot to group DM when requested with channel_id', async () => {
const owner = await createTestAccount(harness);
const friend = await createTestAccount(harness);
@@ -240,27 +219,6 @@ describe('OAuth2 Bot Guild Add', () => {
.expect(HTTP_STATUS.OK)
.execute();
});
test('should add bot without permissions when permissions is 0', async () => {
const owner = await createTestAccount(harness);
const guild = await createGuild(harness, owner.token, 'Bot Test Guild');
const app = await createOAuth2Application(harness, owner.token, {
name: createUniqueApplicationName(),
redirect_uris: ['https://example.com/callback'],
bot_public: true,
});
await createBuilder(harness, owner.token)
.post('/oauth2/authorize/consent')
.body({
client_id: app.application.id,
scope: 'bot',
guild_id: guild.id,
permissions: '0',
})
.expect(HTTP_STATUS.OK)
.execute();
const botMember = await getMember(harness, owner.token, guild.id, app.botUserId);
expect(botMember.user?.id).toBe(app.botUserId);
});
test('should reject bot scope for non-public bot without owner consent', async () => {
const owner = await createTestAccount(harness);
const otherUser = await createTestAccount(harness);
@@ -281,64 +239,6 @@ describe('OAuth2 Bot Guild Add', () => {
.expect(HTTP_STATUS.FORBIDDEN)
.execute();
});
test('should allow public bot to be added by any user with MANAGE_GUILD', async () => {
const owner = await createTestAccount(harness);
const otherUser = await createTestAccount(harness);
const guild = await createGuild(harness, otherUser.token, 'Other User Guild');
const app = await createOAuth2Application(harness, owner.token, {
name: createUniqueApplicationName(),
redirect_uris: ['https://example.com/callback'],
bot_public: true,
});
await createBuilder(harness, otherUser.token)
.post('/oauth2/authorize/consent')
.body({
client_id: app.application.id,
scope: 'bot',
guild_id: guild.id,
permissions: '0',
})
.expect(HTTP_STATUS.OK)
.execute();
});
test('should reject negative permissions', async () => {
const owner = await createTestAccount(harness);
const guild = await createGuild(harness, owner.token, 'Bot Test Guild');
const app = await createOAuth2Application(harness, owner.token, {
name: createUniqueApplicationName(),
redirect_uris: ['https://example.com/callback'],
bot_public: true,
});
await createBuilder(harness, owner.token)
.post('/oauth2/authorize/consent')
.body({
client_id: app.application.id,
scope: 'bot',
guild_id: guild.id,
permissions: '-1',
})
.expect(HTTP_STATUS.BAD_REQUEST)
.execute();
});
test('should reject invalid permissions string', async () => {
const owner = await createTestAccount(harness);
const guild = await createGuild(harness, owner.token, 'Bot Test Guild');
const app = await createOAuth2Application(harness, owner.token, {
name: createUniqueApplicationName(),
redirect_uris: ['https://example.com/callback'],
bot_public: true,
});
await createBuilder(harness, owner.token)
.post('/oauth2/authorize/consent')
.body({
client_id: app.application.id,
scope: 'bot',
guild_id: guild.id,
permissions: 'not_a_number',
})
.expect(HTTP_STATUS.BAD_REQUEST)
.execute();
});
test('should create role with correct permissions and assign to bot', async () => {
const owner = await createTestAccount(harness);
const guild = await createGuild(harness, owner.token, 'Bot Test Guild');
@@ -76,47 +76,6 @@ describe('OAuth2 Scope Enforcement', () => {
expect(json.id).toBe(account.userId);
expect(json.username).toBe(account.username);
});
test('GET /users/@me with bearer token returns user without email when only identify scope', async () => {
const account = await createTestAccount(harness);
const oauth2Token = await createOAuth2Token(harness, account.userId, ['identify']);
const json = await createBuilder<UserMeResponse>(harness, `Bearer ${oauth2Token.token}`)
.get('/users/@me')
.expect(HTTP_STATUS.OK)
.execute();
expect(json.id).toBe(account.userId);
expect(json.email).toBeNull();
});
test('GET /users/@me with bearer token returns email when email scope is present', async () => {
const account = await createTestAccount(harness);
const oauth2Token = await createOAuth2Token(harness, account.userId, ['identify', 'email']);
const json = await createBuilder<UserMeResponse>(harness, `Bearer ${oauth2Token.token}`)
.get('/users/@me')
.expect(HTTP_STATUS.OK)
.execute();
expect(json.id).toBe(account.userId);
expect(json.email).toBe(account.email);
});
test('GET /users/@me with session token (no scope check) succeeds', async () => {
const account = await createTestAccount(harness);
const json = await createBuilder<UserMeResponse>(harness, account.token)
.get('/users/@me')
.expect(HTTP_STATUS.OK)
.execute();
expect(json.id).toBe(account.userId);
expect(json.email).toBe(account.email);
});
test('GET /users/@me with bot token (no scope check) succeeds', async () => {
const appOwner = await createTestAccount(harness);
const application = await createOAuth2Application(harness, appOwner, {
name: 'Bot Token Test',
redirect_uris: [],
});
const json = await createBuilder<UserMeResponse>(harness, `Bot ${application.bot.token}`)
.get('/users/@me')
.expect(HTTP_STATUS.OK)
.execute();
expect(json.id).toBe(application.bot.id);
});
test('GET /users/@me with bearer token fails when identify scope is missing', async () => {
const account = await createTestAccount(harness);
const oauth2Token = await createOAuth2Token(harness, account.userId, ['guilds']);
@@ -185,37 +144,6 @@ describe('OAuth2 Scope Enforcement', () => {
.expect(HTTP_STATUS.FORBIDDEN, 'MISSING_OAUTH_SCOPE')
.execute();
});
test('GET /users/@me/guilds without guilds scope fails with MISSING_OAUTH_SCOPE', async () => {
const account = await createTestAccount(harness);
await createGuild(harness, account.token, 'Test Guild');
const oauth2Token = await createOAuth2Token(harness, account.userId, ['identify']);
await createBuilder(harness, `Bearer ${oauth2Token.token}`)
.get('/users/@me/guilds')
.expect(HTTP_STATUS.FORBIDDEN, 'MISSING_OAUTH_SCOPE')
.execute();
});
test('GET /users/@me/guilds with session token (no scope check) succeeds', async () => {
const account = await createTestAccount(harness);
await createGuild(harness, account.token, 'Test Guild');
const json = await createBuilder<Array<UserGuildResponse>>(harness, account.token)
.get('/users/@me/guilds')
.expect(HTTP_STATUS.OK)
.execute();
expect(Array.isArray(json)).toBe(true);
expect(json.length).toBeGreaterThan(0);
});
test('GET /users/@me/guilds with bot token (no scope check) succeeds', async () => {
const appOwner = await createTestAccount(harness);
const application = await createOAuth2Application(harness, appOwner, {
name: 'Bot Guilds Test',
redirect_uris: [],
});
const json = await createBuilder<Array<UserGuildResponse>>(harness, `Bot ${application.bot.token}`)
.get('/users/@me/guilds')
.expect(HTTP_STATUS.OK)
.execute();
expect(Array.isArray(json)).toBe(true);
});
});
describe('Scope enforcement for connections endpoint (/users/@me/connections)', () => {
test('GET /users/@me/connections with connections scope succeeds', async () => {
@@ -235,34 +163,8 @@ describe('OAuth2 Scope Enforcement', () => {
.expect(HTTP_STATUS.FORBIDDEN, 'MISSING_OAUTH_SCOPE')
.execute();
});
test('GET /users/@me/connections with session token succeeds', async () => {
const account = await createTestAccount(harness);
const json = await createBuilder<Array<unknown>>(harness, account.token)
.get('/users/@me/connections')
.expect(HTTP_STATUS.OK)
.execute();
expect(Array.isArray(json)).toBe(true);
});
});
describe('Scope enforcement for userinfo endpoint (/oauth2/userinfo)', () => {
test('GET /oauth2/userinfo with identify scope succeeds', async () => {
const {endUser, redirectURI, application} = await createOAuth2TestSetup(harness);
const authCodeResponse = await authorizeOAuth2(harness, endUser.token, {
client_id: application.id,
redirect_uri: redirectURI,
scope: 'identify',
});
const tokenResponse = await exchangeOAuth2AuthorizationCode(harness, {
client_id: application.id,
client_secret: application.client_secret,
code: authCodeResponse.code,
redirect_uri: redirectURI,
});
await createBuilder(harness, `Bearer ${tokenResponse.access_token}`)
.get('/oauth2/userinfo')
.expect(HTTP_STATUS.OK)
.execute();
});
test('GET /oauth2/userinfo without identify scope fails with MISSING_OAUTH_SCOPE', async () => {
const account = await createTestAccount(harness);
const oauth2Token = await createOAuth2Token(harness, account.userId, ['guilds']);
@@ -320,34 +222,6 @@ describe('OAuth2 Scope Enforcement', () => {
expect(json.id).toBe(account.userId);
expect(json.email).toBe(account.email);
});
test('Token with guilds but not identify cannot access /users/@me/guilds without auth context', async () => {
const account = await createTestAccount(harness);
await createGuild(harness, account.token, 'Test Guild');
const oauth2Token = await createOAuth2Token(harness, account.userId, ['guilds']);
const json = await createBuilder<Array<UserGuildResponse>>(harness, `Bearer ${oauth2Token.token}`)
.get('/users/@me/guilds')
.expect(HTTP_STATUS.OK)
.execute();
expect(Array.isArray(json)).toBe(true);
expect(json.length).toBeGreaterThan(0);
});
test('Token with all common scopes can access all corresponding endpoints', async () => {
const account = await createTestAccount(harness);
await createGuild(harness, account.token, 'Test Guild');
const oauth2Token = await createOAuth2Token(harness, account.userId, ['identify', 'email', 'guilds']);
const userJson = await createBuilder<UserMeResponse>(harness, `Bearer ${oauth2Token.token}`)
.get('/users/@me')
.expect(HTTP_STATUS.OK)
.execute();
expect(userJson.id).toBe(account.userId);
expect(userJson.email).toBe(account.email);
const guildsJson = await createBuilder<Array<UserGuildResponse>>(harness, `Bearer ${oauth2Token.token}`)
.get('/users/@me/guilds')
.expect(HTTP_STATUS.OK)
.execute();
expect(Array.isArray(guildsJson)).toBe(true);
expect(guildsJson.length).toBeGreaterThan(0);
});
});
describe('Edge cases', () => {
test('Empty scopes set cannot access /users/@me', async () => {
@@ -424,14 +298,6 @@ describe('OAuth2 Scope Enforcement', () => {
.expect(HTTP_STATUS.FORBIDDEN, 'ACCESS_DENIED')
.execute();
});
test('Session token can access admin endpoints with proper ACLs', async () => {
const admin = await createTestAccount(harness);
await setUserACLs(harness, admin, ['admin:authenticate', 'user:lookup']);
await createBuilder(harness, `${admin.token}`)
.get(`/admin/users/${admin.userId}`)
.expect(HTTP_STATUS.OK)
.execute();
});
test('Admin API key can access admin endpoints with granted ACLs', async () => {
const admin = await createTestAccount(harness);
await setUserACLs(harness, admin, ['admin:authenticate', 'admin_api_key:manage', 'user:lookup']);
@@ -466,61 +332,6 @@ describe('OAuth2 Scope Enforcement', () => {
.execute();
});
});
describe('Negative tests - Error response verification', () => {
test('Endpoint without proper guilds scope returns 403 with MISSING_OAUTH_SCOPE code', async () => {
const account = await createTestAccount(harness);
await createGuild(harness, account.token, 'Test Guild');
const oauth2Token = await createOAuth2Token(harness, account.userId, ['identify']);
const json = await createBuilder<{
code: string;
message: string;
}>(harness, `Bearer ${oauth2Token.token}`)
.get('/users/@me/guilds')
.expect(HTTP_STATUS.FORBIDDEN, 'MISSING_OAUTH_SCOPE')
.execute();
expect(json.code).toBe('MISSING_OAUTH_SCOPE');
});
test('Admin endpoint with third-party OAuth2 token returns ACCESS_DENIED', async () => {
const admin = await createTestAccount(harness);
await setUserACLs(harness, admin, ['admin:authenticate', 'user:lookup']);
const oauth2Token = await createOAuth2Token(harness, admin.userId, ['identify', 'email']);
const json = await createBuilder<{
code: string;
message: string;
}>(harness, `Bearer ${oauth2Token.token}`)
.get(`/admin/users/${admin.userId}`)
.expect(HTTP_STATUS.FORBIDDEN, 'ACCESS_DENIED')
.execute();
expect(json.code).toBe('ACCESS_DENIED');
});
test('Admin endpoint with built-in admin OAuth2 token but missing ACL returns MISSING_ACL code', async () => {
const admin = await createTestAccount(harness);
await setUserACLs(harness, admin, ['admin:authenticate']);
const oauth2Token = await createOAuth2Token(
harness,
admin.userId,
['identify', 'email'],
ADMIN_OAUTH2_APPLICATION_ID.toString(),
);
const json = await createBuilder<{
code: string;
message: string;
}>(harness, `Bearer ${oauth2Token.token}`)
.get(`/admin/users/${admin.userId}`)
.expect(HTTP_STATUS.FORBIDDEN, 'MISSING_ACL')
.execute();
expect(json.code).toBe('MISSING_ACL');
});
test('Unauthenticated request returns 401 UNAUTHORIZED', async () => {
await createBuilder(harness, '').get('/users/@me').expect(HTTP_STATUS.UNAUTHORIZED, 'UNAUTHORIZED').execute();
});
test('Invalid bearer token returns 401', async () => {
await createBuilder(harness, 'Bearer invalid_token_12345')
.get('/users/@me')
.expect(HTTP_STATUS.UNAUTHORIZED, 'UNAUTHORIZED')
.execute();
});
});
describe('Scope combinations and boundaries', () => {
test('Token with only email scope (no identify) cannot access user info', async () => {
const account = await createTestAccount(harness);
@@ -566,17 +377,6 @@ describe('OAuth2 Scope Enforcement', () => {
.execute();
expect(guildsJson2.length).toBeGreaterThan(0);
});
test('Token created for one user cannot access another users data', async () => {
const account1 = await createTestAccount(harness);
const account2 = await createTestAccount(harness);
const oauth2Token = await createOAuth2Token(harness, account1.userId, ['identify', 'email']);
const json = await createBuilder<UserMeResponse>(harness, `Bearer ${oauth2Token.token}`)
.get('/users/@me')
.expect(HTTP_STATUS.OK)
.execute();
expect(json.id).toBe(account1.userId);
expect(json.id).not.toBe(account2.userId);
});
});
describe('Real OAuth2 flow scope enforcement', () => {
test('OAuth2 token from real authorization flow respects scope restrictions', async () => {
@@ -1,67 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {createTestAccount} from '@app/api/auth/tests/AuthTestUtils';
import {type ApiTestHarness, createApiTestHarness} from '@app/api/test/ApiTestHarness';
import {HTTP_STATUS} from '@app/api/test/TestConstants';
import {createBuilder} from '@app/api/test/TestRequestBuilder';
import {afterEach, beforeEach, describe, test} from 'vitest';
interface OAuth2TokenResponse {
token: string;
user_id: string;
scopes: Array<string>;
application_id: string;
}
async function createOAuth2Token(
harness: ApiTestHarness,
userId: string,
scopes: Array<string>,
): Promise<OAuth2TokenResponse> {
return createBuilder<OAuth2TokenResponse>(harness, '')
.post('/test/oauth2/access-token')
.body({
user_id: userId,
scopes,
})
.execute();
}
describe('OAuth2 scope middleware', () => {
let harness: ApiTestHarness;
beforeEach(async () => {
harness = await createApiTestHarness();
});
afterEach(async () => {
await harness?.shutdown();
});
test('rejects session tokens for OAuth2-only routes', async () => {
const account = await createTestAccount(harness);
await createBuilder(harness, account.token)
.get('/test/oauth2/require-identify')
.expect(HTTP_STATUS.UNAUTHORIZED, 'UNAUTHORIZED')
.execute();
});
test('rejects unauthenticated requests for OAuth2-only routes', async () => {
await createBuilder(harness, '')
.get('/test/oauth2/require-identify')
.expect(HTTP_STATUS.UNAUTHORIZED, 'UNAUTHORIZED')
.execute();
});
test('rejects bearer tokens missing required scope', async () => {
const account = await createTestAccount(harness);
const oauth2Token = await createOAuth2Token(harness, account.userId, ['email']);
await createBuilder(harness, `Bearer ${oauth2Token.token}`)
.get('/test/oauth2/require-identify')
.expect(HTTP_STATUS.FORBIDDEN, 'MISSING_OAUTH_SCOPE')
.execute();
});
test('allows bearer tokens with required scope', async () => {
const account = await createTestAccount(harness);
const oauth2Token = await createOAuth2Token(harness, account.userId, ['identify']);
await createBuilder(harness, `Bearer ${oauth2Token.token}`)
.get('/test/oauth2/require-identify')
.expect(HTTP_STATUS.OK)
.execute();
});
});
@@ -2,12 +2,7 @@
import {createTestAccount} from '@app/api/auth/tests/AuthTestUtils';
import {Config} from '@app/api/Config';
import {
authorizeOAuth2,
createOAuth2Application,
createOAuth2TestSetup,
exchangeOAuth2AuthorizationCode,
} from '@app/api/oauth/tests/OAuthTestUtils';
import {createOAuth2Application, createOAuth2TestSetup} from '@app/api/oauth/tests/OAuthTestUtils';
import {type ApiTestHarness, createApiTestHarness} from '@app/api/test/ApiTestHarness';
import {HTTP_STATUS} from '@app/api/test/TestConstants';
import {createBuilder} from '@app/api/test/TestRequestBuilder';
@@ -19,51 +14,6 @@ describe('OAuth2 Scope Validation', () => {
beforeEach(async () => {
harness = await createApiTestHarness();
});
test('should accept valid platform scopes', async () => {
const {appOwner, endUser} = await createOAuth2TestSetup(harness);
const validScopes = ['identify', 'email', 'guilds', 'connections'];
for (const scope of validScopes) {
const application = await createOAuth2Application(harness, appOwner, {
name: `Scope Test ${scope}`,
redirect_uris: ['https://example.com/callback'],
});
const authCodeResponse = await authorizeOAuth2(harness, endUser.token, {
client_id: application.id,
redirect_uri: 'https://example.com/callback',
scope,
});
expect(authCodeResponse.code).toBeTruthy();
const tokenResponse = await exchangeOAuth2AuthorizationCode(harness, {
client_id: application.id,
client_secret: application.client_secret,
code: authCodeResponse.code,
redirect_uri: 'https://example.com/callback',
});
expect(tokenResponse.scope).toContain(scope);
}
}, 10000);
test('should accept multiple valid scopes', async () => {
const {appOwner, endUser} = await createOAuth2TestSetup(harness);
const application = await createOAuth2Application(harness, appOwner, {
name: 'Multi Scope Test',
redirect_uris: ['https://example.com/callback'],
});
const authCodeResponse = await authorizeOAuth2(harness, endUser.token, {
client_id: application.id,
redirect_uri: 'https://example.com/callback',
scope: 'identify email guilds',
});
expect(authCodeResponse.code).toBeTruthy();
const tokenResponse = await exchangeOAuth2AuthorizationCode(harness, {
client_id: application.id,
client_secret: application.client_secret,
code: authCodeResponse.code,
redirect_uri: 'https://example.com/callback',
});
expect(tokenResponse.scope).toContain('identify');
expect(tokenResponse.scope).toContain('email');
expect(tokenResponse.scope).toContain('guilds');
}, 10000);
test('should reject unknown scope', async () => {
const {appOwner, endUser} = await createOAuth2TestSetup(harness);
const application = await createOAuth2Application(harness, appOwner, {
@@ -100,45 +50,6 @@ describe('OAuth2 Scope Validation', () => {
.expect(HTTP_STATUS.BAD_REQUEST)
.execute();
});
test('should allow bot scope on applications with bots', async () => {
const {appOwner, endUser} = await createOAuth2TestSetup(harness);
const application = await createOAuth2Application(harness, appOwner, {
name: 'Bot Scope Test',
redirect_uris: [],
});
const json = await createBuilder<{
redirect_to: string;
}>(harness, endUser.token)
.post('/oauth2/authorize/consent')
.body({
response_type: 'code',
client_id: application.id,
scope: 'bot',
state: 'test-state',
})
.expect(HTTP_STATUS.OK)
.execute();
expect(json.redirect_to).toBeTruthy();
});
test('should reject bot scope for non-bot applications', async () => {
const appOwner = await createTestAccount(harness);
const endUser = await createTestAccount(harness);
const application = await createOAuth2Application(harness, appOwner, {
name: 'Bot Scope Test App',
redirect_uris: ['https://example.com/callback'],
});
await createBuilder(harness, endUser.token)
.post('/oauth2/authorize/consent')
.body({
response_type: 'code',
client_id: application.id,
redirect_uri: 'https://example.com/callback',
scope: 'bot',
state: 'test-state',
})
.expect(HTTP_STATUS.OK)
.execute();
});
test('should reject the removed admin scope for third-party applications', async () => {
const {appOwner, endUser} = await createOAuth2TestSetup(harness);
const application = await createOAuth2Application(harness, appOwner, {
@@ -79,27 +79,6 @@ export async function listOAuth2Applications(
return json;
}
export async function updateOAuth2Application(
harness: ApiTestHarness,
token: string,
applicationId: string,
params: {
name?: string;
redirect_uris?: Array<string> | null;
bot_public?: boolean;
bot_require_code_grant?: boolean;
},
): Promise<ApplicationResponse> {
const {response, text, json} = await createBuilder<ApplicationResponse>(harness, token)
.patch(`/oauth2/applications/${applicationId}`)
.body(params)
.executeRaw();
if (response.status !== 200) {
throw new Error(`Expected 200, got ${response.status}: ${text}`);
}
return json;
}
export async function deleteOAuth2Application(
harness: ApiTestHarness,
token: string,
@@ -15,26 +15,6 @@ describe('OAuth2 Token Exchange', () => {
beforeEach(async () => {
harness = await createApiTestHarness();
});
test('should exchange authorization code for tokens', async () => {
const {endUser, redirectURI, application} = await createOAuth2TestSetup(harness);
const authCodeResponse = await authorizeOAuth2(harness, endUser.token, {
client_id: application.id,
redirect_uri: redirectURI,
scope: 'identify email',
});
const tokenResponse = await exchangeOAuth2AuthorizationCode(harness, {
client_id: application.id,
client_secret: application.client_secret,
code: authCodeResponse.code,
redirect_uri: redirectURI,
});
expect(tokenResponse.access_token).toBeTruthy();
expect(tokenResponse.access_token).toHaveLength(43);
expect(tokenResponse.token_type).toBe('Bearer');
expect(tokenResponse.expires_in).toBeGreaterThan(0);
expect(tokenResponse.refresh_token).toBeTruthy();
expect(tokenResponse.scope).toBe('identify email');
});
test('should fail without authorization code', async () => {
const {application, redirectURI} = await createOAuth2TestSetup(harness);
const formData = new URLSearchParams({
@@ -120,26 +100,6 @@ describe('OAuth2 Token Exchange', () => {
.expect(HTTP_STATUS.BAD_REQUEST)
.execute();
});
test('should fail without client authentication', async () => {
const {endUser, redirectURI, application} = await createOAuth2TestSetup(harness);
const authCodeResponse = await authorizeOAuth2(harness, endUser.token, {
client_id: application.id,
redirect_uri: redirectURI,
scope: 'identify',
});
const formData = new URLSearchParams({
grant_type: 'authorization_code',
code: authCodeResponse.code,
redirect_uri: redirectURI,
client_id: application.id,
});
await createBuilder(harness, '')
.post('/oauth2/token')
.header('Content-Type', 'application/x-www-form-urlencoded')
.body(formData.toString())
.expect(HTTP_STATUS.BAD_REQUEST)
.execute();
});
test('should fail with wrong client_id', async () => {
const {endUser, redirectURI, application} = await createOAuth2TestSetup(harness);
const authCodeResponse = await authorizeOAuth2(harness, endUser.token, {
@@ -164,67 +124,6 @@ describe('OAuth2 Token Exchange', () => {
.expect(HTTP_STATUS.BAD_REQUEST)
.execute();
});
test('should support scope subset during exchange', async () => {
const {endUser, redirectURI, application} = await createOAuth2TestSetup(harness);
const authCodeResponse = await authorizeOAuth2(harness, endUser.token, {
client_id: application.id,
redirect_uri: redirectURI,
scope: 'identify email guilds',
});
const tokenResponse = await exchangeOAuth2AuthorizationCode(harness, {
client_id: application.id,
client_secret: application.client_secret,
code: authCodeResponse.code,
redirect_uri: redirectURI,
});
expect(tokenResponse.scope).toContain('identify');
expect(tokenResponse.scope).toContain('email');
expect(tokenResponse.scope).toContain('guilds');
expect(tokenResponse.access_token).toBeTruthy();
});
test('should fail without authentication', async () => {
const {redirectURI, application} = await createOAuth2TestSetup(harness);
const formData = new URLSearchParams({
grant_type: 'authorization_code',
code: 'some_code',
redirect_uri: redirectURI,
client_id: application.id,
});
await createBuilder(harness, '')
.post('/oauth2/token')
.header('Content-Type', 'application/x-www-form-urlencoded')
.body(formData.toString())
.expect(HTTP_STATUS.BAD_REQUEST)
.execute();
});
test('should support basic auth client authentication', async () => {
const {endUser, redirectURI, application} = await createOAuth2TestSetup(harness);
const authCodeResponse = await authorizeOAuth2(harness, endUser.token, {
client_id: application.id,
redirect_uri: redirectURI,
scope: 'identify',
});
const formData = new URLSearchParams({
grant_type: 'authorization_code',
code: authCodeResponse.code,
redirect_uri: redirectURI,
client_id: application.id,
});
const json = await createBuilder<{
access_token: string;
}>(harness, '')
.post('/oauth2/token')
.header('Content-Type', 'application/x-www-form-urlencoded')
.header(
'Authorization',
`Basic ${Buffer.from(`${application.id}:${application.client_secret}`).toString('base64')}`,
)
.body(formData.toString())
.expect(HTTP_STATUS.OK)
.execute();
expect(json.access_token).toBeTruthy();
expect(json.access_token).toHaveLength(43);
});
test('should fail authorization code reuse', async () => {
const {endUser, redirectURI, application} = await createOAuth2TestSetup(harness);
const authCodeResponse = await authorizeOAuth2(harness, endUser.token, {
@@ -256,27 +155,6 @@ describe('OAuth2 Token Exchange', () => {
.expect(HTTP_STATUS.BAD_REQUEST)
.execute();
});
test('should fail with wrong client_secret', async () => {
const {endUser, redirectURI, application} = await createOAuth2TestSetup(harness);
const authCodeResponse = await authorizeOAuth2(harness, endUser.token, {
client_id: application.id,
redirect_uri: redirectURI,
scope: 'identify',
});
const formData = new URLSearchParams({
grant_type: 'authorization_code',
code: authCodeResponse.code,
redirect_uri: redirectURI,
client_id: application.id,
});
await createBuilder(harness, '')
.post('/oauth2/token')
.header('Content-Type', 'application/x-www-form-urlencoded')
.header('Authorization', `Basic ${Buffer.from(`${application.id}:wrong_secret_value`).toString('base64')}`)
.body(formData.toString())
.expect(HTTP_STATUS.BAD_REQUEST)
.execute();
});
test('should fail with missing grant_type', async () => {
const {endUser, redirectURI, application} = await createOAuth2TestSetup(harness);
const authCodeResponse = await authorizeOAuth2(harness, endUser.token, {
@@ -300,23 +178,4 @@ describe('OAuth2 Token Exchange', () => {
.expect(HTTP_STATUS.BAD_REQUEST)
.execute();
});
test('should fail with missing client_id', async () => {
const {endUser, redirectURI, application} = await createOAuth2TestSetup(harness);
const authCodeResponse = await authorizeOAuth2(harness, endUser.token, {
client_id: application.id,
redirect_uri: redirectURI,
scope: 'identify',
});
const formData = new URLSearchParams({
grant_type: 'authorization_code',
code: authCodeResponse.code,
redirect_uri: redirectURI,
});
await createBuilder(harness, '')
.post('/oauth2/token')
.header('Content-Type', 'application/x-www-form-urlencoded')
.body(formData.toString())
.expect(HTTP_STATUS.BAD_REQUEST)
.execute();
});
});
@@ -8,9 +8,6 @@ import {
authorizeOAuth2,
createOAuth2TestSetup,
exchangeOAuth2AuthorizationCode,
getOAuth2UserInfo,
introspectOAuth2Token,
refreshOAuth2Token,
} from '@app/api/oauth/tests/OAuthTestUtils';
import {type ApiTestHarness, createApiTestHarness} from '@app/api/test/ApiTestHarness';
import {HTTP_STATUS} from '@app/api/test/TestConstants';
@@ -25,137 +22,6 @@ describe('OAuth2 Token Expiration', () => {
afterEach(async () => {
await harness?.shutdown();
});
test('should include expires_in in token response', async () => {
const {endUser, redirectURI, application} = await createOAuth2TestSetup(harness);
const authCodeResponse = await authorizeOAuth2(harness, endUser.token, {
client_id: application.id,
redirect_uri: redirectURI,
scope: 'identify email',
});
const tokenResponse = await exchangeOAuth2AuthorizationCode(harness, {
client_id: application.id,
client_secret: application.client_secret,
code: authCodeResponse.code,
redirect_uri: redirectURI,
});
expect(tokenResponse.expires_in).toBeGreaterThan(0);
expect(tokenResponse.expires_in).toBe(604800);
});
test('should have consistent expiration across multiple token exchanges', async () => {
const {endUser, redirectURI, application} = await createOAuth2TestSetup(harness);
const authCode1 = await authorizeOAuth2(harness, endUser.token, {
client_id: application.id,
redirect_uri: redirectURI,
scope: 'identify',
});
const token1 = await exchangeOAuth2AuthorizationCode(harness, {
client_id: application.id,
client_secret: application.client_secret,
code: authCode1.code,
redirect_uri: redirectURI,
});
const authCode2 = await authorizeOAuth2(harness, endUser.token, {
client_id: application.id,
redirect_uri: redirectURI,
scope: 'identify email',
});
const token2 = await exchangeOAuth2AuthorizationCode(harness, {
client_id: application.id,
client_secret: application.client_secret,
code: authCode2.code,
redirect_uri: redirectURI,
});
expect(token1.expires_in).toBe(token2.expires_in);
});
test('should return expiration metadata in introspection response', async () => {
const {endUser, redirectURI, application} = await createOAuth2TestSetup(harness);
const authCodeResponse = await authorizeOAuth2(harness, endUser.token, {
client_id: application.id,
redirect_uri: redirectURI,
scope: 'identify',
});
const tokens = await exchangeOAuth2AuthorizationCode(harness, {
client_id: application.id,
client_secret: application.client_secret,
code: authCodeResponse.code,
redirect_uri: redirectURI,
});
const introspection = await introspectOAuth2Token(harness, {
client_id: application.id,
client_secret: application.client_secret,
token: tokens.access_token,
});
expect(introspection.active).toBe(true);
expect(introspection.exp).toBeGreaterThan(0);
expect(introspection.iat).toBeGreaterThan(0);
expect(introspection.exp).toBeGreaterThan(introspection.iat!);
});
test('should maintain consistent expiration with multiple scopes', async () => {
const {endUser, redirectURI, application} = await createOAuth2TestSetup(harness);
const authCodeResponse = await authorizeOAuth2(harness, endUser.token, {
client_id: application.id,
redirect_uri: redirectURI,
scope: 'identify email guilds',
});
const tokens = await exchangeOAuth2AuthorizationCode(harness, {
client_id: application.id,
client_secret: application.client_secret,
code: authCodeResponse.code,
redirect_uri: redirectURI,
});
expect(tokens.expires_in).toBe(604800);
const introspection = await introspectOAuth2Token(harness, {
client_id: application.id,
client_secret: application.client_secret,
token: tokens.access_token,
});
expect(introspection.active).toBe(true);
expect(introspection.scope).toContain('identify');
expect(introspection.scope).toContain('email');
expect(introspection.scope).toContain('guilds');
});
test('should maintain expiration after token refresh', async () => {
const {endUser, redirectURI, application} = await createOAuth2TestSetup(harness);
const authCodeResponse = await authorizeOAuth2(harness, endUser.token, {
client_id: application.id,
redirect_uri: redirectURI,
scope: 'identify email',
});
const initialTokens = await exchangeOAuth2AuthorizationCode(harness, {
client_id: application.id,
client_secret: application.client_secret,
code: authCodeResponse.code,
redirect_uri: redirectURI,
});
const refreshedTokens = await refreshOAuth2Token(harness, {
client_id: application.id,
client_secret: application.client_secret,
refresh_token: initialTokens.refresh_token!,
});
expect(refreshedTokens.expires_in).toBe(initialTokens.expires_in);
});
test('should allow userinfo access with valid token', async () => {
const {endUser, redirectURI, application} = await createOAuth2TestSetup(harness);
const authCodeResponse = await authorizeOAuth2(harness, endUser.token, {
client_id: application.id,
redirect_uri: redirectURI,
scope: 'identify email',
});
const tokens = await exchangeOAuth2AuthorizationCode(harness, {
client_id: application.id,
client_secret: application.client_secret,
code: authCodeResponse.code,
redirect_uri: redirectURI,
});
const userInfo = await getOAuth2UserInfo(harness, tokens.access_token);
expect(userInfo.sub).toBe(endUser.userId);
});
test('should reject userinfo access with invalid token', async () => {
await createBuilder(harness, 'Bearer invalid_token_12345')
.get('/oauth2/userinfo')
.expect(HTTP_STATUS.UNAUTHORIZED)
.execute();
});
test('should reject userinfo access with an expired access token', async () => {
const {endUser, application} = await createOAuth2TestSetup(harness);
const repository = new OAuth2TokenRepository();
@@ -171,28 +37,6 @@ describe('OAuth2 Token Expiration', () => {
expect(await repository.getAccessToken(token)).toBeNull();
await createBuilder(harness, `Bearer ${token}`).get('/oauth2/userinfo').expect(HTTP_STATUS.UNAUTHORIZED).execute();
});
test('should track token active state in introspection', async () => {
const {endUser, redirectURI, application} = await createOAuth2TestSetup(harness);
const authCodeResponse = await authorizeOAuth2(harness, endUser.token, {
client_id: application.id,
redirect_uri: redirectURI,
scope: 'identify',
});
const tokens = await exchangeOAuth2AuthorizationCode(harness, {
client_id: application.id,
client_secret: application.client_secret,
code: authCodeResponse.code,
redirect_uri: redirectURI,
});
const introspection = await introspectOAuth2Token(harness, {
client_id: application.id,
client_secret: application.client_secret,
token: tokens.access_token,
});
expect(introspection.active).toBe(true);
expect(introspection.token_type).toBe('Bearer');
expect(introspection.client_id).toBe(application.id);
});
test('should provide expires timestamp through oauth2/@me endpoint', async () => {
const {endUser, redirectURI, application} = await createOAuth2TestSetup(harness);
const authCodeResponse = await authorizeOAuth2(harness, endUser.token, {
@@ -49,79 +49,6 @@ describe('OAuth2 Token Refresh', () => {
const userInfo = await getOAuth2UserInfo(harness, refreshedTokens.access_token);
expect(userInfo.sub).toBe(endUser.userId);
});
test('should return new access_token and refresh_token on valid refresh', async () => {
const {endUser, redirectURI, application} = await createOAuth2TestSetup(harness);
const authCodeResponse = await authorizeOAuth2(harness, endUser.token, {
client_id: application.id,
redirect_uri: redirectURI,
scope: 'identify',
});
const initialTokens = await exchangeOAuth2AuthorizationCode(harness, {
client_id: application.id,
client_secret: application.client_secret,
code: authCodeResponse.code,
redirect_uri: redirectURI,
});
const refreshedTokens = await refreshOAuth2Token(harness, {
client_id: application.id,
client_secret: application.client_secret,
refresh_token: initialTokens.refresh_token!,
});
expect(refreshedTokens.access_token).toBeTruthy();
expect(refreshedTokens.access_token).not.toBe(initialTokens.access_token);
expect(refreshedTokens.refresh_token).toBeTruthy();
expect(refreshedTokens.refresh_token).not.toBe(initialTokens.refresh_token);
expect(refreshedTokens.token_type).toBe('Bearer');
expect(refreshedTokens.expires_in).toBeGreaterThan(0);
});
test('should allow multiple sequential refreshes', async () => {
const {endUser, redirectURI, application} = await createOAuth2TestSetup(harness);
const authCodeResponse = await authorizeOAuth2(harness, endUser.token, {
client_id: application.id,
redirect_uri: redirectURI,
scope: 'identify email',
});
const initialTokens = await exchangeOAuth2AuthorizationCode(harness, {
client_id: application.id,
client_secret: application.client_secret,
code: authCodeResponse.code,
redirect_uri: redirectURI,
});
const firstRefresh = await refreshOAuth2Token(harness, {
client_id: application.id,
client_secret: application.client_secret,
refresh_token: initialTokens.refresh_token!,
});
expect(firstRefresh.access_token).toBeTruthy();
const secondRefresh = await refreshOAuth2Token(harness, {
client_id: application.id,
client_secret: application.client_secret,
refresh_token: firstRefresh.refresh_token!,
});
expect(secondRefresh.access_token).toBeTruthy();
});
test('should preserve scopes during refresh', async () => {
const {endUser, redirectURI, application} = await createOAuth2TestSetup(harness);
const authCodeResponse = await authorizeOAuth2(harness, endUser.token, {
client_id: application.id,
redirect_uri: redirectURI,
scope: 'identify email guilds',
});
const initialTokens = await exchangeOAuth2AuthorizationCode(harness, {
client_id: application.id,
client_secret: application.client_secret,
code: authCodeResponse.code,
redirect_uri: redirectURI,
});
const refreshedTokens = await refreshOAuth2Token(harness, {
client_id: application.id,
client_secret: application.client_secret,
refresh_token: initialTokens.refresh_token!,
});
expect(refreshedTokens.scope).toContain('identify');
expect(refreshedTokens.scope).toContain('email');
expect(refreshedTokens.scope).toContain('guilds');
});
test('should fail with invalid refresh token', async () => {
const {application} = await createOAuth2TestSetup(harness);
const formData = new URLSearchParams({
@@ -183,35 +110,6 @@ describe('OAuth2 Token Refresh', () => {
.expect(HTTP_STATUS.BAD_REQUEST)
.execute();
});
test('should fail with wrong client_id', async () => {
const {endUser, redirectURI, application} = await createOAuth2TestSetup(harness);
const authCodeResponse = await authorizeOAuth2(harness, endUser.token, {
client_id: application.id,
redirect_uri: redirectURI,
scope: 'identify',
});
const initialTokens = await exchangeOAuth2AuthorizationCode(harness, {
client_id: application.id,
client_secret: application.client_secret,
code: authCodeResponse.code,
redirect_uri: redirectURI,
});
const formData = new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: initialTokens.refresh_token!,
client_id: '999999999999999999',
});
await createBuilder(harness, '')
.post('/oauth2/token')
.header('Content-Type', 'application/x-www-form-urlencoded')
.header(
'Authorization',
`Basic ${Buffer.from(`999999999999999999:${application.client_secret}`).toString('base64')}`,
)
.body(formData.toString())
.expect(HTTP_STATUS.BAD_REQUEST)
.execute();
});
test('should fail with wrong client_secret', async () => {
const {endUser, redirectURI, application} = await createOAuth2TestSetup(harness);
const authCodeResponse = await authorizeOAuth2(harness, endUser.token, {
@@ -238,31 +136,6 @@ describe('OAuth2 Token Refresh', () => {
.expect(HTTP_STATUS.BAD_REQUEST)
.execute();
});
test('should fail with missing client_secret', async () => {
const {endUser, redirectURI, application} = await createOAuth2TestSetup(harness);
const authCodeResponse = await authorizeOAuth2(harness, endUser.token, {
client_id: application.id,
redirect_uri: redirectURI,
scope: 'identify',
});
const initialTokens = await exchangeOAuth2AuthorizationCode(harness, {
client_id: application.id,
client_secret: application.client_secret,
code: authCodeResponse.code,
redirect_uri: redirectURI,
});
const formData = new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: initialTokens.refresh_token!,
client_id: application.id,
});
await createBuilder(harness, '')
.post('/oauth2/token')
.header('Content-Type', 'application/x-www-form-urlencoded')
.body(formData.toString())
.expect(HTTP_STATUS.BAD_REQUEST)
.execute();
});
test('should fail when using refresh token from different application', async () => {
const setup1 = await createOAuth2TestSetup(harness);
const setup2 = await createOAuth2TestSetup(harness);