Added linting and enforced code styling.
This commit is contained in:
parent
8655eae39c
commit
86967b26cd
37 changed files with 3356 additions and 1875 deletions
|
@ -13,7 +13,8 @@ import { rpID, rpName, origin, challengeStore } from '../server.js'; // Import R
|
|||
const router = express.Router();
|
||||
|
||||
// Helper function to get user authenticators
|
||||
async function getUserAuthenticators(userId) {
|
||||
async function getUserAuthenticators(userId)
|
||||
{
|
||||
return prisma.authenticator.findMany({
|
||||
where: { userId },
|
||||
select: {
|
||||
|
@ -26,34 +27,41 @@ async function getUserAuthenticators(userId) {
|
|||
}
|
||||
|
||||
// Helper function to get a user by username
|
||||
async function getUserByUsername(username) {
|
||||
return prisma.user.findUnique({ where: { username } });
|
||||
async function getUserByUsername(username)
|
||||
{
|
||||
return prisma.user.findUnique({ where: { username } });
|
||||
}
|
||||
|
||||
// Helper function to get a user by ID
|
||||
async function getUserById(id) {
|
||||
return prisma.user.findUnique({ where: { id } });
|
||||
async function getUserById(id)
|
||||
{
|
||||
return prisma.user.findUnique({ where: { id } });
|
||||
}
|
||||
|
||||
// Helper function to get an authenticator by credential ID
|
||||
async function getAuthenticatorByCredentialID(credentialID) {
|
||||
return prisma.authenticator.findUnique({ where: { credentialID } });
|
||||
async function getAuthenticatorByCredentialID(credentialID)
|
||||
{
|
||||
return prisma.authenticator.findUnique({ where: { credentialID } });
|
||||
}
|
||||
|
||||
|
||||
// Generate Registration Options
|
||||
router.post('/generate-registration-options', async (req, res) => {
|
||||
router.post('/generate-registration-options', async(req, res) =>
|
||||
{
|
||||
const { username } = req.body;
|
||||
|
||||
if (!username) {
|
||||
if (!username)
|
||||
{
|
||||
return res.status(400).json({ error: 'Username is required' });
|
||||
}
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
let user = await getUserByUsername(username);
|
||||
|
||||
// If user doesn't exist, create one
|
||||
if (!user) {
|
||||
if (!user)
|
||||
{
|
||||
user = await prisma.user.create({
|
||||
data: { username },
|
||||
});
|
||||
|
@ -61,9 +69,11 @@ router.post('/generate-registration-options', async (req, res) => {
|
|||
|
||||
const userAuthenticators = await getUserAuthenticators(user.id);
|
||||
|
||||
if(userAuthenticators.length > 0) {
|
||||
if(userAuthenticators.length > 0)
|
||||
{
|
||||
//The user is trying to register a new authenticator, so we need to check if the user registering is the same as the one in the session
|
||||
if (!req.session.loggedInUserId || req.session.loggedInUserId !== user.id) {
|
||||
if (!req.session.loggedInUserId || req.session.loggedInUserId !== user.id)
|
||||
{
|
||||
return res.status(403).json({ error: 'Invalid registration attempt.' });
|
||||
}
|
||||
}
|
||||
|
@ -93,31 +103,38 @@ router.post('/generate-registration-options', async (req, res) => {
|
|||
req.session.userId = user.id; // Temporarily store userId in session for verification step
|
||||
|
||||
res.json(options);
|
||||
} catch (error) {
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
console.error('Registration options error:', error);
|
||||
res.status(500).json({ error: 'Failed to generate registration options' });
|
||||
}
|
||||
});
|
||||
|
||||
// Verify Registration
|
||||
router.post('/verify-registration', async (req, res) => {
|
||||
router.post('/verify-registration', async(req, res) =>
|
||||
{
|
||||
const { registrationResponse } = req.body;
|
||||
const userId = req.session.userId; // Retrieve userId stored during options generation
|
||||
|
||||
if (!userId) {
|
||||
return res.status(400).json({ error: 'User session not found. Please start registration again.' });
|
||||
if (!userId)
|
||||
{
|
||||
return res.status(400).json({ error: 'User session not found. Please start registration again.' });
|
||||
}
|
||||
|
||||
const expectedChallenge = challengeStore.get(userId);
|
||||
|
||||
if (!expectedChallenge) {
|
||||
if (!expectedChallenge)
|
||||
{
|
||||
return res.status(400).json({ error: 'Challenge not found or expired' });
|
||||
}
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
const user = await getUserById(userId);
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
if (!user)
|
||||
{
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
|
||||
const verification = await verifyRegistrationResponse({
|
||||
|
@ -132,7 +149,8 @@ router.post('/verify-registration', async (req, res) => {
|
|||
|
||||
console.log(verification);
|
||||
|
||||
if (verified && registrationInfo) {
|
||||
if (verified && registrationInfo)
|
||||
{
|
||||
const { credential, credentialDeviceType, credentialBackedUp } = registrationInfo;
|
||||
|
||||
const credentialID = credential.id;
|
||||
|
@ -143,7 +161,8 @@ router.post('/verify-registration', async (req, res) => {
|
|||
// Check if authenticator with this ID already exists
|
||||
const existingAuthenticator = await getAuthenticatorByCredentialID(isoBase64URL.fromBuffer(credentialID));
|
||||
|
||||
if (existingAuthenticator) {
|
||||
if (existingAuthenticator)
|
||||
{
|
||||
return res.status(409).json({ error: 'Authenticator already registered' });
|
||||
}
|
||||
|
||||
|
@ -168,10 +187,14 @@ router.post('/verify-registration', async (req, res) => {
|
|||
req.session.loggedInUserId = user.id;
|
||||
|
||||
res.json({ verified: true });
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
res.status(400).json({ error: 'Registration verification failed' });
|
||||
}
|
||||
} catch (error) {
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
console.error('Registration verification error:', error);
|
||||
challengeStore.delete(userId); // Clean up challenge on error
|
||||
delete req.session.userId;
|
||||
|
@ -180,19 +203,25 @@ router.post('/verify-registration', async (req, res) => {
|
|||
});
|
||||
|
||||
// Generate Authentication Options
|
||||
router.post('/generate-authentication-options', async (req, res) => {
|
||||
router.post('/generate-authentication-options', async(req, res) =>
|
||||
{
|
||||
const { username } = req.body;
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
let user;
|
||||
if (username) {
|
||||
user = await getUserByUsername(username);
|
||||
} else if (req.session.loggedInUserId) {
|
||||
// If already logged in, allow re-authentication (e.g., for step-up)
|
||||
user = await getUserById(req.session.loggedInUserId);
|
||||
if (username)
|
||||
{
|
||||
user = await getUserByUsername(username);
|
||||
}
|
||||
else if (req.session.loggedInUserId)
|
||||
{
|
||||
// If already logged in, allow re-authentication (e.g., for step-up)
|
||||
user = await getUserById(req.session.loggedInUserId);
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
if (!user)
|
||||
{
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
|
||||
|
@ -218,42 +247,51 @@ router.post('/generate-authentication-options', async (req, res) => {
|
|||
req.session.challengeUserId = user.id; // Store user ID associated with this challenge
|
||||
|
||||
res.json(options);
|
||||
} catch (error) {
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
console.error('Authentication options error:', error);
|
||||
res.status(500).json({ error: 'Failed to generate authentication options' });
|
||||
}
|
||||
});
|
||||
|
||||
// Verify Authentication
|
||||
router.post('/verify-authentication', async (req, res) => {
|
||||
router.post('/verify-authentication', async(req, res) =>
|
||||
{
|
||||
const { authenticationResponse } = req.body;
|
||||
const challengeUserId = req.session.challengeUserId; // Get user ID associated with the challenge
|
||||
|
||||
if (!challengeUserId) {
|
||||
return res.status(400).json({ error: 'Challenge session not found. Please try logging in again.' });
|
||||
if (!challengeUserId)
|
||||
{
|
||||
return res.status(400).json({ error: 'Challenge session not found. Please try logging in again.' });
|
||||
}
|
||||
|
||||
const expectedChallenge = challengeStore.get(challengeUserId);
|
||||
|
||||
if (!expectedChallenge) {
|
||||
if (!expectedChallenge)
|
||||
{
|
||||
return res.status(400).json({ error: 'Challenge not found or expired' });
|
||||
}
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
const user = await getUserById(challengeUserId);
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: 'User associated with challenge not found' });
|
||||
if (!user)
|
||||
{
|
||||
return res.status(404).json({ error: 'User associated with challenge not found' });
|
||||
}
|
||||
|
||||
const authenticator = await getAuthenticatorByCredentialID(authenticationResponse.id);
|
||||
|
||||
if (!authenticator) {
|
||||
if (!authenticator)
|
||||
{
|
||||
return res.status(404).json({ error: 'Authenticator not found' });
|
||||
}
|
||||
|
||||
// Ensure the authenticator belongs to the user attempting to log in
|
||||
if (authenticator.userId !== user.id) {
|
||||
return res.status(403).json({ error: 'Authenticator does not belong to this user' });
|
||||
if (authenticator.userId !== user.id)
|
||||
{
|
||||
return res.status(403).json({ error: 'Authenticator does not belong to this user' });
|
||||
}
|
||||
|
||||
const verification = await verifyAuthenticationResponse({
|
||||
|
@ -272,7 +310,8 @@ router.post('/verify-authentication', async (req, res) => {
|
|||
|
||||
const { verified, authenticationInfo } = verification;
|
||||
|
||||
if (verified) {
|
||||
if (verified)
|
||||
{
|
||||
// Update the authenticator counter
|
||||
await prisma.authenticator.update({
|
||||
where: { credentialID: authenticator.credentialID },
|
||||
|
@ -287,10 +326,14 @@ router.post('/verify-authentication', async (req, res) => {
|
|||
req.session.loggedInUserId = user.id;
|
||||
|
||||
res.json({ verified: true, user: { id: user.id, username: user.username } });
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
res.status(400).json({ error: 'Authentication verification failed' });
|
||||
}
|
||||
} catch (error) {
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
console.error('Authentication verification error:', error);
|
||||
challengeStore.delete(challengeUserId); // Clean up challenge on error
|
||||
delete req.session.challengeUserId;
|
||||
|
@ -299,12 +342,15 @@ router.post('/verify-authentication', async (req, res) => {
|
|||
});
|
||||
|
||||
// GET Passkeys for Logged-in User
|
||||
router.get('/passkeys', async (req, res) => {
|
||||
if (!req.session.loggedInUserId) {
|
||||
router.get('/passkeys', async(req, res) =>
|
||||
{
|
||||
if (!req.session.loggedInUserId)
|
||||
{
|
||||
return res.status(401).json({ error: 'Not authenticated' });
|
||||
}
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
const userId = req.session.loggedInUserId;
|
||||
const authenticators = await prisma.authenticator.findMany({
|
||||
where: { userId },
|
||||
|
@ -317,25 +363,31 @@ router.get('/passkeys', async (req, res) => {
|
|||
|
||||
// No need to convert credentialID here as it's stored as Base64URL string
|
||||
res.json(authenticators);
|
||||
} catch (error) {
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
console.error('Error fetching passkeys:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch passkeys' });
|
||||
}
|
||||
});
|
||||
|
||||
// DELETE Passkey
|
||||
router.delete('/passkeys/:credentialID', async (req, res) => {
|
||||
if (!req.session.loggedInUserId) {
|
||||
router.delete('/passkeys/:credentialID', async(req, res) =>
|
||||
{
|
||||
if (!req.session.loggedInUserId)
|
||||
{
|
||||
return res.status(401).json({ error: 'Not authenticated' });
|
||||
}
|
||||
|
||||
const { credentialID } = req.params; // This is already a Base64URL string from the client
|
||||
|
||||
if (!credentialID) {
|
||||
if (!credentialID)
|
||||
{
|
||||
return res.status(400).json({ error: 'Credential ID is required' });
|
||||
}
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
const userId = req.session.loggedInUserId;
|
||||
|
||||
// Find the authenticator first to ensure it belongs to the logged-in user
|
||||
|
@ -343,12 +395,14 @@ router.delete('/passkeys/:credentialID', async (req, res) => {
|
|||
where: { credentialID: credentialID }, // Use the Base64URL string directly
|
||||
});
|
||||
|
||||
if (!authenticator) {
|
||||
if (!authenticator)
|
||||
{
|
||||
return res.status(404).json({ error: 'Passkey not found' });
|
||||
}
|
||||
|
||||
// Security check: Ensure the passkey belongs to the user trying to delete it
|
||||
if (authenticator.userId !== userId) {
|
||||
if (authenticator.userId !== userId)
|
||||
{
|
||||
return res.status(403).json({ error: 'Permission denied' });
|
||||
}
|
||||
|
||||
|
@ -358,28 +412,36 @@ router.delete('/passkeys/:credentialID', async (req, res) => {
|
|||
});
|
||||
|
||||
res.json({ message: 'Passkey deleted successfully' });
|
||||
} catch (error) {
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
console.error('Error deleting passkey:', error);
|
||||
// Handle potential Prisma errors, e.g., record not found if deleted between check and delete
|
||||
if (error.code === 'P2025') { // Prisma code for record not found on delete/update
|
||||
return res.status(404).json({ error: 'Passkey not found' });
|
||||
if (error.code === 'P2025')
|
||||
{ // Prisma code for record not found on delete/update
|
||||
return res.status(404).json({ error: 'Passkey not found' });
|
||||
}
|
||||
res.status(500).json({ error: 'Failed to delete passkey' });
|
||||
}
|
||||
});
|
||||
|
||||
// Check Authentication Status
|
||||
router.get('/status', (req, res) => {
|
||||
if (req.session.loggedInUserId) {
|
||||
router.get('/status', (req, res) =>
|
||||
{
|
||||
if (req.session.loggedInUserId)
|
||||
{
|
||||
return res.json({ status: 'authenticated' });
|
||||
}
|
||||
res.json({ status: 'unauthenticated' });
|
||||
});
|
||||
|
||||
// Logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
router.post('/logout', (req, res) =>
|
||||
{
|
||||
req.session.destroy(err =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
console.error('Logout error:', err);
|
||||
return res.status(500).json({ error: 'Failed to logout' });
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue