This is an automated email from the ASF dual-hosted git repository.

gstein pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/steve.git


The following commit(s) were added to refs/heads/trunk by this push:
     new d836a6d  Add sign-in/out management to the webapp.
d836a6d is described below

commit d836a6d39527b03ccb4ffe4adb455c3327802f1a
Author: Greg Stein <[email protected]>
AuthorDate: Mon Sep 29 13:46:35 2025 -0500

    Add sign-in/out management to the webapp.
    
    * pages.py:
      (signin_info): set up basic template data for the signed-in-state
      (*): all handlers now include the sign-in info in their result dict
      (sign_out): clear the session and redirect to Home
      (sign_in): new func to force a sign-in
    
    * header.ezt:
      - use the .svg for the icon in the navbar
      - switch on UID to display a sign-in link, or the name/dropdown
---
 v3/server/pages.py             | 90 +++++++++++++++++++++++++++++-------------
 v3/server/templates/header.ezt | 15 ++++++-
 2 files changed, 75 insertions(+), 30 deletions(-)

diff --git a/v3/server/pages.py b/v3/server/pages.py
index 757745e..33f2174 100644
--- a/v3/server/pages.py
+++ b/v3/server/pages.py
@@ -21,7 +21,7 @@ import pathlib
 from easydict import EasyDict as edict
 import asfpy.stopwatch
 import quart
-import asfquart
+import asfquart.session
 from asfquart.auth import Requirements as R
 
 APP = asfquart.APP
@@ -33,12 +33,27 @@ sys.path.insert(0, str(THIS_DIR.parent))
 import steve.election
 
 
+async def signin_info():
+    "Return EZT template data for the Sign-In, in the upper right."
+    s = await asfquart.session.read()
+    if s:
+        return {
+            'uid': s['uid'],
+            'name': s['fullname'],
+            'email': s['email'],
+            }
+
+    # No session.
+    return { 'uid': None, 'name': None, 'email': None, }
+
+
 @APP.get('/')
 @APP.use_template('templates/home.ezt')
 async def home_page():
-    return {
-        'title': 'Home',
-    }
+    result = edict(await signin_info())
+    result.title = 'Home'
+
+    return result
 
 
 @APP.get('/voter')
@@ -49,64 +64,83 @@ async def voter_page():
         election = steve.election.Election.open_to_pid(DB_FNAME, 'gstein')
         owned = steve.election.Election.owned_elections(DB_FNAME, 'gstein')
 
-    election = [ edict(eid='123', title='test election') ]
-    owned = [ edict(eid='456', title='another', authz=None, closed=None) ]
+    result = edict(await signin_info())
+    result.title = 'Voting'
 
-    return {
-        'title': 'Voting',
-        'election': election,
-        'owned': owned,
-    }
+    result.election = [ edict(eid='123', title='test election') ]
+    result.owned = [ edict(eid='456', title='another', authz=None, 
closed=None) ]
 
+    return result
 
+
+### NOTE: this is for ASF committers only. Obviously, this is not a
+### general purpose solution. Something for the future, to figure out
+### how we'd like to do configuration authorization for various install
+### scenarios and authn systems.
 @APP.get('/admin')
 @asfquart.auth.require({R.committer})
 @APP.use_template('templates/admin.ezt')
 async def admin_page():
-    return {
-        'title': 'Administration',
-    }
+    result = edict(await signin_info())
+    result.title = 'Administration'
+
+    return result
 
 
 @APP.get('/profile')
 @asfquart.auth.require  # Bare decorator means just require a valid session
 @APP.use_template('templates/profile.ezt')
 async def profile_page():
-    return {
-        'title': 'Profile',
-    }
+    result = edict(await signin_info())
+    result.title = 'Profile'
+
+    return result
 
 
 @APP.get('/settings')
 @asfquart.auth.require  # Bare decorator means just require a valid session
 @APP.use_template('templates/settings.ezt')
 async def settings_page():
-    return {
-        'title': 'Settings',
-    }
+    result = edict(await signin_info())
+    result.title = 'Settings'
+
+    return result
 
 
 @APP.get('/sign-out')
 @asfquart.auth.require  # Bare decorator means just require a valid session
 async def sign_out():
-    ### clear the cookie?
-    return '', 204
+    asfquart.session.clear()
+
+    # When signing out, go to the Home page.
+    return quart.redirect('/')
+
+
[email protected]('/sign-in')
[email protected]  # Bare decorator means just require a valid session
+async def sign_in():
+    "Forces sign-in (via OAuth), then redirects to the Home page."
+
+    # And if we are back here, we are signed-in. Go to the Home page.
+    return quart.redirect('/')
 
 
 @APP.get('/privacy')
 @APP.use_template('templates/privacy.ezt')
 async def privacy_page():
-    return {
-        'title': 'Privacy',
-    }
+    result = edict(await signin_info())
+    result.title = 'Privacy'
+
+    return result
 
 
 @APP.get('/about')
 @APP.use_template('templates/about.ezt')
 async def about_page():
-    return {
-        'title': 'About',
-    }
+    result = edict(await signin_info())
+    result.title = 'About'
+
+    return result
 
 
 # Route to serve static files (CSS and JS)
diff --git a/v3/server/templates/header.ezt b/v3/server/templates/header.ezt
index 17805c4..fd72de1 100644
--- a/v3/server/templates/header.ezt
+++ b/v3/server/templates/header.ezt
@@ -12,19 +12,20 @@
     <div class="container-fluid">
         <!-- Left-aligned icon and title -->
         <a class="navbar-brand" href="/">
-            <img src="https://www.apache.org/foundation/press/kit/feather.png"; 
alt="Logo" width="30" height="30" class="d-inline-block align-text-top">
+            <img src="https://www.apache.org/foundation/press/kit/feather.svg"; 
alt="Logo" width="30" height="30" class="d-inline-block align-text-top">
             Apache STeVe
         </a>
         <!-- Toggler for mobile view -->
         <button class="navbar-toggler" type="button" data-bs-toggle="collapse" 
data-bs-target="#navbarContent" aria-controls="navbarContent" 
aria-expanded="false" aria-label="Toggle navigation">
             <span class="navbar-toggler-icon"></span>
         </button>
+        [if-any uid]
         <!-- Right-aligned user name and dropdown -->
         <div class="collapse navbar-collapse" id="navbarContent">
             <ul class="navbar-nav ms-auto mb-2 mb-lg-0">
                 <li class="nav-item dropdown">
                     <a class="nav-link dropdown-toggle" href="#" 
id="userDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
-                        John Doe
+                        [name]
                     </a>
                     <ul class="dropdown-menu dropdown-menu-end" 
aria-labelledby="userDropdown">
                         <li><a class="dropdown-item" 
href="/profile">Profile</a></li>
@@ -35,6 +36,16 @@
                 </li>
             </ul>
         </div>
+        [else]
+            <!-- Sign-in link for users not signed in -->
+            <div class="collapse navbar-collapse" id="navbarContent">
+                <ul class="navbar-nav ms-auto mb-2 mb-lg-0">
+                    <li class="nav-item">
+                        <a class="nav-link" href="/sign-in">Sign In</a>
+                    </li>
+                </ul>
+            </div>
+        [end]
     </div>
 </nav>
 

Reply via email to