Skip to content

Commit 6f4befe

Browse files
authored
Merge pull request #1 from LightYagami28/main
Enhances security and code quality
2 parents b269828 + 30384e3 commit 6f4befe

4 files changed

Lines changed: 151 additions & 77 deletions

File tree

.github/dependabot.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: "npm"
4+
directory: "/API"
5+
schedule:
6+
interval: "weekly"
7+
open-pull-requests-limit: 5
8+
commit-message:
9+
prefix: "deps"
10+
labels:
11+
- "dependencies"
12+
- "npm"

API/server.js

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ import { checkIsValid, newSecretKey } from "./totp.js"
33
import { hashPassword, checkPassword } from "./hashingPasswd.js"
44

55
import express from 'express';
6+
const helmet = require('helmet');
67
import cors from 'cors';
78
import { use } from "react";
89

910
const app = express();
1011
const PORT = 3000;
1112

1213
app.use(cors());
14+
app.use(helmet());
1315
app.use(express.json());
1416

1517
const pendingLogin = {};
@@ -38,7 +40,7 @@ app.post('/api/login', async (req, res) => {
3840
let _count = new Database("users", ["username"], [username]);
3941
let __count = await _count.count();
4042

41-
if(__count >= 1) {
43+
if (__count >= 1) {
4244
let DB = new Database("users", ["username"], [username]);
4345
let values = await DB.read();
4446

@@ -51,8 +53,8 @@ app.post('/api/login', async (req, res) => {
5153
const now = new Date();
5254
const expire = new Date(now.getTime() + 5 * 60 * 1000);
5355

54-
while(true) {
55-
if(Array.isArray(pendingLogin[rndID])) {
56+
while (true) {
57+
if (Array.isArray(pendingLogin[rndID])) {
5658
rndID = Math.floor(Math.random() * 9999999999);
5759
}
5860
else {
@@ -62,13 +64,13 @@ app.post('/api/login', async (req, res) => {
6264

6365
pendingLogin[rndID] = pendingLogin[rndID] || [];
6466

65-
pendingLogin[rndID].push({
66-
USERNAME: username,
67+
pendingLogin[rndID].push({
68+
USERNAME: username,
6769
TOTP_SECRET_KEY: values[0].totp_secret_key,
6870
EXPIRE: expire,
6971
ATTEMPTS: 0
7072
});
71-
73+
7274
const data = {
7375
"STATUS": "SUCCESS",
7476
"MESSAGE": "ACCEPTED - VALID CREDENTIALS, NOW YOU MUST VALIDATE YOUR ACCESS WITH THE OTP",
@@ -116,8 +118,8 @@ app.post('/api/otp_verification', async (req, res) => {
116118

117119
let _expire = new Date(expire);
118120
let now = new Date();
119-
120-
if(now > _expire) {
121+
122+
if (now > _expire) {
121123
// The pending request has expired
122124
delete pendingLogin[pendingID];
123125

@@ -133,8 +135,8 @@ app.post('/api/otp_verification', async (req, res) => {
133135

134136
else {
135137
let otpIsValid = checkIsValid(otp, totp_secretKey);
136-
137-
if(otpIsValid) {
138+
139+
if (otpIsValid) {
138140
// Done, the user is authenticated! :)
139141
// You can now implement token management for user logins, including expiration control, multiple login management, and other related features
140142

@@ -159,7 +161,7 @@ app.post('/api/otp_verification', async (req, res) => {
159161
pendingLogin[pendingID][0].ATTEMPTS = attemps + 1;
160162

161163
// Incorrect OTP entered 5 times in a row, i'm canceling the request for security reasons
162-
if(attemps >= 5) {
164+
if (attemps >= 5) {
163165
delete pendingLogin[pendingID];
164166

165167
const data = {
@@ -205,7 +207,7 @@ app.post('/api/signup', async (req, res) => {
205207
}
206208

207209
// Check if the username contains at least 5 characters
208-
if (username.length < 5) {
210+
if (String(username).length < 5) {
209211
const data = {
210212
"STATUS": "ERROR",
211213
"MESSAGE": "BAD REQUEST - THE USERNAME HAS LESS THAN 5 CHARACTERS",
@@ -219,7 +221,7 @@ app.post('/api/signup', async (req, res) => {
219221
}
220222

221223
// Check if the username is more than 20 characters
222-
if (username.length > 20) {
224+
if (String(username).length > 20) {
223225
const data = {
224226
"STATUS": "ERROR",
225227
"MESSAGE": "BAD REQUEST - THE USERNAME HAS MORE THAN 20 CHARACTERS",
@@ -233,7 +235,7 @@ app.post('/api/signup', async (req, res) => {
233235
}
234236

235237
// Check if the password contains at least 6 characters
236-
if (password.length < 6) {
238+
if (String(password).length < 6) {
237239
const data = {
238240
"STATUS": "ERROR",
239241
"MESSAGE": "BAD REQUEST - THE PASSWORD HAS LESS THAN 6 CHARACTERS",
@@ -247,7 +249,7 @@ app.post('/api/signup', async (req, res) => {
247249
}
248250

249251
// Check if the password is more than 80 characters
250-
if (password.length > 80) {
252+
if (String(password).length > 80) {
251253
const data = {
252254
"STATUS": "ERROR",
253255
"MESSAGE": "BAD REQUEST - THE PASSWORD HAS MORE THAN 80 CHARACTERS",
@@ -276,14 +278,14 @@ app.post('/api/signup', async (req, res) => {
276278

277279
return;
278280
}
279-
281+
280282
const cryptedPasswd = await hashPassword(password);
281283

282284
let _newSecretKey = newSecretKey();
283-
285+
284286
const columns = ["username", "password", "totp_secret_key"];
285287
const parameters = [username, cryptedPasswd, _newSecretKey];
286-
288+
287289
let mysqlCMD = new Database("users", columns, parameters);
288290
mysqlCMD.insert();
289291

README.md

Lines changed: 80 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,89 @@
1-
# Secure auth with NodeJS
2-
> Login and registration system with TOTP (Time-based One-Time Password), password hashing using bcrypt, and some basic server-side validation.
3-
4-
## Features
5-
- Hash the password with bcrypt before saving it to the database.
6-
- Server-side checks: alphanumeric character validation for the username, minimum and maximum length checks for both password and username, check if the username is already registered, and a maximum attempt limit for entering the OTP code.
7-
- Handling of pending login requests awaiting OTP code verification, with expiration after 5 minutes.
8-
- Client-side QR code generation to easily add the secret key to your authentication app (e.g. Google Authenticator).
9-
- Simple use of JSON to send and receive requests, with realistic status codes for responses.
10-
- Use SQL parameters to prevent SQL injection.
11-
12-
## How to use
13-
### Install the dependencies
1+
# Secure Authentication with Node.js
2+
3+
> Un sistema di registrazione e autenticazione professionale con supporto a **TOTP (Time-based One-Time Password)**, hashing sicuro delle password tramite **bcrypt** e solide validazioni lato server.
4+
5+
6+
7+
Language:
8+
9+
10+
![NodeJS](https://img.shields.io/badge/node.js-6DA55F?style=for-the-badge&logo=node.js&logoColor=white)
11+
12+
![Express](https://img.shields.io/badge/express.js-000000?style=for-the-badge&logo=express&logoColor=white)
13+
14+
![Dependabot](https://img.shields.io/badge/dependabot-025E8C?style=for-the-badge&logo=dependabot&logoColor=white)
15+
16+
17+
18+
19+
## Caratteristiche principali
20+
21+
* **Archiviazione sicura delle password**
22+
Tutte le password vengono sottoposte ad hashing con **bcrypt** prima di essere salvate nel database.
23+
24+
* **Validazione completa**
25+
26+
* Controllo alfanumerico per il nome utente
27+
* Verifica della lunghezza minima e massima di username e password
28+
* Prevenzione della registrazione con username duplicati
29+
* Limitazione dei tentativi di inserimento OTP
30+
31+
* **Gestione dei login in sospeso**
32+
Supporto per richieste di accesso in attesa di verifica OTP, con scadenza automatica dopo 5 minuti.
33+
34+
* **Configurazione semplice della 2FA**
35+
Generazione di QR code lato client per un’integrazione immediata con app di autenticazione (es. Google Authenticator).
36+
37+
* **API moderne e standardizzate**
38+
Tutte le interazioni avvengono tramite JSON, con utilizzo di codici di stato HTTP appropriati.
39+
40+
* **Protezione contro SQL Injection**
41+
Tutte le query al database utilizzano **parametri preparati**.
42+
43+
---
44+
45+
## Avvio rapido
46+
47+
### 1. Installazione delle dipendenze
48+
1449
```bash
15-
# Run the command inside the API/ folder
50+
# All’interno della directory API/
1651
npm install
1752
```
18-
### Start the backend server
53+
54+
### 2. Avvio del server backend
55+
1956
```bash
20-
# Run the command inside the API/ folder
57+
# All’interno della directory API/
2158
node server.js
2259
```
23-
> Make sure the HTML files are served from a server (hosted) and not opened directly as local files, because the login stores the pending login request ID in cookies, which won’t work if you open the file locally.
2460

25-
I hope this helps you learn how a robust login and signup system works. Have fun experimenting and modifying my code by adding extra features, for example, a token-based system after the user logs in.
61+
> **Nota importante:** i file HTML devono essere serviti tramite un web server. L’apertura diretta in locale impedirà il corretto funzionamento dei cookie utilizzati per la gestione dei login in sospeso.
62+
63+
---
64+
65+
## Azioni rapide
66+
67+
<p align="center">
68+
<a href="https://github.com/LightYagami28/secure-auth-nodejs" target="_blank">
69+
<img src="https://img.shields.io/badge/Visualizza%20su-GitHub-181717?logo=github&style=for-the-badge" alt="View on GitHub"/>
70+
</a>
71+
<a href="https://your-demo-link.com" target="_blank">
72+
<img src="https://img.shields.io/badge/Demo%20Online-Visita-4CAF50?style=for-the-badge" alt="Live Demo"/>
73+
</a>
74+
<a href="https://github.com/LightYagami28/secure-auth-nodejs/fork" target="_blank">
75+
<img src="https://img.shields.io/badge/Fork%20Repository-Crea%20una%20copia-blue?style=for-the-badge" alt="Fork Repo"/>
76+
</a>
77+
</p>
78+
79+
---
80+
81+
## Dimostrazione
82+
83+
### Registrazione
84+
85+
![Demo Registrazione](signup.gif)
2686

27-
### Sign up phase
28-
![tuto-gif](signup.gif)
87+
### Accesso
2988

30-
### Log in phase
31-
![tuto-gif](login.gif)
89+
![Demo Login](login.gif)

login.html

Lines changed: 39 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
<!DOCTYPE html>
22
<html lang="it">
3+
34
<head>
4-
<meta charset="UTF-8">
5-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6-
<title>Log in</title>
5+
<meta charset="UTF-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<title>Log in</title>
78

8-
<link href="assets/css/bootstrap.min.css" rel="stylesheet">
9-
<link href="assets/css/style.css" rel="stylesheet">
9+
<link href="assets/css/bootstrap.min.css" rel="stylesheet">
10+
<link href="assets/css/style.css" rel="stylesheet">
1011
</head>
1112

1213
<body>
@@ -31,12 +32,12 @@ <h2>Log In</h2>
3132
const otp_txt = document.getElementById("otp_txt");
3233
const checkOtpBtn = document.getElementById("checkOtpBtn");
3334

34-
loginBtn.addEventListener('click', function() {
35+
loginBtn.addEventListener('click', function () {
3536
const data = {
3637
"username": username_txt.value,
3738
"password": password_txt.value
3839
};
39-
40+
4041
fetch("http://localhost:3000/api/login",
4142
{
4243
method: "POST",
@@ -46,33 +47,33 @@ <h2>Log In</h2>
4647
body: JSON.stringify(data)
4748
}
4849
)
49-
.then(response => response.json())
50-
.then(data => {
51-
if(data["STATUS_CODE"] == 202) {
52-
username_txt.style.display = "none";
53-
loginBtn.style.display = "none";
54-
password_txt.style.display = "none";
55-
56-
otp_txt.style.display = "block";
57-
checkOtpBtn.style.display = "block";
58-
59-
setCookie("pendingID", data["PENDING_ID"], 10);
60-
}
61-
62-
else {
63-
alert("An error occurred:\nSTATUS CODE: " + data["STATUS_CODE"] + "\n\n" + data["MESSAGE"]);
64-
}
65-
});
50+
.then(response => response.json())
51+
.then(data => {
52+
if (data["STATUS_CODE"] == 202) {
53+
username_txt.style.display = "none";
54+
loginBtn.style.display = "none";
55+
password_txt.style.display = "none";
56+
57+
otp_txt.style.display = "block";
58+
checkOtpBtn.style.display = "block";
59+
60+
setCookie("pendingID", data["PENDING_ID"], 10);
61+
}
62+
63+
else {
64+
alert("An error occurred:\nSTATUS CODE: " + data["STATUS_CODE"] + "\n\n" + data["MESSAGE"]);
65+
}
66+
});
6667
});
6768

68-
checkOtpBtn.addEventListener('click', function() {
69+
checkOtpBtn.addEventListener('click', function () {
6970
let pendingID = getCookie("pendingID");
7071

7172
const data = {
7273
"pendingID": pendingID,
7374
"otp": otp_txt.value
7475
};
75-
76+
7677
fetch("http://localhost:3000/api/otp_verification",
7778
{
7879
method: "POST",
@@ -82,16 +83,16 @@ <h2>Log In</h2>
8283
body: JSON.stringify(data)
8384
}
8485
)
85-
.then(response => response.json())
86-
.then(data => {
87-
if(data["STATUS_CODE"] == 200) {
88-
alert("Login successful:\nSTATUS CODE: " + data["STATUS_CODE"] + "\n\n" + data["MESSAGE"] + "\n\n\nI hope this repository has been helpful in understanding and learning how to manage a secure login!\n\nFeel free to experiment you can modify and improve my code, add extra checks, or implement new actions to execute after login.");
89-
}
90-
91-
else {
92-
alert("An error occurred:\nSTATUS CODE: " + data["STATUS_CODE"] + "\n\n" + data["MESSAGE"]);
93-
}
94-
});
86+
.then(response => response.json())
87+
.then(data => {
88+
if (data["STATUS_CODE"] == 200) {
89+
alert("Login successful:\nSTATUS CODE: " + data["STATUS_CODE"] + "\n\n" + data["MESSAGE"] + "\n\n\nI hope this repository has been helpful in understanding and learning how to manage a secure login!\n\nFeel free to experiment you can modify and improve my code, add extra checks, or implement new actions to execute after login.");
90+
}
91+
92+
else {
93+
alert("An error occurred:\nSTATUS CODE: " + data["STATUS_CODE"] + "\n\n" + data["MESSAGE"]);
94+
}
95+
});
9596
});
9697

9798
function setCookie(name, value, minutes) {
@@ -101,7 +102,7 @@ <h2>Log In</h2>
101102
date.setTime(date.getTime() + (minutes * 60 * 1000));
102103
expires = "; expires=" + date.toUTCString();
103104
}
104-
document.cookie = name + "=" + encodeURIComponent(value) + expires + "; path=/";
105+
document.cookie = name + "=" + encodeURIComponent(value) + expires + "; path=/; secure";
105106
}
106107

107108
function getCookie(name) {
@@ -118,4 +119,5 @@ <h2>Log In</h2>
118119

119120
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
120121
</body>
122+
121123
</html>

0 commit comments

Comments
 (0)