Authentication flow change - Remove requirement for user to enter username - Use Passkey discoverability instead.
This commit is contained in:
parent
0d277e3035
commit
7564937faa
8 changed files with 120 additions and 75 deletions
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
|
@ -16,5 +16,8 @@
|
|||
"editor.formatOnSave": true,
|
||||
"files.eol": "\n",
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"editor.trimAutoWhitespace": true
|
||||
"editor.trimAutoWhitespace": true,
|
||||
"[scss]": {
|
||||
"editor.defaultFormatter": "vscode.css-language-features"
|
||||
}
|
||||
}
|
|
@ -50,7 +50,10 @@ export default defineConfig((/* ctx */) =>
|
|||
|
||||
// publicPath: '/',
|
||||
// analyze: true,
|
||||
// env: {},
|
||||
env: {
|
||||
API_URL: process.env.API_URL || '/api',
|
||||
PRODUCT_NAME: process.env.PRODUCT_NAME || 'StylePoint',
|
||||
},
|
||||
// rawDefine: {}
|
||||
// ignorePublicFolder: true,
|
||||
// minify: false,
|
||||
|
|
|
@ -60,7 +60,7 @@ router.post('/generate-registration-options', async(req, res) =>
|
|||
//Check if the registrationToken matches the setting
|
||||
const registrationTokenSetting = await getSetting('REGISTRATION_TOKEN');
|
||||
|
||||
if (registrationTokenSetting !== registrationToken)
|
||||
if (registrationTokenSetting !== registrationToken && !req.session.loggedInUserId)
|
||||
{
|
||||
return res.status(403).json({ error: 'Invalid registration token' });
|
||||
}
|
||||
|
@ -200,9 +200,6 @@ router.post('/verify-registration', async(req, res) =>
|
|||
challengeStore.delete(userId);
|
||||
delete req.session.userId;
|
||||
|
||||
// Log the user in by setting the final session userId
|
||||
req.session.loggedInUserId = user.id;
|
||||
|
||||
res.json({ verified: true });
|
||||
}
|
||||
else
|
||||
|
@ -243,27 +240,29 @@ router.post('/generate-authentication-options', async(req, res) =>
|
|||
user = await getUserById(req.session.loggedInUserId);
|
||||
}
|
||||
|
||||
if (!user)
|
||||
{
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
// if (!user)
|
||||
// {
|
||||
// return res.status(404).json({ error: 'User not found' });
|
||||
// }
|
||||
|
||||
const userAuthenticators = await getUserAuthenticators(user.id);
|
||||
// const userAuthenticators = await getUserAuthenticators(user.id);
|
||||
|
||||
const options = await generateAuthenticationOptions({
|
||||
rpID,
|
||||
// Require users to use a previously-registered authenticator
|
||||
allowCredentials: userAuthenticators.map(auth => ({
|
||||
id: auth.credentialID,
|
||||
type: 'public-key',
|
||||
transports: auth.transports ? auth.transports.split(',') : undefined,
|
||||
})),
|
||||
// allowCredentials: userAuthenticators.map(auth => ({
|
||||
// id: auth.credentialID,
|
||||
// type: 'public-key',
|
||||
// transports: auth.transports ? auth.transports.split(',') : undefined,
|
||||
// })),
|
||||
allowCredentials: [],
|
||||
userVerification: 'preferred',
|
||||
});
|
||||
|
||||
// Store the challenge associated with the user ID for verification
|
||||
challengeStore.set(user.id, options.challenge);
|
||||
req.session.challengeUserId = user.id; // Store user ID associated with this challenge
|
||||
//Store challenge associated with random ID
|
||||
const challengeId = crypto.randomUUID();
|
||||
challengeStore.set(challengeId, options.challenge);
|
||||
req.session.challengeUserId = challengeId; // Store user ID associated with this challenge
|
||||
|
||||
res.json(options);
|
||||
}
|
||||
|
@ -294,11 +293,11 @@ router.post('/verify-authentication', async(req, res) =>
|
|||
|
||||
try
|
||||
{
|
||||
const user = await getUserById(challengeUserId);
|
||||
if (!user)
|
||||
{
|
||||
return res.status(404).json({ error: 'User associated with challenge not found' });
|
||||
}
|
||||
// const user = await getUserById(challengeUserId);
|
||||
// if (!user)
|
||||
// {
|
||||
// return res.status(404).json({ error: 'User associated with challenge not found' });
|
||||
// }
|
||||
|
||||
const authenticator = await getAuthenticatorByCredentialID(authenticationResponse.id);
|
||||
|
||||
|
@ -307,6 +306,12 @@ router.post('/verify-authentication', async(req, res) =>
|
|||
return res.status(404).json({ error: 'Authenticator not found' });
|
||||
}
|
||||
|
||||
const user = await getUserById(authenticator.userId);
|
||||
if (!user)
|
||||
{
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
|
||||
// Ensure the authenticator belongs to the user attempting to log in
|
||||
if (authenticator.userId !== user.id)
|
||||
{
|
||||
|
|
|
@ -2,6 +2,23 @@
|
|||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Exo+2:ital,wght@0,100..900;1,100..900&display=swap');
|
||||
|
||||
html, body {
|
||||
html,
|
||||
body {
|
||||
font-family: 'Exo 2', sans-serif;
|
||||
}
|
||||
|
||||
.bg-theme {
|
||||
background: linear-gradient(to top left,
|
||||
#fdc730,
|
||||
#ea2963,
|
||||
#bd288a 50%,
|
||||
#6e43ac,
|
||||
#4763bf,
|
||||
#16a3e8) !important;
|
||||
}
|
||||
|
||||
|
||||
.bg-blurred {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
|
@ -18,7 +18,7 @@
|
|||
</q-item-section>
|
||||
<q-item-section v-if="leftDrawerOpen">
|
||||
<q-item-label class="text-h4 absolute-center">
|
||||
StylePoint
|
||||
{{ productName }}
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
@ -195,7 +195,7 @@
|
|||
<q-fab
|
||||
v-model="fabOpen"
|
||||
icon="chat"
|
||||
color="accent"
|
||||
class="bg-theme"
|
||||
direction="up"
|
||||
@click="toggleChat"
|
||||
/>
|
||||
|
@ -265,6 +265,8 @@ import { usePreferencesStore } from 'stores/preferences'; // Import the preferen
|
|||
import ChatInterface from 'components/ChatInterface.vue'; // Adjust path as needed
|
||||
import routes from '../router/routes'; // Import routes
|
||||
|
||||
const productName = process.env.PRODUCT_NAME;
|
||||
|
||||
const $q = useQuasar();
|
||||
const leftDrawerOpen = ref(false);
|
||||
const router = useRouter();
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
<template>
|
||||
<q-page class="landing-page column items-center q-pa-md wallpaper">
|
||||
<q-page class="landing-page column items-center q-pa-md bg-theme">
|
||||
<div class="hero text-center q-pa-xl full-width">
|
||||
<q-img
|
||||
<img
|
||||
src="/stylepoint.png"
|
||||
alt="StylePoint Logo"
|
||||
class="logo q-mb-md"
|
||||
style="max-width: 300px; width: 100%;"
|
||||
/>
|
||||
>
|
||||
<h1 class="text-h3 text-weight-bold text-yellow q-mb-sm">
|
||||
Welcome to StylePoint
|
||||
Welcome to {{ productName }}
|
||||
</h1>
|
||||
<p class="text-h6 text-white q-mb-lg">
|
||||
An all-in-one tool designed for StyleTech Developers.
|
||||
|
@ -47,6 +47,8 @@
|
|||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
const productName = process.env.PRODUCT_NAME;
|
||||
|
||||
const features = ref([
|
||||
'Automated Daily Reports',
|
||||
'Deep Mantis Integration',
|
||||
|
@ -65,18 +67,6 @@ const features = ref([
|
|||
filter: drop-shadow(0 0 25px rgba(0, 0, 0, 0.5));
|
||||
}
|
||||
|
||||
.wallpaper {
|
||||
background: linear-gradient(
|
||||
to top left,
|
||||
#fdc730,
|
||||
#ea2963,
|
||||
#bd288a 50%,
|
||||
#6e43ac,
|
||||
#4763bf,
|
||||
#16a3e8
|
||||
);
|
||||
}
|
||||
|
||||
.features {
|
||||
background-color: rgba(0, 0, 0, 0.25);
|
||||
border-radius: 10px;
|
||||
|
|
|
@ -1,29 +1,38 @@
|
|||
<template>
|
||||
<q-page class="flex flex-center">
|
||||
<q-card style="width: 400px; max-width: 90vw;">
|
||||
<q-page class="flex flex-center bg-theme">
|
||||
<img
|
||||
src="/stylepoint.png"
|
||||
alt="StylePoint Logo"
|
||||
class="logo q-mb-md absolute"
|
||||
style="max-width: 300px; width: 100%; top: 75px;"
|
||||
>
|
||||
<q-card
|
||||
style="width: 400px; max-width: 90vw;"
|
||||
dark
|
||||
class="bg-blurred"
|
||||
flat
|
||||
bordered
|
||||
>
|
||||
<q-card-section>
|
||||
<div class="text-h6">
|
||||
Login
|
||||
<div
|
||||
class="
|
||||
text-h4
|
||||
text-center"
|
||||
>
|
||||
<small>Login</small>
|
||||
</div>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-section>
|
||||
<q-input
|
||||
v-model="username"
|
||||
label="Username"
|
||||
outlined
|
||||
dense
|
||||
class="q-mb-md"
|
||||
@keyup.enter="handleLogin"
|
||||
:hint="errorMessage ? errorMessage : ''"
|
||||
:rules="[val => !!val || 'Username is required']"
|
||||
/>
|
||||
<q-separator />
|
||||
|
||||
<q-card-section class="row justify-center">
|
||||
<q-btn
|
||||
label="Login with Passkey"
|
||||
color="primary"
|
||||
class="full-width"
|
||||
icon="key"
|
||||
round
|
||||
@click="handleLogin"
|
||||
:loading="loading"
|
||||
size="xl"
|
||||
class="bg-theme shadow-5"
|
||||
/>
|
||||
<div
|
||||
v-if="errorMessage"
|
||||
|
@ -35,7 +44,8 @@
|
|||
|
||||
<q-card-actions align="center">
|
||||
<q-btn
|
||||
flat
|
||||
outline
|
||||
color="secondary"
|
||||
label="Don't have an account? Register"
|
||||
to="/register"
|
||||
/>
|
||||
|
@ -51,7 +61,8 @@ import { startAuthentication } from '@simplewebauthn/browser';
|
|||
import axios from 'boot/axios';
|
||||
import { useAuthStore } from 'stores/auth'; // Import the auth store
|
||||
|
||||
const username = ref('');
|
||||
const productName = process.env.PRODUCT_NAME;
|
||||
|
||||
const loading = ref(false);
|
||||
const errorMessage = ref('');
|
||||
const router = useRouter();
|
||||
|
@ -65,9 +76,7 @@ async function handleLogin()
|
|||
try
|
||||
{
|
||||
// 1. Get options from server
|
||||
const optionsRes = await axios.post('/api/auth/generate-authentication-options', {
|
||||
username: username.value || undefined, // Send username if provided
|
||||
});
|
||||
const optionsRes = await axios.post('/api/auth/generate-authentication-options');
|
||||
const options = optionsRes.data;
|
||||
|
||||
// 2. Start authentication ceremony in browser
|
||||
|
@ -125,4 +134,4 @@ async function handleLogin()
|
|||
loading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
|
@ -1,13 +1,27 @@
|
|||
<template>
|
||||
<q-page class="flex flex-center">
|
||||
<q-card style="width: 400px; max-width: 90vw;">
|
||||
<q-page class="flex flex-center bg-theme">
|
||||
<img
|
||||
src="/stylepoint.png"
|
||||
alt="StylePoint Logo"
|
||||
class="logo q-mb-md absolute"
|
||||
style="max-width: 300px; width: 100%; top: 75px;"
|
||||
>
|
||||
<q-card
|
||||
style="width: 400px; max-width: 90vw;"
|
||||
dark
|
||||
class="bg-blurred"
|
||||
flat
|
||||
bordered
|
||||
>
|
||||
<q-card-section>
|
||||
<!-- Update title -->
|
||||
<div class="text-h6">
|
||||
Register Passkey
|
||||
<div class="text-h4 text-center">
|
||||
Register Account
|
||||
</div>
|
||||
</q-card-section>
|
||||
|
||||
<q-separator />
|
||||
|
||||
<q-card-section>
|
||||
<q-input
|
||||
v-model="username"
|
||||
|
@ -45,12 +59,12 @@
|
|||
@keyup.enter="handleRegister"
|
||||
/>
|
||||
<q-btn
|
||||
label="Register Passkey"
|
||||
label="Register Account"
|
||||
color="primary"
|
||||
class="full-width"
|
||||
@click="handleRegister"
|
||||
:loading="loading"
|
||||
:disable="loading || !username || !email || !fullName"
|
||||
:disable="loading || !username || !email || !fullName || (showTokenInput && !registrationToken)"
|
||||
/>
|
||||
<div
|
||||
v-if="successMessage"
|
||||
|
@ -66,14 +80,16 @@
|
|||
</div>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-actions align="center">
|
||||
<q-card-section>
|
||||
<!-- Always show login link -->
|
||||
<q-btn
|
||||
color="secondary"
|
||||
flat
|
||||
label="Already have an account? Login"
|
||||
to="/login"
|
||||
class="full-width"
|
||||
/>
|
||||
</q-card-actions>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-page>
|
||||
</template>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue