fix: refresh token unified, and found some bugs
This commit is contained in:
parent
71174c1b8f
commit
943acec8e4
|
|
@ -38,6 +38,7 @@ import {
|
|||
Sections,
|
||||
} from '@gitroom/backend/services/auth/permissions/permission.exception.class';
|
||||
import { uniqBy } from 'lodash';
|
||||
import { RefreshIntegrationService } from '@gitroom/nestjs-libraries/integrations/refresh.integration.service';
|
||||
|
||||
@ApiTags('Integrations')
|
||||
@Controller('/integrations')
|
||||
|
|
@ -45,7 +46,8 @@ export class IntegrationsController {
|
|||
constructor(
|
||||
private _integrationManager: IntegrationManager,
|
||||
private _integrationService: IntegrationService,
|
||||
private _postService: PostsService
|
||||
private _postService: PostsService,
|
||||
private _refreshIntegrationService: RefreshIntegrationService
|
||||
) {}
|
||||
@Get('/')
|
||||
getIntegrations() {
|
||||
|
|
@ -338,37 +340,24 @@ export class IntegrationsController {
|
|||
return load;
|
||||
} catch (err) {
|
||||
if (err instanceof RefreshToken) {
|
||||
const { accessToken, refreshToken, expiresIn, additionalSettings } =
|
||||
await integrationProvider.refreshToken(getIntegration.refreshToken);
|
||||
const data = await this._refreshIntegrationService.refresh(
|
||||
getIntegration
|
||||
);
|
||||
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { accessToken } = data;
|
||||
|
||||
if (accessToken) {
|
||||
await this._integrationService.createOrUpdateIntegration(
|
||||
additionalSettings,
|
||||
!!integrationProvider.oneTimeToken,
|
||||
getIntegration.organizationId,
|
||||
getIntegration.name,
|
||||
getIntegration.picture!,
|
||||
'social',
|
||||
getIntegration.internalId,
|
||||
getIntegration.providerIdentifier,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
expiresIn
|
||||
);
|
||||
|
||||
getIntegration.token = accessToken;
|
||||
|
||||
if (integrationProvider.refreshWait) {
|
||||
await timer(10000);
|
||||
}
|
||||
return this.functionIntegration(org, body);
|
||||
} else {
|
||||
await this._integrationService.disconnectChannel(
|
||||
org.id,
|
||||
getIntegration
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
@ -459,7 +448,7 @@ export class IntegrationsController {
|
|||
refresh,
|
||||
auth.accessToken
|
||||
);
|
||||
return res(newAuth);
|
||||
return res({ ...newAuth, refreshToken: body.refresh });
|
||||
}
|
||||
|
||||
return res(auth);
|
||||
|
|
|
|||
|
|
@ -11,12 +11,14 @@ import { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/in
|
|||
import { RefreshToken } from '@gitroom/nestjs-libraries/integrations/social.abstract';
|
||||
import { timer } from '@gitroom/helpers/utils/timer';
|
||||
import { checkAuth } from '@gitroom/nestjs-libraries/chat/auth.context';
|
||||
import { RefreshIntegrationService } from '@gitroom/nestjs-libraries/integrations/refresh.integration.service';
|
||||
|
||||
@Injectable()
|
||||
export class IntegrationTriggerTool implements AgentToolInterface {
|
||||
constructor(
|
||||
private _integrationManager: IntegrationManager,
|
||||
private _integrationService: IntegrationService
|
||||
private _integrationService: IntegrationService,
|
||||
private _refreshIntegrationService: RefreshIntegrationService
|
||||
) {}
|
||||
name = 'triggerTool';
|
||||
|
||||
|
|
@ -103,40 +105,12 @@ export class IntegrationTriggerTool implements AgentToolInterface {
|
|||
|
||||
return { output: load };
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
if (err instanceof RefreshToken) {
|
||||
const {
|
||||
accessToken,
|
||||
refreshToken,
|
||||
expiresIn,
|
||||
additionalSettings,
|
||||
} = await integrationProvider.refreshToken(
|
||||
getIntegration.refreshToken
|
||||
const data = await this._refreshIntegrationService.refresh(
|
||||
getIntegration
|
||||
);
|
||||
|
||||
if (accessToken) {
|
||||
await this._integrationService.createOrUpdateIntegration(
|
||||
additionalSettings,
|
||||
!!integrationProvider.oneTimeToken,
|
||||
getIntegration.organizationId,
|
||||
getIntegration.name,
|
||||
getIntegration.picture!,
|
||||
'social',
|
||||
getIntegration.internalId,
|
||||
getIntegration.providerIdentifier,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
expiresIn
|
||||
);
|
||||
|
||||
getIntegration.token = accessToken;
|
||||
|
||||
if (integrationProvider.refreshWait) {
|
||||
await timer(10000);
|
||||
}
|
||||
|
||||
continue;
|
||||
} else {
|
||||
if (!data) {
|
||||
await this._integrationService.disconnectChannel(
|
||||
organizationId,
|
||||
getIntegration
|
||||
|
|
@ -146,6 +120,19 @@ export class IntegrationTriggerTool implements AgentToolInterface {
|
|||
'We had to disconnect the channel as the token expired',
|
||||
};
|
||||
}
|
||||
|
||||
const { accessToken } = data;
|
||||
|
||||
if (accessToken) {
|
||||
getIntegration.token = accessToken;
|
||||
|
||||
if (integrationProvider.refreshWait) {
|
||||
await timer(10000);
|
||||
}
|
||||
|
||||
continue;
|
||||
} else {
|
||||
}
|
||||
}
|
||||
return { output: 'Unexpected error' };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ import { ThirdPartyRepository } from '@gitroom/nestjs-libraries/database/prisma/
|
|||
import { ThirdPartyService } from '@gitroom/nestjs-libraries/database/prisma/third-party/third-party.service';
|
||||
import { VideoManager } from '@gitroom/nestjs-libraries/videos/video.manager';
|
||||
import { FalService } from '@gitroom/nestjs-libraries/openai/fal.service';
|
||||
import { RefreshIntegrationService } from '@gitroom/nestjs-libraries/integrations/refresh.integration.service';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
|
|
@ -80,6 +81,7 @@ import { FalService } from '@gitroom/nestjs-libraries/openai/fal.service';
|
|||
ItemUserService,
|
||||
MessagesService,
|
||||
IntegrationManager,
|
||||
RefreshIntegrationService,
|
||||
ExtractContentService,
|
||||
OpenaiService,
|
||||
FalService,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { forwardRef, HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
|
||||
import { IntegrationRepository } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.repository';
|
||||
import { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integration.manager';
|
||||
import {
|
||||
|
|
@ -19,6 +19,7 @@ import { BullMqClient } from '@gitroom/nestjs-libraries/bull-mq-transport-new/cl
|
|||
import { difference, uniq } from 'lodash';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
import { AutopostRepository } from '@gitroom/nestjs-libraries/database/prisma/autopost/autopost.repository';
|
||||
import { RefreshIntegrationService } from '@gitroom/nestjs-libraries/integrations/refresh.integration.service';
|
||||
|
||||
dayjs.extend(utc);
|
||||
|
||||
|
|
@ -30,7 +31,9 @@ export class IntegrationService {
|
|||
private _autopostsRepository: AutopostRepository,
|
||||
private _integrationManager: IntegrationManager,
|
||||
private _notificationService: NotificationService,
|
||||
private _workerServiceProducer: BullMqClient
|
||||
private _workerServiceProducer: BullMqClient,
|
||||
@Inject(forwardRef(() => RefreshIntegrationService))
|
||||
private _refreshIntegrationService: RefreshIntegrationService
|
||||
) {}
|
||||
|
||||
async changeActiveCron(orgId: string) {
|
||||
|
|
@ -333,39 +336,16 @@ export class IntegrationService {
|
|||
dayjs(getIntegration?.tokenExpiration).isBefore(dayjs()) ||
|
||||
forceRefresh
|
||||
) {
|
||||
const { accessToken, expiresIn, refreshToken, additionalSettings } =
|
||||
await new Promise<AuthTokenDetails>((res) => {
|
||||
return integrationProvider
|
||||
.refreshToken(getIntegration.refreshToken!)
|
||||
.then((r) => res(r))
|
||||
.catch(() => {
|
||||
res({
|
||||
error: '',
|
||||
accessToken: '',
|
||||
id: '',
|
||||
name: '',
|
||||
picture: '',
|
||||
username: '',
|
||||
additionalSettings: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
const data = await this._refreshIntegrationService.refresh(
|
||||
getIntegration
|
||||
);
|
||||
if (!data) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const { accessToken } = data;
|
||||
|
||||
if (accessToken) {
|
||||
await this.createOrUpdateIntegration(
|
||||
additionalSettings,
|
||||
!!integrationProvider.oneTimeToken,
|
||||
getIntegration.organizationId,
|
||||
getIntegration.name,
|
||||
getIntegration.picture!,
|
||||
'social',
|
||||
getIntegration.internalId,
|
||||
getIntegration.providerIdentifier,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
expiresIn
|
||||
);
|
||||
|
||||
getIntegration.token = accessToken;
|
||||
|
||||
if (integrationProvider.refreshWait) {
|
||||
|
|
@ -464,51 +444,13 @@ export class IntegrationService {
|
|||
dayjs(getIntegration?.tokenExpiration).isBefore(dayjs()) ||
|
||||
forceRefresh
|
||||
) {
|
||||
const { accessToken, expiresIn, refreshToken, additionalSettings } =
|
||||
await new Promise<AuthTokenDetails>((res) => {
|
||||
getSocialIntegration
|
||||
.refreshToken(getIntegration.refreshToken!)
|
||||
.then((r) => res(r))
|
||||
.catch(() =>
|
||||
res({
|
||||
accessToken: '',
|
||||
expiresIn: 0,
|
||||
refreshToken: '',
|
||||
id: '',
|
||||
name: '',
|
||||
username: '',
|
||||
picture: '',
|
||||
additionalSettings: undefined,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
if (!accessToken) {
|
||||
await this.refreshNeeded(
|
||||
getIntegration.organizationId,
|
||||
getIntegration.id
|
||||
);
|
||||
|
||||
await this.informAboutRefreshError(
|
||||
getIntegration.organizationId,
|
||||
getIntegration
|
||||
);
|
||||
return {};
|
||||
}
|
||||
|
||||
await this.createOrUpdateIntegration(
|
||||
additionalSettings,
|
||||
!!getSocialIntegration.oneTimeToken,
|
||||
getIntegration.organizationId,
|
||||
getIntegration.name,
|
||||
getIntegration.picture!,
|
||||
'social',
|
||||
getIntegration.internalId,
|
||||
getIntegration.providerIdentifier,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
expiresIn
|
||||
const data = await this._refreshIntegrationService.refresh(
|
||||
getIntegration
|
||||
);
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
const { accessToken } = data;
|
||||
|
||||
getIntegration.token = accessToken;
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import { validate } from 'class-validator';
|
|||
import { stripHtmlValidation } from '@gitroom/helpers/utils/strip.html.validation';
|
||||
dayjs.extend(utc);
|
||||
import * as Sentry from '@sentry/nestjs';
|
||||
import { RefreshIntegrationService } from '@gitroom/nestjs-libraries/integrations/refresh.integration.service';
|
||||
|
||||
type PostWithConditionals = Post & {
|
||||
integration?: Integration;
|
||||
|
|
@ -59,7 +60,8 @@ export class PostsService {
|
|||
private _mediaService: MediaService,
|
||||
private _shortLinkService: ShortLinkService,
|
||||
private _webhookService: WebhooksService,
|
||||
private openaiService: OpenaiService
|
||||
private openaiService: OpenaiService,
|
||||
private _refreshIntegrationService: RefreshIntegrationService
|
||||
) {}
|
||||
|
||||
checkPending15minutesBack() {
|
||||
|
|
@ -405,7 +407,7 @@ export class PostsService {
|
|||
integration: Integration,
|
||||
posts: Post[],
|
||||
forceRefresh = false
|
||||
): Promise<Partial<{ postId: string; releaseURL: string }>> {
|
||||
): Promise<Partial<{ postId: string; releaseURL: string }> | undefined> {
|
||||
const getIntegration = this._integrationManager.getSocialIntegration(
|
||||
integration.providerIdentifier
|
||||
);
|
||||
|
|
@ -415,53 +417,13 @@ export class PostsService {
|
|||
}
|
||||
|
||||
if (dayjs(integration?.tokenExpiration).isBefore(dayjs()) || forceRefresh) {
|
||||
const { accessToken, expiresIn, refreshToken, additionalSettings } =
|
||||
await new Promise<AuthTokenDetails>((res) => {
|
||||
getIntegration
|
||||
.refreshToken(integration.refreshToken!)
|
||||
.then((r) => res(r))
|
||||
.catch(() =>
|
||||
res({
|
||||
accessToken: '',
|
||||
expiresIn: 0,
|
||||
refreshToken: '',
|
||||
id: '',
|
||||
name: '',
|
||||
username: '',
|
||||
picture: '',
|
||||
additionalSettings: undefined,
|
||||
})
|
||||
);
|
||||
});
|
||||
const data = await this._refreshIntegrationService.refresh(integration);
|
||||
|
||||
if (!accessToken) {
|
||||
await this._integrationService.refreshNeeded(
|
||||
integration.organizationId,
|
||||
integration.id
|
||||
);
|
||||
|
||||
await this._integrationService.informAboutRefreshError(
|
||||
integration.organizationId,
|
||||
integration
|
||||
);
|
||||
return {};
|
||||
if (!data) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
await this._integrationService.createOrUpdateIntegration(
|
||||
additionalSettings,
|
||||
!!getIntegration.oneTimeToken,
|
||||
integration.organizationId,
|
||||
integration.name,
|
||||
integration.picture!,
|
||||
'social',
|
||||
integration.internalId,
|
||||
integration.providerIdentifier,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
expiresIn
|
||||
);
|
||||
|
||||
integration.token = accessToken;
|
||||
integration.token = data.accessToken;
|
||||
|
||||
if (getIntegration.refreshWait) {
|
||||
await timer(10000);
|
||||
|
|
@ -718,7 +680,7 @@ export class PostsService {
|
|||
});
|
||||
}
|
||||
|
||||
Sentry.metrics.count("post_created", 1);
|
||||
Sentry.metrics.count('post_created', 1);
|
||||
postList.push({
|
||||
postId: posts[0].id,
|
||||
integration: post.integration.id,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
import { forwardRef, Inject, Injectable } from '@nestjs/common';
|
||||
import { Integration } from '@prisma/client';
|
||||
import { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integration.manager';
|
||||
import { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';
|
||||
import {
|
||||
AuthTokenDetails,
|
||||
SocialProvider,
|
||||
} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';
|
||||
|
||||
@Injectable()
|
||||
export class RefreshIntegrationService {
|
||||
constructor(
|
||||
private _integrationManager: IntegrationManager,
|
||||
@Inject(forwardRef(() => IntegrationService))
|
||||
private _integrationService: IntegrationService
|
||||
) {}
|
||||
async refresh(integration: Integration): Promise<false | AuthTokenDetails> {
|
||||
const socialProvider = this._integrationManager.getSocialIntegration(
|
||||
integration.providerIdentifier
|
||||
);
|
||||
|
||||
const refresh = await this.refreshProcess(integration, socialProvider);
|
||||
|
||||
if (!refresh) {
|
||||
return false as const;
|
||||
}
|
||||
|
||||
await this._integrationService.createOrUpdateIntegration(
|
||||
undefined,
|
||||
!!socialProvider.oneTimeToken,
|
||||
integration.organizationId,
|
||||
integration.name,
|
||||
integration.picture!,
|
||||
'social',
|
||||
integration.internalId,
|
||||
integration.providerIdentifier,
|
||||
refresh.accessToken,
|
||||
refresh.refreshToken,
|
||||
refresh.expiresIn
|
||||
);
|
||||
|
||||
return refresh;
|
||||
}
|
||||
|
||||
private async refreshProcess(
|
||||
integration: Integration,
|
||||
socialProvider: SocialProvider
|
||||
): Promise<AuthTokenDetails | false> {
|
||||
const refresh: false | AuthTokenDetails = await socialProvider
|
||||
.refreshToken(integration.refreshToken)
|
||||
.catch((err) => false);
|
||||
|
||||
if (!refresh) {
|
||||
await this._integrationService.refreshNeeded(
|
||||
integration.organizationId,
|
||||
integration.id
|
||||
);
|
||||
|
||||
await this._integrationService.informAboutRefreshError(
|
||||
integration.organizationId,
|
||||
integration
|
||||
);
|
||||
|
||||
await this._integrationService.disconnectChannel(integration.organizationId, integration);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!socialProvider.reConnect) {
|
||||
return refresh;
|
||||
}
|
||||
|
||||
const reConnect = await socialProvider.reConnect(
|
||||
integration.rootInternalId,
|
||||
integration.internalId,
|
||||
refresh.accessToken
|
||||
);
|
||||
|
||||
return {
|
||||
...refresh,
|
||||
...reConnect,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -182,7 +182,7 @@ export class FacebookProvider extends SocialAbstract implements SocialProvider {
|
|||
id: string,
|
||||
requiredId: string,
|
||||
accessToken: string
|
||||
): Promise<AuthTokenDetails> {
|
||||
): Promise<Omit<AuthTokenDetails, 'refreshToken' | 'expiresIn'>> {
|
||||
const information = await this.fetchPageInformation(accessToken, {
|
||||
page: requiredId,
|
||||
});
|
||||
|
|
@ -191,8 +191,6 @@ export class FacebookProvider extends SocialAbstract implements SocialProvider {
|
|||
id: information.id,
|
||||
name: information.name,
|
||||
accessToken: information.access_token,
|
||||
refreshToken: information.access_token,
|
||||
expiresIn: dayjs().add(59, 'days').unix() - dayjs().unix(),
|
||||
picture: information.picture,
|
||||
username: information.username,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -320,7 +320,7 @@ export class GmbProvider extends SocialAbstract implements SocialProvider {
|
|||
id: string,
|
||||
requiredId: string,
|
||||
accessToken: string
|
||||
): Promise<AuthTokenDetails> {
|
||||
): Promise<Omit<AuthTokenDetails, 'refreshToken' | 'expiresIn'>> {
|
||||
const pages = await this.pages(accessToken);
|
||||
const findPage = pages.find((p) => p.id === requiredId);
|
||||
|
||||
|
|
@ -338,8 +338,6 @@ export class GmbProvider extends SocialAbstract implements SocialProvider {
|
|||
id: information.id,
|
||||
name: information.name,
|
||||
accessToken: information.access_token,
|
||||
refreshToken: information.access_token,
|
||||
expiresIn: dayjs().add(59, 'days').unix() - dayjs().unix(),
|
||||
picture: information.picture,
|
||||
username: information.username,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -292,7 +292,6 @@ export class InstagramProvider
|
|||
};
|
||||
}
|
||||
|
||||
console.log('err', body);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
|
@ -300,7 +299,7 @@ export class InstagramProvider
|
|||
id: string,
|
||||
requiredId: string,
|
||||
accessToken: string
|
||||
): Promise<AuthTokenDetails> {
|
||||
): Promise<Omit<AuthTokenDetails, 'refreshToken' | 'expiresIn'>> {
|
||||
const findPage = (await this.pages(accessToken)).find(
|
||||
(p) => p.id === requiredId
|
||||
);
|
||||
|
|
@ -314,8 +313,6 @@ export class InstagramProvider
|
|||
id: information.id,
|
||||
name: information.name,
|
||||
accessToken: information.access_token,
|
||||
refreshToken: information.access_token,
|
||||
expiresIn: dayjs().add(59, 'days').unix() - dayjs().unix(),
|
||||
picture: information.picture,
|
||||
username: information.username,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ export class LinkedinPageProvider
|
|||
id: string,
|
||||
requiredId: string,
|
||||
accessToken: string
|
||||
): Promise<AuthTokenDetails> {
|
||||
): Promise<Omit<AuthTokenDetails, 'refreshToken' | 'expiresIn'>> {
|
||||
const information = await this.fetchPageInformation(accessToken, {
|
||||
page: requiredId,
|
||||
});
|
||||
|
|
@ -158,8 +158,6 @@ export class LinkedinPageProvider
|
|||
id: information.id,
|
||||
name: information.name,
|
||||
accessToken: information.access_token,
|
||||
refreshToken: information.access_token,
|
||||
expiresIn: dayjs().add(59, 'days').unix() - dayjs().unix(),
|
||||
picture: information.picture,
|
||||
username: information.username,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export interface IAuthenticator {
|
|||
id: string,
|
||||
requiredId: string,
|
||||
accessToken: string
|
||||
): Promise<AuthTokenDetails>;
|
||||
): Promise<Omit<AuthTokenDetails, 'refreshToken' | 'expiresIn'>>;
|
||||
generateAuthUrl(
|
||||
clientInformation?: ClientInformation
|
||||
): Promise<GenerateAuthUrlResponse>;
|
||||
|
|
|
|||
|
|
@ -257,7 +257,7 @@ export class YoutubeProvider extends SocialAbstract implements SocialProvider {
|
|||
id: string,
|
||||
requiredId: string,
|
||||
accessToken: string
|
||||
): Promise<AuthTokenDetails> {
|
||||
): Promise<Omit<AuthTokenDetails, 'refreshToken' | 'expiresIn'>> {
|
||||
const pages = await this.pages(accessToken);
|
||||
const findPage = pages.find((p) => p.id === requiredId);
|
||||
|
||||
|
|
@ -273,8 +273,6 @@ export class YoutubeProvider extends SocialAbstract implements SocialProvider {
|
|||
id: information.id,
|
||||
name: information.name,
|
||||
accessToken: information.access_token,
|
||||
refreshToken: information.access_token,
|
||||
expiresIn: dayjs().add(59, 'days').unix() - dayjs().unix(),
|
||||
picture: information.picture,
|
||||
username: information.username,
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue