Various fixes and cleanup, regressions from prior commits.
This commit is contained in:
@@ -26,15 +26,14 @@ const showBackMessage = ref(false)
|
||||
let validationTimer = null
|
||||
let authIframe = null
|
||||
|
||||
async function tryLoadUserInfo() {
|
||||
async function loadUserInfo() {
|
||||
try {
|
||||
await store.loadUserInfo()
|
||||
store.userInfo = await apiJson('/auth/api/user-info', { method: 'POST' })
|
||||
authenticated.value = true
|
||||
loading.value = false
|
||||
startSessionValidation()
|
||||
return true
|
||||
} catch (error) {
|
||||
// User info load failed - apiJson will show iframe if needed
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -74,7 +73,7 @@ function handleAuthMessage(event) {
|
||||
hideAuthIframe()
|
||||
loading.value = true
|
||||
loadingMessage.value = 'Loading user profile...'
|
||||
tryLoadUserInfo()
|
||||
loadUserInfo()
|
||||
break
|
||||
|
||||
case 'auth-error':
|
||||
@@ -150,7 +149,7 @@ onMounted(async () => {
|
||||
if (store.settings?.rp_name) document.title = store.settings.rp_name
|
||||
|
||||
// Try to load user info
|
||||
const success = await tryLoadUserInfo()
|
||||
const success = await loadUserInfo()
|
||||
|
||||
if (!success) {
|
||||
// Need authentication - show login iframe
|
||||
|
||||
@@ -152,27 +152,36 @@ async function loadPermissions() {
|
||||
permissions.value = await apiJson('/auth/api/admin/permissions')
|
||||
}
|
||||
|
||||
async function loadUserInfo() {
|
||||
try {
|
||||
info.value = await apiJson('/auth/api/user-info', { method: 'POST' })
|
||||
authenticated.value = true
|
||||
return true
|
||||
} catch (e) {
|
||||
error.value = e.message
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
loadingMessage.value = 'Loading...'
|
||||
error.value = null
|
||||
try {
|
||||
const data = await apiJson('/auth/api/user-info', { method: 'POST' })
|
||||
info.value = data
|
||||
authenticated.value = true
|
||||
if (!await loadUserInfo()) return
|
||||
|
||||
// Check if user has required permissions
|
||||
if (data.authenticated && !(data.is_global_admin || data.is_org_admin)) {
|
||||
if (info.value.authenticated && !(info.value.is_global_admin || info.value.is_org_admin)) {
|
||||
// User is authenticated but lacks required permissions - show forbidden view
|
||||
error.value = 'You do not have permission to access this area.'
|
||||
loading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
if (data.authenticated && (data.is_global_admin || data.is_org_admin)) {
|
||||
if (info.value.authenticated && (info.value.is_global_admin || info.value.is_org_admin)) {
|
||||
await Promise.all([loadOrgs(), loadPermissions()])
|
||||
}
|
||||
if (!data.is_global_admin && data.is_org_admin && orgs.value.length === 1) {
|
||||
if (!info.value.is_global_admin && info.value.is_org_admin && orgs.value.length === 1) {
|
||||
if (!window.location.hash || window.location.hash === '#overview') {
|
||||
currentOrgId.value = orgs.value[0].uuid
|
||||
window.location.hash = `#org/${currentOrgId.value}`
|
||||
|
||||
@@ -145,7 +145,7 @@ async function registerPasskey() {
|
||||
}
|
||||
|
||||
try {
|
||||
await setSessionCookie(result.session_token)
|
||||
await setSessionCookie(result)
|
||||
} catch (error) {
|
||||
loading.value = false
|
||||
const message = error?.message || 'Failed to establish session'
|
||||
@@ -153,23 +153,23 @@ async function registerPasskey() {
|
||||
return
|
||||
}
|
||||
|
||||
showMessage('Passkey registered successfully!', 'success', 2000)
|
||||
setTimeout(() => {
|
||||
loading.value = false
|
||||
redirectHome()
|
||||
}, 800)
|
||||
showMessage('Passkey registered successfully!', 'success', 800)
|
||||
setTimeout(() => { loading.value = false; goHome() }, 800)
|
||||
}
|
||||
|
||||
async function setSessionCookie(sessionToken) {
|
||||
async function setSessionCookie(result) {
|
||||
if (!result?.session_token) {
|
||||
throw new Error('Registration response missing session_token')
|
||||
}
|
||||
return await apiJson('/auth/api/set-session', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${sessionToken}`
|
||||
Authorization: `Bearer ${result.session_token}`
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function redirectHome() {
|
||||
function goHome() {
|
||||
const target = uiBasePath.value || '/auth/'
|
||||
if (window.location.pathname !== target) {
|
||||
history.replaceState(null, '', target)
|
||||
@@ -177,10 +177,6 @@ function redirectHome() {
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
function goHome() {
|
||||
redirectHome()
|
||||
}
|
||||
|
||||
function extractTokenFromPath() {
|
||||
const segments = window.location.pathname.split('/').filter(Boolean)
|
||||
if (!segments.length) return ''
|
||||
|
||||
@@ -153,7 +153,7 @@ async function authenticateUser() {
|
||||
emit('auth-error', { message, cancelled })
|
||||
return
|
||||
}
|
||||
try { await setSessionCookie(result.session_token) } catch (error) {
|
||||
try { await setSessionCookie(result) } catch (error) {
|
||||
loading.value = false
|
||||
const message = error?.message || 'Failed to establish session'
|
||||
showMessage(message, 'error', 4000)
|
||||
@@ -186,9 +186,13 @@ function openProfile() {
|
||||
if (profileWindow) profileWindow.focus()
|
||||
}
|
||||
|
||||
async function setSessionCookie(sessionToken) {
|
||||
async function setSessionCookie(result) {
|
||||
if (!result?.session_token) {
|
||||
console.error('setSessionCookie called with missing session_token:', result)
|
||||
throw new Error('Authentication response missing session_token')
|
||||
}
|
||||
return await apiJson('/auth/api/set-session', {
|
||||
method: 'POST', headers: { Authorization: `Bearer ${sessionToken}` }
|
||||
method: 'POST', headers: { Authorization: `Bearer ${result.session_token}` }
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -38,18 +38,21 @@ export const useAuthStore = defineStore('auth', {
|
||||
}, duration)
|
||||
}
|
||||
},
|
||||
async setSessionCookie(sessionToken) {
|
||||
const result = await apiJson('/auth/api/set-session', {
|
||||
async setSessionCookie(result) {
|
||||
if (!result?.session_token) {
|
||||
console.error('setSessionCookie called with missing session_token:', result)
|
||||
throw new Error('Authentication response missing session_token')
|
||||
}
|
||||
return await apiJson('/auth/api/set-session', {
|
||||
method: 'POST',
|
||||
headers: {'Authorization': `Bearer ${sessionToken}`},
|
||||
headers: {'Authorization': `Bearer ${result.session_token}`},
|
||||
})
|
||||
return result
|
||||
},
|
||||
async register() {
|
||||
this.isLoading = true
|
||||
try {
|
||||
const result = await register()
|
||||
await this.setSessionCookie(result.session_token)
|
||||
await this.setSessionCookie(result)
|
||||
await this.loadUserInfo()
|
||||
this.selectView()
|
||||
return result
|
||||
@@ -62,7 +65,7 @@ export const useAuthStore = defineStore('auth', {
|
||||
try {
|
||||
const result = await authenticate()
|
||||
|
||||
await this.setSessionCookie(result.session_token)
|
||||
await this.setSessionCookie(result)
|
||||
await this.loadUserInfo()
|
||||
this.selectView()
|
||||
|
||||
@@ -83,7 +86,12 @@ export const useAuthStore = defineStore('auth', {
|
||||
this.userInfo = await apiJson('/auth/api/user-info', { method: 'POST' })
|
||||
console.log('User info loaded:', this.userInfo)
|
||||
} catch (error) {
|
||||
this.showMessage(error.message || 'Failed to load user info', 'error', 5000)
|
||||
// Suppress toast for 401/403 errors - the auth iframe will handle these
|
||||
if (error.status === 401 || error.status === 403) {
|
||||
console.log('Authentication required:', error.message)
|
||||
} else {
|
||||
this.showMessage(error.message || 'Failed to load user info', 'error', 5000)
|
||||
}
|
||||
throw error
|
||||
}
|
||||
},
|
||||
@@ -113,7 +121,10 @@ export const useAuthStore = defineStore('auth', {
|
||||
location.reload()
|
||||
} catch (error) {
|
||||
console.error('Logout error:', error)
|
||||
this.showMessage(error.message, 'error')
|
||||
// Suppress toast for 401/403 errors - the auth iframe will handle these
|
||||
if (error.status !== 401 && error.status !== 403) {
|
||||
this.showMessage(error.message, 'error')
|
||||
}
|
||||
}
|
||||
},
|
||||
async logoutEverywhere() {
|
||||
@@ -123,7 +134,10 @@ export const useAuthStore = defineStore('auth', {
|
||||
location.reload()
|
||||
} catch (error) {
|
||||
console.error('Logout-all error:', error)
|
||||
this.showMessage(error.message, 'error')
|
||||
// Suppress toast for 401/403 errors - the auth iframe will handle these
|
||||
if (error.status !== 401 && error.status !== 403) {
|
||||
this.showMessage(error.message, 'error')
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -315,6 +315,8 @@ export function shouldShowErrorToast(error) {
|
||||
// Don't show toast for user cancellations
|
||||
if (error instanceof AuthCancelledError) return false
|
||||
if (error.name === 'AbortError') return false
|
||||
// Don't show toast for 401/403 errors - the auth iframe will handle these
|
||||
if (error instanceof ApiError && (error.status === 401 || error.status === 403)) return false
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ class AwaitableWebSocket extends WebSocket {
|
||||
#opened = false
|
||||
|
||||
constructor(resolve, reject, url, protocols, binaryType) {
|
||||
// Support relative URLs even on old browsers that don't
|
||||
super(new URL(url, location.href.replace(/^http/, 'ws')), protocols)
|
||||
// Support relative URLs even on old browsers that don't natively support them
|
||||
super(new URL(url, document.baseURI.replace(/^http/, 'ws')), protocols)
|
||||
this.binaryType = binaryType || 'blob'
|
||||
this.onopen = () => {
|
||||
this.#opened = true
|
||||
@@ -51,21 +51,12 @@ class AwaitableWebSocket extends WebSocket {
|
||||
console.error("WebSocket received binary data, expected JSON string", data)
|
||||
throw new Error("WebSocket received binary data, expected JSON string")
|
||||
}
|
||||
let parsed
|
||||
try {
|
||||
parsed = JSON.parse(data)
|
||||
return JSON.parse(data)
|
||||
} catch (err) {
|
||||
console.error("Failed to parse JSON from WebSocket message", data, err)
|
||||
throw new Error("Failed to parse JSON from WebSocket message")
|
||||
}
|
||||
// Wrap in response-like object with ok based on status field
|
||||
// Status 2xx = ok, 4xx/5xx = not ok, no status = ok (normal response)
|
||||
const status = parsed.status || 200
|
||||
return {
|
||||
ok: status >= 200 && status < 300,
|
||||
status,
|
||||
data: parsed,
|
||||
}
|
||||
}
|
||||
|
||||
send_json(data) {
|
||||
|
||||
@@ -23,28 +23,28 @@ export async function register(resetToken = null, displayName = null, onstartreg
|
||||
const res = await ws.receive_json()
|
||||
|
||||
// Handle auth errors (401/403) with iframe
|
||||
if ((res.status === 401 || res.status === 403) && res.data.auth?.iframe) {
|
||||
if ((res.status === 401 || res.status === 403) && res.auth?.iframe) {
|
||||
ws.close()
|
||||
await showAuthIframe(res.data.auth.iframe)
|
||||
await showAuthIframe(res.auth.iframe)
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle other errors
|
||||
if (!res.ok) {
|
||||
throw new Error(res.data.detail || `Registration failed: ${res.status}`)
|
||||
// Handle other errors (status field present means error)
|
||||
if (res.status) {
|
||||
throw new Error(res.detail || `Registration failed: ${res.status}`)
|
||||
}
|
||||
|
||||
// Notify caller that we're about to show the browser prompt
|
||||
if (onstartreg) onstartreg()
|
||||
|
||||
const registrationResponse = await startRegistration({ optionsJSON: res.data })
|
||||
const registrationResponse = await startRegistration({ optionsJSON: res })
|
||||
ws.send_json(registrationResponse)
|
||||
|
||||
const result = await ws.receive_json()
|
||||
if (!result.ok) {
|
||||
throw new Error(result.data.detail || `Registration failed: ${result.status}`)
|
||||
if (result.status) {
|
||||
throw new Error(result.detail || `Registration failed: ${result.status}`)
|
||||
}
|
||||
return result.data
|
||||
return result
|
||||
} catch (error) {
|
||||
ws.close()
|
||||
console.error('Registration error:', error)
|
||||
@@ -58,18 +58,19 @@ export async function authenticate() {
|
||||
const ws = await aWebSocket(await makeUrl('/auth/ws/authenticate'))
|
||||
try {
|
||||
const res = await ws.receive_json()
|
||||
if (!res.ok) {
|
||||
throw new Error(res.data.detail || `Authentication failed: ${res.status}`)
|
||||
// status field present means error
|
||||
if (res.status) {
|
||||
throw new Error(res.detail || `Authentication failed: ${res.status}`)
|
||||
}
|
||||
|
||||
const authResponse = await startAuthentication({ optionsJSON: res.data })
|
||||
const authResponse = await startAuthentication({ optionsJSON: res })
|
||||
ws.send_json(authResponse)
|
||||
|
||||
const result = await ws.receive_json()
|
||||
if (!result.ok) {
|
||||
throw new Error(result.data.detail || `Authentication failed: ${result.status}`)
|
||||
if (result.status) {
|
||||
throw new Error(result.detail || `Authentication failed: ${result.status}`)
|
||||
}
|
||||
return result.data
|
||||
return result
|
||||
} catch (error) {
|
||||
console.error('Authentication error:', error)
|
||||
throw Error(error.name === "NotAllowedError" ? 'Passkey authentication cancelled' : error.message)
|
||||
|
||||
@@ -228,7 +228,11 @@ async def api_user_info(
|
||||
target_user_uuid = reset_token.user_uuid
|
||||
else:
|
||||
if auth is None:
|
||||
raise ValueError("Authentication Required")
|
||||
raise authz.AuthException(
|
||||
status_code=401,
|
||||
detail="Authentication required",
|
||||
mode="login",
|
||||
)
|
||||
session_record = await get_session(auth, host=request.headers.get("host"))
|
||||
authenticated = True
|
||||
target_user_uuid = session_record.user_uuid
|
||||
|
||||
@@ -30,10 +30,10 @@ def websocket_error_handler(func):
|
||||
}
|
||||
)
|
||||
except (ValueError, InvalidAuthenticationResponse) as e:
|
||||
await ws.send_json({"detail": str(e)})
|
||||
await ws.send_json({"status": 401, "detail": str(e)})
|
||||
except Exception:
|
||||
logging.exception("Internal Server Error")
|
||||
await ws.send_json({"detail": "Internal Server Error"})
|
||||
await ws.send_json({"status": 500, "detail": "Internal Server Error"})
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
Reference in New Issue
Block a user