fix(API): Let global members grant folder scopes to API keys (#37654)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Guillaume Jacquart 2026-09-08 10:50:16 +00:00 committed by GitHub
parent 418f85e9eb
commit aa91e71893
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 59 additions and 1 deletions

View File

@ -212,6 +212,11 @@ export const API_KEY_SCOPES_FOR_IMPLICIT_PERSONAL_PROJECT: ApiKeyScope[] = [
'dataTableRow:update',
'dataTableRow:delete',
'dataTableRow:upsert',
'folder:create',
'folder:delete',
'folder:read',
'folder:update',
'folder:list',
'dataTableColumn:create',
'dataTableColumn:read',
'dataTableColumn:update',

View File

@ -144,5 +144,27 @@ describe('PublicApiKeyService', () => {
expect(result).toBe(false);
});
it('should let a member grant folder scopes, which apply to their own projects', async () => {
// Arrange
const folderScopes: ApiKeyScope[] = [
'folder:create',
'folder:read',
'folder:update',
'folder:delete',
'folder:list',
];
// Act
const result = publicApiKeyService.apiKeyHasValidScopesForRole(
{
role: GLOBAL_MEMBER_ROLE,
},
folderScopes,
);
// Assert
expect(result).toBe(true);
});
});
});

View File

@ -507,6 +507,24 @@ describe('Member', () => {
expect(newApiKeyResponse.statusCode).toBe(400);
});
test('POST /api-keys should create an api key with folder scopes', async () => {
const folderScopes: ApiKeyScope[] = [
'folder:create',
'folder:read',
'folder:update',
'folder:delete',
'folder:list',
];
const newApiKeyResponse = await testServer
.authAgentFor(member)
.post('/api-keys')
.send({ label: 'My API Key', expiresAt: null, scopes: folderScopes })
.expect(200);
expect(newApiKeyResponse.body.data.scopes).toEqual(folderScopes);
});
test('GET /api-keys should fetch the api key redacted', async () => {
const expirationDateInTheFuture = Date.now() + 1000;

View File

@ -15,6 +15,7 @@ import * as utils from '../shared/utils/';
let owner: User;
let member: User;
let ownerPersonalProject: Project;
let memberPersonalProject: Project;
let authOwnerAgent: SuperAgentTest;
let authMemberAgent: SuperAgentTest;
@ -57,7 +58,7 @@ beforeEach(async () => {
};
ownerPersonalProject = await createPersonalProject(owner);
await createPersonalProject(member);
memberPersonalProject = await createPersonalProject(member);
authOwnerAgent = testServer.publicApiAgentFor(owner);
authMemberAgent = testServer.publicApiAgentFor(member);
@ -295,6 +296,18 @@ describe('GET /projects/:projectId/folders', () => {
expect(response.body.data).toHaveLength(2);
});
test('should list folders in their own personal project when user is a member', async () => {
testServer.license.enable('feat:folders');
await createFolder(memberPersonalProject, { name: 'Member Folder' });
const response = await authMemberAgent.get(`/projects/${memberPersonalProject.id}/folders`);
expect(response.statusCode).toBe(200);
expect(response.body.count).toBe(1);
expect(response.body.data).toHaveLength(1);
});
test('should list folders in a team project when user is a member', async () => {
testServer.license.enable('feat:folders');
testServer.license.setQuota('quota:maxTeamProjects', -1);