Download apps/backend/src/api/routes/no.auth.integrations.controller.ts from Leon4gr45/postio: direct link, hf CLI and curl.
- Browser
- Download file 11.8 kB
-
https://huggingface.co/spaces/Leon4gr45/postio/resolve/main/apps/backend/src/api/routes/no.auth.integrations.controller.ts
- Command line
-
hf download hf://spaces/Leon4gr45/postio/apps/backend/src/api/routes/no.auth.integrations.controller.ts
-
curl -L -o no.auth.integrations.controller.ts https://huggingface.co/spaces/Leon4gr45/postio/resolve/main/apps/backend/src/api/routes/no.auth.integrations.controller.ts
11.8 kB
| import { | |
| Body, | |
| Controller, | |
| Get, | |
| HttpException, | |
| Param, | |
| Post, | |
| UseFilters, | |
| } from '@nestjs/common'; | |
| import { ioRedis } from '@gitroom/nestjs-libraries/redis/redis.service'; | |
| import { ConnectIntegrationDto } from '@gitroom/nestjs-libraries/dtos/integrations/connect.integration.dto'; | |
| import { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integration.manager'; | |
| import { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service'; | |
| import { CheckPolicies } from '@gitroom/backend/services/auth/permissions/permissions.ability'; | |
| import { ApiTags } from '@nestjs/swagger'; | |
| import { NotEnoughScopesFilter } from '@gitroom/nestjs-libraries/integrations/integration.missing.scopes'; | |
| import { AuthService } from '@gitroom/helpers/auth/auth.service'; | |
| import { AuthTokenDetails } from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface'; | |
| import { NotEnoughScopes } from '@gitroom/nestjs-libraries/integrations/social.abstract'; | |
| import { | |
| AuthorizationActions, | |
| Sections, | |
| } from '@gitroom/backend/services/auth/permissions/permission.exception.class'; | |
| import { RefreshIntegrationService } from '@gitroom/nestjs-libraries/integrations/refresh.integration.service'; | |
| import { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service'; | |
| ('Integrations') | |
| ('/integrations') | |
| export class NoAuthIntegrationsController { | |
| constructor( | |
| private _integrationManager: IntegrationManager, | |
| private _integrationService: IntegrationService, | |
| private _refreshIntegrationService: RefreshIntegrationService, | |
| private _organizationService: OrganizationService | |
| ) {} | |
| ('/') | |
| getIntegrations() { | |
| return this._integrationManager.getAllIntegrations(); | |
| } | |
| ('/social-connect/:integration') | |
| ([AuthorizationActions.Create, Sections.CHANNEL]) | |
| (new NotEnoughScopesFilter()) | |
| async connectSocialMedia( | |
| ('integration') integration: string, | |
| () body: ConnectIntegrationDto | |
| ) { | |
| if ( | |
| !this._integrationManager | |
| .getAllowedSocialsIntegrations() | |
| .includes(integration) | |
| ) { | |
| throw new Error('Integration not allowed'); | |
| } | |
| const integrationProvider = | |
| this._integrationManager.getSocialIntegration(integration); | |
| const getCodeVerifier = integrationProvider.customFields | |
| ? 'none' | |
| : await ioRedis.get(`login:${body.state}`); | |
| if (!getCodeVerifier) { | |
| throw new Error('Invalid state'); | |
| } | |
| const organization = await ioRedis.get(`organization:${body.state}`); | |
| if (!organization) { | |
| throw new Error('Organization not found'); | |
| } | |
| const org = await this._organizationService.getOrgById(organization); | |
| if (!integrationProvider.customFields) { | |
| await ioRedis.del(`login:${body.state}`); | |
| } | |
| const details = integrationProvider.externalUrl | |
| ? await ioRedis.get(`external:${body.state}`) | |
| : undefined; | |
| if (details) { | |
| await ioRedis.del(`external:${body.state}`); | |
| } | |
| const refresh = await ioRedis.get(`refresh:${body.state}`); | |
| if (refresh) { | |
| await ioRedis.del(`refresh:${body.state}`); | |
| } | |
| const onboarding = await ioRedis.get(`onboarding:${body.state}`); | |
| if (onboarding) { | |
| await ioRedis.del(`onboarding:${body.state}`); | |
| } | |
| const { | |
| error, | |
| accessToken, | |
| expiresIn, | |
| refreshToken, | |
| id, | |
| name, | |
| picture, | |
| username, | |
| additionalSettings, | |
| // eslint-disable-next-line no-async-promise-executor | |
| } = await new Promise<AuthTokenDetails>(async (res) => { | |
| try { | |
| const auth = await integrationProvider.authenticate( | |
| { | |
| code: body.code, | |
| codeVerifier: getCodeVerifier, | |
| refresh: body.refresh, | |
| }, | |
| details ? JSON.parse(details) : undefined | |
| ); | |
| if (typeof auth === 'string') { | |
| return res({ | |
| error: auth, | |
| accessToken: '', | |
| id: '', | |
| name: '', | |
| picture: '', | |
| username: '', | |
| additionalSettings: [], | |
| }); | |
| } | |
| if (refresh && integrationProvider.reConnect) { | |
| console.log('reconnect'); | |
| try { | |
| const newAuth = await integrationProvider.reConnect( | |
| auth.id, | |
| refresh, | |
| auth.accessToken | |
| ); | |
| return res({ ...newAuth, refreshToken: body.refresh }); | |
| } catch (err: any) { | |
| return res({ | |
| error: err.message, | |
| accessToken: '', | |
| id: '', | |
| name: '', | |
| picture: '', | |
| username: '', | |
| additionalSettings: [], | |
| }); | |
| } | |
| } | |
| return res(auth); | |
| } catch (err) { | |
| if (err instanceof NotEnoughScopes) { | |
| return res({ | |
| error: err.message, | |
| accessToken: '', | |
| id: '', | |
| name: '', | |
| picture: '', | |
| username: '', | |
| additionalSettings: [], | |
| }); | |
| } | |
| return res({ | |
| error: 'Authentication failed', | |
| accessToken: '', | |
| id: '', | |
| name: '', | |
| picture: '', | |
| username: '', | |
| additionalSettings: [], | |
| }); | |
| } | |
| }); | |
| if (error) { | |
| throw new NotEnoughScopes(error); | |
| } | |
| if (!id) { | |
| throw new NotEnoughScopes('Invalid API key'); | |
| } | |
| if (refresh && String(id) !== String(refresh)) { | |
| throw new NotEnoughScopes( | |
| 'Please refresh the channel that needs to be refreshed' | |
| ); | |
| } | |
| let validName = name; | |
| if (!validName) { | |
| if (username) { | |
| validName = username.split('.')[0] ?? username; | |
| } else { | |
| validName = `Channel_${String(id).slice(0, 8)}`; | |
| } | |
| } | |
| if ( | |
| process.env.STRIPE_PUBLISHABLE_KEY && | |
| org.isTrailing && | |
| (await this._integrationService.checkPreviousConnections( | |
| org.id, | |
| String(id) | |
| )) | |
| ) { | |
| throw new HttpException('', 412); | |
| } | |
| const createUpdate = | |
| await this._integrationService.createOrUpdateIntegration( | |
| additionalSettings, | |
| !!integrationProvider.oneTimeToken, | |
| org.id, | |
| validName.trim(), | |
| picture, | |
| 'social', | |
| String(id), | |
| integration, | |
| accessToken, | |
| refreshToken, | |
| expiresIn, | |
| username, | |
| refresh ? false : integrationProvider.isBetweenSteps, | |
| body.refresh, | |
| +body.timezone, | |
| details | |
| ? AuthService.fixedEncryption(details) | |
| : integrationProvider.customFields | |
| ? AuthService.fixedEncryption( | |
| Buffer.from(body.code, 'base64').toString() | |
| ) | |
| : integrationProvider.isChromeExtension | |
| ? AuthService.fixedEncryption( | |
| Buffer.from(body.code, 'base64').toString() | |
| ) | |
| : undefined | |
| ); | |
| this._refreshIntegrationService | |
| .startRefreshWorkflow(org.id, createUpdate.id, integrationProvider) | |
| .catch((err) => { | |
| console.log(err); | |
| }); | |
| // Fetch pages if this is a two-step provider and not a refresh | |
| let pages: any[] = []; | |
| if (integrationProvider.isBetweenSteps && !refresh) { | |
| try { | |
| // Check which method the provider uses (pages or companies) | |
| const fetchMethod = | |
| 'pages' in integrationProvider | |
| ? 'pages' | |
| : 'companies' in integrationProvider | |
| ? 'companies' | |
| : null; | |
| if (fetchMethod) { | |
| // @ts-ignore - dynamic method call | |
| pages = await integrationProvider[fetchMethod](accessToken); | |
| } | |
| } catch (err) { | |
| console.log('Failed to fetch pages:', err); | |
| } | |
| } | |
| const webhookUrl = await ioRedis.get(`webhookUrl:${body.state}`); | |
| if (webhookUrl) { | |
| try { | |
| await fetch(webhookUrl, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| params: AuthService.signJWT({ | |
| apiKey: org.apiKey, | |
| }), | |
| }), | |
| }); | |
| } catch (err) {} | |
| await ioRedis.del(`webhookUrl:${body.state}`); | |
| } | |
| const returnURL = await ioRedis.get(`redirect:${body.state}`); | |
| if (returnURL) { | |
| await ioRedis.del(`redirect:${body.state}`); | |
| } | |
| const extensionToken = integrationProvider.isChromeExtension | |
| ? AuthService.signJWT({ | |
| integrationId: createUpdate.id, | |
| organizationId: org.id, | |
| internalId: String(id), | |
| provider: integration, | |
| }) | |
| : undefined; | |
| // Never leak stored credentials (signed/encrypted secrets) back to the | |
| // caller. These columns hold the integration access token, refresh token | |
| // and encrypted custom instance details and must stay server-side. | |
| const { | |
| token: _token, | |
| refreshToken: _refreshToken, | |
| customInstanceDetails: _customInstanceDetails, | |
| ...safeIntegration | |
| } = createUpdate as any; | |
| return { | |
| ...safeIntegration, | |
| onboarding: onboarding === 'true', | |
| pages, | |
| ...(returnURL ? { returnURL } : {}), | |
| ...(extensionToken ? { extensionToken } : {}), | |
| }; | |
| } | |
| ('/public/provider/:id/connect') | |
| async saveProviderPage(('id') id: string, () body: any) { | |
| if (!body.state) { | |
| throw new Error('Invalid state'); | |
| } | |
| const organization = await ioRedis.get(`organization:${body.state}`); | |
| if (!organization) { | |
| throw new Error('Organization not found'); | |
| } | |
| const org = await this._organizationService.getOrgById(organization); | |
| return this._integrationService.saveProviderPage(org.id, id, body); | |
| } | |
| ('/extension-refresh') | |
| async extensionRefreshCookies( | |
| () body: { jwt: string; cookies: string } | |
| ) { | |
| let payload: any; | |
| try { | |
| payload = AuthService.verifyJWT(body.jwt); | |
| } catch { | |
| throw new HttpException('Invalid token', 401); | |
| } | |
| const { integrationId, organizationId, internalId, provider } = payload; | |
| if (!integrationId || !organizationId || !internalId || !provider) { | |
| throw new HttpException('Invalid token payload', 400); | |
| } | |
| const integration = await this._integrationService.getIntegrationById( | |
| organizationId, | |
| integrationId | |
| ); | |
| if (!integration || integration.internalId !== internalId) { | |
| throw new HttpException('Integration not found', 404); | |
| } | |
| const integrationProvider = | |
| this._integrationManager.getSocialIntegration(provider); | |
| if (!integrationProvider?.isChromeExtension) { | |
| throw new HttpException('Not a Chrome extension integration', 400); | |
| } | |
| const authResult = await integrationProvider.authenticate({ | |
| code: body.cookies, | |
| codeVerifier: '', | |
| }); | |
| if (typeof authResult === 'string') { | |
| throw new HttpException(authResult, 400); | |
| } | |
| if (String(authResult.id) !== String(integration.internalId)) { | |
| await this._integrationService.refreshNeeded( | |
| organizationId, | |
| integrationId | |
| ); | |
| return { success: false, reason: 'account_mismatch' }; | |
| } | |
| await this._integrationService.createOrUpdateIntegration( | |
| undefined, | |
| false, | |
| organizationId, | |
| integration.name, | |
| undefined, | |
| 'social', | |
| integration.internalId, | |
| integration.providerIdentifier, | |
| authResult.accessToken, | |
| '', | |
| authResult.expiresIn, | |
| undefined, | |
| false, | |
| undefined, | |
| undefined, | |
| AuthService.signJWT( | |
| JSON.parse(Buffer.from(body.cookies, 'base64').toString()) | |
| ) | |
| ); | |
| return { success: true }; | |
| } | |
| } | |