Feat/session-name #6

Merged
BothimTV merged 2 commits from feat/session-name into main 2026-01-30 19:28:30 +00:00
BothimTV commented 2026-01-30 19:27:12 +00:00 (Migrated from github.com)

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced login flow: users now provide a display name when creating a passkey for better session identification.
    • New Sessions management dashboard in the admin panel enables viewing all active sessions with details and batch deactivation capabilities.
  • Bug Fixes

    • Extended CORS configuration to properly support DELETE requests.

✏️ Tip: You can customize this high-level summary in your review settings.

<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **New Features** * Enhanced login flow: users now provide a display name when creating a passkey for better session identification. * New Sessions management dashboard in the admin panel enables viewing all active sessions with details and batch deactivation capabilities. * **Bug Fixes** * Extended CORS configuration to properly support DELETE requests. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
coderabbitai[bot] commented 2026-01-30 19:27:22 +00:00 (Migrated from github.com)
📝 Walkthrough

Walkthrough

This PR adds session management functionality to the platform by introducing a "name" field to the Session database model, updating authentication flows to capture and store display names, implementing GET and DELETE API endpoints for session management, and providing a frontend UI for administrators to view and manage active sessions with token masking and batch deletion.

Changes

Cohort / File(s) Summary
Database Schema Updates
backend/prisma/migrations/20260130123000_add_session_name/migration.sql, backend/prisma/schema.prisma
Added "name" TEXT column to Session table with default value 'OLD SESSION', expanding schema to support session naming.
PasskeyLogin Utility
backend/src/util/PasskeyLogin.ts
Restructured internal map from storing PublicKeyCredentialRequestOptionsJSON to storing { options, name } objects; updated set() and get() method signatures to handle display names alongside credential options.
Authentication Routes
backend/src/routes/auth/POST.ts, backend/src/routes/auth/PATCH.ts
Extended POST to require and validate a "name" field in request body; updated PATCH to generate session tokens via UUID, retrieve names from stored options, and insert session records with both token and name; both now pass display name to PasskeyLogin.
Session Management Routes
backend/src/routes/sessions/GET.ts, backend/src/routes/sessions/DELETE.ts, backend/src/routes/passkeys/PATCH.ts
Added new GET endpoint to retrieve masked session tokens sorted by creation time; added new DELETE endpoint to batch-delete sessions via token matching (substring or exact); updated passkeys PATCH to use raw SQL insert for admin sessions with "Admin" name.
Backend Configuration
backend/src/index.ts
Updated CORS configuration to include DELETE HTTP method in allowed cross-origin requests.
Frontend Login UI
frontend/src/pages/login.vue
Added displayName input field with validation; updated POST payload to include name parameter; introduced reactive type state for success/error feedback and disabled login button until name is provided.
Frontend Session Management
frontend/src/pages/verwaltung/sessions.vue, frontend/src/pages/verwaltung/index.vue
Created new sessions management page with data table displaying session tokens (masked), names, roles, and creation times; implemented Deauth Selected action for batch deletion with loading and snackbar feedback; added navigation link in Verwaltung index.
Frontend Type Definitions
frontend/src/typed-router.d.ts
Added module declaration for vue-router/auto-resolver; expanded RouteRecordInfo type parameters across all routes; added entries for new routes /verwaltung/mail and /verwaltung/sessions; converted routes and views properties to union types in _RouteFileInfoMap.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client (Browser)
    participant AuthAPI as Auth API
    participant PasskeyMgr as PasskeyLogin
    participant Database as Database
    
    Note over Client,Database: Authentication with Session Creation
    
    Client->>AuthAPI: POST /auth with name, role, credentials
    AuthAPI->>AuthAPI: Validate name field
    AuthAPI->>PasskeyMgr: set(id, options, displayName)
    PasskeyMgr->>PasskeyMgr: Store {options, name}
    
    Client->>AuthAPI: PATCH /auth with credential verification
    AuthAPI->>PasskeyMgr: get(id) → {options, name}
    PasskeyMgr-->>AuthAPI: Return stored object with name
    AuthAPI->>AuthAPI: Verify credential against challenge
    AuthAPI->>AuthAPI: Generate sessionToken (UUID)
    AuthAPI->>Database: INSERT Session (sessionToken, name, role)
    Database-->>AuthAPI: Session created
    AuthAPI-->>Client: Return sessionToken
    
    Note over Client,Database: Session Management
    
    Client->>AuthAPI: GET /sessions with auth token
    AuthAPI->>Database: SELECT all sessions ORDER BY createdAt DESC
    Database-->>AuthAPI: Return sessions
    AuthAPI->>AuthAPI: Mask tokens (first 4 + last 4 chars)
    AuthAPI-->>Client: Return masked sessions
    
    Client->>AuthAPI: DELETE /sessions with tokens array
    AuthAPI->>Database: DELETE WHERE sessionToken in (tokens)
    Database-->>AuthAPI: Confirm deleted count
    AuthAPI-->>Client: Return {ok: true, deleted: count}

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~40 minutes

Poem

🐰 A bunny hops through sessions with glee,
Each one now has a name, don't you see!
Login with flair, pick a title with pride,
Then manage them all with the admin guide.


Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/login.

Comment @coderabbitai help to get the list of available commands and usage tips.

<!-- This is an auto-generated comment: summarize by coderabbit.ai --> <!-- walkthrough_start --> <details> <summary>📝 Walkthrough</summary> ## Walkthrough This PR adds session management functionality to the platform by introducing a "name" field to the Session database model, updating authentication flows to capture and store display names, implementing GET and DELETE API endpoints for session management, and providing a frontend UI for administrators to view and manage active sessions with token masking and batch deletion. ## Changes |Cohort / File(s)|Summary| |---|---| |**Database Schema Updates** <br> `backend/prisma/migrations/20260130123000_add_session_name/migration.sql`, `backend/prisma/schema.prisma`|Added "name" TEXT column to Session table with default value 'OLD SESSION', expanding schema to support session naming.| |**PasskeyLogin Utility** <br> `backend/src/util/PasskeyLogin.ts`|Restructured internal map from storing `PublicKeyCredentialRequestOptionsJSON` to storing `{ options, name }` objects; updated `set()` and `get()` method signatures to handle display names alongside credential options.| |**Authentication Routes** <br> `backend/src/routes/auth/POST.ts`, `backend/src/routes/auth/PATCH.ts`|Extended POST to require and validate a "name" field in request body; updated PATCH to generate session tokens via UUID, retrieve names from stored options, and insert session records with both token and name; both now pass display name to PasskeyLogin.| |**Session Management Routes** <br> `backend/src/routes/sessions/GET.ts`, `backend/src/routes/sessions/DELETE.ts`, `backend/src/routes/passkeys/PATCH.ts`|Added new GET endpoint to retrieve masked session tokens sorted by creation time; added new DELETE endpoint to batch-delete sessions via token matching (substring or exact); updated passkeys PATCH to use raw SQL insert for admin sessions with "Admin" name.| |**Backend Configuration** <br> `backend/src/index.ts`|Updated CORS configuration to include DELETE HTTP method in allowed cross-origin requests.| |**Frontend Login UI** <br> `frontend/src/pages/login.vue`|Added displayName input field with validation; updated POST payload to include name parameter; introduced reactive type state for success/error feedback and disabled login button until name is provided.| |**Frontend Session Management** <br> `frontend/src/pages/verwaltung/sessions.vue`, `frontend/src/pages/verwaltung/index.vue`|Created new sessions management page with data table displaying session tokens (masked), names, roles, and creation times; implemented Deauth Selected action for batch deletion with loading and snackbar feedback; added navigation link in Verwaltung index.| |**Frontend Type Definitions** <br> `frontend/src/typed-router.d.ts`|Added module declaration for vue-router/auto-resolver; expanded RouteRecordInfo type parameters across all routes; added entries for new routes `/verwaltung/mail` and `/verwaltung/sessions`; converted routes and views properties to union types in _RouteFileInfoMap.| ## Sequence Diagram(s) ```mermaid sequenceDiagram participant Client as Client (Browser) participant AuthAPI as Auth API participant PasskeyMgr as PasskeyLogin participant Database as Database Note over Client,Database: Authentication with Session Creation Client->>AuthAPI: POST /auth with name, role, credentials AuthAPI->>AuthAPI: Validate name field AuthAPI->>PasskeyMgr: set(id, options, displayName) PasskeyMgr->>PasskeyMgr: Store {options, name} Client->>AuthAPI: PATCH /auth with credential verification AuthAPI->>PasskeyMgr: get(id) → {options, name} PasskeyMgr-->>AuthAPI: Return stored object with name AuthAPI->>AuthAPI: Verify credential against challenge AuthAPI->>AuthAPI: Generate sessionToken (UUID) AuthAPI->>Database: INSERT Session (sessionToken, name, role) Database-->>AuthAPI: Session created AuthAPI-->>Client: Return sessionToken Note over Client,Database: Session Management Client->>AuthAPI: GET /sessions with auth token AuthAPI->>Database: SELECT all sessions ORDER BY createdAt DESC Database-->>AuthAPI: Return sessions AuthAPI->>AuthAPI: Mask tokens (first 4 + last 4 chars) AuthAPI-->>Client: Return masked sessions Client->>AuthAPI: DELETE /sessions with tokens array AuthAPI->>Database: DELETE WHERE sessionToken in (tokens) Database-->>AuthAPI: Confirm deleted count AuthAPI-->>Client: Return {ok: true, deleted: count} ``` ## Estimated code review effort 🎯 3 (Moderate) | ⏱️ ~40 minutes ## Poem > 🐰 *A bunny hops through sessions with glee,* > *Each one now has a name, don't you see!* > *Login with flair, pick a title with pride,* > *Then manage them all with the admin guide.* ✨ </details> <!-- walkthrough_end --> <!-- tips_start --> --- > [!NOTE] > <details> > <summary>🎁 Summarized by CodeRabbit Free</summary> > > Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting <https://app.coderabbit.ai/login>. > > </details> <sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub> <!-- tips_end --> <!-- internal state start --> <!-- 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 --> <!-- internal state end -->
Commenting is not possible because the repository is archived.
No description provided.