Skip to content

Conversation

@vncloudsco
Copy link
Contributor

feat: Implement first login password change and 2FA setup flow

  • Added isFirstLogin flag to User model
  • Created endpoints for changing password on first login
  • Implemented 2FA setup process
  • Updated validation for password strength
  • Enhanced login flow to handle first-time login scenarios
  • Added UI components for password change and 2FA setup

Copilot AI review requested due to automatic review settings October 8, 2025 12:56
@sonarqubecloud
Copy link

sonarqubecloud bot commented Oct 8, 2025

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements a comprehensive first login password change and 2FA setup flow to enhance security. When users log in for the first time, they are required to change their default password and optionally set up two-factor authentication before accessing the system.

  • Added first-time login detection with isFirstLogin flag and mandatory password change
  • Implemented 2FA setup flow with QR code generation and backup codes
  • Enhanced password validation to require uppercase, lowercase, numbers, and special characters

Reviewed Changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
apps/web/src/services/auth.service.ts Added first login password change endpoint and extended LoginResponse interface
apps/web/src/components/pages/Users.tsx Enhanced password reset dialog with secure copy functionality and improved UX
apps/web/src/components/pages/Login.tsx Updated login flow to handle first-time login and 2FA setup steps
apps/web/src/components/pages/ForcePasswordChange.tsx New component for mandatory password change with strength validation
apps/web/src/components/pages/Force2FASetup.tsx New component for 2FA setup with QR code and backup codes
apps/web/src/components/pages/Account.tsx Updated password validation to match new security requirements
apps/web/src/auth.tsx Extended LoginResponse interface for first login handling
apps/api/src/utils/password.ts Added secure password generation utility
apps/api/src/utils/jwt.ts Added temporary token generation for first login flow
apps/api/src/domains/users/users.service.ts Updated to use secure password generation
apps/api/src/domains/auth/ Added first login password change endpoint, validation, and service logic
apps/api/src/domains/account/account.validation.ts Enhanced password validation rules
apps/api/prisma/ Added isFirstLogin flag to User model with migration
Comments suppressed due to low confidence (1)

apps/web/src/components/pages/Users.tsx:1

  • The useTranslation import is removed but the translation hook t is still being used in the component. This will cause undefined variable errors.
import { useState } from "react";

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import { toast } from 'sonner';
import { accountService } from '@/services/auth.service';
Copy link

Copilot AI Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import statement references accountService but the auth.service.ts file exports authService. This will cause a runtime error when trying to call setup2FA and enable2FA methods.

Copilot uses AI. Check for mistakes.
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { UserPlus, Mail, Key, Trash2, Edit, Shield, Loader2, Users as UsersIcon } from "lucide-react";
import { UserPlus, Key, Trash2, Edit, Shield, Loader2, Users as UsersIcon, Copy, CheckCircle2, AlertTriangle } from "lucide-react";
Copy link

Copilot AI Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Mail icon import was removed but may still be used elsewhere in the component, potentially causing undefined variable errors.

Suggested change
import { UserPlus, Key, Trash2, Edit, Shield, Loader2, Users as UsersIcon, Copy, CheckCircle2, AlertTriangle } from "lucide-react";
import { UserPlus, Key, Trash2, Edit, Shield, Loader2, Users as UsersIcon, Copy, CheckCircle2, AlertTriangle, Mail } from "lucide-react";

Copilot uses AI. Check for mistakes.
Comment on lines +244 to +259
const currentUserId = resetPasswordDialog.userId;
const currentUsername = resetPasswordDialog.username;

// Close dialog first
setResetPasswordDialog({ isOpen: false, userId: '', username: '' });

// Then reopen with password after a tiny delay
setTimeout(() => {
setResetPasswordDialog({
isOpen: true,
userId: currentUserId,
username: currentUsername,
newPassword: newPassword
});
setPasswordCopied(false);
}, 100);
Copy link

Copilot AI Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach of closing and reopening the dialog with a setTimeout to force re-render is fragile and could cause race conditions. Consider using a proper state update pattern or separate state for the password result.

Copilot uses AI. Check for mistakes.
Comment on lines +301 to +342
const textArea = document.createElement('textarea');
textArea.value = password;

// Make it visible but off-screen to ensure it works
textArea.style.position = 'fixed';
textArea.style.top = '0';
textArea.style.left = '-9999px';
textArea.style.opacity = '0';
textArea.setAttribute('readonly', '');
textArea.contentEditable = 'true';

document.body.appendChild(textArea);

// iOS Safari needs this
if (navigator.userAgent.match(/ipad|iphone/i)) {
const range = document.createRange();
range.selectNodeContents(textArea);
const selection = window.getSelection();
selection?.removeAllRanges();
selection?.addRange(range);
textArea.setSelectionRange(0, password.length);
} else {
textArea.focus();
textArea.select();
textArea.setSelectionRange(0, password.length);
}

// Small delay before executing copy
await new Promise(resolve => setTimeout(resolve, 50));

const successful = document.execCommand('copy');

if (successful) {
copySuccess = true;
copyMethod = 'execCommand';

// Keep element for a bit to ensure clipboard is populated
await new Promise(resolve => setTimeout(resolve, 100));
}

document.body.removeChild(textArea);
} catch (execError) {
Copy link

Copilot AI Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using arbitrary delays in copy functionality is unreliable across different browsers and devices. Consider using more robust clipboard detection or event-based approaches.

Suggested change
const textArea = document.createElement('textarea');
textArea.value = password;
// Make it visible but off-screen to ensure it works
textArea.style.position = 'fixed';
textArea.style.top = '0';
textArea.style.left = '-9999px';
textArea.style.opacity = '0';
textArea.setAttribute('readonly', '');
textArea.contentEditable = 'true';
document.body.appendChild(textArea);
// iOS Safari needs this
if (navigator.userAgent.match(/ipad|iphone/i)) {
const range = document.createRange();
range.selectNodeContents(textArea);
const selection = window.getSelection();
selection?.removeAllRanges();
selection?.addRange(range);
textArea.setSelectionRange(0, password.length);
} else {
textArea.focus();
textArea.select();
textArea.setSelectionRange(0, password.length);
}
// Small delay before executing copy
await new Promise(resolve => setTimeout(resolve, 50));
const successful = document.execCommand('copy');
if (successful) {
copySuccess = true;
copyMethod = 'execCommand';
// Keep element for a bit to ensure clipboard is populated
await new Promise(resolve => setTimeout(resolve, 100));
}
document.body.removeChild(textArea);
} catch (execError) {
// Prefer modern Clipboard API if available
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
await navigator.clipboard.writeText(password);
copySuccess = true;
copyMethod = 'clipboard.writeText';
} else {
const textArea = document.createElement('textarea');
textArea.value = password;
// Make it visible but off-screen to ensure it works
textArea.style.position = 'fixed';
textArea.style.top = '0';
textArea.style.left = '-9999px';
textArea.style.opacity = '0';
textArea.setAttribute('readonly', '');
textArea.contentEditable = 'true';
document.body.appendChild(textArea);
// iOS Safari needs this
if (navigator.userAgent.match(/ipad|iphone/i)) {
const range = document.createRange();
range.selectNodeContents(textArea);
const selection = window.getSelection();
selection?.removeAllRanges();
selection?.addRange(range);
textArea.setSelectionRange(0, password.length);
} else {
textArea.focus();
textArea.select();
textArea.setSelectionRange(0, password.length);
}
// No arbitrary delay; execute copy immediately
const successful = document.execCommand('copy');
if (successful) {
copySuccess = true;
copyMethod = 'execCommand';
}
document.body.removeChild(textArea);
}

Copilot uses AI. Check for mistakes.
Comment on lines +32 to +34
return jwt.sign({ userId, type: 'temp' }, config.jwt.accessSecret, {
expiresIn: '15m', // Temporary token valid for 15 minutes
} as any);
Copy link

Copilot AI Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using as any to bypass TypeScript checking is not recommended. Define proper types for the JWT sign options or use the correct interface from the jwt library.

Copilot uses AI. Check for mistakes.
Comment on lines +268 to +274
const result = await this.completeLogin(user, metadata, false);

// Add flag to indicate if 2FA setup is needed
return {
...result,
require2FASetup: !user.twoFactor?.enabled,
};
Copy link

Copilot AI Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The require2FASetup flag is calculated based on the user state before password change, but after password change the user relationship might have changed. Consider fetching fresh user data or ensuring this flag is accurate.

Copilot uses AI. Check for mistakes.
Comment on lines +79 to +80
// Return successful login response (LoginResult type)
if ('accessToken' in result && 'refreshToken' in result) {
Copy link

Copilot AI Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using type guards with string property checks is fragile and doesn't provide type safety. Consider using discriminated unions with explicit type fields or proper type narrowing.

Copilot uses AI. Check for mistakes.
@vncloudsco vncloudsco merged commit 0ecb757 into TinyActive:feature_force_2fa Oct 8, 2025
2 checks passed
vncloudsco added a commit that referenced this pull request Oct 8, 2025
* feat: Implement first login password change and 2FA setup flow

* feat: Enhance first login flow with token return and 2FA setup option

* feat: Implement secure password generation and reset confirmation dialog in Users component
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant