Skip to content

Add Email/Password Login to Mobile App#860

Merged
CodeWithCJ merged 6 commits intoCodeWithCJ:mainfrom
apedley:app-new-auth
Mar 7, 2026
Merged

Add Email/Password Login to Mobile App#860
CodeWithCJ merged 6 commits intoCodeWithCJ:mainfrom
apedley:app-new-auth

Conversation

@apedley
Copy link
Contributor

@apedley apedley commented Mar 7, 2026

Tip

Help us review and merge your PR faster!
Please ensure you have completed the Checklist below.
For Frontend changes, please run pnpm run validate to check for any errors.
PRs that include tests and clear screenshots are highly preferred!

Description

Main feature: Adds support for session based (email/pw) authentication to the mobile app.

  • Included tests for all authentication
  • MFA support (TOTP and email)
  • Fully backwards compatible
    • new users can choose to authenticate using an API key
    • Existing users of the app will not need to reenter their information
    • anyone using the api for their own scripts should be covered too

Aligned mobile types with server API in preparation for shared zod schemas
Fixed a typo a front end URL that was preventing users from setting up TOTP and Email MFA
Removed unused auth middleware leftover from chat bot routes. This let me get rid of a big scary comment that started with "In a real application, you would fetch user permissions from the DB..." thankfully the comment no longer applies to us since we are using RLS

Related Issue

PR type [ ] Issue [ ] New Feature [ ] Documentation
Linked Issue: #

Checklist

Please check all that apply:

  • [MANDATORY for new feature] Alignment: I have raised a GitHub issue and it was reviewed/approved by maintainers
  • Tests: I have included automated tests for my changes.
  • [MANDATORY for UI changes] Screenshots: I have attached "Before" vs "After" screenshots below.
  • [MANDATORY for Frontend changes] Quality: I have run pnpm run validate (especially for Frontend).
  • Translations: I have only updated the English (en) translation file (if applicable).
  • Architecture: My code follows the existing architecture standards.
  • Database Security: I have updated rls_policies.sql for any new user-specific tables.
  • [MANDATORY - ALL] Integrity & License: I certify this is my own work, free of malicious code(phishing, malware, etc.) and I agree to the License terms.

Screenshots (if applicable)

Before

[Insert screenshot/GIF here]

After

[Insert screenshot/GIF here]

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a robust session-based authentication system to the mobile application, allowing users to log in with email and password, complete with multi-factor authentication options like TOTP and email OTP. The changes ensure a smooth transition for existing users while maintaining API key compatibility. Additionally, the server's authentication middleware has been streamlined, and several API-related types have been updated for better alignment and future development.

Highlights

  • Email/Password Login: Implemented email and password-based session authentication for the mobile application, providing an alternative to API key login.
  • Multi-Factor Authentication (MFA): Integrated support for TOTP (Authenticator App) and email-based MFA during the login process, enhancing security.
  • Backward Compatibility & User Experience: Ensured full backward compatibility for existing API key users and designed the new login flow to be seamless, preventing current users from needing to re-enter information.
  • Authentication Middleware Refinement: Removed unused authentication middleware from the server, simplifying the authentication logic and removing a 'big scary comment' related to permission fetching.
  • Frontend URL Correction: Fixed a typo in a frontend URL that was previously preventing users from setting up TOTP and Email MFA.
  • API Type Alignment: Aligned mobile application types with the server API in preparation for shared Zod schemas, improving consistency and maintainability.
Changelog
  • SparkyFitnessFrontend/src/api/Settings/profileService.ts
    • Updated API call paths by removing '/api' prefix for TOTP sync and email MFA toggle endpoints.
  • SparkyFitnessMobile/App.tsx
    • Imported Alert, LoginModal, ServerConfigModal, useAuth, saveServerConfig, getActiveServerConfig, and notifyNoConfigs.
    • Integrated LoginModal and ServerConfigModal components into the main application structure to handle user authentication and API key configuration.
  • SparkyFitnessMobile/tests/components/LoginModal.test.tsx
    • Added comprehensive test suite for the new LoginModal component, covering credentials form, server selection, validation, successful login, various login errors, MFA flow (TOTP and email OTP), MFA error handling, callbacks, and state reset.
  • SparkyFitnessMobile/tests/hooks/useAuth.test.ts
    • Added tests for the useAuth hook, verifying its behavior in showing the login modal based on active config presence, registering session expired and no configs callbacks, and correctly resetting state on dismiss or successful login.
  • SparkyFitnessMobile/tests/services/apiClient.test.ts
    • Imported notifySessionExpired from authService.
    • Added tests for session authentication, ensuring Bearer tokens are sent correctly.
    • Added tests to verify notifySessionExpired is called on 401 responses for session-authenticated requests but not for API key requests or other error statuses.
  • SparkyFitnessMobile/tests/services/authService.test.ts
    • Added extensive test suite for the new authService, covering getAuthHeaders logic, setOnSessionExpired/notifySessionExpired and setOnNoConfigs/notifyNoConfigs callbacks, login functionality (success, MFA required, various errors, HTTPS enforcement), fetchMfaFactors, verifyTotp (including trusted origin handling), sendEmailOtp, verifyEmailOtp, logout, clearAuthCookies, and trusted origin caching behavior.
  • SparkyFitnessMobile/tests/services/storage.test.ts
    • Imported clearSessionToken.
    • Added tests for saveServerConfig to verify secure storage of session tokens and correct handling of authType.
    • Added tests for getAllServerConfigs to ensure session tokens are retrieved from SecureStore and authType is correctly returned.
    • Added tests for deleteServerConfig to confirm session tokens are also removed from SecureStore.
    • Added tests for the new clearSessionToken function, verifying it deletes the token and invalidates the active config cache.
  • SparkyFitnessMobile/global.css
    • Adjusted --color-accent-muted and --color-text-success for both light and dark themes to improve visual consistency.
  • SparkyFitnessMobile/src/components/DevTools.tsx
    • Removed OnboardingModal import and related state/logic.
    • Added platform-specific buttons for Android to open Health Connect settings and data management.
  • SparkyFitnessMobile/src/components/Icon.tsx
    • Added new icon mappings for radio-button-on and radio-button-off to support selection states.
  • SparkyFitnessMobile/src/components/LoginModal.tsx
    • Added new component LoginModal to handle email/password login, server selection, and multi-factor authentication (TOTP and email OTP) flows.
  • SparkyFitnessMobile/src/components/OnboardingModal.tsx
    • Removed the OnboardingModal component entirely.
  • SparkyFitnessMobile/src/hooks/useAuth.ts
    • Added new hook useAuth to manage the visibility and state of the login modal, including handling session expiration and no active configurations.
  • SparkyFitnessMobile/src/screens/DashboardScreen.tsx
    • Removed imports and state related to OnboardingModal.
    • Removed useEffect hook that checked for onboarding on initial mount.
  • SparkyFitnessMobile/src/screens/SettingsScreen.tsx
    • Imported notifyNoConfigs from authService.
    • Updated useEffect dependency array for loadConfig to include isConnected.
    • Modified handleSaveConfig to conditionally require an API key based on whether the existing configuration uses session authentication.
    • Adjusted configToSave logic in handleSaveConfig to correctly set authType and sessionToken when an API key is provided.
    • Updated handleDeleteConfig to notify if no server configurations remain after deletion, prompting the user to set one up.
    • Modified handleEditConfig to clear the API key input when editing a session-authenticated configuration, preventing accidental auth type changes.
  • SparkyFitnessMobile/src/services/api/apiClient.ts
    • Imported getAuthHeaders and notifySessionExpired from authService.
    • Modified apiFetch to use getAuthHeaders for authorization, supporting both API keys and session tokens.
    • Added logic to call notifySessionExpired if a 401 status is received for a session-authenticated request.
  • SparkyFitnessMobile/src/services/api/authService.ts
    • Added new service authService to encapsulate all authentication-related logic, including LoginError class, login function for email/password, fetchMfaFactors, verifyTotp, sendEmailOtp, verifyEmailOtp, logout, clearAuthCookies, and callback mechanisms for session expiration and no configurations.
  • SparkyFitnessMobile/src/services/api/healthDataApi.ts
    • Imported getAuthHeaders and notifySessionExpired from authService.
    • Removed direct apiKey usage and replaced with getAuthHeaders for syncHealthData and checkServerConnection.
    • Added logic to call notifySessionExpired on 401 responses for session-authenticated requests during health data sync and connection checks.
  • SparkyFitnessMobile/src/services/storage.ts
    • Extended ServerConfig and StoredServerConfig interfaces to include authType and sessionToken properties.
    • Added sessionTokenSecureStoreKey constant for secure storage of session tokens.
    • Updated saveServerConfig to securely store and manage sessionToken and authType.
    • Modified getAllServerConfigs to retrieve sessionToken from SecureStore and correctly handle authType during migration.
    • Updated deleteServerConfig to also remove the corresponding sessionToken from SecureStore.
    • Added new function clearSessionToken to specifically delete a session token for a given config ID and invalidate the cache.
  • SparkyFitnessMobile/src/types/foodEntries.ts
    • Updated FoodEntry interface to remove food_variants and water_ml, and added serving_size and serving_unit.
    • Modified FoodEntry and related types to align nutrient fields with server API, replacing fiber with dietary_fiber and glycemic_index type.
  • SparkyFitnessMobile/src/types/foodInfo.ts
    • Updated foodItemToFoodInfo mapping to use dietary_fiber from item.default_variant.
  • SparkyFitnessMobile/src/types/foods.ts
    • Expanded FoodDefaultVariant and FoodVariantDetail interfaces to include a wider range of nutrient fields (e.g., polyunsaturated_fat, monounsaturated_fat, cholesterol, potassium, vitamin_a, vitamin_c, calcium, iron, glycemic_index, custom_nutrients).
  • SparkyFitnessMobile/src/types/goals.ts
    • Extended DailyGoals interface to include additional nutrient goals (e.g., saturated_fat, cholesterol, potassium, sugars, vitamin_a, vitamin_c, calcium, iron), macro percentages, meal percentages, and custom nutrients.
  • SparkyFitnessMobile/src/types/mealTypes.ts
    • Added show_in_quick_log property to the MealType interface.
  • SparkyFitnessMobile/src/types/meals.ts
    • Expanded MealFood interface to include a wider range of nutrient fields, aligning with the updated FoodDefaultVariant and FoodVariantDetail.
  • SparkyFitnessMobile/src/types/preferences.ts
    • Extended UserPreferences interface with numerous new preference settings, including auto_scale_open_food_facts_imports, exercise_calorie_percentage, activity_level, tdee_allow_negative_adjustment, system_prompt, auto_clear_history, logging_level, timezone, item_display_limit, and language.
  • SparkyFitnessServer/auth.js
    • Removed a redundant comment regarding duplicate origins in finalOrigins processing.
  • SparkyFitnessServer/middleware/authMiddleware.js
    • Imported serializeSignedCookie from better-call.
    • Enhanced authenticate middleware to intelligently handle Bearer tokens: routing API keys to x-api-key header and converting session tokens into signed session cookies for better-auth compatibility.
    • Removed the authorize middleware, as permission checks are now handled by RLS in the DB layer.
  • SparkyFitnessServer/models/chatRepository.js
    • Changed clearOldChatHistory to use getClient(userId) instead of getSystemClient(), ensuring RLS is applied.
  • SparkyFitnessServer/routes/auth/authCoreRoutes.js
    • Added logic to determine and include a trusted_origin in the /api/auth/settings response, derived from SPARKY_FITNESS_FRONTEND_URL environment variable, to support MFA flows.
  • SparkyFitnessServer/routes/chatRoutes.js
    • Removed the authorize middleware from all chat-related routes (/ai-service-settings, /ai-service-settings/active, /ai-service-settings/:id, /sparky-chat-history, /sparky-chat-history/entry/:id, /sparky-chat-history/:id, /clear-all-history, /save-history), relying solely on authenticate and RLS for access control.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive session-based authentication system to the mobile application, replacing the previous API key-only approach. Key changes include the addition of LoginModal and ServerConfigModal components to App.tsx for handling user sign-in (email/password, multi-factor authentication including TOTP and email OTP) and server configuration. The useAuth hook is added to manage authentication state and modal visibility, triggering the login modal if no active server configuration is found or if a session expires. The authService and apiClient services are updated to support session tokens, handle MFA flows, and notify the app of session expiration or missing configurations. Corresponding test files (LoginModal.test.tsx, useAuth.test.ts, apiClient.test.ts, authService.test.ts, storage.test.ts) have been added or updated to cover the new authentication logic, including secure storage of session tokens. The OnboardingModal component has been removed, as its functionality is now integrated into the new login flow. Minor adjustments were made to API endpoint paths in profileService.ts and color variables in global.css. Additionally, the server-side authMiddleware.js is updated to correctly interpret Bearer tokens as either API keys or session tokens, and the /api/auth/settings endpoint now provides a trusted_origin for MFA. The authorize middleware was removed from authMiddleware.js and from several chatRoutes.js endpoints, simplifying permission checks. The chatRepository.js was updated to use getClient instead of getSystemClient for clearing chat history. Finally, several data models (foodEntries.ts, foodInfo.ts, foods.ts, goals.ts, mealTypes.ts, meals.ts, preferences.ts) were updated to include additional nutritional fields and preferences.

Comment on lines 22 to 43
if (req.headers.authorization && req.headers.authorization.startsWith("Bearer ")) {
const token = req.headers.authorization.split(' ')[1];
if (token) {
if (token && token.length >= 64 && !token.includes('.')) {
req.headers['x-api-key'] = token;
log("debug", "Authentication: Mapped Bearer token to x-api-key header for Better Auth.");
delete req.headers.authorization;
log("debug", "Authentication: Mapped Bearer token to x-api-key (API key detected).");
} else if (token) {
// Session token: sign it and inject as a session cookie so getSession() resolves it.
// We do this here instead of relying on the bearer plugin due to a compatibility
// issue with Buffer secrets in @better-auth/utils/hmac.
const prefix = auth.options.advanced?.cookiePrefix || "better-auth";
const secureCookiePrefix = auth.options.advanced?.useSecureCookies ? "__Secure-" : "";
const cookieName = `${secureCookiePrefix}${prefix}.session_token`;
const signed = await serializeSignedCookie("", token, auth.options.secret);
const signedValue = signed.replace("=", ""); // Strip leading = from empty cookie name
const cookieHeader = `${cookieName}=${signedValue}`;
req.headers.cookie = req.headers.cookie
? `${req.headers.cookie}; ${cookieHeader}`
: cookieHeader;
delete req.headers.authorization;
log("debug", "Authentication: Converted Bearer session token to session cookie.");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

The middleware manually injects a session cookie into req.headers.cookie when a Bearer token is provided. However, it simply appends the new cookie to the existing string. If an attacker provides a Cookie header that already contains a session token, many cookie parsers will prioritize the first occurrence. This could allow an attacker to bypass the Bearer token validation logic by providing a valid session token in the Cookie header and a dummy token in the Authorization header.

});
} else {
await saveServerConfig({
id: Date.now().toString(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using Date.now().toString() is not guaranteed to generate a unique ID. While a collision is unlikely for this use case, a more robust method like generating a UUID would be preferable to prevent potential issues. If you have a crypto library available (like expo-crypto), using Crypto.randomUUID() would be ideal. Otherwise, you could improve uniqueness by adding a random component, for example: `${Date.now()}-${Math.random().toString(36).slice(2)}`.

This same pattern is also used in App.tsx for creating API key configurations and should be updated there as well.

@CodeWithCJ CodeWithCJ merged commit e46c22a into CodeWithCJ:main Mar 7, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants