Use rp-name for frontend branding

This commit is contained in:
Leo Vasanko 2025-09-01 18:48:59 -06:00
parent 0b285e6ef0
commit 7036338b33
7 changed files with 37 additions and 5 deletions

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" href="/src/assets/icon.webp" /> <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>Passkey Admin</title> <title>Admin</title>
</head> </head>
<body> <body>
<div id="admin-app"></div> <div id="admin-app"></div>

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="icon" href="/src/assets/icon.webp"> <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>Passkey Authentication</title> <title>Authentication</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View File

@ -26,6 +26,8 @@ onMounted(async () => {
if (!location.pathname.startsWith('/auth/')) { if (!location.pathname.startsWith('/auth/')) {
store.setRestrictedMode(true) store.setRestrictedMode(true)
} }
// Load branding / settings first (non-blocking for auth flow)
await store.loadSettings()
// Was an error message passed in the URL hash? // Was an error message passed in the URL hash?
const message = location.hash.substring(1) const message = location.hash.substring(1)
if (message) { if (message) {

View File

@ -277,8 +277,12 @@ function deletePermission(p) {
} }) } })
} }
onMounted(() => { onMounted(async () => {
window.addEventListener('hashchange', parseHash) window.addEventListener('hashchange', parseHash)
await authStore.loadSettings()
if (authStore.settings?.rp_name) {
document.title = authStore.settings.rp_name + ' Admin'
}
load() load()
}) })
@ -435,7 +439,7 @@ async function submitDialog() {
<template> <template>
<div class="container"> <div class="container">
<h1 v-if="!selectedUser"> <h1 v-if="!selectedUser">
<template v-if="!selectedOrg">Passkey Admin</template> <template v-if="!selectedOrg">{{ (authStore.settings?.rp_name || 'Passkey') + ' Admin' }}</template>
<template v-else>Organization Admin</template> <template v-else>Organization Admin</template>
<a href="/auth/" class="back-link" title="Back to User App">User</a> <a href="/auth/" class="back-link" title="Back to User App">User</a>
<a v-if="selectedOrg && info?.is_global_admin" @click.prevent="goOverview" href="#overview" class="nav-link" title="Back to overview">Overview</a> <a v-if="selectedOrg && info?.is_global_admin" @click.prevent="goOverview" href="#overview" class="nav-link" title="Back to overview">Overview</a>

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="container"> <div class="container">
<div class="view active"> <div class="view active">
<h1>🔐 Passkey Login</h1> <h1>🔐 {{ (authStore.settings?.rp_name || 'Passkey') + ' Login' }}</h1>
<form @submit.prevent="handleLogin"> <form @submit.prevent="handleLogin">
<button <button
type="submit" type="submit"

View File

@ -5,6 +5,7 @@ export const useAuthStore = defineStore('auth', {
state: () => ({ state: () => ({
// Auth State // Auth State
userInfo: null, // Contains the full user info response: {user, credentials, aaguid_info, session_type, authenticated} userInfo: null, // Contains the full user info response: {user, credentials, aaguid_info, session_type, authenticated}
settings: null, // Server provided settings (/auth/settings)
isLoading: false, isLoading: false,
resetToken: null, // transient reset token resetToken: null, // transient reset token
restrictedMode: false, // If true, app loaded outside /auth/ and should restrict to login or permission denied restrictedMode: false, // If true, app loaded outside /auth/ and should restrict to login or permission denied
@ -106,6 +107,19 @@ export const useAuthStore = defineStore('auth', {
this.userInfo = result this.userInfo = result
console.log('User info loaded:', result) console.log('User info loaded:', result)
}, },
async loadSettings() {
try {
const res = await fetch('/auth/settings')
if (!res.ok) return
const data = await res.json()
this.settings = data
if (data?.rp_name) {
document.title = data.rp_name
}
} catch (_) {
// ignore
}
},
async deleteCredential(uuid) { async deleteCredential(uuid) {
const response = await fetch(`/auth/credential/${uuid}`, {method: 'Delete'}) const response = await fetch(`/auth/credential/${uuid}`, {method: 'Delete'})
const result = await response.json() const result = await response.json()

View File

@ -53,6 +53,18 @@ def register_api_routes(app: FastAPI):
s = await authz.verify(auth, perm) s = await authz.verify(auth, perm)
return {"valid": True, "user_uuid": str(s.user_uuid)} return {"valid": True, "user_uuid": str(s.user_uuid)}
@app.get("/auth/settings")
async def get_settings():
"""Return server runtime settings safe for public consumption.
Provides relying party metadata used by the frontend to brand UI.
"""
pk = global_passkey.instance
return {
"rp_id": pk.rp_id,
"rp_name": pk.rp_name,
}
@app.post("/auth/user-info") @app.post("/auth/user-info")
async def api_user_info(reset: str | None = None, auth=Cookie(None)): async def api_user_info(reset: str | None = None, auth=Cookie(None)):
"""Get user information. """Get user information.