Expose ui_layout and ai_enabled to mobile clients and add enable_ai endpoint (#983)

* Wire ui layout and AI flags into mobile auth

Include ui_layout and ai_enabled in mobile login/signup/SSO payloads,
add an authenticated endpoint to enable AI from Flutter, and gate
mobile navigation based on intro layout and AI consent flow.

* Linter

* Ensure write scope on enable_ai

* Make sure AI is available before enabling it

* Test improvements

* PR comment

* Fix review issues: test assertion bug, missing coverage, and Dart defaults (#985)

- Fix login test to use ai_enabled? (method) instead of ai_enabled (column)
  to match what mobile_user_payload actually serializes
- Add test for enable_ai when ai_available? returns false (403 path)
- Default aiEnabled to false when user is null in AuthProvider to avoid
  showing AI as available before authentication completes
- Remove extra blank lines in auth_provider.dart and auth_service.dart

https://claude.ai/code/session_01LEYYmtsDBoqizyihFtkye4

Co-authored-by: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Juan José Mata
2026-02-14 00:39:03 +01:00
committed by GitHub
parent e99e38a91c
commit bf0be85859
10 changed files with 774 additions and 104 deletions

View File

@@ -6,6 +6,10 @@ module Api
skip_before_action :authenticate_request!
skip_before_action :check_api_key_rate_limit
skip_before_action :log_api_access
before_action :authenticate_request!, only: :enable_ai
before_action :ensure_write_scope, only: :enable_ai
before_action :check_api_key_rate_limit, only: :enable_ai
before_action :log_api_access, only: :enable_ai
def signup
# Check if invite code is required
@@ -54,14 +58,7 @@ module Api
return
end
render json: token_response.merge(
user: {
id: user.id,
email: user.email,
first_name: user.first_name,
last_name: user.last_name
}
), status: :created
render json: token_response.merge(user: mobile_user_payload(user)), status: :created
else
render json: { errors: user.errors.full_messages }, status: :unprocessable_entity
end
@@ -97,14 +94,7 @@ module Api
return
end
render json: token_response.merge(
user: {
id: user.id,
email: user.email,
first_name: user.first_name,
last_name: user.last_name
}
)
render json: token_response.merge(user: mobile_user_payload(user))
else
render json: { error: "Invalid email or password" }, status: :unauthorized
end
@@ -143,11 +133,28 @@ module Api
id: cached[:user_id],
email: cached[:user_email],
first_name: cached[:user_first_name],
last_name: cached[:user_last_name]
last_name: cached[:user_last_name],
ui_layout: cached[:user_ui_layout],
ai_enabled: cached[:user_ai_enabled]
}
}
end
def enable_ai
user = current_resource_owner
unless user.ai_available?
render json: { error: "AI is not available for your account" }, status: :forbidden
return
end
if user.update(ai_enabled: true)
render json: { user: mobile_user_payload(user) }
else
render json: { errors: user.errors.full_messages }, status: :unprocessable_entity
end
end
def refresh
# Find the refresh token
refresh_token = params[:refresh_token]
@@ -229,6 +236,21 @@ module Api
def sso_exchange_params
params.require(:code)
end
def mobile_user_payload(user)
{
id: user.id,
email: user.email,
first_name: user.first_name,
last_name: user.last_name,
ui_layout: user.ui_layout,
ai_enabled: user.ai_enabled?
}
end
def ensure_write_scope
authorize_scope!(:write)
end
end
end
end