Cleaned up login/logout flows.
This commit is contained in:
parent
10e55f63b5
commit
b324276173
@ -22,8 +22,12 @@ import PermissionDeniedView from '@/components/PermissionDeniedView.vue'
|
||||
const store = useAuthStore()
|
||||
|
||||
onMounted(async () => {
|
||||
// Detect restricted mode: any path not starting with /auth/
|
||||
if (!location.pathname.startsWith('/auth/')) {
|
||||
// Detect restricted mode:
|
||||
// We only allow full functionality on the exact /auth/ (or /auth) path.
|
||||
// Any other path (including /, /foo, /auth/admin, etc.) is treated as restricted
|
||||
// so the app will only show login or permission denied views.
|
||||
const path = location.pathname
|
||||
if (!(path === '/auth/' || path === '/auth')) {
|
||||
store.setRestrictedMode(true)
|
||||
}
|
||||
// Load branding / settings first (non-blocking for auth flow)
|
||||
|
@ -27,9 +27,9 @@ const handleLogin = async () => {
|
||||
await authStore.authenticate()
|
||||
authStore.showMessage('Authentication successful!', 'success', 2000)
|
||||
if (authStore.restrictedMode) {
|
||||
// In restricted mode after successful auth show permission denied (no profile outside /auth/)
|
||||
authStore.currentView = 'permission-denied'
|
||||
} else if (location.pathname.startsWith('/auth/')) {
|
||||
// Restricted mode: reload so the app re-mounts and selectView() applies (will become permission denied)
|
||||
location.reload()
|
||||
} else if (location.pathname === '/auth/') {
|
||||
authStore.currentView = 'profile'
|
||||
} else {
|
||||
location.reload()
|
||||
|
@ -32,7 +32,6 @@ function back() {
|
||||
}
|
||||
async function logout() {
|
||||
await authStore.logout()
|
||||
authStore.currentView = 'login'
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
|
@ -144,7 +144,6 @@ const deleteCredential = async (credentialId) => {
|
||||
|
||||
const logout = async () => {
|
||||
await authStore.logout()
|
||||
authStore.currentView = 'login'
|
||||
}
|
||||
|
||||
const isAdmin = computed(() => !!(authStore.userInfo?.is_global_admin || authStore.userInfo?.is_org_admin))
|
||||
|
@ -8,7 +8,7 @@ export const useAuthStore = defineStore('auth', {
|
||||
settings: null, // Server provided settings (/auth/settings)
|
||||
isLoading: false,
|
||||
resetToken: null, // transient reset token
|
||||
restrictedMode: false, // If true, app loaded outside /auth/ and should restrict to login or permission denied
|
||||
restrictedMode: false, // Anywhere other than /auth/: restrict to login or permission denied
|
||||
|
||||
// UI State
|
||||
currentView: 'login',
|
||||
@ -129,12 +129,13 @@ export const useAuthStore = defineStore('auth', {
|
||||
},
|
||||
async logout() {
|
||||
try {
|
||||
await fetch('/auth/api/logout', {method: 'POST'})
|
||||
await fetch('/auth/api/logout', {method: 'POST'})
|
||||
this.userInfo = null
|
||||
if (this.restrictedMode) location.reload()
|
||||
} catch (error) {
|
||||
console.error('Logout error:', error)
|
||||
this.showMessage(error.message, 'error')
|
||||
}
|
||||
|
||||
this.userInfo = null
|
||||
},
|
||||
}
|
||||
})
|
||||
|
@ -1,8 +1,8 @@
|
||||
import logging
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
from fastapi import Body, Cookie, FastAPI, HTTPException
|
||||
from fastapi.responses import FileResponse, JSONResponse
|
||||
from fastapi import Body, Cookie, FastAPI, HTTPException, Request
|
||||
from fastapi.responses import FileResponse, JSONResponse, RedirectResponse
|
||||
|
||||
from ..authsession import expires
|
||||
from ..globals import db
|
||||
@ -24,8 +24,14 @@ async def general_exception_handler(_request, exc: Exception):
|
||||
return JSONResponse(status_code=500, content={"detail": "Internal server error"})
|
||||
|
||||
|
||||
@app.get("")
|
||||
def adminapp_slashmissing(request: Request):
|
||||
print("HERE")
|
||||
return RedirectResponse(url=request.url_for("adminapp"))
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def admin_frontend(auth=Cookie(None)):
|
||||
async def adminapp(auth=Cookie(None)):
|
||||
try:
|
||||
await authz.verify(auth, ["auth:admin", "auth:org:*"], match=permutil.has_any)
|
||||
return FileResponse(frontend.file("admin/index.html"))
|
||||
|
@ -1,7 +1,11 @@
|
||||
import logging
|
||||
|
||||
from fastapi import HTTPException
|
||||
|
||||
from ..util import permutil
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def verify(auth: str | None, perm: list[str], match=permutil.has_all):
|
||||
"""Validate session token and optional list of required permissions.
|
||||
@ -20,6 +24,16 @@ async def verify(auth: str | None, perm: list[str], match=permutil.has_all):
|
||||
raise HTTPException(status_code=401, detail="Session not found")
|
||||
|
||||
if not match(ctx, perm):
|
||||
# Determine which permissions are missing for clearer diagnostics
|
||||
missing = sorted(set(perm) - set(ctx.role.permissions))
|
||||
logger.warning(
|
||||
"Permission denied: user=%s role=%s missing=%s required=%s granted=%s", # noqa: E501
|
||||
getattr(ctx.user, "uuid", "?"),
|
||||
getattr(ctx.role, "display_name", "?"),
|
||||
missing,
|
||||
perm,
|
||||
ctx.role.permissions,
|
||||
)
|
||||
raise HTTPException(status_code=403, detail="Permission required")
|
||||
|
||||
return ctx
|
||||
|
@ -46,10 +46,12 @@ async def lifespan(app: FastAPI): # pragma: no cover - startup path
|
||||
|
||||
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
app.mount("/auth/admin", admin.app)
|
||||
app.mount("/auth/api", api.app)
|
||||
app.mount("/auth/ws", ws.app)
|
||||
app.mount("/auth/assets", StaticFiles(directory=frontend.file("assets")), name="assets")
|
||||
app.mount("/auth/admin/", admin.app)
|
||||
app.mount("/auth/api/", api.app)
|
||||
app.mount("/auth/ws/", ws.app)
|
||||
app.mount(
|
||||
"/auth/assets/", StaticFiles(directory=frontend.file("assets")), name="assets"
|
||||
)
|
||||
|
||||
|
||||
@app.get("/auth/")
|
||||
@ -61,6 +63,9 @@ async def frontapp():
|
||||
@app.get("/auth/{reset}")
|
||||
async def reset_link(request: Request, reset: str):
|
||||
"""Pretty URL for reset links."""
|
||||
if reset == "admin":
|
||||
# Admin app missing trailing slash lands here, be friendly to user
|
||||
return RedirectResponse(request.url_for("adminapp"), status_code=303)
|
||||
if not passphrase.is_well_formed(reset):
|
||||
raise HTTPException(status_code=404)
|
||||
url = request.url_for("frontapp").include_query_params(reset=reset)
|
||||
|
Loading…
x
Reference in New Issue
Block a user