Skip to content

Commit f5ff9ea

Browse files
fix(web): update withAuthV2 tests for getAuthenticatedUser source field
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 24b941d commit f5ff9ea

File tree

1 file changed

+95
-16
lines changed

1 file changed

+95
-16
lines changed

packages/web/src/withAuthV2.test.ts

Lines changed: 95 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@ import { notAuthenticated } from './lib/serviceError';
44
import { getAuthContext, getAuthenticatedUser, withAuthV2, withOptionalAuthV2 } from './withAuthV2';
55
import { MOCK_API_KEY, MOCK_OAUTH_TOKEN, MOCK_ORG, MOCK_USER_WITH_ACCOUNTS, prisma } from './__mocks__/prisma';
66
import { OrgRole } from '@sourcebot/db';
7+
import { ErrorCode } from './lib/errorCodes';
8+
import { StatusCodes } from 'http-status-codes';
79

810
const mocks = vi.hoisted(() => {
911
return {
1012
// Defaults to a empty session.
1113
auth: vi.fn(async (): Promise<Session | null> => null),
1214
headers: vi.fn(async (): Promise<Headers> => new Headers()),
1315
hasEntitlement: vi.fn((_entitlement: string) => false),
16+
env: {} as Record<string, string>,
1417
}
1518
});
1619

@@ -40,7 +43,7 @@ vi.mock('@sourcebot/shared', () => ({
4043
OAUTH_ACCESS_TOKEN_PREFIX: 'sboa_',
4144
API_KEY_PREFIX: 'sbk_',
4245
LEGACY_API_KEY_PREFIX: 'sourcebot-',
43-
env: {}
46+
env: mocks.env,
4447
}));
4548

4649
// Test utility to set the mock session
@@ -70,6 +73,8 @@ beforeEach(() => {
7073
vi.clearAllMocks();
7174
mocks.auth.mockResolvedValue(null);
7275
mocks.headers.mockResolvedValue(new Headers());
76+
// Reset env flags between tests
77+
Object.keys(mocks.env).forEach(key => delete mocks.env[key]);
7378
});
7479

7580
describe('getAuthenticatedUser', () => {
@@ -80,9 +85,10 @@ describe('getAuthenticatedUser', () => {
8085
id: userId,
8186
});
8287
setMockSession(createMockSession({ user: { id: 'test-user-id' } }));
83-
const user = await getAuthenticatedUser();
84-
expect(user).not.toBeUndefined();
85-
expect(user?.id).toBe(userId);
88+
const result = await getAuthenticatedUser();
89+
expect(result).not.toBeUndefined();
90+
expect(result?.user.id).toBe(userId);
91+
expect(result?.source).toBe('session');
8692
});
8793

8894
test('should return a user object if a valid api key is present', async () => {
@@ -98,9 +104,10 @@ describe('getAuthenticatedUser', () => {
98104
});
99105

100106
setMockHeaders(new Headers({ 'X-Sourcebot-Api-Key': 'sourcebot-apikey' }));
101-
const user = await getAuthenticatedUser();
102-
expect(user).not.toBeUndefined();
103-
expect(user?.id).toBe(userId);
107+
const result = await getAuthenticatedUser();
108+
expect(result).not.toBeUndefined();
109+
expect(result?.user.id).toBe(userId);
110+
expect(result?.source).toBe('api_key');
104111
expect(prisma.apiKey.update).toHaveBeenCalledWith({
105112
where: {
106113
hash: 'apikey',
@@ -124,9 +131,10 @@ describe('getAuthenticatedUser', () => {
124131
});
125132

126133
setMockHeaders(new Headers({ 'X-Sourcebot-Api-Key': 'sbk_apikey' }));
127-
const user = await getAuthenticatedUser();
128-
expect(user).not.toBeUndefined();
129-
expect(user?.id).toBe(userId);
134+
const result = await getAuthenticatedUser();
135+
expect(result).not.toBeUndefined();
136+
expect(result?.user.id).toBe(userId);
137+
expect(result?.source).toBe('api_key');
130138
expect(prisma.apiKey.update).toHaveBeenCalledWith({
131139
where: { hash: 'apikey' },
132140
data: { lastUsedAt: expect.any(Date) },
@@ -146,9 +154,10 @@ describe('getAuthenticatedUser', () => {
146154
});
147155

148156
setMockHeaders(new Headers({ 'Authorization': 'Bearer sourcebot-apikey' }));
149-
const user = await getAuthenticatedUser();
150-
expect(user).not.toBeUndefined();
151-
expect(user?.id).toBe(userId);
157+
const result = await getAuthenticatedUser();
158+
expect(result).not.toBeUndefined();
159+
expect(result?.user.id).toBe(userId);
160+
expect(result?.source).toBe('api_key');
152161
expect(prisma.apiKey.update).toHaveBeenCalledWith({
153162
where: {
154163
hash: 'apikey',
@@ -170,9 +179,10 @@ describe('getAuthenticatedUser', () => {
170179
mocks.hasEntitlement.mockReturnValue(true);
171180
prisma.oAuthToken.findUnique.mockResolvedValue(MOCK_OAUTH_TOKEN);
172181
setMockHeaders(new Headers({ 'Authorization': 'Bearer sboa_oauthtoken' }));
173-
const user = await getAuthenticatedUser();
174-
expect(user).not.toBeUndefined();
175-
expect(user?.id).toBe(MOCK_USER_WITH_ACCOUNTS.id);
182+
const result = await getAuthenticatedUser();
183+
expect(result).not.toBeUndefined();
184+
expect(result?.user.id).toBe(MOCK_USER_WITH_ACCOUNTS.id);
185+
expect(result?.source).toBe('oauth');
176186
});
177187

178188
test('should update lastUsedAt when an OAuth Bearer token is used', async () => {
@@ -380,6 +390,75 @@ describe('getAuthContext', () => {
380390
prisma: undefined,
381391
});
382392
});
393+
394+
describe('DISABLE_API_KEY_USAGE_FOR_NON_OWNER_USERS', () => {
395+
test('should return a 403 service error when flag is enabled and a non-owner authenticates via api key', async () => {
396+
mocks.env.DISABLE_API_KEY_USAGE_FOR_NON_OWNER_USERS = 'true';
397+
const userId = 'test-user-id';
398+
prisma.user.findUnique.mockResolvedValue({ ...MOCK_USER_WITH_ACCOUNTS, id: userId });
399+
prisma.org.findUnique.mockResolvedValue({ ...MOCK_ORG });
400+
prisma.userToOrg.findUnique.mockResolvedValue({
401+
joinedAt: new Date(),
402+
userId,
403+
orgId: MOCK_ORG.id,
404+
role: OrgRole.MEMBER,
405+
});
406+
prisma.apiKey.findUnique.mockResolvedValue({ ...MOCK_API_KEY, hash: 'apikey', createdById: userId });
407+
setMockHeaders(new Headers({ 'X-Sourcebot-Api-Key': 'sourcebot-apikey' }));
408+
409+
const authContext = await getAuthContext();
410+
expect(authContext).toStrictEqual({
411+
statusCode: StatusCodes.FORBIDDEN,
412+
errorCode: ErrorCode.API_KEY_USAGE_DISABLED,
413+
message: 'API key usage is disabled for non-admin users.',
414+
});
415+
});
416+
417+
test('should allow an owner to authenticate via api key when flag is enabled', async () => {
418+
mocks.env.DISABLE_API_KEY_USAGE_FOR_NON_OWNER_USERS = 'true';
419+
const userId = 'test-user-id';
420+
prisma.user.findUnique.mockResolvedValue({ ...MOCK_USER_WITH_ACCOUNTS, id: userId });
421+
prisma.org.findUnique.mockResolvedValue({ ...MOCK_ORG });
422+
prisma.userToOrg.findUnique.mockResolvedValue({
423+
joinedAt: new Date(),
424+
userId,
425+
orgId: MOCK_ORG.id,
426+
role: OrgRole.OWNER,
427+
});
428+
prisma.apiKey.findUnique.mockResolvedValue({ ...MOCK_API_KEY, hash: 'apikey', createdById: userId });
429+
setMockHeaders(new Headers({ 'X-Sourcebot-Api-Key': 'sourcebot-apikey' }));
430+
431+
const authContext = await getAuthContext();
432+
expect(authContext).toStrictEqual({
433+
user: { ...MOCK_USER_WITH_ACCOUNTS, id: userId },
434+
org: MOCK_ORG,
435+
role: OrgRole.OWNER,
436+
prisma: undefined,
437+
});
438+
});
439+
440+
test('should allow a non-owner to authenticate via session when flag is enabled', async () => {
441+
mocks.env.DISABLE_API_KEY_USAGE_FOR_NON_OWNER_USERS = 'true';
442+
const userId = 'test-user-id';
443+
prisma.user.findUnique.mockResolvedValue({ ...MOCK_USER_WITH_ACCOUNTS, id: userId });
444+
prisma.org.findUnique.mockResolvedValue({ ...MOCK_ORG });
445+
prisma.userToOrg.findUnique.mockResolvedValue({
446+
joinedAt: new Date(),
447+
userId,
448+
orgId: MOCK_ORG.id,
449+
role: OrgRole.MEMBER,
450+
});
451+
setMockSession(createMockSession({ user: { id: userId } }));
452+
453+
const authContext = await getAuthContext();
454+
expect(authContext).toStrictEqual({
455+
user: { ...MOCK_USER_WITH_ACCOUNTS, id: userId },
456+
org: MOCK_ORG,
457+
role: OrgRole.MEMBER,
458+
prisma: undefined,
459+
});
460+
});
461+
});
383462
});
384463

385464
describe('withAuthV2', () => {

0 commit comments

Comments
 (0)