diff --git a/fastagency/ui/mesop/auth/basic_auth/basic_auth.py b/fastagency/ui/mesop/auth/basic_auth/basic_auth.py index 5e2edc96..bf536763 100644 --- a/fastagency/ui/mesop/auth/basic_auth/basic_auth.py +++ b/fastagency/ui/mesop/auth/basic_auth/basic_auth.py @@ -37,11 +37,11 @@ def _verify_password(self, password: str, password_hash: str) -> bool: def is_authorized(self, username: str, password: str) -> bool: if username not in self.allowed_users: - raise ValueError("Invalid username or password") + return False password_hash = self.allowed_users[username] if not self._verify_password(password, password_hash): - raise ValueError("Invalid username or password") + return False return True @@ -54,14 +54,8 @@ def on_auth_changed(self, e: mel.WebEvent) -> None: username, password = e.value["username"], e.value["password"] - try: - if not self.is_authorized(username, password): - raise me.MesopUserException( - "You are not authorized to access this application. " - "Please contact the application administrators for access." - ) - except ValueError as e: - raise me.MesopUserException(str(e)) from e + if not self.is_authorized(username, password): + raise me.MesopUserException("Invalid username or password") state.authenticated_user = username @@ -78,7 +72,6 @@ def auth_component(self) -> me.component: else: with me.box(style=styles.login_box): # noqa: SIM117 with me.box(style=styles.login_btn_container): - me.text("Sign in to your account", style=styles.header_text) basic_auth_component( on_auth_changed=self.on_auth_changed, authenticated_user=state.authenticated_user, diff --git a/javascript/basic_auth_component.js b/javascript/basic_auth_component.js index 9e052cab..38c40184 100644 --- a/javascript/basic_auth_component.js +++ b/javascript/basic_auth_component.js @@ -1,9 +1,144 @@ import { LitElement, + css, html, } from "https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.js"; class BasicAuthComponent extends LitElement { + static styles = css` + :host { + display: block; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; + } + + .auth-container { + width: 100%; + max-width: 400px; + margin: 1rem auto; + padding: 2rem; + border-radius: 8px; + background: white; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + } + + form { + display: flex; + flex-direction: column; + gap: 1.5rem; + } + + .input-group { + position: relative; + } + + .input-group input { + width: 96%; + padding: 0.75rem 0 0.75rem 0.5rem; + font-size: 1rem; + border: 1px solid #e2e8f0; + border-radius: 6px; + background: #f8fafc; + transition: all 0.2s ease; + } + + .input-group input:focus { + outline: none; + border-color: #3b82f6; + background: white; + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); + } + + .input-group input::placeholder { + color: #94a3b8; + } + + .auth-button { + width: 100%; + padding: 0.75rem 1.5rem; + font-size: 1rem; + font-weight: 500; + color: white; + background: #3b82f6; + border: none; + border-radius: 6px; + cursor: pointer; + transition: all 0.2s ease; + } + + .auth-button:hover { + background: #2563eb; + transform: translateY(-1px); + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + } + + .auth-button:active { + transform: translateY(0); + box-shadow: none; + } + + .auth-button:disabled { + background: #94a3b8; + cursor: not-allowed; + transform: none; + box-shadow: none; + } + + .error { + padding: 0.75rem 1rem; + margin-bottom: 1rem; + color: #dc2626; + background: #fee2e2; + border: 1px solid #fecaca; + border-radius: 6px; + font-size: 0.875rem; + } + + /* Sign out button specific styles */ + .auth-container:has(> .auth-button) { + padding: 0rem; + box-shadow: none; + background: transparent; + margin-top:0rem; + } + + .auth-container:has(> .auth-button) .auth-button { + width: auto; + max-width: 200px; + background: #ef4444; + border: 1px solid #dc2626; + padding: 0.5rem 0.75rem; + font-size: 0.75rem; + } + + .auth-container:has(> .auth-button) .auth-button:hover { + background: #dc2626; + } + + /* Loading state styles */ + .auth-button.loading { + position: relative; + color: transparent; + } + + .auth-button.loading::after { + content: ''; + position: absolute; + left: 50%; + top: 50%; + width: 20px; + height: 20px; + border: 2px solid white; + border-radius: 50%; + border-top-color: transparent; + animation: spin 0.8s linear infinite; + } + + @keyframes spin { + to { + transform: translate(-50%, -50%) rotate(360deg); + } + } + `; static properties = { isSignedIn: { type: Boolean }, authChanged: { type: String }, @@ -54,12 +189,10 @@ class BasicAuthComponent extends LitElement { // Dispatch auth event with credentials using MesopEvent this.dispatchEvent(new MesopEvent(this.authChanged, credentials)); - // Clear form this.username = ''; this.password = ''; - this.error = ''; - this.isSignedIn = true; - + // this.error = ''; + // this.isSignedIn = true; } catch (error) { this.error = error.message || 'Authentication failed'; @@ -99,55 +232,55 @@ class BasicAuthComponent extends LitElement { render() { // Use conditional rendering without style display properties return html` -