Seeking Help with Login Authentication and Router Configuration Issues #3005
-
Question
from router import Router
from nicegui import Client, app, ui
from typing import Optional
from fastapi import Request
from fastapi.responses import RedirectResponse
from starlette.middleware.base import BaseHTTPMiddleware
# in reality users passwords would obviously need to be hashed
passwords = {'user1': 'pass1', 'user2': 'pass2'}
unrestricted_page_routes = {'/login'}
class AuthMiddleware(BaseHTTPMiddleware):
"""This middleware restricts access to all NiceGUI pages.
It redirects the user to the login page if they are not authenticated.
"""
async def dispatch(self, request: Request, call_next):
if not app.storage.user.get('authenticated', False):
if request.url.path in Client.page_routes.values() and request.url.path not in unrestricted_page_routes:
app.storage.user['referrer_path'] = request.url.path # remember where the user wanted to go
return RedirectResponse('/login')
return await call_next(request)
app.add_middleware(AuthMiddleware)
@ui.page('/')
def main_page() -> None:
with ui.column().classes('absolute-center items-center'):
ui.label(f'Hello {app.storage.user["username"]}!').classes('text-2xl')
ui.button(on_click=lambda: (app.storage.user.clear(), ui.navigate.to('/login')), icon='logout') \
.props('outline round')
@ui.page('/login')
def login() -> Optional[RedirectResponse]:
def try_login() -> None: # local function to avoid passing username and password as arguments
if passwords.get(username.value) == password.value:
app.storage.user.update({'username': username.value, 'authenticated': True})
ui.navigate.to(app.storage.user.get('referrer_path', '/')) # go back to where the user wanted to go
else:
ui.notify('Wrong username or password', color='negative')
if app.storage.user.get('authenticated', False):
return RedirectResponse('/')
with ui.card().classes('absolute-center'):
username = ui.input('Username').on('keydown.enter', try_login)
password = ui.input('Password', password=True, password_toggle_button=True).on('keydown.enter', try_login)
ui.button('Log in', on_click=try_login)
return None
@ui.page('/{id}') # All other pages are handled by the router but registered to show the SPA index page
def main(id: str | None = None):
patients = [
{'id': '21221', 'name':'Patient1', 'gender': 'male'},
{'id': '11223', 'name':'Patient2', 'gender': 'female'},
{'id': '31225', 'name':'Patient3', 'gender': 'male'}
]
first_patient = patients[0]
router = Router()
with ui.header(elevated=False):
with ui.tabs() as tabs:
summary_tab = ui.tab('summary', label='Summary', icon='o_contact_emergency')
complete_list = [i['id'] for i in patients]
print(complete_list)
ui.select(
value=complete_list[0], # Default selection.
options=complete_list, # Patient options for selection.
on_change=lambda e: (router.open(f'/{e.value}')) # Navigate on selection change.
).classes('w-40').style('width:222px;max-height:70px;').props('outlined input-class=text-white')
for _, patient in enumerate(patients):
@router.add(f"/{patient['id']}")
def generate_patient_page(patient=patient): # Use closure to fix the current loop variable value
with ui.tab_panels(tabs, value=summary_tab):
with ui.tab_panel(summary_tab):
app.add_static_files('/examples', 'one')
ui.label(f"Some NiceGUI Examples (for patient {patient['id']})").classes('text-h5')
ui.link('AI interface', '/examples/main.py')
ui.image('/examples/sample.jpg')
router.frame()
router.open(f"/{first_patient['id']}")
ui.run(storage_secret='IAmAGoldenSecret')
from typing import Callable, Dict, Union
from nicegui import background_tasks, helpers, ui
class RouterFrame(ui.element, component='router_frame.js'):
pass
class Router():
def __init__(self) -> None:
self.routes: Dict[str, Callable] = {}
self.content: ui.element = None
def add(self, path: str):
def decorator(func: Callable):
self.routes[path] = func
return func
return decorator
def open(self, target: Union[Callable, str]) -> None:
try:
if isinstance(target, str):
path = target
builder = self.routes[target]
else:
path = {v: k for k, v in self.routes.items()}[target]
builder = target
async def build() -> None:
with self.content:
ui.run_javascript(f'''
if (window.location.pathname !== "{path}") {{
history.pushState({{page: "{path}"}}, "", "{path}");
}}
''')
result = builder()
if helpers.is_coroutine_function(builder):
await result
self.content.clear()
background_tasks.create(build())
except KeyError as e:
if str(e) == "'/'":
# 对于特定的KeyError ('/'),选择忽略它或者进行特定的处理
pass
else:
# 其他KeyError可以按需处理或重新抛出
raise
def frame(self) -> ui.element:
self.content = RouterFrame().on('open', lambda e: self.open(e.args))
return self.content
export default {
template: "<div><slot></slot></div>",
mounted() {
window.addEventListener("popstate", (event) => {
if (event.state?.page) {
this.$emit("open", event.state.page);
}
});
const connectInterval = setInterval(async () => {
if (window.socket.id === undefined) return;
this.$emit("open", window.location.pathname);
clearInterval(connectInterval);
}, 10);
},
props: {},
};
The command line error looks like:
🌻 🌻 🌻 Thank you so much! 🌻 🌻 🌻 |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 3 replies
-
Hello @Daniel-Fei, thanks for reaching out. Packing three issues in one post makes each individual solution hard to track. I'll try my best to answer them in one sweep but try separate issues in the future. 1: due to the Router you can remove the |
Beta Was this translation helpful? Give feedback.
Hello @Daniel-Fei, thanks for reaching out. Packing three issues in one post makes each individual solution hard to track. I'll try my best to answer them in one sweep but try separate issues in the future.
1: due to the Router you can remove the
AuthMiddleware
(which normally checks the permissions). You need to do so inside the router class. This is something we should implement in the upcoming SPA API in #2811.2: this also should be implemented in the Router.
3: Yes. You can start a timer when the user logs in; or even better: for each user remember an timestamp for "last activity" and then have one timer to check if an app.storage.user is "out of time". You can then simply set
authen…