276 lines
7.7 KiB
JavaScript
276 lines
7.7 KiB
JavaScript
const express = require('express');
|
|
const cors = require('cors');
|
|
const base64url = require('base64url');
|
|
const { v4: uuidv4 } = require('uuid');
|
|
const {
|
|
generateRegistrationOptions,
|
|
verifyRegistrationResponse,
|
|
generateAuthenticationOptions,
|
|
verifyAuthenticationResponse,
|
|
} = require('@simplewebauthn/server');
|
|
// CommonJS
|
|
const { isoBase64URL } = require('@simplewebauthn/server/helpers');
|
|
const session = require('express-session');
|
|
const cookieParser = require('cookie-parser');
|
|
|
|
const app = express();
|
|
app.set('trust proxy', 1); // dietro NGINX/HTTPS
|
|
app.use(express.json());
|
|
|
|
|
|
|
|
app.use(session({
|
|
name: 'sid',
|
|
secret: process.env.SESSION_SECRET || 'sono1chiave#moltolunga99esicura',
|
|
resave: false,
|
|
saveUninitialized: false,
|
|
cookie: {
|
|
httpOnly: true,
|
|
secure: true, // true in produzione con HTTPS
|
|
sameSite: 'none', // 'none' per cross-site; richiede secure:true
|
|
maxAge: 24 * 60 * 60 * 1000
|
|
}
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
const allowedOrigins = [
|
|
'https://app.patachina.it',
|
|
'https://my.patachina2.casacam.net',
|
|
'http://localhost:5000'
|
|
];
|
|
|
|
app.use(cors({
|
|
origin: function (origin, callback) {
|
|
if (!origin) return callback(null, true); // permette richieste server-to-server senza Origin
|
|
if (allowedOrigins.includes(origin)) {
|
|
return callback(null, origin); // restituisce SOLO l'origin valido
|
|
} else {
|
|
return callback(new Error('Not allowed by CORS'));
|
|
}
|
|
},
|
|
credentials: true
|
|
}));
|
|
*/
|
|
|
|
app.use(cors({
|
|
origin: [
|
|
'https://app.patachina.it',
|
|
'https://my.patachina2.casacam.net',
|
|
'http://localhost:5000'
|
|
],
|
|
credentials: true
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
const rpID = 'my.patachina2.casacam.net'; // Android emulator localhost
|
|
const origin = `https://${rpID}`;
|
|
|
|
const userDB = {}; // In-memory user store
|
|
const appDB = {};
|
|
const user1DB = {};
|
|
const challregDB = {};
|
|
const challlogDB = {};
|
|
const uuDB = {};
|
|
const uidDB = {};
|
|
function isBase64Url(str) {
|
|
return typeof str === 'string' && /^[A-Za-z0-9\-_]+$/.test(str);
|
|
}
|
|
|
|
const appMy = {
|
|
id: 'my',
|
|
appname: 'my',
|
|
rpID: 'my.patachina2.casacam.net',
|
|
rpName: 'my Local Authn service',
|
|
origin: `https://my.patachina2.casacam.net`,
|
|
}
|
|
|
|
appDB['my'] = appMy;
|
|
|
|
|
|
app.post('/:appId/register-options', async (req, res) => {
|
|
console.log('start register options');
|
|
const appId = req.params.appId;
|
|
const { username } = req.body;
|
|
|
|
// Genera un nuovo UUID v4
|
|
const userIdString = uuidv4();
|
|
// Se ti serve in formato Buffer per WebAuthn:
|
|
const userId = Buffer.from(userIdString, 'utf8');
|
|
const userId64 = isoBase64URL.fromBuffer(userId);
|
|
const key = `${username}:${appId}`;
|
|
uuDB[key] = {
|
|
id: key,
|
|
userId: userId64,
|
|
date: new Date(),
|
|
};
|
|
uidDB[userId64] = {
|
|
id: userId64,
|
|
app: appId,
|
|
devices: [],
|
|
};
|
|
const chall = {
|
|
id: userId64,
|
|
};
|
|
const options = await generateRegistrationOptions({
|
|
rpName: appDB[appId].rpName,
|
|
rpID: appDB[appId].rpID,
|
|
userID: userId,
|
|
userName: username,
|
|
/*
|
|
authenticatorSelection: {
|
|
userVerification: 'discouraged' // oppure 'preferred' se vuoi tentare o "required" se vuoi che chieda impronta digitale
|
|
}
|
|
*/
|
|
});
|
|
uidDB[userId64].challengereg = options.challenge;
|
|
console.log('options reg: ',options);
|
|
console.log('uidDB[userId64]: ',uidDB[userId64]);
|
|
req.session.userID = userId64;
|
|
console.log('session reg opt',req.session);
|
|
res.json(options);
|
|
});
|
|
|
|
app.post('/:appId/register-verify', async (req, res) => {
|
|
const { username, attestationResponse } = req.body;
|
|
console.log('session reg ver: ',req.session);
|
|
const appId = req.params.appId;
|
|
const userId64 = req.session.userID;
|
|
const uid = uidDB[userId64];
|
|
console.log('uid.challengereg: ', uid.challengereg);
|
|
const verification = await verifyRegistrationResponse({
|
|
response: attestationResponse,
|
|
expectedChallenge: uid.challengereg,
|
|
expectedOrigin: appDB[appId].origin,
|
|
expectedRPID: appDB[appId].rpID,
|
|
// requireUserVerification: false, // <-- disattiva requisito UV
|
|
});
|
|
console.log('verification: ',verification);
|
|
delete uidDB[userId64].challengereg;
|
|
if (verification.verified) {
|
|
uidDB[userId64].devices.push(verification.registrationInfo);
|
|
console.log('uidDB[userId64] dopo ver reg: ', uidDB[userId64]);
|
|
console.log('success true');
|
|
req.session.destroy(err => {
|
|
if (err) {
|
|
console.log("errore cancellazione sessione");
|
|
res.status(400).json({ success: true });
|
|
} else {
|
|
res.clearCookie('sid').json({ success: true });
|
|
}
|
|
});
|
|
} else {
|
|
req.session.destroy(err => {
|
|
if (err) {
|
|
console.log("errore cancellazione sessione");
|
|
res.status(400).json({ success: false });
|
|
} else {
|
|
res.clearCookie('sid').json({ success: false });
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
app.post('/:appId/login-options', async (req, res) => {
|
|
// console.log('req.body', req.body);
|
|
console.log('session login opt: ',req.session);
|
|
const { username } = req.body;
|
|
const appId = req.params.appId;
|
|
const key = `${username}:${appId}`;
|
|
if (uuDB[key]) {
|
|
const userId64 = uuDB[key].userId;
|
|
const user = uidDB[userId64]; console.log('xxx devices:',user);
|
|
// console.log('user', user.devices[0].credential.id);
|
|
if (!user || user.devices.length === 0) return res.status(404).json({ error: 'User not found' });
|
|
|
|
const all = {
|
|
allowCredentials: user.devices.map(dev => ({
|
|
id: dev.credential.id, // <-- STRINGA base64url, non Buffer
|
|
type: 'public-key',
|
|
transports: dev.transports || ['internal', 'hybrid'],
|
|
})),
|
|
userVerification: 'discouraged', // o 'preferred' / 'required'
|
|
rpID: appDB[appId].rpID,
|
|
timeout: 60000,
|
|
};
|
|
//console.log('all: ',all);
|
|
const options = await generateAuthenticationOptions(all);
|
|
|
|
//console.log('login options ',options);
|
|
uidDB[userId64].challengelog = options.challenge;
|
|
console.log('uidDB[userId64] ',uidDB[userId64]);
|
|
req.session.userID = userId64;
|
|
res.json(options);
|
|
} else {
|
|
console.log(`Utente ${username} non registrato`);
|
|
res.status(401).json({ success: false, message: `Utente ${username} non registrato`});
|
|
};
|
|
});
|
|
|
|
|
|
app.post('/:appId/login-verify', async (req, res) => {
|
|
const { username, assertionResponse } = req.body;
|
|
const userId64 = req.session.userID;
|
|
const user = uidDB[userId64];
|
|
const appId = req.params.appId;
|
|
|
|
let verified = false;
|
|
let verification;
|
|
let verlog;
|
|
|
|
for (const device of user.devices) {
|
|
verlog = {
|
|
response: assertionResponse,
|
|
expectedChallenge: user.challengelog, //user.challenge,
|
|
expectedOrigin: appDB[appId].origin,
|
|
expectedRPID: appDB[appId].rpID,
|
|
credential: device.credential,
|
|
//credential: user.devices.map(d => d.credential),
|
|
};
|
|
verification = await verifyAuthenticationResponse(verlog);
|
|
|
|
if (verification.verified) {
|
|
verified = true;
|
|
// aggiorna il counter del device usato
|
|
device.counter = verification.authenticationInfo.newCounter;
|
|
break;
|
|
}
|
|
}
|
|
delete uidDB[userId64].challengelog;
|
|
|
|
if (verified) {
|
|
console.log('uidDB[userId64] dopo ver log: ', uidDB[userId64]);
|
|
console.log('success true');
|
|
req.session.destroy(err => {
|
|
if (err) {
|
|
console.log("errore cancellazione sessione");
|
|
res.status(500).json({ success: true });
|
|
} else {
|
|
res.clearCookie('sid').json({ success: true });
|
|
}
|
|
});
|
|
} else {
|
|
req.session.destroy(err => {
|
|
if (err) {
|
|
console.log("errore cancellazione sessione");
|
|
res.status(401).json({ success: false });
|
|
} else {
|
|
res.clearCookie('sid').status(401).json({ success: false });
|
|
}
|
|
});
|
|
};
|
|
});
|
|
|
|
//app.listen(3400, () => console.log('WebAuthn server running on http://localhost:3400'));
|
|
app.listen(3400, '192.168.1.3', () => {
|
|
console.log('WebAuthn server running on http://192.168.1.3:3400');
|
|
});
|