303 lines
8.2 KiB
JavaScript
303 lines
8.2 KiB
JavaScript
const express = require('express');
|
|
const cors = require('cors');
|
|
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 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
|
|
}
|
|
}));
|
|
|
|
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) => {
|
|
try {
|
|
console.log('start register options');
|
|
const appId = req.params.appId;
|
|
const { username } = req.body;
|
|
|
|
if (!appId || !username) {
|
|
throw new Error('Missing appId or username');
|
|
}
|
|
|
|
// Genera un nuovo UUID v4
|
|
const userIdString = uuidv4();
|
|
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 options = await generateRegistrationOptions({
|
|
rpName: appDB[appId]?.rpName || 'Default RP',
|
|
rpID: appDB[appId]?.rpID || 'localhost',
|
|
userID: userId,
|
|
userName: username,
|
|
// authenticatorSelection: { userVerification: 'preferred' }
|
|
});
|
|
|
|
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);
|
|
console.log('end register options user:', username);
|
|
res.json(options);
|
|
} catch (err) {
|
|
console.error('❌ Error in register-options:', err);
|
|
|
|
// Risposta di errore al client
|
|
res.status(500).json({
|
|
error: 'Registration options failed',
|
|
message: err.message,
|
|
});
|
|
}
|
|
});
|
|
|
|
app.post('/:appId/register-verify', async (req, res) => {
|
|
try {
|
|
const { username, attestationResponse } = req.body;
|
|
console.log('start session reg ver');
|
|
|
|
const appId = req.params.appId;
|
|
const userId64 = req.session.userID;
|
|
|
|
if (!userId64 || !uidDB[userId64]) {
|
|
throw new Error('User session or uidDB entry not found');
|
|
}
|
|
|
|
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,
|
|
});
|
|
|
|
//console.log('verification: ', verification);
|
|
|
|
delete uidDB[userId64].challengereg;
|
|
|
|
const success = verification.verified;
|
|
if (success) {
|
|
uidDB[userId64].devices.push(verification.registrationInfo);
|
|
//console.log('uidDB[userId64] dopo ver reg: ', uidDB[userId64]);
|
|
console.log('success true');
|
|
} else {
|
|
console.log('success false');
|
|
}
|
|
|
|
// Gestione distruzione sessione con error handling
|
|
req.session.destroy(err => {
|
|
if (err) {
|
|
console.error('❌ Errore cancellazione sessione:', err);
|
|
return res.status(500).json({ success });
|
|
}
|
|
res.clearCookie('sid').json({ success });
|
|
});
|
|
|
|
} catch (err) {
|
|
console.error('❌ Error in register-verify:', err);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Registration verification failed',
|
|
message: err.message,
|
|
});
|
|
}
|
|
});
|
|
|
|
|
|
app.post('/:appId/login-options', async (req, res) => {
|
|
try {
|
|
console.log('start session login opt');
|
|
|
|
const { username } = req.body;
|
|
const appId = req.params.appId;
|
|
|
|
if (!username || !appId) {
|
|
throw new Error('Missing username or appId');
|
|
}
|
|
|
|
const key = `${username}:${appId}`;
|
|
if (!uuDB[key]) {
|
|
console.log(`Utente ${username} non registrato`);
|
|
return res.status(401).json({
|
|
success: false,
|
|
message: `Utente ${username} non registrato`,
|
|
});
|
|
}
|
|
|
|
const userId64 = uuDB[key].userId;
|
|
const user = uidDB[userId64];
|
|
//console.log('xxx devices:', user);
|
|
|
|
if (!user || !user.devices || user.devices.length === 0) {
|
|
return res.status(404).json({ error: 'User not found or no devices registered' });
|
|
}
|
|
|
|
const all = {
|
|
allowCredentials: user.devices.map(dev => ({
|
|
id: dev.credential.id, // base64url string
|
|
type: 'public-key',
|
|
transports: dev.transports || ['internal', 'hybrid'],
|
|
})),
|
|
userVerification: 'discouraged', // oppure 'preferred' / 'required'
|
|
rpID: appDB[appId]?.rpID || 'localhost',
|
|
timeout: 60000,
|
|
};
|
|
|
|
const options = await generateAuthenticationOptions(all);
|
|
|
|
uidDB[userId64].challengelog = options.challenge;
|
|
//console.log('uidDB[userId64] ', uidDB[userId64]);
|
|
console.log('end session logion options username:',username);
|
|
req.session.userID = userId64;
|
|
res.json(options);
|
|
|
|
} catch (err) {
|
|
console.error('❌ Error in login-options:', err);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Login options failed',
|
|
message: err.message,
|
|
});
|
|
}
|
|
});
|
|
|
|
app.post('/:appId/login-verify', async (req, res) => {
|
|
try {
|
|
console.log('start session login verify');
|
|
const { username, assertionResponse } = req.body;
|
|
const appId = req.params.appId;
|
|
const userId64 = req.session.userID;
|
|
|
|
if (!userId64 || !uidDB[userId64]) {
|
|
throw new Error('User session or uidDB entry not found');
|
|
}
|
|
|
|
const user = uidDB[userId64];
|
|
if (!user || !user.devices || user.devices.length === 0) {
|
|
throw new Error('No devices registered for this user');
|
|
}
|
|
|
|
let verified = false;
|
|
let verification;
|
|
|
|
for (const device of user.devices) {
|
|
const verlog = {
|
|
response: assertionResponse,
|
|
expectedChallenge: user.challengelog,
|
|
expectedOrigin: appDB[appId]?.origin,
|
|
expectedRPID: appDB[appId]?.rpID,
|
|
credential: device.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;
|
|
|
|
// Risposta finale con gestione sessione
|
|
req.session.destroy(err => {
|
|
if (err) {
|
|
console.error('❌ Errore cancellazione sessione:', err);
|
|
return res.status(500).json({ success: verified });
|
|
}
|
|
|
|
if (verified) {
|
|
//console.log('uidDB[userId64] dopo ver log: ', uidDB[userId64]);
|
|
console.log('success true');
|
|
return res.clearCookie('sid').json({ success: true });
|
|
} else {
|
|
console.log('success false');
|
|
return res.clearCookie('sid').status(401).json({ success: false });
|
|
}
|
|
});
|
|
|
|
} catch (err) {
|
|
console.error('❌ Error in login-verify:', err);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Login verification failed',
|
|
message: err.message,
|
|
});
|
|
}
|
|
});
|
|
|
|
|
|
app.listen(3400, '192.168.1.3', () => {
|
|
console.log('WebAuthn server running on http://192.168.1.3:3400');
|
|
});
|