Skip to content

Commit

Permalink
Added console admin login and updated endpoint
Browse files Browse the repository at this point in the history
Added request control limit for avoiding brute force attacks
  • Loading branch information
CSantosM committed Oct 29, 2024
1 parent 634d3ee commit eab7eb2
Show file tree
Hide file tree
Showing 12 changed files with 310 additions and 26 deletions.
27 changes: 16 additions & 11 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"dotenv": "16.4.5",
"express": "4.21.0",
"express-basic-auth": "1.2.1",
"express-rate-limit": "^7.4.1",
"livekit-server-sdk": "2.6.2",
"mysql2": "^3.11.3",
"reflect-metadata": "^0.2.2",
Expand Down
15 changes: 9 additions & 6 deletions backend/src/controllers/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,22 @@ export const adminLogin = (req: Request, res: Response) => {
logger.verbose('Admin login request received');
const { username, password } = req.body;

if (!username || !password) {
logger.warn('Missing username or password');
return res.status(400).json({ message: 'Missing username or password' });
const validationErrors = authService.validateCredentials(username, password);

if (validationErrors.length > 0) {
logger.warn('Validation errors:' + validationErrors);
return res.status(400).json({ message: 'Invalid input', errors: validationErrors });
}

const authenticated = authService.authenticateAdmin(username, password);

if (!authenticated) {
logger.warn('Admin login failed');
return res.status(401).json({ message: 'Admin login failed' });
logger.warn(`Admin login failed for username: ${username}`);
return res.status(401).json({ message: 'Admin login failed. Invalid username or password' });
}

logger.info('Admin login succeeded');
//TODO: Generate JWT token
logger.info(`Admin login succeeded for username: ${username}`);
return res.status(200).json({ message: 'Admin login succeeded' });
};

Expand Down
9 changes: 8 additions & 1 deletion backend/src/routes/api.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,15 @@ import {
getAppearancePreferences,
updateAppearancePreferences
} from '../controllers/global-preferences/appearance-preferences.controller.js';
import rateLimit from 'express-rate-limit';

const apiRouter = Router();
// Limit login attempts for avoiding brute force attacks
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 min
max: 5,
message: 'Too many login attempts, please try again later.'
});

apiRouter.use(bodyParser.urlencoded({ extended: true }));
apiRouter.use(bodyParser.json());
Expand All @@ -40,7 +47,7 @@ apiRouter.put('/broadcasts/:broadcastId', withUserBasicAuth, broadcastCtrl.stopB
// Auth Routes
apiRouter.post('/login', authCtrl.login);
apiRouter.post('/logout', authCtrl.logout);
apiRouter.post('/admin/login', authCtrl.adminLogin);
apiRouter.post('/admin/login', loginLimiter, authCtrl.adminLogin);
apiRouter.post('/admin/logout', authCtrl.adminLogout);

// Global Preferences Routes
Expand Down
15 changes: 15 additions & 0 deletions backend/src/services/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,22 @@ export class AuthService {
return true;
}

// TODO: use hash and salt for password storage
authenticateAdmin(username: string, password: string): boolean {
return username === CALL_ADMIN_USER && password === CALL_ADMIN_SECRET;
}

validateCredentials(username: string, password: string): string[] {
const errors: string[] = [];

if (!username || username.length < 4) {
errors.push('Username must be at least 4 characters long.');
}

if (!password || password.length < 4) {
errors.push('Password must be at least 4 characters long.');
}

return errors;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<div class="login-container">
<div class="card-container">
<mat-card class="login-card">
<h1>Log in</h1>
<p>Continue to OpenVidu Console</p>
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
<mat-form-field class="form-input" appearance="fill">
<mat-label>Username</mat-label>
<input matInput formControlName="username" required />
<mat-icon matPrefix>person</mat-icon>
</mat-form-field>

<mat-form-field class="form-input" appearance="fill">
<mat-label>Password</mat-label>
<input matInput type="password" formControlName="password" required />
<mat-icon matPrefix>lock</mat-icon>
</mat-form-field>

<div class="login-button">
<button mat-raised-button color="primary" type="submit">Login</button>
</div>
<!-- <p class="forgot-password"><a href="#">Forgot password?</a></p> -->
</form>
</mat-card>
<div class="signup-section">
<div>
<img src="https://openvidu.io/assets/images/openvidu_white_bg_transp.png" alt="Sign Up" />
</div>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
* {
box-sizing: border-box;
}

body {
margin: 0;
font-family: Arial, sans-serif;
background-color: #F8FAFA;
}

.login-container {
height: 100vh;
}

.card-container {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
width: 75%;
margin: auto;
max-width: 1200px;
}

.login-card {
padding: 40px;
width: 400px;
height: 400px;
// box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
box-shadow: none;
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
}

.form-input {
width: 100%;
}

.login-button {
text-align: center;
button {
width: 100%;
background-color: #0088aa;
color: #ffffff;
border-radius: 4px;
transition: background-color 0.3s;
}
}
.login-button button:hover {
background-color: #00777f;
}

.signup-section {
display: flex;
text-align: center;
justify-content: center;
align-items: center;
align-content: center;
background-color: #4d4d4d;
padding: 20px;
flex: 1;
width: 400px;
height: 400px;
max-width: 400px;
max-height: 400px;
border-top-right-radius: 12px;
border-bottom-right-radius: 12px;
// box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
box-shadow: none;
}

.signup-section img {
max-width: 80%;
height: auto;
}

// .forgot-password {
// margin-top: 10px;
// text-align: center;
// }

a {
color: #6a5acd; /* Color del enlace */
text-decoration: none;
}

a:hover {
text-decoration: underline;
}

@media (max-width: 991px) {
/* Ocultar .signup-section en pantallas de 991px o menos */
.signup-section {
display: none;
}

.card-container {
width: 75%;
flex-direction: column;
}

.login-card {
width: 100%;
max-width: 100%;
border-radius: 12px;
height: auto;
// box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.15);
padding: 40px;
margin-bottom: 15px;
}

.login-button button {
width: 100%;
padding: 12px;
font-size: 1em;
}

.form-input {
margin-bottom: 10px;
}
}

/* Pantallas pequeñas adicionales (576px o menos) */
@media (max-width: 576px) {
.login-card {
padding: 40px;
font-size: 0.9em;
}

.login-button button {
font-size: 0.85em;
padding: 10px;
}

.card-container {
width: 95%;
}

.signup-section {
display: none;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { LoginComponent } from './login.component';

describe('LoginComponent', () => {
let component: LoginComponent;
let fixture: ComponentFixture<LoginComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [LoginComponent]
})
.compileComponents();

fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Loading

0 comments on commit eab7eb2

Please sign in to comment.