A modular proxy server for managing multiple AI provider integrations with unified API interface. AISBF provides intelligent routing, load balancing, and AI-assisted model selection to optimize AI service usage across multiple providers.

## Try AISBF
Try AISBF live at [https://aisbf.cloud](https://aisbf.cloud) or via TOR at [http://aisbfity4ud6nsht53tsh2iauaur2e4dah2gplcprnikyjpkg72vfjad.onion](http://aisbfity4ud6nsht53tsh2iauaur2e4dah2gplcprnikyjpkg72vfjad.onion) - no installation required!
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Implement separate username and display_name fields to handle OAuth signup with full names while maintaining clean usernames for system use.
**Architecture:** Add display_name column to users table, update user creation and retrieval functions, modify OAuth callbacks to generate usernames from display names with email fallback, and update UI to display display_name where appropriate.
Implement separate username and display_name fields to handle OAuth signup with full names while maintaining clean usernames for system use.
## Problem Statement
Google OAuth returns display names like "Stefy "nextime" Lanza" which contain spaces and special characters not allowed in usernames. Current system tries to use raw display name as username, causing validation failures.
## Solution
- Add `display_name` VARCHAR(255) column to users table
- Generate clean username from display_name (with email fallback)
- Store raw OAuth display name in display_name field
- Use display_name for UI display, username for system identification
logger.error(f"Error checking email verification status for user {user_id}: {e}")
# Check if user still exists (handle case where user was deleted by admin)
# This ensures deleted users are logged out on their next request
user_id=request.session.get('user_id')
ifuser_id:
try:
fromaisbf.databaseimportget_database
db=DatabaseRegistry.get_config_database()
current_user=db.get_user_by_id(user_id)
ifnotcurrent_user:
# User has been deleted, log them out
logger.info(f"User {user_id} has been deleted, logging out session")
request.session.clear()
returnRedirectResponse(url=url_for(request,"/dashboard/login")+"?error=Your account has been deleted. Please contact an administrator.",status_code=303)
exceptExceptionase:
logger.error(f"Error checking user existence for user {user_id}: {e}")
# Only check email_verified if verification is required
HiddenServicePort <spanid="torrc-hs-port">80</span> 127.0.0.1:<spanid="torrc-local-port">{{ config.server.port if config.server else 17765 }}</span></code></pre>
<pstyle="margin: 8px 0 0 0; color: #ff9800; font-size: 0.85em;">⚠️ Replace <codestyle="background: #16213e; padding: 2px 4px; border-radius: 2px;">/home/yourusername</code> with your actual home directory path (torrc does NOT expand ~)</p>
<prestyle="margin: 8px 0 0 0; padding: 8px; background: #000; border-radius: 3px; overflow-x: auto;"><code>sudo systemctl restart tor # Linux
brew services restart tor # macOS</code></pre>
</div>
</div>
<divclass="form-group">
<labelfor="tor_control_host">TOR Control Host</label>
<inputtype="text"id="tor_control_host"name="tor_control_host"value="{{ config.tor.control_host if config.tor else '127.0.0.1' }}">
...
...
@@ -479,28 +522,32 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<divclass="form-group">
<labelfor="tor_control_port">TOR Control Port</label>
<inputtype="number"id="tor_control_port"name="tor_control_port"value="{{ config.tor.control_port if config.tor else 9051 }}">
<smallstyle="color: #666; display: block; margin-top: 5px;">TOR control port (default: 9051)</small>
<smallstyle="color: #666; display: block; margin-top: 5px;">TOR control port (default: 9051) - must match ControlPort in torrc</small>
</div>
<divclass="form-group">
<labelfor="tor_control_password">TOR Control Password</label>
<inputtype="password"id="tor_control_password"name="tor_control_password"value="{{ config.tor.control_password if config.tor and config.tor.control_password else '' }}"placeholder="Leave blank if no password">
<smallstyle="color: #666; display: block; margin-top: 5px;">Password for TOR control port authentication (optional)</small>
<inputtype="password"id="tor_control_password"name="tor_control_password"value="{{ config.tor.control_password if config.tor and config.tor.control_password else '' }}"placeholder="Enter the password you used to generate the hash">
<smallstyle="color: #666; display: block; margin-top: 5px;">The password you used with <code>tor --hash-password</code> (NOT the hash itself)</small>
</div>
<divclass="form-group">
<labelfor="tor_hidden_service_dir">Hidden Service Directory</label>
<inputtype="text"id="tor_hidden_service_dir"name="tor_hidden_service_dir"value="{{ config.tor.hidden_service_dir if config.tor and config.tor.hidden_service_dir else '' }}"placeholder="Leave blank for ephemeral service"style="flex: 1;">
<inputtype="text"id="tor_hidden_service_dir"name="tor_hidden_service_dir"value="{{ config.tor.hidden_service_dir if config.tor and config.tor.hidden_service_dir else '' }}"placeholder="Leave blank for ephemeral service"style="flex: 1;"onchange="updateTorrcExample()">
<strong>Ephemeral:</strong> Leave blank for temporary service (new address each restart)<br>
<strong>Persistent:</strong> Enter full absolute path (e.g., /home/username/.aisbf/tor_hidden_service) - must match HiddenServiceDir in torrc<br>
<spanstyle="color: #ff9800;">⚠️ Note: torrc does NOT expand ~ - use full paths like /home/username instead</span>
</small>
</div>
<divclass="form-group">
<labelfor="tor_hidden_service_port">Hidden Service Port</label>
<inputtype="number"id="tor_hidden_service_port"name="tor_hidden_service_port"value="{{ config.tor.hidden_service_port if config.tor else 80 }}">
<smallstyle="color: #666; display: block; margin-top: 5px;">Port exposed on the hidden service (default: 80)</small>
<inputtype="number"id="tor_hidden_service_port"name="tor_hidden_service_port"value="{{ config.tor.hidden_service_port if config.tor else 80 }}"onchange="updateTorrcExample()">
<smallstyle="color: #666; display: block; margin-top: 5px;">Port exposed on the onion address (default: 80) - must match HiddenServicePort in torrc for persistent services</small>
</div>
<divclass="form-group">
...
...
@@ -926,7 +973,8 @@ function toggleResponseCacheFields() {