Merged
Conversation
…ogle OAuth error handling
- Fix unsafe return type in twilio.module.ts - Remove unnecessary condition checks in auth.controller.ts - Fix type definitions in google.strategy.ts - Add eslint-disable comments for console statements - Fix type assertions in auth.controller.ts
- Use 'as unknown as Twilio.Twilio' for mock client type assertion - Add eslint-disable comment for unsafe return type - Fixes TS2740 error for missing properties in mock object
- Add return types to mock Twilio client functions - Replace || with ?? operators in auth.controller.ts - Fix unnecessary optional chain in google.strategy.ts - Add eslint-disable for console.warn in google.strategy.ts - Use non-null assertion for validated profile fields
- Fix unnecessary optional chain error - Add non-null assertion since emails[0] is validated in the check above
- Replace profile?.emails?.[0] with explicit checks - profile parameter is not optional, so optional chain is unnecessary - Fixes @typescript-eslint/no-unnecessary-condition error
…eter - profile is a required parameter, so !profile check is unnecessary - Use profile.emails.length === 0 instead of !profile.emails[0] - Destructure name and emails directly from profile after validation - Fixes @typescript-eslint/no-unnecessary-condition error
- Add apiVersion to Stripe initialization for consistency - Fix street address parsing for 3-part and 4-part address formats - Add explicit demo step ID check (step 6) for skip action - Improve address parsing error message to show missing fields
- Create unified app-config.ts utility for frontend URL and CORS origin - Support multiple environment variable names for flexibility: - APP_URL, FRONTEND_URL, NEXT_PUBLIC_APP_URL (for frontend URL) - CORS_ORIGIN, FRONTEND_URL, APP_URL (for CORS origin) - Update all files to use centralized config functions - Improve compatibility with different environment variable naming conventions Files updated: - src/utils/app-config.ts (new) - src/main.ts - src/modules/auth/auth.controller.ts - src/modules/stripe/stripe.service.ts - src/modules/google-calendar/calendar-oauth.controller.ts
- Use only APP_URL for frontend URL (remove FRONTEND_URL, NEXT_PUBLIC_APP_URL) - Use only CORS_ORIGIN for CORS (remove FRONTEND_URL, APP_URL fallbacks) - Simplify code by using single environment variable names
- Add import statement for getAppUrl from @/utils/app-config - Fixes TypeScript error: Cannot find name 'getAppUrl'
- Add forgot-password endpoint - Add reset-password endpoint - Add ResetPasswordDto - Update auth service with password reset logic
- Replace getAppUrl() with process.env.APP_URL ?? 'http://localhost:3000' - Replace getCorsOrigin() with process.env.CORS_ORIGIN - Ensure environment variable names match main branch exactly
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes issues with the onboarding flow by improving address parsing flexibility, adding better error handling for OAuth flows, and enabling the application to run in development mode without third-party service credentials.
Key changes:
- Enhanced Australian address parsing with multiple regex patterns and fallback logic to handle various address formats
- Added validation and error handling for Google OAuth authentication failures
- Introduced mock clients for Twilio and Google OAuth to allow development without credentials
- Improved error messages and code formatting consistency
Reviewed changes
Copilot reviewed 8 out of 10 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| src/utils/app-config.ts | Adds centralized utility functions for app URL and CORS origin configuration |
| src/modules/onboarding/onboarding.service.ts | Implements flexible address parsing with multiple regex patterns, manual parsing fallback, and demo step handling |
| src/modules/health/health.controller.ts | Reformats API decorator parameters for improved readability |
| src/modules/google-calendar/calendar-oauth.controller.ts | Adds explicit type annotation for frontend URL variable |
| src/modules/company/schema/company.schema.ts | Removes trailing whitespace |
| src/modules/auth/strategies/google.strategy.ts | Adds validation for Google profile data and mock credentials for development |
| src/modules/auth/dto/reset-password.dto.ts | Removes trailing whitespace |
| src/modules/auth/auth.service.ts | Reorders import statement |
| src/modules/auth/auth.controller.ts | Adds validation checks for OAuth callback and improves type safety in user object construction |
| src/lib/twilio/twilio.module.ts | Introduces mock Twilio client for development without credentials |
Comments suppressed due to low confidence (2)
src/modules/auth/auth.controller.ts:225
- The pattern
process.env.APP_URL ?? 'http://localhost:3000'is duplicated three times in this method (lines 178, 191, and 225). This should be extracted to a single variable at the beginning of the method to avoid repetition and ensure consistency.
googleAuthRedirect(@Req() req: Request, @Res() res: Response): void {
// Check if user is authenticated
if (!req.user) {
const frontendUrl = process.env.APP_URL ?? 'http://localhost:3000';
res.redirect(`${frontendUrl}/login?error=oauth_failed`);
return;
}
const { user, token, csrfToken } = req.user as {
user: Record<string, unknown>;
token: string;
csrfToken: string;
};
// Validate required fields
if (!token || !csrfToken) {
const frontendUrl = process.env.APP_URL ?? 'http://localhost:3000';
res.redirect(`${frontendUrl}/login?error=oauth_incomplete`);
return;
}
// Manually construct safe user object to preserve ObjectId
const safeUser = {
_id: user._id?.toString() ?? (user._id as string | undefined) ?? '',
email: (user.email as string | undefined) ?? '',
firstName: (user.firstName as string | undefined) ?? '',
lastName: (user.lastName as string | undefined) ?? '',
role: (user.role as EUserRole | undefined) ?? EUserRole.user,
status: (user.status as UserStatus | undefined) ?? UserStatus.active,
};
// Set JWT token as httpOnly cookie
res.cookie('jwtToken', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: process.env.NODE_ENV === 'production' ? 'strict' : 'lax',
maxAge: 24 * 60 * 60 * 1000, // 24 hours
path: '/',
});
// Set CSRF token as httpOnly cookie for double submit pattern
res.cookie('csrfToken', csrfToken, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: process.env.NODE_ENV === 'production' ? 'strict' : 'lax',
maxAge: 24 * 60 * 60 * 1000, // 24 hours
path: '/',
});
// Redirect to frontend with user data (CSRF token is in regular cookie)
const frontendUrl = process.env.APP_URL ?? 'http://localhost:3000';
src/modules/onboarding/onboarding.service.ts:120
- The complex address parsing logic introduced in this PR lacks test coverage. Given the multiple regex patterns, fallback parsing logic, and various edge cases being handled (3-part vs 4-part addresses, state/postcode together vs separated), this functionality should have comprehensive unit tests to ensure all parsing paths work correctly and edge cases are handled properly.
'user.address.full': function (answer, update) {
const trimmedAnswer = answer.trim();
// Try multiple regex patterns
let match = this.AU_ADDR_REGEX_STRICT.exec(trimmedAnswer);
if (!match?.groups) {
match = this.AU_ADDR_REGEX_FLEXIBLE.exec(trimmedAnswer);
}
if (!match?.groups) {
match = this.AU_ADDR_REGEX_SEPARATED.exec(trimmedAnswer);
}
// If regex doesn't match, try to parse manually from comma-separated values
if (!match?.groups) {
const parts = trimmedAnswer
.split(',')
.map(p => p.trim())
.filter(p => p);
if (parts.length >= 3) {
// Try to extract state and postcode from the last part
const lastPart = parts[parts.length - 1];
const statePostcodeMatch = /^([A-Z]{2,3})\s*(\d{4})$/.exec(lastPart);
if (statePostcodeMatch) {
// Format: "Street, Suburb, State Postcode" (3 parts) or more
// For 3 parts: parts[0] is street, parts[1] is suburb
// For more parts: join all but last 2 as street
const streetAddress =
parts.length === 3 ? parts[0] : parts.slice(0, -2).join(', ');
const suburb = parts[parts.length - 2];
const state = statePostcodeMatch[1];
const postcode = statePostcodeMatch[2];
update.$set['answers.user.address.streetAddress'] = streetAddress;
update.$set['answers.user.address.suburb'] = suburb;
update.$set['answers.user.address.state'] = state;
update.$set['answers.user.address.postcode'] = postcode;
update.$set['answers.user.address.full'] = answer;
return;
}
// Try to extract state and postcode separately
if (parts.length >= 4) {
const stateMatch = /^([A-Z]{2,3})$/.exec(parts[parts.length - 2]);
const postcodeMatch = /^(\d{4})$/.exec(parts[parts.length - 1]);
if (stateMatch && postcodeMatch) {
// Format: "Street, Suburb, State, Postcode" (4 parts) or more
// For 4 parts: parts[0] is street, parts[1] is suburb
// For more parts: join all but last 3 as street
const streetAddress =
parts.length === 4 ? parts[0] : parts.slice(0, -3).join(', ');
const suburb = parts[parts.length - 3];
const state = stateMatch[1];
const postcode = postcodeMatch[1];
update.$set['answers.user.address.streetAddress'] = streetAddress;
update.$set['answers.user.address.suburb'] = suburb;
update.$set['answers.user.address.state'] = state;
update.$set['answers.user.address.postcode'] = postcode;
update.$set['answers.user.address.full'] = answer;
return;
}
}
}
// If all parsing attempts fail, throw error
throw new BadRequestException(
'Unable to parse address; please check the format. Expected format: "Street Address, Suburb, State Postcode" (e.g., "123 Main St, Sydney, NSW 2000")',
);
}
// Use regex match results
update.$set['answers.user.address.streetAddress'] =
match.groups.street.trim();
update.$set['answers.user.address.suburb'] = match.groups.suburb.trim();
update.$set['answers.user.address.state'] = match.groups.state;
update.$set['answers.user.address.postcode'] = match.groups.postcode;
update.$set['answers.user.address.full'] = answer; // raw string
},
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fix onboarding!