Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
36 changes: 36 additions & 0 deletions apps/api/src/app/controllers/desktop-app.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@ import { createHmac } from 'crypto';
import { fromUnixTime } from 'date-fns';
import { z } from 'zod';
import * as userSyncDbService from '../db/data-sync.db';
import * as orgGroupsDb from '../db/organization.db';
import * as salesforceOrgsDb from '../db/salesforce-org.db';
import * as userDbService from '../db/user.db';
import { checkUserEntitlement } from '../db/user.db';
import * as webExtDb from '../db/web-extension.db';
import { emitRecordSyncEventsToOtherClients, SyncEvent } from '../services/data-sync-broadcast.service';
import * as externalAuthService from '../services/external-auth.service';
import { decryptJwtTokenOrPlaintext } from '../services/jwt-token-encryption.service';
import { UserFacingError } from '../utils/error-handler';
import { redirect, sendJson } from '../utils/response.handlers';
import { createRoute, RouteValidator } from '../utils/route.utils';
import { routeDefinition as dataSyncController } from './data-sync.controller';
Expand Down Expand Up @@ -82,6 +85,13 @@ export const routeDefinition = {
hasSourceOrg: false,
} satisfies RouteValidator,
},
getOrgs: {
controllerFn: () => getOrgs,
responseType: z.any(),
validators: {
hasSourceOrg: false,
} satisfies RouteValidator,
},
Comment on lines +88 to +94
dataSyncPull: {
controllerFn: () => dataSyncPull,
responseType: z.any(),
Expand All @@ -104,6 +114,13 @@ export const routeDefinition = {
hasSourceOrg: false,
} satisfies RouteValidator,
},
googleConfig: {
controllerFn: () => googleConfig,
responseType: z.object({ appId: z.string(), apiKey: z.string(), clientId: z.string() }),
validators: {
hasSourceOrg: false,
} satisfies RouteValidator,
},
};

/**
Expand Down Expand Up @@ -224,6 +241,17 @@ const logout = createRoute(routeDefinition.logout.validators, async ({ user }, _
}
});

const getOrgs = createRoute(routeDefinition.getOrgs.validators, async ({ user }, _, res, next) => {
try {
const orgs = await salesforceOrgsDb.findByUserId(user.id);
const organizations = await orgGroupsDb.findByUserId({ userId: user.id });

sendJson(res, { orgs, organizations });
} catch (ex) {
next(new UserFacingError(ex));
}
});

const dataSyncPull = createRoute(routeDefinition.dataSyncPull.validators, async ({ user, query }, _, res) => {
const { lastKey, updatedAt, limit } = query;
const response = await userSyncDbService.findByUpdatedAt({
Expand Down Expand Up @@ -259,6 +287,14 @@ const dataSyncPush = createRoute(routeDefinition.dataSyncPush.validators, async
sendJson(res, response);
});

const googleConfig = createRoute(routeDefinition.googleConfig.validators, async (_params, _req, res) => {
sendJson(res, {
appId: ENV.GOOGLE_APP_ID,
apiKey: ENV.GOOGLE_API_KEY,
clientId: ENV.GOOGLE_CLIENT_ID,
});
});

const notifications = createRoute(routeDefinition.notifications.validators, async ({ query, user }, req, res) => {
// TODO: reserved for future use (e.g. check if there is a critical update required, or auto-update is broken etc..)
const { os, version } = query;
Expand Down
15 changes: 15 additions & 0 deletions apps/api/src/app/controllers/web-extension.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ export const routeDefinition = {
...dataSyncController.push.validators,
} satisfies RouteValidator,
},
googleConfig: {
controllerFn: () => googleConfig,
responseType: z.object({ appId: z.string(), apiKey: z.string(), clientId: z.string() }),
validators: {
hasSourceOrg: false,
} satisfies RouteValidator,
},
};

/**
Expand Down Expand Up @@ -229,3 +236,11 @@ const dataSyncPush = createRoute(routeDefinition.dataSyncPush.validators, async

sendJson(res, response);
});

const googleConfig = createRoute(routeDefinition.googleConfig.validators, async (_params, _req, res) => {
sendJson(res, {
appId: ENV.GOOGLE_APP_ID,
apiKey: ENV.GOOGLE_API_KEY,
clientId: ENV.GOOGLE_CLIENT_ID,
});
});
15 changes: 13 additions & 2 deletions apps/api/src/app/routes/desktop-app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,19 @@ routes.use(
blockAllMixedContent: [],
fontSrc: ["'self'", 'https:'],
frameAncestors: ["'self'"],
imgSrc: ["'self'", '*.cloudinary.com'],
frameSrc: ["'self'", 'https://accounts.google.com', 'https://docs.google.com', 'https://drive.google.com'],
imgSrc: ["'self'", '*.cloudinary.com', '*.googleusercontent.com'],
objectSrc: ["'none'"],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
scriptSrc: ["'self'", (_, res) => `'nonce-${(res as any)?.locals?.nonce}'`],
scriptSrc: [
"'self'",
'https://apis.google.com',
'https://accounts.google.com',
(_, res) => `'nonce-${(res as any)?.locals?.nonce}'`,
],
scriptSrcAttr: ["'none'"],
styleSrc: ["'self'", 'https:', "'unsafe-inline'"],
connectSrc: ["'self'", 'https://www.googleapis.com', 'https://oauth2.googleapis.com'],
upgradeInsecureRequests: [],
},
},
Expand Down Expand Up @@ -78,9 +85,13 @@ routes.delete('/auth/logout', STRICT_AuthRateLimit, authMiddleware, desktopAppCo
/**
* Other Routes
*/
routes.get('/orgs', LAX_AuthRateLimit, authMiddleware, desktopAppController.routeDefinition.getOrgs.controllerFn());

routes.get('/data-sync/pull', authMiddleware, desktopAppController.routeDefinition.dataSyncPull.controllerFn());
routes.post('/data-sync/push', authMiddleware, desktopAppController.routeDefinition.dataSyncPush.controllerFn());

routes.get('/google-config', LAX_AuthRateLimit, authMiddleware, desktopAppController.routeDefinition.googleConfig.controllerFn());

routes.get('/v1/notifications', STRICT_2X_AuthRateLimit, authMiddleware, desktopAppController.routeDefinition.notifications.controllerFn());

routes.post(
Expand Down
13 changes: 11 additions & 2 deletions apps/api/src/app/routes/web-extension-server.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,19 @@ routes.use(
blockAllMixedContent: [],
fontSrc: ["'self'", 'https:'],
frameAncestors: ["'self'"],
imgSrc: ["'self'", '*.cloudinary.com'],
frameSrc: ["'self'", 'https://accounts.google.com', 'https://docs.google.com', 'https://drive.google.com'],
imgSrc: ["'self'", '*.cloudinary.com', '*.googleusercontent.com'],
objectSrc: ["'none'"],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
scriptSrc: ["'self'", (_, res) => `'nonce-${(res as any)?.locals?.nonce}'`],
scriptSrc: [
"'self'",
'https://apis.google.com',
'https://accounts.google.com',
(_, res) => `'nonce-${(res as any)?.locals?.nonce}'`,
],
scriptSrcAttr: ["'none'"],
styleSrc: ["'self'", 'https:', "'unsafe-inline'"],
connectSrc: ["'self'", 'https://www.googleapis.com', 'https://oauth2.googleapis.com'],
upgradeInsecureRequests: [],
},
},
Expand Down Expand Up @@ -100,6 +107,8 @@ routes.delete('/auth/logout', STRICT_AuthRateLimit, authMiddleware, webExtension
routes.get('/data-sync/pull', authMiddleware, webExtensionController.routeDefinition.dataSyncPull.controllerFn());
routes.post('/data-sync/push', authMiddleware, webExtensionController.routeDefinition.dataSyncPush.controllerFn());

routes.get('/google-config', LAX_AuthRateLimit, authMiddleware, webExtensionController.routeDefinition.googleConfig.controllerFn());

routes.post(
'/feedback',
feedbackRateLimit,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { HTTP } from '@jetstream/shared/constants';
import { app } from 'electron';
import { z } from 'zod';
import { ENV } from '../config/environment';
import { getAppData } from '../services/persistence.service';
import { createRoute, handleErrorResponse, RouteValidator } from '../utils/route.utils';
import { createRoute, getTokens, handleErrorResponse, RouteValidator } from '../utils/route.utils';

/**
* FIXME: this file needs to be worked on
Expand All @@ -27,16 +26,6 @@ export const routeDefinition = {
},
};

function getTokens() {
const { accessToken, deviceId } = getAppData();

if (!accessToken || !deviceId) {
throw new Error('Unauthorized');
}

return { authTokens: { accessToken }, extIdentifier: { id: deviceId } };
}

const pull = createRoute(routeDefinition.pull.validators, async ({ query }) => {
try {
const { authTokens, extIdentifier } = getTokens();
Expand Down
34 changes: 30 additions & 4 deletions apps/jetstream-desktop/src/controllers/orgs.desktop.controller.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { ApiRequestError } from '@jetstream/salesforce-api';
import { ERROR_MESSAGES } from '@jetstream/shared/constants';
import { SObjectOrganization } from '@jetstream/types';
import { ERROR_MESSAGES, HTTP } from '@jetstream/shared/constants';
import { OrgsWithGroupResponse, SObjectOrganization } from '@jetstream/types';
import { app } from 'electron';
import logger from 'electron-log';
import { z } from 'zod';
import { ENV } from '../config/environment';
import * as dataService from '../services/persistence.service';
import { createRoute, handleErrorResponse, handleJsonResponse, RouteValidator } from '../utils/route.utils';
import { createRoute, getTokens, handleErrorResponse, handleJsonResponse, RouteValidator } from '../utils/route.utils';

export const routeDefinition = {
getOrgs: {
Expand Down Expand Up @@ -52,7 +55,30 @@ export const routeDefinition = {

const getOrgs = createRoute(routeDefinition.getOrgs.validators, async ({}) => {
try {
const orgs = dataService.getSalesforceOrgs().map((org) => ({ ...org, accessToken: undefined }));
const { authTokens, extIdentifier } = getTokens();

const webAppOrgs = await fetch(`${ENV.SERVER_URL}/desktop-app/orgs`, {
method: 'GET',
headers: {
Accept: 'application/json',
Authorization: `Bearer ${authTokens?.accessToken}`,
[HTTP.HEADERS.X_EXT_DEVICE_ID]: extIdentifier.id,
[HTTP.HEADERS.X_APP_VERSION]: app.getVersion(),
},
})
.then(async (res) => {
if (!res.ok) {
logger.warn('Failed to fetch orgs from web app', { error: await res.text().catch(() => 'Unable to read response body') });
return null;
}
return (res.json() as Promise<{ data: OrgsWithGroupResponse }>).then(({ data }) => data);
})
.catch((ex) => {
logger.warn('Failed to fetch orgs from web app', { error: ex });
return null;
});

const orgs = dataService.mergeWebAppOrgsWithDesktopOrgs(webAppOrgs).map((org) => ({ ...org, accessToken: undefined }));

return handleJsonResponse(orgs);
} catch (ex) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { app } from 'electron';
import { z } from 'zod';
import { ENV } from '../config/environment';
import * as dataService from '../services/persistence.service';
import { createRoute, handleErrorResponse, handleJsonResponse, RouteValidator } from '../utils/route.utils';
import { createRoute, getTokens, handleErrorResponse, handleJsonResponse, RouteValidator } from '../utils/route.utils';

export const routeDefinition = {
getUserProfile: {
Expand Down Expand Up @@ -59,16 +59,6 @@ const updateProfile = createRoute(routeDefinition.updateProfile.validators, asyn
}
});

function getTokens() {
const { accessToken, deviceId } = dataService.getAppData();

if (!accessToken || !deviceId) {
throw new Error('Unauthorized');
}

return { authTokens: { accessToken }, extIdentifier: { id: deviceId } };
}

const sendUserFeedbackEmail = createRoute(routeDefinition.sendUserFeedbackEmail.validators, async ({}, req) => {
try {
const { authTokens, extIdentifier } = getTokens();
Expand Down
6 changes: 6 additions & 0 deletions apps/jetstream-desktop/src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ const API: ElectronAPI = {
ipcRenderer.on(IpcEventChannel.openSettings, handler);
return () => ipcRenderer.removeListener(IpcEventChannel.openSettings, handler);
},
onGooglePickerResult: (callback) => {
const handler = (_event, payload) => callback(payload);
ipcRenderer.on(IpcEventChannel.googlePickerResult, handler);
return () => ipcRenderer.removeListener(IpcEventChannel.googlePickerResult, handler);
},
// One-Way from Client
login: () => ipcRenderer.invoke('login'),
logout: () => ipcRenderer.invoke('logout'),
Expand All @@ -55,6 +60,7 @@ const API: ElectronAPI = {
checkForUpdates: (userInitiated) => ipcRenderer.invoke('checkForUpdates', userInitiated),
getUpdateStatus: () => ipcRenderer.invoke('getUpdateStatus'),
installUpdate: () => ipcRenderer.invoke('installUpdate'),
openGooglePicker: (payload) => ipcRenderer.invoke('openGooglePicker', payload),
};

contextBridge.exposeInMainWorld('electronAPI', API);
Loading
Loading