Add in registration token requirement to prevent unauthorised registrations.

This commit is contained in:
Cameron Redmore 2025-04-25 23:54:55 +01:00
parent 5268d6aecd
commit 0d277e3035
4 changed files with 50 additions and 8 deletions

View file

@ -9,6 +9,7 @@ import {
import { isoBase64URL } from '@simplewebauthn/server/helpers'; // Ensure this is imported if not already import { isoBase64URL } from '@simplewebauthn/server/helpers'; // Ensure this is imported if not already
import prisma from '../database.js'; import prisma from '../database.js';
import { rpID, rpName, origin, challengeStore } from '../server.js'; // Import RP details and challenge store import { rpID, rpName, origin, challengeStore } from '../server.js'; // Import RP details and challenge store
import { getSetting } from '../utils/settings.js';
const router = express.Router(); const router = express.Router();
@ -49,13 +50,21 @@ async function getAuthenticatorByCredentialID(credentialID)
router.post('/generate-registration-options', async(req, res) => router.post('/generate-registration-options', async(req, res) =>
{ {
// Destructure username, email, and fullName from the request body // Destructure username, email, and fullName from the request body
const { username, email, fullName } = req.body; const { username, email, fullName, registrationToken } = req.body;
if (!username) if (!username)
{ {
return res.status(400).json({ error: 'Username is required' }); return res.status(400).json({ error: 'Username is required' });
} }
//Check if the registrationToken matches the setting
const registrationTokenSetting = await getSetting('REGISTRATION_TOKEN');
if (registrationTokenSetting !== registrationToken)
{
return res.status(403).json({ error: 'Invalid registration token' });
}
try try
{ {
let user = await getUserByUsername(username); let user = await getUserByUsername(username);
@ -71,7 +80,6 @@ router.post('/generate-registration-options', async(req, res) =>
data: userData, data: userData,
}); });
} }
// ... rest of the existing logic ...
const userAuthenticators = await getUserAuthenticators(user.id); const userAuthenticators = await getUserAuthenticators(user.id);

View file

@ -35,6 +35,15 @@
:rules="[val => !!val || 'Full Name is required']" :rules="[val => !!val || 'Full Name is required']"
@keyup.enter="handleRegister" @keyup.enter="handleRegister"
/> />
<q-input
v-model="registrationToken"
label="Registration Token"
outlined
class="q-mb-md"
:rules="[val => !!val || 'Registration Token is required']"
:readonly="!showTokenInput"
@keyup.enter="handleRegister"
/>
<q-btn <q-btn
label="Register Passkey" label="Register Passkey"
color="primary" color="primary"
@ -70,8 +79,8 @@
</template> </template>
<script setup> <script setup>
import { ref } from 'vue'; // Remove computed and onMounted import { ref, onMounted } from 'vue'; // Add onMounted
import { useRouter } from 'vue-router'; import { useRouter, useRoute } from 'vue-router'; // Add useRoute
import { startRegistration } from '@simplewebauthn/browser'; import { startRegistration } from '@simplewebauthn/browser';
import axios from 'boot/axios'; import axios from 'boot/axios';
// Remove auth store import // Remove auth store import
@ -80,6 +89,7 @@ const loading = ref(false);
const errorMessage = ref(''); const errorMessage = ref('');
const successMessage = ref(''); const successMessage = ref('');
const router = useRouter(); const router = useRouter();
const route = useRoute(); // Get route object
// Remove auth store usage // Remove auth store usage
// Remove isLoggedIn computed property // Remove isLoggedIn computed property
@ -87,13 +97,30 @@ const router = useRouter();
const username = ref(''); const username = ref('');
const email = ref(''); const email = ref('');
const fullName = ref(''); const fullName = ref('');
const registrationToken = ref(''); // Add registrationToken ref
const showTokenInput = ref(false); // Control visibility of token input
// Check for token in route params when component mounts
onMounted(() =>
{
const tokenFromRoute = route.params.token;
if (tokenFromRoute && typeof tokenFromRoute === 'string')
{
registrationToken.value = tokenFromRoute;
showTokenInput.value = false;
}
else
{
showTokenInput.value = true; // Show input if no token in route
}
});
// Remove onMounted hook // Remove onMounted hook
async function handleRegister() async function handleRegister()
{ {
// Validate all fields // Validate all fields including registration token if input is shown
if (!username.value || !email.value || !fullName.value) if (!username.value || !email.value || !fullName.value || (showTokenInput.value && !registrationToken.value))
{ {
errorMessage.value = 'Please fill in all required fields.'; errorMessage.value = 'Please fill in all required fields.';
return; return;
@ -111,11 +138,12 @@ async function handleRegister()
try try
{ {
// Prepare payload - always include all fields // Prepare payload - always include all fields + registrationToken
const payload = { const payload = {
username: username.value, username: username.value,
email: email.value, email: email.value,
fullName: fullName.value, fullName: fullName.value,
registrationToken: registrationToken.value, // Add registrationToken to payload
}; };
// 1. Get options from server // 1. Get options from server

View file

@ -87,6 +87,12 @@ const $q = useQuasar();
// Define the structure of settings // Define the structure of settings
const settings = ref({ const settings = ref({
General: [
{
name: 'Registration Token',
key: 'REGISTRATION_TOKEN',
}
],
Mantis: [ Mantis: [
{ {
name: 'Mantis API Key', name: 'Mantis API Key',

View file

@ -22,7 +22,7 @@ const routes = [
} }
}, },
{ {
path: '/register', path: '/register/:token?',
name: 'register', name: 'register',
component: () => import('pages/RegisterPage.vue'), component: () => import('pages/RegisterPage.vue'),
meta: { meta: {