Nxing/Caddy forward_auth support. Various fixes to bugs created in earlier edits. Vite server needs different base in dev mode, fixed.

This commit is contained in:
Leo Vasanko 2025-07-13 16:54:53 -06:00
parent 99b5187a33
commit 1c79132e22
6 changed files with 42 additions and 26 deletions

View File

@ -31,7 +31,11 @@ const handleLogin = async () => {
authStore.showMessage('Starting authentication...', 'info') authStore.showMessage('Starting authentication...', 'info')
await authStore.authenticate() await authStore.authenticate()
authStore.showMessage('Authentication successful!', 'success', 2000) authStore.showMessage('Authentication successful!', 'success', 2000)
authStore.currentView = 'profile' if (location.pathname.startsWith('/auth/')) {
authStore.currentView = 'profile'
} else {
location.reload()
}
} catch (error) { } catch (error) {
authStore.showMessage(`Authentication failed: ${error.message}`, 'error') authStore.showMessage(`Authentication failed: ${error.message}`, 'error')
} }

View File

@ -5,7 +5,7 @@
<form @submit.prevent="handleRegister"> <form @submit.prevent="handleRegister">
<input <input
type="text" type="text"
v-model="username" v-model="user_name"
placeholder="Enter username" placeholder="Enter username"
required required
:disabled="authStore.isLoading" :disabled="authStore.isLoading"
@ -13,7 +13,7 @@
<button <button
type="submit" type="submit"
class="btn-primary" class="btn-primary"
:disabled="authStore.isLoading || !username.trim()" :disabled="authStore.isLoading || !user_name.trim()"
> >
{{ authStore.isLoading ? 'Registering...' : 'Register Passkey' }} {{ authStore.isLoading ? 'Registering...' : 'Register Passkey' }}
</button> </button>

View File

@ -15,7 +15,7 @@ export async function register(url, options) {
} }
export async function registerUser(user_name) { export async function registerUser(user_name) {
return register('/auth/ws/new_user_registration', { user_name }) return register('/auth/ws/register_new', { user_name })
} }
export async function registerCredential() { export async function registerCredential() {

View File

@ -4,7 +4,7 @@ import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig(({ command, mode }) => ({
plugins: [ plugins: [
vue(), vue(),
], ],
@ -13,7 +13,7 @@ export default defineConfig({
'@': fileURLToPath(new URL('./src', import.meta.url)) '@': fileURLToPath(new URL('./src', import.meta.url))
}, },
}, },
base: '/auth/', base: command == 'build' ? '/auth/' : '/',
server: { server: {
port: 3000, port: 3000,
proxy: { proxy: {
@ -29,4 +29,4 @@ export default defineConfig({
emptyOutDir: true, emptyOutDir: true,
assetsDir: 'assets' assetsDir: 'assets'
} }
}) }))

View File

@ -15,11 +15,17 @@ from datetime import datetime
from pathlib import Path from pathlib import Path
from uuid import UUID, uuid4 from uuid import UUID, uuid4
from fastapi import FastAPI, Request, Response, WebSocket, WebSocketDisconnect from fastapi import (
FastAPI,
Request,
Response,
WebSocket,
WebSocketDisconnect,
)
from fastapi import ( from fastapi import (
Path as FastAPIPath, Path as FastAPIPath,
) )
from fastapi.responses import FileResponse, RedirectResponse from fastapi.responses import FileResponse, JSONResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from webauthn.helpers.exceptions import InvalidAuthenticationResponse from webauthn.helpers.exceptions import InvalidAuthenticationResponse
@ -204,7 +210,7 @@ async def register_chat(
) )
await ws.send_json(options) await ws.send_json(options)
response = await ws.receive_json() response = await ws.receive_json()
return passkey.reg_verify(response, challenge, user_id) return passkey.reg_verify(response, challenge, user_id, origin=origin)
@app.websocket("/auth/ws/authenticate") @app.websocket("/auth/ws/authenticate")
@ -269,6 +275,27 @@ async def api_validate_token(request: Request):
return await validate_token(request) return await validate_token(request)
@app.get("/auth/forward-auth")
async def forward_authentication(request: Request):
"""A verification endpoint to use with Caddy forward_auth or Nginx auth_request."""
result = await validate_token(request)
if result.get("status") != "success":
# Serve the index.html of the authentication app if not authenticated
return FileResponse(
STATIC_DIR / "index.html",
status_code=401,
headers={"www-authenticate": "PrivateToken"},
)
# If authenticated, return a success response
return JSONResponse(
result,
headers={
"x-auth-user-id": result["user_id"],
},
)
@app.post("/auth/logout") @app.post("/auth/logout")
async def api_logout(response: Response): async def api_logout(response: Response):
"""Log out the current user by clearing the session cookie.""" """Log out the current user by clearing the session cookie."""
@ -315,20 +342,6 @@ async def reset_authentication(
return response return response
@app.get("/auth/user-info-by-passphrase")
async def api_get_user_info_by_passphrase(token: str):
"""Get user information using the passphrase."""
reset_token = await db.get_reset_token(token)
if not reset_token:
return Response(content="Invalid or expired passphrase", status_code=403)
user = await db.get_user_by_id(reset_token.user_id)
if not user:
return Response(content="User not found", status_code=404)
return {"user_name": user.user_name}
# Serve static files # Serve static files
app.mount( app.mount(
"/auth/assets", StaticFiles(directory=STATIC_DIR / "assets"), name="static assets" "/auth/assets", StaticFiles(directory=STATIC_DIR / "assets"), name="static assets"

View File

@ -141,11 +141,10 @@ class Passkey:
Registration verification result Registration verification result
""" """
credential = parse_registration_credential_json(response_json) credential = parse_registration_credential_json(response_json)
expected_origin = origin or self.origin
registration = verify_registration_response( registration = verify_registration_response(
credential=credential, credential=credential,
expected_challenge=expected_challenge, expected_challenge=expected_challenge,
expected_origin=expected_origin, expected_origin=origin or self.origin,
expected_rp_id=self.rp_id, expected_rp_id=self.rp_id,
) )
return StoredCredential( return StoredCredential(