Fix invalid token after credentials change (#4717)
- If sync fails we set authFailedAt - This information is displayed in the frontend in accounts with a `Sync Failed` pill - The user can reconnect his account in the dropdown menu - A new OAuth flow is triggered - The account is synced
This commit is contained in:
@ -47,11 +47,10 @@ export class GoogleAPIsAuthController {
|
||||
throw new Error('Workspace not found');
|
||||
}
|
||||
|
||||
await this.googleAPIsService.saveConnectedAccount({
|
||||
await this.googleAPIsService.saveOrUpdateConnectedAccount({
|
||||
handle: email,
|
||||
workspaceMemberId: workspaceMemberId,
|
||||
workspaceId: workspaceId,
|
||||
provider: 'google',
|
||||
accessToken,
|
||||
refreshToken,
|
||||
});
|
||||
|
||||
@ -12,7 +12,7 @@ import { EnvironmentService } from 'src/engine/integrations/environment/environm
|
||||
@Controller('auth/google-gmail')
|
||||
export class GoogleGmailAuthController {
|
||||
constructor(
|
||||
private readonly googleGmailService: GoogleAPIsService,
|
||||
private readonly googleAPIsService: GoogleAPIsService,
|
||||
private readonly tokenService: TokenService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
) {}
|
||||
@ -47,11 +47,10 @@ export class GoogleGmailAuthController {
|
||||
throw new Error('Workspace not found');
|
||||
}
|
||||
|
||||
await this.googleGmailService.saveConnectedAccount({
|
||||
await this.googleAPIsService.saveOrUpdateConnectedAccount({
|
||||
handle: email,
|
||||
workspaceMemberId: workspaceMemberId,
|
||||
workspaceId: workspaceId,
|
||||
provider: 'gmail',
|
||||
accessToken,
|
||||
refreshToken,
|
||||
});
|
||||
|
||||
@ -3,7 +3,7 @@ import { ArgsType, Field } from '@nestjs/graphql';
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
@ArgsType()
|
||||
export class SaveConnectedAccountInput {
|
||||
export class SaveOrUpdateConnectedAccountInput {
|
||||
@Field(() => String)
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
@ -19,11 +19,6 @@ export class SaveConnectedAccountInput {
|
||||
@IsString()
|
||||
workspaceId: string;
|
||||
|
||||
@Field(() => String)
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
provider: string;
|
||||
|
||||
@Field(() => String)
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
import { ArgsType, Field } from '@nestjs/graphql';
|
||||
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
@ArgsType()
|
||||
export class UpdateConnectedAccountInput {
|
||||
@Field(() => String)
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
workspaceId: string;
|
||||
|
||||
@Field(() => String)
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
accessToken: string;
|
||||
|
||||
@Field(() => String)
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
refreshToken: string;
|
||||
|
||||
@Field(() => String)
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
connectedAccountId: string;
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
import { Injectable, CanActivate, NotFoundException } from '@nestjs/common';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { GoogleAPIsStrategy } from 'src/engine/core-modules/auth/strategies/google-apis.auth.strategy';
|
||||
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||
|
||||
@Injectable()
|
||||
export class GoogleGmailProviderEnabledGuard implements CanActivate {
|
||||
constructor(private readonly environmentService: EnvironmentService) {}
|
||||
|
||||
canActivate(): boolean | Promise<boolean> | Observable<boolean> {
|
||||
if (!this.environmentService.get('MESSAGING_PROVIDER_GMAIL_ENABLED')) {
|
||||
throw new NotFoundException('Gmail auth is not enabled');
|
||||
}
|
||||
|
||||
new GoogleAPIsStrategy(this.environmentService);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import { ConflictException, Inject, Injectable } from '@nestjs/common';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { v4 } from 'uuid';
|
||||
@ -6,7 +6,7 @@ import { Repository } from 'typeorm';
|
||||
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
||||
import { SaveConnectedAccountInput } from 'src/engine/core-modules/auth/dto/save-connected-account';
|
||||
import { SaveOrUpdateConnectedAccountInput } from 'src/engine/core-modules/auth/dto/save-connected-account';
|
||||
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
|
||||
import { MessageQueueService } from 'src/engine/integrations/message-queue/services/message-queue.service';
|
||||
import {
|
||||
@ -26,6 +26,7 @@ import {
|
||||
GmailFullSyncV2Job,
|
||||
GmailFullSyncV2JobData,
|
||||
} from 'src/modules/messaging/jobs/gmail-full-sync-v2.job';
|
||||
import { UpdateConnectedAccountInput } from 'src/engine/core-modules/auth/dto/update-connected-account';
|
||||
|
||||
@Injectable()
|
||||
export class GoogleAPIsService {
|
||||
@ -43,8 +44,37 @@ export class GoogleAPIsService {
|
||||
|
||||
providerName = 'google';
|
||||
|
||||
async saveOrUpdateConnectedAccount(
|
||||
saveOrUpdateConnectedAccountInput: SaveOrUpdateConnectedAccountInput,
|
||||
) {
|
||||
const { handle, workspaceId, workspaceMemberId } =
|
||||
saveOrUpdateConnectedAccountInput;
|
||||
|
||||
const dataSourceMetadata =
|
||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const workspaceDataSource =
|
||||
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
||||
|
||||
const connectedAccount = await workspaceDataSource?.query(
|
||||
`SELECT * FROM ${dataSourceMetadata.schema}."connectedAccount" WHERE "handle" = $1 AND "provider" = $2 AND "accountOwnerId" = $3`,
|
||||
[handle, this.providerName, workspaceMemberId],
|
||||
);
|
||||
|
||||
if (connectedAccount.length > 0) {
|
||||
await this.updateConnectedAccount({
|
||||
...saveOrUpdateConnectedAccountInput,
|
||||
connectedAccountId: connectedAccount[0].id,
|
||||
});
|
||||
} else {
|
||||
await this.saveConnectedAccount(saveOrUpdateConnectedAccountInput);
|
||||
}
|
||||
}
|
||||
|
||||
async saveConnectedAccount(
|
||||
saveConnectedAccountInput: SaveConnectedAccountInput,
|
||||
saveConnectedAccountInput: SaveOrUpdateConnectedAccountInput,
|
||||
) {
|
||||
const {
|
||||
handle,
|
||||
@ -62,15 +92,6 @@ export class GoogleAPIsService {
|
||||
const workspaceDataSource =
|
||||
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
||||
|
||||
const connectedAccount = await workspaceDataSource?.query(
|
||||
`SELECT * FROM ${dataSourceMetadata.schema}."connectedAccount" WHERE "handle" = $1 AND "provider" = $2 AND "accountOwnerId" = $3`,
|
||||
[handle, this.providerName, workspaceMemberId],
|
||||
);
|
||||
|
||||
if (connectedAccount.length > 0) {
|
||||
throw new ConflictException('Connected account already exists');
|
||||
}
|
||||
|
||||
const connectedAccountId = v4();
|
||||
|
||||
const IsCalendarEnabled = await this.featureFlagRepository.findOneBy({
|
||||
@ -79,12 +100,6 @@ export class GoogleAPIsService {
|
||||
value: true,
|
||||
});
|
||||
|
||||
const isFullSyncV2Enabled = await this.featureFlagRepository.findOneBy({
|
||||
workspaceId,
|
||||
key: FeatureFlagKeys.IsFullSyncV2Enabled,
|
||||
value: true,
|
||||
});
|
||||
|
||||
await workspaceDataSource?.transaction(async (manager) => {
|
||||
await manager.query(
|
||||
`INSERT INTO ${dataSourceMetadata.schema}."connectedAccount" ("id", "handle", "provider", "accessToken", "refreshToken", "accountOwnerId") VALUES ($1, $2, $3, $4, $5, $6)`,
|
||||
@ -117,34 +132,78 @@ export class GoogleAPIsService {
|
||||
});
|
||||
|
||||
if (this.environmentService.get('MESSAGING_PROVIDER_GMAIL_ENABLED')) {
|
||||
if (isFullSyncV2Enabled) {
|
||||
await this.messageQueueService.add<GmailFullSyncV2JobData>(
|
||||
GmailFullSyncV2Job.name,
|
||||
{
|
||||
workspaceId,
|
||||
connectedAccountId,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
await this.messageQueueService.add<GmailFullSyncJobData>(
|
||||
GmailFullSyncJob.name,
|
||||
{
|
||||
workspaceId,
|
||||
connectedAccountId,
|
||||
},
|
||||
{
|
||||
retryLimit: 2,
|
||||
},
|
||||
);
|
||||
}
|
||||
await this.enqueueGmailFullSyncJob(workspaceId, connectedAccountId);
|
||||
}
|
||||
|
||||
if (
|
||||
this.environmentService.get('CALENDAR_PROVIDER_GOOGLE_ENABLED') &&
|
||||
IsCalendarEnabled
|
||||
) {
|
||||
await this.calendarQueueService.add<GoogleCalendarFullSyncJobData>(
|
||||
GoogleCalendarFullSyncJob.name,
|
||||
await this.enqueueGoogleCalendarFullSyncJob(
|
||||
workspaceId,
|
||||
connectedAccountId,
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
async updateConnectedAccount(
|
||||
updateConnectedAccountInput: UpdateConnectedAccountInput,
|
||||
) {
|
||||
const { workspaceId, accessToken, refreshToken, connectedAccountId } =
|
||||
updateConnectedAccountInput;
|
||||
|
||||
const dataSourceMetadata =
|
||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const workspaceDataSource =
|
||||
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
||||
|
||||
await workspaceDataSource?.transaction(async (manager) => {
|
||||
await manager.query(
|
||||
`UPDATE ${dataSourceMetadata.schema}."connectedAccount" SET "accessToken" = $1, "refreshToken" = $2, "authFailedAt" = NULL WHERE "id" = $3`,
|
||||
[accessToken, refreshToken, connectedAccountId],
|
||||
);
|
||||
});
|
||||
|
||||
if (this.environmentService.get('MESSAGING_PROVIDER_GMAIL_ENABLED')) {
|
||||
await this.enqueueGmailFullSyncJob(workspaceId, connectedAccountId);
|
||||
}
|
||||
|
||||
if (this.environmentService.get('CALENDAR_PROVIDER_GOOGLE_ENABLED')) {
|
||||
await this.enqueueGoogleCalendarFullSyncJob(
|
||||
workspaceId,
|
||||
connectedAccountId,
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
async enqueueGmailFullSyncJob(
|
||||
workspaceId: string,
|
||||
connectedAccountId: string,
|
||||
) {
|
||||
const isFullSyncV2Enabled = await this.featureFlagRepository.findOneBy({
|
||||
workspaceId,
|
||||
key: FeatureFlagKeys.IsFullSyncV2Enabled,
|
||||
value: true,
|
||||
});
|
||||
|
||||
if (isFullSyncV2Enabled) {
|
||||
await this.messageQueueService.add<GmailFullSyncV2JobData>(
|
||||
GmailFullSyncV2Job.name,
|
||||
{
|
||||
workspaceId,
|
||||
connectedAccountId,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
await this.messageQueueService.add<GmailFullSyncJobData>(
|
||||
GmailFullSyncJob.name,
|
||||
{
|
||||
workspaceId,
|
||||
connectedAccountId,
|
||||
@ -154,7 +213,21 @@ export class GoogleAPIsService {
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
async enqueueGoogleCalendarFullSyncJob(
|
||||
workspaceId: string,
|
||||
connectedAccountId: string,
|
||||
) {
|
||||
await this.calendarQueueService.add<GoogleCalendarFullSyncJobData>(
|
||||
GoogleCalendarFullSyncJob.name,
|
||||
{
|
||||
workspaceId,
|
||||
connectedAccountId,
|
||||
},
|
||||
{
|
||||
retryLimit: 2,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user