diff --git a/package.json b/package.json index 302bf37..648bee3 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "scripts": { "build": "nest build", "start": "cross-env NODE_ENV=production node dist/main.js", + "predev": "node scripts/ensure-port-free.js", "dev": "cross-env NODE_ENV=development nest start --watch", "start:dev": "npm run dev" }, diff --git a/scripts/ensure-port-free.js b/scripts/ensure-port-free.js new file mode 100644 index 0000000..db16aed --- /dev/null +++ b/scripts/ensure-port-free.js @@ -0,0 +1,89 @@ +const { execSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +function loadEnvFile(filePath) { + if (!fs.existsSync(filePath)) return {}; + + return fs + .readFileSync(filePath, 'utf8') + .split(/\r?\n/) + .reduce((acc, line) => { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith('#')) return acc; + const separatorIndex = trimmed.indexOf('='); + if (separatorIndex === -1) return acc; + const key = trimmed.slice(0, separatorIndex).trim(); + const value = trimmed.slice(separatorIndex + 1).trim(); + acc[key] = value; + return acc; + }, {}); +} + +function getConfiguredPort() { + const env = { + ...loadEnvFile(path.resolve(process.cwd(), '.env.development')), + ...process.env, + }; + + return Number(env.PORT || env.BACKEND_PORT || 3001); +} + +function getWindowsPids(port) { + const output = execSync(`netstat -ano -p tcp | findstr :${port}`, { + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'ignore'], + }); + + return Array.from( + new Set( + output + .split(/\r?\n/) + .filter((line) => line.includes('LISTENING')) + .map((line) => line.trim().split(/\s+/).at(-1)) + .filter(Boolean) + .filter((pid) => pid !== String(process.pid)), + ), + ); +} + +function getUnixPids(port) { + const output = execSync(`lsof -ti tcp:${port} -sTCP:LISTEN`, { + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'ignore'], + }); + + return Array.from( + new Set( + output + .split(/\r?\n/) + .map((pid) => pid.trim()) + .filter(Boolean) + .filter((pid) => pid !== String(process.pid)), + ), + ); +} + +function killPid(pid) { + if (process.platform === 'win32') { + execSync(`taskkill /PID ${pid} /F /T`, { stdio: 'ignore' }); + return; + } + + execSync(`kill -TERM ${pid}`, { stdio: 'ignore' }); +} + +const port = getConfiguredPort(); + +try { + const pids = process.platform === 'win32' ? getWindowsPids(port) : getUnixPids(port); + + if (!pids.length) { + process.exit(0); + } + + pids.forEach(killPid); + console.log(`Porta ${port} liberada. Processo(s) encerrado(s): ${pids.join(', ')}`); +} catch { + process.exit(0); +} diff --git a/src/main.ts b/src/main.ts index e9d8c52..d005164 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,10 +9,24 @@ async function bootstrap() { const app = await NestFactory.create(AppModule); const frontendUrl = process.env.FRONTEND_URL || 'http://localhost:3000'; + const allowedOrigins = new Set( + frontendUrl + .split(',') + .map((origin) => origin.trim()) + .filter(Boolean), + ); + allowedOrigins.add('http://localhost:3000'); + allowedOrigins.add('http://localhost:5173'); const port = Number(process.env.PORT || process.env.BACKEND_PORT || 3001); app.enableCors({ - origin: frontendUrl, + origin(origin, callback) { + if (!origin || allowedOrigins.has(origin)) { + callback(null, true); + return; + } + callback(new Error(`Origem CORS nao permitida: ${origin}`)); + }, credentials: true, }); diff --git a/src/modules/auth/providers/ldap-auth.provider.ts b/src/modules/auth/providers/ldap-auth.provider.ts index 25d1c69..491160e 100644 --- a/src/modules/auth/providers/ldap-auth.provider.ts +++ b/src/modules/auth/providers/ldap-auth.provider.ts @@ -15,6 +15,7 @@ export class LdapAuthProvider { async authenticate({ username, password }: LoginData): Promise { const config = this.authConfig.getConfig(); + const normalizedUsername = this.normalizeUsername(username); if (!config.ldap.enabled) { throw new ForbiddenException('Login AD/LDAP desabilitado'); @@ -24,7 +25,7 @@ export class LdapAuthProvider { throw new Error('LDAP_URL nao configurado'); } - if (!username || !password) { + if (!normalizedUsername || !password) { throw new UnauthorizedException('Usuario e senha sao obrigatorios'); } @@ -39,17 +40,19 @@ export class LdapAuthProvider { await client.bind(config.ldap.bindDn, config.ldap.bindPassword); } - const userPrincipal = this.buildUserPrincipal(username); + const userPrincipal = this.buildUserPrincipal(normalizedUsername); await client.bind(userPrincipal, password); - const directoryUser = await this.searchUser(client, username); + const directoryUser = await this.searchUser(client, this.getSearchUsername(normalizedUsername)); const providerUser = { id: directoryUser?.email || userPrincipal, - name: directoryUser?.name || username, + name: directoryUser?.name || normalizedUsername, email: directoryUser?.email || - (config.ldap.domain ? `${username}@${config.ldap.domain}` : null), - username: directoryUser?.username || username, + (config.ldap.domain && !normalizedUsername.includes('@') + ? `${normalizedUsername}@${config.ldap.domain}` + : normalizedUsername), + username: directoryUser?.username || normalizedUsername, provider: 'ldap' as const, }; const user = await this.userAccess.syncAuthenticatedUser(providerUser); @@ -59,7 +62,7 @@ export class LdapAuthProvider { user, }; } catch (_error) { - throw new UnauthorizedException('Autenticacao AD/LDAP falhou'); + throw new UnauthorizedException('Autenticação falhou'); } finally { await client.unbind().catch(() => undefined); } @@ -72,6 +75,10 @@ export class LdapAuthProvider { return config.ldap.userDnTemplate.replaceAll('{{username}}', username); } + if (username.includes('@')) { + return username; + } + if (config.ldap.domain) { return `${username}@${config.ldap.domain}`; } @@ -79,6 +86,14 @@ export class LdapAuthProvider { return username; } + private normalizeUsername(username: string) { + return String(username || '').trim(); + } + + private getSearchUsername(username: string) { + return username.includes('@') ? username.split('@')[0] : username; + } + private async searchUser(client: Client, username: string) { const config = this.authConfig.getConfig();