--- sessionId: session-261005-185830-1pry --- # Requirements ### Overview & Goals The goal is to ensure that all generated API requests in the frontend application automatically and dynamically include the user's `Authorization: Bearer ` header once authenticated upon login. Currently, the OpenAPI configuration in `src/main.ts` statically evaluates `localStorage` once at application startup using an inconsistent key, preventing subsequent API calls made after login from being authenticated. By configuring `OpenAPI.TOKEN` with a dynamic token resolver in `src/main.ts` and standardizing token persistence across authentication flows, all generated API service calls (e.g. `ProposalService`, `AmendmentService`, `CommentService`, `AuthService`) will seamlessly include the active bearer token. ### Scope - **In Scope**: - Updating `src/main.ts` to configure `OpenAPI.TOKEN` with a dynamic token resolver function using `getAuthToken()` or `localStorage`. - Standardizing the token storage key across `BrandBar.vue`, `src/utils/auth.ts`, and runtime configurations. - Ensuring generated service requests (`openapi-typescript-codegen` client) dynamically attach the `Authorization: Bearer ` header on every HTTP request whenever a token is present. - Verifying clean execution and graceful handling when a user is unauthenticated or logs out. - **Out of Scope**: - Changing backend JWT signing, expiration, or validation algorithms. - Modifying `gen-api.sh` script (handled previously or independently). ### User Stories - As an authenticated user, I want all my API actions (such as voting, creating proposals, adding amendments, and posting comments) to automatically include my bearer token so that the backend can authenticate and authorize my operations. - As a user logging in via Freighter wallet, I want my session JWT to take effect immediately without requiring a full page refresh. - As a developer, I want the generated OpenAPI client to dynamically resolve the current auth token on each request so that authentication state remains reactive and reliable. ### Functional Requirements - **Dynamic Token Resolution**: Configure `OpenAPI.TOKEN` in `src/main.ts` as an asynchronous resolver function `async () => getAuthToken() || ''` (or `() => getAuthToken() ?? undefined`) so that `resolve(options, config.TOKEN)` evaluates per-request. - **Header Injection**: Ensure `src/generated/typescript/core/request.ts` automatically attaches `Authorization: Bearer ` to request headers whenever `token` is non-empty. - **Consistent Storage Keys**: Standardize token retrieval through `getAuthToken()` from `src/utils/auth.ts` (which checks `authToken` and `token` fallbacks in `localStorage`). - **Reactivity on Login**: When `BrandBar.vue` completes `AuthService.loginFreighter`, stored token in `localStorage` is immediately picked up by subsequent API requests without reloading the page. - **Safe Fallback for Unauthenticated Requests**: When no token exists in storage, `OpenAPI.TOKEN` resolves to empty/undefined and requests are sent without `Authorization` header, preventing malformed headers or runtime errors. ### Non-Functional Requirements - **Performance**: Zero overhead beyond a single synchronous/cached `localStorage` read per outgoing request. - **Reliability & Type Safety**: Preserve strict TypeScript types and compatibility with `openapi-typescript-codegen` generated core contracts. # Technical Design ### Current Implementation 1. **`src/main.ts`**: Currently sets static headers at module load time: ```typescript OpenAPI.BASE = '' OpenAPI.HEADERS = localStorage.getItem("token") ? {"Authorization": "Bearer " + localStorage.getItem("token")} : {}; ``` **Issues**: - `OpenAPI.HEADERS` is evaluated once when the bundle loads. If the user logs in afterwards, `OpenAPI.HEADERS` remains `{}`. - It checks key `"token"`, while `BrandBar.vue` saves to `"authToken"`. - It leaves `OpenAPI.TOKEN` `undefined`, bypassing the built-in token resolver in `request.ts`. 2. **`src/components/BrandBar.vue`**: Upon connecting Freighter wallet: ```typescript const jwt = await AuthService.loginFreighter({stellarAddress: addr.address}); try { localStorage.setItem("authToken", jwt.token as string); } catch (_) {} ``` 3. **`src/utils/auth.ts`**: Provides helper functions: ```typescript export function getAuthToken(): string | null { try { return localStorage.getItem('authToken') || localStorage.getItem('token'); } catch { return null; } } ``` 4. **`src/generated/typescript/core/request.ts`**: The generated client's `getHeaders()` function natively supports dynamic token resolvers: ```typescript const [token, username, password, additionalHeaders] = await Promise.all([ resolve(options, config.TOKEN), resolve(options, config.USERNAME), resolve(options, config.PASSWORD), resolve(options, config.HEADERS), ]); ... if (isStringWithValue(token)) { headers['Authorization'] = `Bearer ${token}`; } ``` ### Key Decisions - **Use `OpenAPI.TOKEN` Resolver Function**: Instead of assigning static headers or managing manual interceptors, leverage `openapi-typescript-codegen`'s native `TOKEN` resolver `OpenAPI.TOKEN = async () => getAuthToken() || ''`. This ensures zero architectural changes to generated services and automatic per-request resolution. - **Centralize Token Lookup in `src/utils/auth.ts`**: Use `getAuthToken()` in `src/main.ts` to maintain unified support for both `authToken` and `token` storage keys and graceful exception handling if storage is restricted. - **Clean Up Redundant Imports and Static Headers**: Remove the broken `OpenAPI.HEADERS` static assignment and duplicate `./index.css` import in `src/main.ts`. ### Architecture Diagram ```mermaid graph LR User[User / Freighter Wallet] -->|Login| BrandBar[BrandBar.vue] BrandBar -->|AuthService.loginFreighter| Backend[Backend API] Backend -->|Return JWT Token| BrandBar BrandBar -->|localStorage.setItem authToken| Storage[(localStorage)] Services[Generated API Services\nProposal / Amendment / Comment] -->|__request| RequestCore[core/request.ts] RequestCore -->|Resolve OpenAPI.TOKEN| AuthUtil[utils/auth.ts: getAuthToken] AuthUtil -->|Read Token| Storage RequestCore -->|Headers with Authorization: Bearer token| Backend ``` ### Proposed Changes 1. **`src/main.ts`**: - Import `getAuthToken` from `./utils/auth`. - Set `OpenAPI.TOKEN = async () => getAuthToken() || ''` (or `() => getAuthToken() ?? undefined`). - Remove obsolete `OpenAPI.HEADERS` static assignment. - Clean up duplicate `import './index.css'`. 2. **`src/utils/auth.ts`**: - Ensure `getAuthToken()` safely handles nulls and retrieves the active token. 3. **`src/components/BrandBar.vue`**: - Ensure the token retrieved from `AuthService.loginFreighter` is consistently saved to `localStorage` under `authToken`. ### File Structure - `src/main.ts` (modified: configure `OpenAPI.TOKEN` resolver) - `src/components/BrandBar.vue` (verified / modified: ensure consistent token persistence on login) - `src/utils/auth.ts` (verified / modified: token utility resolution) ### Risks - **Storage Availability**: If local storage is disabled (e.g. private browsing restrictions), `getAuthToken()` returns `null` safely without unhandled exceptions. - **Token Invalidation / Expiry**: If a token expires, `request.ts` will receive standard 401 response from backend, allowing frontend components to handle re-authentication. # Testing ### Validation Approach Verify that outgoing HTTP requests dispatched via generated API services include the `Authorization: Bearer ` header when authenticated, and omit it when unauthenticated. ### Key Scenarios 1. **Authenticated Requests After Login**: - Connect Freighter wallet and login via `BrandBar.vue`. - Verify that subsequent calls to `ProposalService.listProposals()`, `AmendmentService.listAmendments()`, `CommentService.addComment()`, etc. include `Authorization: Bearer ` in the request headers. 2. **Initial App Load with Pre-existing Token**: - With a valid `authToken` already stored in `localStorage`, refresh the page. - Verify that immediate API calls during initial mount include the bearer token. 3. **Unauthenticated Requests**: - With empty `localStorage`, verify that public API requests execute without an `Authorization` header and without JavaScript runtime errors. ### Edge Cases - Dynamic token change (e.g., logging in as a different user) is immediately reflected in the very next request without requiring application restart. - Null or whitespace token values do not produce malformed `Authorization: Bearer ` headers.