Skip to content

Commit b43a04f

Browse files
committed
fix: refactor codebase and remove sensitive details
1 parent 0072812 commit b43a04f

22 files changed

+661
-290
lines changed

Diff for: packages/core/src/lib/auth/auth.controller.ts

+36-1
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ import {
88
Headers,
99
Query,
1010
UseGuards,
11-
BadRequestException
11+
BadRequestException,
12+
Res
1213
} from '@nestjs/common';
14+
import { Response } from 'express';
1315
import { ApiTags, ApiOperation, ApiResponse, ApiOkResponse, ApiBadRequestResponse } from '@nestjs/swagger';
1416
import { CommandBus } from '@nestjs/cqrs';
1517
import { I18nLang } from 'nestjs-i18n';
@@ -149,6 +151,39 @@ export class AuthController {
149151
return await this.commandBus.execute(new AuthLoginCommand(input));
150152
}
151153

154+
/**
155+
* Handle successful login for Zapier OAuth flow
156+
* This endpoint is called after successful authentication when the login was initiated by Zapier
157+
*/
158+
@ApiOperation({ summary: 'Handle successful login for Zapier OAuth' })
159+
@ApiResponse({
160+
status: HttpStatus.CREATED,
161+
description: 'Successful authentication and redirection to the callback URL'
162+
})
163+
@ApiResponse({
164+
status: HttpStatus.BAD_REQUEST,
165+
description: 'Invalid input, the response body may contain clues as to what went wrong'
166+
})
167+
@Get('login/success')
168+
async loginSuccess(@Query() query: any, @Res() res: Response) {
169+
try {
170+
// Check if this is a Zapier auth flow
171+
const { zapier_redirect_uri, zapier_state } = query;
172+
173+
if (zapier_redirect_uri && zapier_state) {
174+
// This was a Zapier OAuth flow, redirect to callback with state
175+
const callbackUrl = `${process.env['API_BASE_URL']}/api/integration/zapier/oauth/callback?state=${zapier_state}`;
176+
177+
return res.redirect(callbackUrl);
178+
}
179+
180+
// Regular login flow
181+
return res.redirect('/dashboard');
182+
} catch (error) {
183+
return res.redirect('/auth/login?error=authentication_failed');
184+
}
185+
}
186+
152187
/**
153188
* Sign in workspaces by email and password.
154189
*

Diff for: packages/core/src/lib/core/context/request-context.ts

+31
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,37 @@ export class RequestContext {
216216
}
217217
}
218218

219+
/**
220+
* Retrieves the current organization ID from the request context.
221+
* @returns {string | null} - The current organization ID if available, otherwise null.
222+
*/
223+
static currentOrganizationId(): ID | null {
224+
try {
225+
// Retrieve the current user from the request context
226+
const user: IUser = RequestContext.currentUser();
227+
if(!user) {
228+
return null;
229+
}
230+
// First check if lastOrganizationId exists (most recently used organization)
231+
if (user.lastOrganizationId) {
232+
return user.lastOrganizationId;
233+
}
234+
235+
// If not, check defaultOrganizationId
236+
if (user.defaultOrganizationId) {
237+
return user.defaultOrganizationId;
238+
}
239+
240+
// If none of the above, try to get the first organization from the organizations array
241+
if (user.organizations && user.organizations.length > 0) {
242+
return user.organizations[0].organizationId;
243+
}
244+
return null;
245+
} catch (error) {
246+
// Return null if an error occurs
247+
return null;
248+
}
249+
}
219250
/**
220251
* Retrieves the current user from the request context.
221252
* @param {boolean} throwError - Flag indicating whether to throw an error if user is not found.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { ZapierTimerStartedHandler } from './zapier-timer-started.handler';
2+
import { ZapierTimerStoppedHandler } from './zapier-timer-stopped.handler';
3+
4+
5+
export const EventHandlers = [ZapierTimerStartedHandler, ZapierTimerStoppedHandler]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { EventsHandler, IEventHandler } from '@nestjs/cqrs';
2+
import { Injectable } from '@nestjs/common';
3+
import { TimerStartedEvent } from '@gauzy/core';
4+
import { ZapierWebhookService } from '../zapier-webhook.service';
5+
6+
@Injectable()
7+
@EventsHandler(TimerStartedEvent)
8+
export class ZapierTimerStartedHandler implements IEventHandler<TimerStartedEvent> {
9+
constructor(private readonly zapierWebhookService: ZapierWebhookService) {}
10+
/**
11+
* Handles the TimerStartedEvent by notifying Zapier webhooks
12+
*
13+
* @param event - The TimerStartedEvent that contains the time log details
14+
* @returns A Promise that resolves once the webhooks are notified
15+
*/
16+
async handle(event: TimerStartedEvent): Promise<void> {
17+
await this.zapierWebhookService.notifyTimerStatusChanged({
18+
event: 'timer.status.changed',
19+
action: 'start',
20+
data: event.timeLog,
21+
});
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { EventsHandler, IEventHandler } from '@nestjs/cqrs';
2+
import { Injectable } from '@nestjs/common';
3+
import { TimerStoppedEvent } from '@gauzy/core';
4+
import { ZapierWebhookService } from '../zapier-webhook.service';
5+
6+
@Injectable()
7+
@EventsHandler(TimerStoppedEvent)
8+
export class ZapierTimerStoppedHandler implements IEventHandler<TimerStoppedEvent> {
9+
constructor(private readonly zapierWebhookService: ZapierWebhookService) {}
10+
11+
/**
12+
* Handles the TimerStoppedEvent by notifying Zapier webhooks
13+
*
14+
* @param event - The TimerStoppedEvent that contains the time log details.
15+
* @returns A Promise that resolves once the webhooks are notified
16+
*/
17+
async handle(event: TimerStoppedEvent): Promise<void> {
18+
await this.zapierWebhookService.notifyTimerStatusChanged({
19+
event: 'timer.status.changed',
20+
action: 'stop',
21+
data: event.timeLog
22+
});
23+
}
24+
}

Diff for: packages/plugins/integration-zapier/src/lib/integration-zapier.plugin.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,23 @@ interface ZapierCustomFields extends CustomEmbeddedFields {
8080
export class IntegrationZapierPlugin implements IOnPluginBootstrap, IOnPluginDestroy {
8181
// We enable by default additional logging for each event to avoid cluttering the logs
8282
private logEnabled = true;
83+
private readonly logger = new Logger(IntegrationZapierPlugin.name);
8384

8485
/**
8586
* Called when the plugin is being initialized.
8687
*/
8788
onPluginBootstrap(): void | Promise<void> {
8889
if (this.logEnabled) {
8990
console.log(chalk.green(`${IntegrationZapierPlugin.name} is being bootstrapped...`));
91+
92+
// Log Zapier configuration status
93+
const clientId = process.env['GAUZY_ZAPIER_CLIENT_ID'];
94+
const clientSecret = process.env['GAUZY_ZAPIER_CLIENT_SECRET'];
95+
if(!clientId || !clientSecret) {
96+
this.logger.warn('Zapier OAuth credentials not fully configured! Please set GAUZY_ZAPIER_CLIENT_ID and GAUZY_ZAPIER_CLIENT_SECRET')
97+
} else {
98+
this.logger.log('Zapier OAuth credentials configured successfully');
99+
}
90100
}
91101
}
92102

@@ -95,8 +105,7 @@ export class IntegrationZapierPlugin implements IOnPluginBootstrap, IOnPluginDes
95105
*/
96106
onPluginDestroy(): void | Promise<void> {
97107
if (this.logEnabled) {
98-
const logger = new Logger(IntegrationZapierPlugin.name);
99-
logger.log(`${IntegrationZapierPlugin.name} is being destroyed...`)
108+
this.logger.log(`${IntegrationZapierPlugin.name} is being destroyed...`)
100109
}
101110
}
102111
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { Injectable, Logger } from '@nestjs/common';
2+
import * as moment from 'moment';
3+
import { v4 as uuidv4 } from 'uuid';
4+
import { ID } from '@gauzy/contracts';
5+
6+
@Injectable()
7+
export class ZapierAuthCodeService {
8+
private readonly logger = new Logger(ZapierAuthCodeService.name);
9+
10+
// Using a Map to store temporary auth codes - this is temporary storage
11+
// and doesn't need to be persisted to the database
12+
private authCodes: Map<string, {
13+
userId: string,
14+
expiresAt: Date,
15+
tenantId: string,
16+
organizationId?: string
17+
}> = new Map();
18+
19+
/**
20+
* Generates and stores an authentication code for a user
21+
*
22+
* @param userId The user's ID
23+
* @param tenantId The tenant ID
24+
* @param organizationId The organization ID
25+
* @returns The generated authorization code
26+
*/
27+
28+
generateAuthCode(userId: ID, tenantId: ID, organizationId?: ID): String {
29+
// Generation of a unique code
30+
const code = uuidv4();
31+
// Auth codes expire in 60 minutes
32+
const expiresAt = moment().add(60, 'minutes').toDate();
33+
34+
// Stores the code with user infos
35+
this.authCodes.set(code, {
36+
userId: userId.toString(),
37+
tenantId: tenantId.toString(),
38+
organizationId: organizationId?.toString(),
39+
expiresAt
40+
});
41+
this.logger.debug(`Generated auth code for user ${userId}, expires at ${expiresAt}`);
42+
return code;
43+
}
44+
45+
/**
46+
* Gets the user information associated with an auth code
47+
*
48+
* @param code The authorization code
49+
* @returns The user info or null if code is invalid or expired
50+
*/
51+
getUserInfoFromAuthCode(code: string): {
52+
userId: string,
53+
tenantId: string,
54+
organizationId?: string
55+
} | null {
56+
const authCodeData = this.authCodes.get(code);
57+
58+
// Check if code exists and is not expired
59+
if (authCodeData && moment().isBefore(authCodeData.expiresAt)) {
60+
this.authCodes.delete(code);
61+
62+
return {
63+
userId: authCodeData.userId,
64+
tenantId: authCodeData.tenantId,
65+
organizationId: authCodeData.organizationId
66+
};
67+
}
68+
return null;
69+
}
70+
}

0 commit comments

Comments
 (0)