Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

5748 Create contacts for emails sent and received by email aliases #5855

Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ad13e25
create service
bosiraphael Jun 11, 2024
fb9a34b
implement email-alias-manager
bosiraphael Jun 12, 2024
b462058
create oauth2-client-manager and refactor accordingly
bosiraphael Jun 12, 2024
57c3035
add isProfileEmailsReadEnabled logic in google-apis-provider-enabled.…
bosiraphael Jun 12, 2024
5c35469
create feature flag
bosiraphael Jun 12, 2024
1bb80df
add gate
bosiraphael Jun 12, 2024
1a18fe5
refactor email-alias-manager
bosiraphael Jun 12, 2024
e508134
refactor
bosiraphael Jun 12, 2024
37fa8aa
Merge branch 'main' into 5748-emails-sent-and-received-by-email-alias…
bosiraphael Jun 12, 2024
636ebe0
wip
bosiraphael Jun 12, 2024
9b5c872
wip
bosiraphael Jun 12, 2024
6141d28
saving email aliases is working
bosiraphael Jun 13, 2024
81e2516
update contact creation rule
bosiraphael Jun 13, 2024
df83e70
don't create contacts for own aliases
bosiraphael Jun 13, 2024
13ba49e
add try catch
bosiraphael Jun 13, 2024
d994d90
create IsFeatureEnabledService
bosiraphael Jun 13, 2024
08190d9
check if feature flag is enabled before refreshinf email alias
bosiraphael Jun 13, 2024
c2b35c4
fix modules
bosiraphael Jun 13, 2024
7b2950e
working with and without feature flag
bosiraphael Jun 13, 2024
ad68567
Merge branch 'main' into 5748-emails-sent-and-received-by-email-alias…
charlesBochet Jun 19, 2024
81c66bf
fix log
bosiraphael Jun 20, 2024
aec42b0
refactor auth gards
bosiraphael Jun 20, 2024
fc1dc06
Merge branch 'main' into 5748-emails-sent-and-received-by-email-alias…
bosiraphael Jun 24, 2024
c9fd241
Merge branch 'main' into 5748-emails-sent-and-received-by-email-alias…
bosiraphael Jun 27, 2024
fd232c4
Merge branch 'main' into 5748-emails-sent-and-received-by-email-alias…
charlesBochet Jul 1, 2024
df2083c
Rename flag
charlesBochet Jul 1, 2024
69ea9e6
Fixes post merge
charlesBochet Jul 1, 2024
32e42ed
Refactor into service vs workspace-service
charlesBochet Jul 1, 2024
bc43017
Fix lint
charlesBochet Jul 1, 2024
6f59eaa
Refactor auth guards
charlesBochet Jul 1, 2024
52ea360
Merge branch 'main' into 5748-emails-sent-and-received-by-email-alias…
charlesBochet Jul 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ export const seedFeatureFlags = async (
workspaceId: workspaceId,
value: true,
},
{
key: FeatureFlagKeys.IsProfileEmailsReadEnabled,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IsMessagingAliasFetchingEnabled

workspaceId: workspaceId,
value: true,
},
{
key: FeatureFlagKeys.IsGoogleCalendarSyncV2Enabled,
workspaceId: workspaceId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {

import { Response } from 'express';

import { GoogleAPIsProviderEnabledGuard } from 'src/engine/core-modules/auth/guards/google-apis-provider-enabled.guard';
import { GoogleAPIsOauthGuard } from 'src/engine/core-modules/auth/guards/google-apis-oauth.guard';
import { GoogleAPIsRequest } from 'src/engine/core-modules/auth/strategies/google-apis.auth.strategy';
import { GoogleAPIsService } from 'src/engine/core-modules/auth/services/google-apis.service';
Expand All @@ -29,22 +28,22 @@ export class GoogleAPIsAuthController {
) {}

@Get()
@UseGuards(GoogleAPIsProviderEnabledGuard, GoogleAPIsOauthGuard)
@UseGuards(GoogleAPIsOauthGuard)
async googleAuth() {
// As this method is protected by Google Auth guard, it will trigger Google SSO flow
return;
}

@Get('get-access-token')
@UseGuards(GoogleAPIsProviderEnabledGuard, GoogleAPIsOauthGuard)
@UseGuards()
async googleAuthGetAccessToken(
@Req() req: GoogleAPIsRequest,
@Res() res: Response,
) {
const { user } = req;

const {
email,
emails,
accessToken,
refreshToken,
transientToken,
Expand All @@ -68,14 +67,16 @@ export class GoogleAPIsAuthController {
throw new Error('Workspace not found');
}

const handle = emails[0].value;

const googleAPIsServiceInstance =
await this.loadServiceWithWorkspaceContext.load(
this.googleAPIsService,
workspaceId,
);

await googleAPIsServiceInstance.refreshGoogleRefreshToken({
handle: email,
handle,
workspaceMemberId: workspaceMemberId,
workspaceId: workspaceId,
accessToken,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,32 @@
import { ExecutionContext, Injectable } from '@nestjs/common';
import {
ExecutionContext,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { InjectRepository } from '@nestjs/typeorm';

import { Repository } from 'typeorm';

import { TokenService } from 'src/engine/core-modules/auth/services/token.service';
import {
GoogleAPIScopeConfig,
GoogleAPIsStrategy,
} from 'src/engine/core-modules/auth/strategies/google-apis.auth.strategy';
import {
FeatureFlagEntity,
FeatureFlagKeys,
} from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';

@Injectable()
export class GoogleAPIsOauthGuard extends AuthGuard('google-apis') {
constructor() {
constructor(
private readonly environmentService: EnvironmentService,
private readonly tokenService: TokenService,
@InjectRepository(FeatureFlagEntity, 'core')
private readonly featureFlagRepository: Repository<FeatureFlagEntity>,
) {
super({
prompt: 'select_account',
});
Expand All @@ -16,6 +39,31 @@ export class GoogleAPIsOauthGuard extends AuthGuard('google-apis') {
const calendarVisibility = request.query.calendarVisibility;
const messageVisibility = request.query.messageVisibility;

if (
!this.environmentService.get('MESSAGING_PROVIDER_GMAIL_ENABLED') &&
!this.environmentService.get('CALENDAR_PROVIDER_GOOGLE_ENABLED')
) {
throw new NotFoundException('Google apis auth is not enabled');
}

const { workspaceId } =
await this.tokenService.verifyTransientToken(transientToken);

const scopeConfig: GoogleAPIScopeConfig = {
isCalendarEnabled: !!this.environmentService.get(
'MESSAGING_PROVIDER_GMAIL_ENABLED',
),
isProfileEmailsReadEnabled: !!(await this.featureFlagRepository.findOneBy(
{
workspaceId,
key: FeatureFlagKeys.IsProfileEmailsReadEnabled,
value: true,
},
)),
};

new GoogleAPIsStrategy(this.environmentService, scopeConfig);

if (transientToken && typeof transientToken === 'string') {
request.params.transientToken = transientToken;
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export type GoogleAPIsRequest = Omit<
user: {
firstName?: string | null;
lastName?: string | null;
email: string;
emails: { value: string }[];
picture: string | null;
workspaceInviteHash?: string;
accessToken: string;
Expand All @@ -29,6 +29,7 @@ export type GoogleAPIsRequest = Omit<

export type GoogleAPIScopeConfig = {
isCalendarEnabled?: boolean;
isProfileEmailsReadEnabled?: boolean;
};

@Injectable()
Expand All @@ -53,6 +54,10 @@ export class GoogleAPIsStrategy extends PassportStrategy(
scope.push('https://www.googleapis.com/auth/calendar.events');
}

if (scopeConfig?.isProfileEmailsReadEnabled) {
scope.push('https://www.googleapis.com/auth/profile.emails.read');
}

super({
clientID: environmentService.get('AUTH_GOOGLE_CLIENT_ID'),
clientSecret: environmentService.get('AUTH_GOOGLE_CLIENT_SECRET'),
Expand Down Expand Up @@ -93,7 +98,7 @@ export class GoogleAPIsStrategy extends PassportStrategy(
: undefined;

const user: GoogleAPIsRequest['user'] = {
email: emails[0].value,
emails,
firstName: name.givenName,
lastName: name.familyName,
picture: photos?.[0]?.value,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export enum FeatureFlagKeys {
IsPostgreSQLIntegrationEnabled = 'IS_POSTGRESQL_INTEGRATION_ENABLED',
IsStripeIntegrationEnabled = 'IS_STRIPE_INTEGRATION_ENABLED',
IsContactCreationForSentAndReceivedEmailsEnabled = 'IS_CONTACT_CREATION_FOR_SENT_AND_RECEIVED_EMAILS_ENABLED',
IsProfileEmailsReadEnabled = 'IS_PROFILE_EMAILS_READ_ENABLED',
IsGoogleCalendarSyncV2Enabled = 'IS_GOOGLE_CALENDAR_SYNC_V2_ENABLED',
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';

import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { IsFeatureEnabledService } from 'src/engine/core-modules/feature-flag/services/is-feature-enabled.service';

@Module({
imports: [
Expand All @@ -17,7 +18,7 @@ import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-
resolvers: [],
}),
],
exports: [],
providers: [],
exports: [IsFeatureEnabledService],
providers: [IsFeatureEnabledService],
})
export class FeatureFlagModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';

import { Repository } from 'typeorm';

import {
FeatureFlagEntity,
FeatureFlagKeys,
} from 'src/engine/core-modules/feature-flag/feature-flag.entity';

@Injectable()
export class IsFeatureEnabledService {
constructor(
@InjectRepository(FeatureFlagEntity, 'core')
private readonly featureFlagRepository: Repository<FeatureFlagEntity>,
) {}

public async isFeatureEnabled(
key: FeatureFlagKeys,
workspaceId: string,
): Promise<boolean> {
const featureFlag = await this.featureFlagRepository.findOneBy({
workspaceId,
key,
value: true,
});
Comment on lines +22 to +26
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider handling potential errors from the database query.


return !!featureFlag?.value;
bosiraphael marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export class AddStandardIdCommand extends CommandRunner {
IS_POSTGRESQL_INTEGRATION_ENABLED: true,
IS_STRIPE_INTEGRATION_ENABLED: false,
IS_CONTACT_CREATION_FOR_SENT_AND_RECEIVED_EMAILS_ENABLED: true,
IS_PROFILE_EMAILS_READ_ENABLED: true,
IS_GOOGLE_CALENDAR_SYNC_V2_ENABLED: true,
},
);
Expand All @@ -75,6 +76,7 @@ export class AddStandardIdCommand extends CommandRunner {
IS_POSTGRESQL_INTEGRATION_ENABLED: true,
IS_STRIPE_INTEGRATION_ENABLED: false,
IS_CONTACT_CREATION_FOR_SENT_AND_RECEIVED_EMAILS_ENABLED: true,
IS_PROFILE_EMAILS_READ_ENABLED: true,
IS_GOOGLE_CALENDAR_SYNC_V2_ENABLED: true,
},
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export const CONNECTED_ACCOUNT_STANDARD_FIELD_IDS = {
authFailedAt: '20202020-d268-4c6b-baff-400d402b430a',
messageChannels: '20202020-24f7-4362-8468-042204d1e445',
calendarChannels: '20202020-af4a-47bb-99ec-51911c1d3977',
emailAliases: '20202020-8a3d-46be-814f-6228af16c47b',
};

export const EVENT_STANDARD_FIELD_IDS = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { Logger, Scope } from '@nestjs/common';

import { GoogleAPIRefreshAccessTokenService } from 'src/modules/connected-account/services/google-api-refresh-access-token/google-api-refresh-access-token.service';
import { GoogleCalendarSyncService } from 'src/modules/calendar/services/google-calendar-sync/google-calendar-sync.service';
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
import { ConnectedAccountRepository } from 'src/modules/connected-account/repositories/connected-account.repository';
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
Expand All @@ -21,6 +24,8 @@ export class GoogleCalendarSyncJob {
constructor(
private readonly googleAPIsRefreshAccessTokenService: GoogleAPIRefreshAccessTokenService,
private readonly googleCalendarSyncService: GoogleCalendarSyncService,
@InjectObjectMetadataRepository(ConnectedAccountWorkspaceEntity)
private readonly connectedAccountRepository: ConnectedAccountRepository,
) {}

@Process(GoogleCalendarSyncJob.name)
Expand All @@ -29,9 +34,22 @@ export class GoogleCalendarSyncJob {
`google calendar sync for workspace ${data.workspaceId} and account ${data.connectedAccountId}`,
);
try {
const { connectedAccountId, workspaceId } = data;

const connectedAccount = await this.connectedAccountRepository.getById(
connectedAccountId,
workspaceId,
);

if (!connectedAccount) {
throw new Error(
`No connected account found for ${connectedAccountId} in workspace ${workspaceId}`,
);
}

await this.googleAPIsRefreshAccessTokenService.refreshAndSaveAccessToken(
data.workspaceId,
data.connectedAccountId,
connectedAccount,
workspaceId,
);
} catch (e) {
this.logger.error(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,8 @@ export class GoogleCalendarSyncService {
const calendarChannelId = calendarChannel.id;

const { events, nextSyncToken } = await this.getEventsFromGoogleCalendar(
refreshToken,
connectedAccount,
workspaceId,
connectedAccountId,
emailOrDomainToReimport,
syncToken,
);
Expand Down Expand Up @@ -322,9 +321,8 @@ export class GoogleCalendarSyncService {
}

public async getEventsFromGoogleCalendar(
refreshToken: string,
connectedAccount: ObjectRecord<ConnectedAccountWorkspaceEntity>,
workspaceId: string,
connectedAccountId: string,
emailOrDomainToReimport?: string,
syncToken?: string,
): Promise<{
Expand All @@ -333,7 +331,7 @@ export class GoogleCalendarSyncService {
}> {
const googleCalendarClient =
await this.googleCalendarClientProvider.getGoogleCalendarClient(
refreshToken,
connectedAccount,
);

const startTime = Date.now();
Expand Down Expand Up @@ -361,15 +359,15 @@ export class GoogleCalendarSyncService {

await this.calendarChannelRepository.update(
{
id: connectedAccountId,
id: connectedAccount.id,
},
{
syncCursor: '',
},
);

this.logger.log(
`Sync token is no longer valid for connected account ${connectedAccountId} in workspace ${workspaceId}, resetting sync cursor.`,
`Sync token is no longer valid for connected account ${connectedAccount.id} in workspace ${workspaceId}, resetting sync cursor.`,
);

return {
Expand Down Expand Up @@ -400,9 +398,9 @@ export class GoogleCalendarSyncService {
const endTime = Date.now();

this.logger.log(
`google calendar sync for workspace ${workspaceId} and account ${connectedAccountId} getting events list in ${
endTime - startTime
}ms.`,
`google calendar sync for workspace ${workspaceId} and account ${
connectedAccount.id
} getting events list in ${endTime - startTime}ms.`,
);

return { events, nextSyncToken };
Expand Down
Loading
Loading