Move the whole app under /auth/, fix static build.

This commit is contained in:
Leo Vasanko 2025-07-13 14:03:15 -06:00
parent 7665044032
commit f9f263171b
9 changed files with 22 additions and 46 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ dist/
*.lock *.lock
*.db *.db
server-secret.bin server-secret.bin
/passkeyauth/frontend-static

Binary file not shown.

View File

@ -2,9 +2,9 @@
<html lang=""> <html lang="">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="icon" href="/favicon.ico"> <link rel="icon" href="/src/assets/icon.webp">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title> <title>Passkey Authentication</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View File

@ -16,7 +16,6 @@
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^6.0.0", "@vitejs/plugin-vue": "^6.0.0",
"vite": "^7.0.4", "vite": "^7.0.4"
"vite-plugin-vue-devtools": "^7.7.7"
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View File

@ -1,6 +1,5 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { registerUser, authenticateUser, registerWithToken } from '@/utils/passkey' import { registerUser, authenticateUser, registerWithToken } from '@/utils/passkey'
import aWebSocket from '@/utils/awaitable-websocket'
export const useAuthStore = defineStore('auth', { export const useAuthStore = defineStore('auth', {
state: () => ({ state: () => ({
@ -102,18 +101,6 @@ export const useAuthStore = defineStore('auth', {
this.isLoading = false this.isLoading = false
} }
}, },
async addNewCredential() {
this.isLoading = true;
try {
const result = await registerWithToken()
await this.loadCredentials()
return result;
} catch (error) {
throw new Error(`Failed to add new credential: ${error.message}`)
} finally {
this.isLoading = false
}
},
async deleteCredential(credentialId) { async deleteCredential(credentialId) {
const response = await fetch('/auth/delete-credential', { const response = await fetch('/auth/delete-credential', {
method: 'POST', method: 'POST',
@ -138,24 +125,5 @@ export const useAuthStore = defineStore('auth', {
this.currentCredentials = [] this.currentCredentials = []
this.aaguidInfo = {} this.aaguidInfo = {}
}, },
async checkResetCookieAndRegister() {
const passphrase = getCookie('reset')
if (passphrase) {
// Abandon existing session
await fetch('/auth/logout', { method: 'POST', credentials: 'include' })
// Register additional token for the user
try {
const result = await registerUserFromCookie()
await this.setSessionCookie(result.session_token)
this.currentUser = {
user_id: result.user_id,
user_name: result.user_name,
}
} catch (error) {
console.error('Failed to register additional token:', error)
}
}
},
} }
}) })

View File

@ -22,7 +22,7 @@ export async function registerCredential() {
return register('/auth/ws/add_credential') return register('/auth/ws/add_credential')
} }
export async function registerWithToken(token) { export async function registerWithToken(token) {
return register('/auth/ws/add_device_credential', {token}) return register('/auth/ws/add_device_credential', { token })
} }
export async function authenticateUser() { export async function authenticateUser() {

View File

@ -2,19 +2,18 @@ import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
vue(), vue(),
vueDevTools(),
], ],
resolve: { resolve: {
alias: { alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)) '@': fileURLToPath(new URL('./src', import.meta.url))
}, },
}, },
base: '/auth/',
server: { server: {
port: 3000, port: 3000,
proxy: { proxy: {
@ -26,7 +25,7 @@ export default defineConfig({
} }
}, },
build: { build: {
outDir: '../static/dist', outDir: '../passkeyauth/frontend-static',
emptyOutDir: true, emptyOutDir: true,
assetsDir: 'assets' assetsDir: 'assets'
} }

View File

@ -39,8 +39,7 @@ from .passkey import Passkey
from .reset_handlers import create_device_addition_link, validate_device_addition_token from .reset_handlers import create_device_addition_link, validate_device_addition_token
from .session_manager import get_user_from_cookie_string from .session_manager import get_user_from_cookie_string
STATIC_DIR = Path(__file__).parent.parent / "static" STATIC_DIR = Path(__file__).parent / "frontend-static"
passkey = Passkey( passkey = Passkey(
rp_id="localhost", rp_id="localhost",
@ -213,7 +212,7 @@ async def websocket_authenticate(ws: WebSocket):
await ws.accept() await ws.accept()
origin = ws.headers.get("origin") origin = ws.headers.get("origin")
try: try:
options, challenge = passkey.auth_generate_options(origin=origin) options, challenge = passkey.auth_generate_options()
await ws.send_json(options) await ws.send_json(options)
# Wait for the client to use his authenticator to authenticate # Wait for the client to use his authenticator to authenticate
credential = passkey.auth_parse(await ws.receive_json()) credential = passkey.auth_parse(await ws.receive_json())
@ -331,13 +330,23 @@ async def api_get_user_info_by_passphrase(token: str):
# Serve static files # Serve static files
app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static") app.mount(
"/auth/assets", StaticFiles(directory=STATIC_DIR / "assets"), name="static assets"
)
@app.get("/auth")
async def redirect_to_index():
"""Serve the main authentication app."""
return FileResponse(STATIC_DIR / "index.html")
# Catch-all route for SPA - serve index.html for all non-API routes # Catch-all route for SPA - serve index.html for all non-API routes
@app.get("/{path:path}") @app.get("/{path:path}")
async def spa_handler(path: str): async def spa_handler(request: Request, path: str):
"""Serve the Vue SPA for all routes (except API and static)""" """Serve the Vue SPA for all routes (except API and static)"""
if "text/html" not in request.headers.get("accept", ""):
return Response(content="Not Found", status_code=404)
return FileResponse(STATIC_DIR / "index.html") return FileResponse(STATIC_DIR / "index.html")