remove delted at clause on user

This commit is contained in:
dal 2025-07-16 10:27:33 -06:00
parent 2bc11900b9
commit cbd8b21203
No known key found for this signature in database
GPG Key ID: 16F4B0E1E9F61122
3 changed files with 133 additions and 34 deletions

View File

@ -20,7 +20,7 @@ describe('slack-authentication', () => {
const mockIntegration = {
id: 'int-123',
organizationId: 'org-123',
userId: 'user-123',
userId: 'installer-123',
tokenVaultKey: 'token-key',
};
@ -40,7 +40,7 @@ describe('slack-authentication', () => {
mockIntegration as any
);
vi.mocked(SlackHelpers.getAccessToken).mockResolvedValue('slack-token');
vi.mocked(SlackHelpers.getUserById).mockResolvedValue(mockUser as any);
vi.mocked(SlackHelpers.getUserByEmail).mockResolvedValue(mockUser as any);
const mockSlackUserService = {
isBot: vi.fn().mockResolvedValue(false),
@ -76,6 +76,11 @@ describe('slack-authentication', () => {
expect(mockSlackUserService.isBot).toHaveBeenCalledWith('slack-token', 'U123456');
expect(mockSlackUserService.isDeleted).toHaveBeenCalledWith('slack-token', 'U123456');
expect(mockSlackUserService.getUserInfo).toHaveBeenCalledWith('slack-token', 'U123456');
expect(vi.mocked(SlackHelpers.getUserByEmail)).toHaveBeenCalledWith('john@example.com');
expect(vi.mocked(accessControls.checkUserInOrganization)).toHaveBeenCalledWith(
'user-123',
'org-123'
);
});
it('should return unauthorized for bot users', async () => {
@ -152,14 +157,21 @@ describe('slack-authentication', () => {
const mockIntegration = {
id: 'int-123',
organizationId: 'org-123',
userId: 'user-123',
userId: 'installer-123',
tokenVaultKey: 'token-key',
};
const mockUser = {
id: 'user-123',
email: 'john@example.com',
name: 'John Doe',
};
vi.mocked(SlackHelpers.getActiveIntegrationByTeamId).mockResolvedValue(
mockIntegration as any
);
vi.mocked(SlackHelpers.getAccessToken).mockResolvedValue('slack-token');
vi.mocked(SlackHelpers.getUserByEmail).mockResolvedValue(mockUser as any);
const mockSlackUserService = {
isBot: vi.fn().mockResolvedValue(false),
@ -215,6 +227,7 @@ describe('slack-authentication', () => {
mockIntegration as any
);
vi.mocked(SlackHelpers.getAccessToken).mockResolvedValue('slack-token');
vi.mocked(SlackHelpers.getUserByEmail).mockResolvedValue(null); // No existing user
const mockSlackUserService = {
isBot: vi.fn().mockResolvedValue(false),
@ -230,7 +243,6 @@ describe('slack-authentication', () => {
};
vi.mocked(SlackUserService).mockImplementation(() => mockSlackUserService as any);
vi.mocked(accessControls.checkUserInOrganization).mockResolvedValue(null);
vi.mocked(accessControls.getOrganizationWithDefaults).mockResolvedValue(mockOrg as any);
vi.mocked(accessControls.checkEmailDomainForOrganization).mockResolvedValue(true);
vi.mocked(accessControls.createUserInOrganization).mockResolvedValue({
@ -259,6 +271,69 @@ describe('slack-authentication', () => {
);
});
it('should auto-provision existing user when they are not in org but domain matches', async () => {
const mockIntegration = {
id: 'int-123',
organizationId: 'org-123',
userId: 'installer-123',
tokenVaultKey: 'token-key',
};
const mockOrg = {
id: 'org-123',
name: 'Example Org',
defaultRole: 'restricted_querier',
domains: ['example.com'],
};
const mockExistingUser = {
id: 'existing-user-123',
email: 'existing@example.com',
name: 'Existing User',
};
vi.mocked(SlackHelpers.getActiveIntegrationByTeamId).mockResolvedValue(
mockIntegration as any
);
vi.mocked(SlackHelpers.getAccessToken).mockResolvedValue('slack-token');
vi.mocked(SlackHelpers.getUserByEmail).mockResolvedValue(mockExistingUser as any);
const mockSlackUserService = {
isBot: vi.fn().mockResolvedValue(false),
isDeleted: vi.fn().mockResolvedValue(false),
getUserInfo: vi.fn().mockResolvedValue({
id: 'U123456',
real_name: 'Existing User',
name: 'existing',
profile: {
email: 'existing@example.com',
},
}),
};
vi.mocked(SlackUserService).mockImplementation(() => mockSlackUserService as any);
vi.mocked(accessControls.checkUserInOrganization).mockResolvedValue(null); // Not in org
vi.mocked(accessControls.getOrganizationWithDefaults).mockResolvedValue(mockOrg as any);
vi.mocked(accessControls.checkEmailDomainForOrganization).mockResolvedValue(true);
vi.mocked(accessControls.createUserInOrganization).mockResolvedValue({
user: mockExistingUser as any,
membership: {
userId: 'existing-user-123',
organizationId: 'org-123',
role: 'restricted_querier',
status: 'active',
},
});
const result = await authenticateSlackUser('U123456', 'T123456');
expect(result).toEqual({
type: 'auto_provisioned',
user: mockExistingUser,
organization: mockOrg,
});
});
it('should return unauthorized when domain does not match', async () => {
const mockIntegration = {
id: 'int-123',
@ -278,6 +353,7 @@ describe('slack-authentication', () => {
mockIntegration as any
);
vi.mocked(SlackHelpers.getAccessToken).mockResolvedValue('slack-token');
vi.mocked(SlackHelpers.getUserByEmail).mockResolvedValue(null); // No existing user
const mockSlackUserService = {
isBot: vi.fn().mockResolvedValue(false),
@ -293,7 +369,6 @@ describe('slack-authentication', () => {
};
vi.mocked(SlackUserService).mockImplementation(() => mockSlackUserService as any);
vi.mocked(accessControls.checkUserInOrganization).mockResolvedValue(null);
vi.mocked(accessControls.getOrganizationWithDefaults).mockResolvedValue(mockOrg as any);
vi.mocked(accessControls.checkEmailDomainForOrganization).mockResolvedValue(false);

View File

@ -98,39 +98,42 @@ export async function authenticateSlackUser(
};
}
// Check if user exists in the organization
const userOrgInfo = await checkUserInOrganization(
integration.userId,
integration.organizationId
);
// Check if a Buster user exists with this email
const existingUser = await SlackHelpers.getUserByEmail(userEmail);
if (existingUser) {
// User exists - check if they belong to this organization
const userOrgInfo = await checkUserInOrganization(
existingUser.id,
integration.organizationId
);
if (userOrgInfo) {
// User exists in organization - check their status
if (userOrgInfo.status !== 'active') {
return {
type: 'unauthorized',
reason: `User account is ${userOrgInfo.status}. Please contact your administrator.`,
};
}
// Get organization data
const organization = await getOrganizationWithDefaults(integration.organizationId);
if (!organization) {
return {
type: 'unauthorized',
reason: 'Failed to load organization data',
};
}
if (userOrgInfo) {
// User exists - check their status
if (userOrgInfo.status !== 'active') {
return {
type: 'unauthorized',
reason: `User account is ${userOrgInfo.status}. Please contact your administrator.`,
type: 'authorized',
user: existingUser,
organization,
};
}
// Get full user and organization data concurrently
const [user, organization] = await Promise.all([
SlackHelpers.getUserById(integration.userId),
getOrganizationWithDefaults(integration.organizationId),
]);
if (!user || !organization) {
return {
type: 'unauthorized',
reason: 'Failed to load user or organization data',
};
}
return {
type: 'authorized',
user,
organization,
};
// User exists but not in this organization - fall through to domain check
}
// User doesn't exist - check if we can auto-provision

View File

@ -460,6 +460,26 @@ export async function getUserById(userId: string): Promise<User | null> {
}
}
/**
* Get user by email address
*/
export async function getUserByEmail(email: string): Promise<User | null> {
try {
const [user] = await db
.select()
.from(users)
.where(and(eq(users.email, email)))
.limit(1);
return user || null;
} catch (error) {
console.error('Failed to get user by email:', error);
throw new Error(
`Failed to get user by email: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}
// Export as namespace for easier import
export const SlackHelpers = {
getActiveIntegration,
@ -477,4 +497,5 @@ export const SlackHelpers = {
getActiveIntegrationByTeamId,
getAccessToken,
getUserById,
getUserByEmail,
};