Skip to content
Merged
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
8 changes: 8 additions & 0 deletions Backend/app-talen-backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,20 @@ DB_DATABASE=talent_db
TYPEORM_SYNC=true
JWT_SECRET=change-this-secret
JWT_EXPIRES_IN=7d
SEED_DEFAULT_ADMIN=true
DEFAULT_ADMIN_EMAIL=admin@talen.local
DEFAULT_ADMIN_PASSWORD=AdminTemp2026!
DEFAULT_ADMIN_FORCE_SYNC=false
```

`TYPEORM_SYNC=true` permite que TypeORM cree o sincronice tablas automaticamente durante desarrollo. Para produccion se recomienda usar migraciones y dejarlo en `false`.

`JWT_EXPIRES_IN=7d` define que los tokens de autenticacion expiran a los 7 dias.

Cuando `SEED_DEFAULT_ADMIN=true`, al iniciar la API se crea automaticamente un usuario ADMIN en la base de datos si no existe. Los datos se configuran con `DEFAULT_ADMIN_EMAIL` y `DEFAULT_ADMIN_PASSWORD`.

Si `DEFAULT_ADMIN_FORCE_SYNC=true`, en cada inicio se vuelve a sincronizar ese usuario (rol ADMIN y password hasheada a partir de `DEFAULT_ADMIN_PASSWORD`).

## Autenticacion

Se implementa autenticacion con JWT en el modulo `auth`.
Expand Down
4 changes: 4 additions & 0 deletions Backend/app-talen-backend/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ services:
TYPEORM_SYNC: ${TYPEORM_SYNC:-false}
JWT_SECRET: ${JWT_SECRET}
JWT_EXPIRES_IN: ${JWT_EXPIRES_IN}
SEED_DEFAULT_ADMIN: ${SEED_DEFAULT_ADMIN:-true}
DEFAULT_ADMIN_EMAIL: ${DEFAULT_ADMIN_EMAIL:-admin@talen.local}
DEFAULT_ADMIN_PASSWORD: ${DEFAULT_ADMIN_PASSWORD:-AdminTemp2026!}
DEFAULT_ADMIN_FORCE_SYNC: ${DEFAULT_ADMIN_FORCE_SYNC:-false}
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
ports:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { InjectRepository } from '@nestjs/typeorm';
import * as bcrypt from 'bcrypt';
import { QueryFailedError } from 'typeorm';
import { Repository } from 'typeorm';
import { UserRole } from '../../users/domain/user-role.enum';
import { User } from '../../users/infrastructure/entities/user.entity';

@Injectable()
export class AdminBootstrapService implements OnModuleInit {
private readonly logger = new Logger(AdminBootstrapService.name);

constructor(
@InjectRepository(User)
private readonly usersRepository: Repository<User>,
private readonly configService: ConfigService,
) {}

async onModuleInit(): Promise<void> {
const seedEnabled =
this.configService.get<string>('SEED_DEFAULT_ADMIN', 'true') === 'true';

if (!seedEnabled) {
this.logger.log('Default admin seed disabled (SEED_DEFAULT_ADMIN=false)');
return;
}

const email = this.configService
.get<string>('DEFAULT_ADMIN_EMAIL', 'admin@talen.local')
.trim()
.toLowerCase();
const plainPassword = this.configService.get<string>(
'DEFAULT_ADMIN_PASSWORD',
'AdminTemp2026!',
);
const forcePasswordSync =
this.configService.get<string>('DEFAULT_ADMIN_FORCE_SYNC', 'false') ===
'true';

if (!email || !plainPassword) {
this.logger.warn(
'Default admin seed skipped: missing DEFAULT_ADMIN_EMAIL or DEFAULT_ADMIN_PASSWORD',
);
return;
}

const existingUser = await this.usersRepository.findOne({
where: { email },
});

if (!existingUser) {
const hashedPassword = await bcrypt.hash(plainPassword, 10);
const user = this.usersRepository.create({
email,
password: hashedPassword,
role: UserRole.ADMIN,
});
try {
await this.usersRepository.save(user);
} catch (error) {
// If multiple instances boot at the same time, another instance may create the user first.
if (!this.isUniqueViolation(error)) {
throw error;
}
}
this.logger.log(`Default admin created: ${email}`);
return;
}

if (!forcePasswordSync) {
this.logger.log(`Default admin already exists: ${email}`);
return;
}

existingUser.password = await bcrypt.hash(plainPassword, 10);
existingUser.role = UserRole.ADMIN;
await this.usersRepository.save(existingUser);
this.logger.log(`Default admin synchronized: ${email}`);
}

private isUniqueViolation(error: unknown): boolean {
if (!(error instanceof QueryFailedError)) {
return false;
}

const code = (error as QueryFailedError & { code?: string }).code;
return code === '23505';
}
}
2 changes: 2 additions & 0 deletions Backend/app-talen-backend/src/modules/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { AuthController } from './infrastructure/auth.controller';
import { LinkedInStrategy } from './infrastructure/strategies/linkedin.strategy';
import { GoogleStrategy } from './infrastructure/strategies/google.strategy';
import { MailModule } from '../mail/mail.module';
import { AdminBootstrapService } from './application/admin-bootstrap.service';

@Module({
imports: [
Expand Down Expand Up @@ -50,6 +51,7 @@ import { MailModule } from '../mail/mail.module';
LinkedInAuthGuard,
LinkedInStrategy,
GoogleStrategy,
AdminBootstrapService,
],
exports: [JwtModule, JwtAuthGuard],
})
Expand Down
8 changes: 8 additions & 0 deletions Frontend/appTalenFront/vercel.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"rewrites": [
{
"source": "/(.*)",
"destination": "/index.html"
}
]
}
8 changes: 8 additions & 0 deletions vercel.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"rewrites": [
{
"source": "/(.*)",
"destination": "/index.html"
}
]
}
Loading