Commit 2aa9d96d authored by Your Name's avatar Your Name

Version 0.99.4: Fix OAuth2 proxy-awareness for reverse proxy deployments

- Fixed OAuth2 authentication endpoints not respecting reverse proxy subpaths
- Updated all OAuth2 JavaScript fetch calls to use url_for() instead of hardcoded /dashboard/ paths
- Fixed Claude, Kilo, Codex, and Qwen OAuth2 authentication flows for reverse proxy deployments
- Fixed file upload endpoints, rate limits data endpoint, user management endpoints, and autoselect save endpoint
- OAuth2 authentication buttons now work correctly behind nginx reverse proxy with /aisbf/ location
- Updated version in setup.py, pyproject.toml, aisbf/__init__.py, and PYPI.md
- Added changelog entry for 0.99.4
parent 8b94a935
...@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ...@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [0.99.4] - 2026-04-09
### Fixed
- **OAuth2 Proxy-Awareness**: Fixed OAuth2 authentication endpoints not respecting reverse proxy subpaths
- Updated all OAuth2 JavaScript fetch calls to use `{{ url_for(request, "...") }}` instead of hardcoded `/dashboard/` paths
- Fixed Claude, Kilo, Codex, and Qwen OAuth2 authentication flows for reverse proxy deployments
- Fixed file upload endpoints, rate limits data endpoint, user management endpoints, and autoselect save endpoint
- OAuth2 authentication buttons now work correctly behind nginx reverse proxy with `/aisbf/` location
### Changed
- **Version Bump**: Updated version to 0.99.4 in setup.py, pyproject.toml, and aisbf/__init__.py
## [0.99.3] - 2026-04-09 ## [0.99.3] - 2026-04-09
### Fixed ### Fixed
......
...@@ -41,8 +41,8 @@ python -m build ...@@ -41,8 +41,8 @@ python -m build
``` ```
This creates: This creates:
- `dist/aisbf-0.99.3.tar.gz` - Source distribution - `dist/aisbf-0.99.4.tar.gz` - Source distribution
- `dist/aisbf-0.99.3-py3-none-any.whl` - Wheel distribution - `dist/aisbf-0.99.4-py3-none-any.whl` - Wheel distribution
## Testing the Package ## Testing the Package
...@@ -50,7 +50,7 @@ This creates: ...@@ -50,7 +50,7 @@ This creates:
```bash ```bash
# Install from the built wheel # Install from the built wheel
pip install dist/aisbf-0.99.3-py3-none-any.whl pip install dist/aisbf-0.99.4-py3-none-any.whl
# Test the installation # Test the installation
aisbf status aisbf status
......
...@@ -54,7 +54,7 @@ from .auth.qwen import QwenOAuth2 ...@@ -54,7 +54,7 @@ from .auth.qwen import QwenOAuth2
from .handlers import RequestHandler, RotationHandler, AutoselectHandler from .handlers import RequestHandler, RotationHandler, AutoselectHandler
from .utils import count_messages_tokens, split_messages_into_chunks, get_max_request_tokens_for_model from .utils import count_messages_tokens, split_messages_into_chunks, get_max_request_tokens_for_model
__version__ = "0.99.3" __version__ = "0.99.4"
__all__ = [ __all__ = [
# Config # Config
"config", "config",
......
...@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" ...@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "aisbf" name = "aisbf"
version = "0.99.3" version = "0.99.4"
description = "AISBF - AI Service Broker Framework || AI Should Be Free - A modular proxy server for managing multiple AI provider integrations" description = "AISBF - AI Service Broker Framework || AI Should Be Free - A modular proxy server for managing multiple AI provider integrations"
readme = "README.md" readme = "README.md"
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
......
...@@ -49,7 +49,7 @@ class InstallCommand(_install): ...@@ -49,7 +49,7 @@ class InstallCommand(_install):
setup( setup(
name="aisbf", name="aisbf",
version="0.99.3", version="0.99.4",
author="AISBF Contributors", author="AISBF Contributors",
author_email="stefy@nexlab.net", author_email="stefy@nexlab.net",
description="AISBF - AI Service Broker Framework || AI Should Be Free - A modular proxy server for managing multiple AI provider integrations", description="AISBF - AI Service Broker Framework || AI Should Be Free - A modular proxy server for managing multiple AI provider integrations",
......
...@@ -381,7 +381,7 @@ function updateAutoselectModel(autoselectKey, index, field, value) { ...@@ -381,7 +381,7 @@ function updateAutoselectModel(autoselectKey, index, field, value) {
async function saveAutoselect() { async function saveAutoselect() {
try { try {
const response = await fetch('/dashboard/autoselect', { const response = await fetch('{{ url_for(request, "/dashboard/autoselect") }}', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded', 'Content-Type': 'application/x-www-form-urlencoded',
...@@ -390,7 +390,7 @@ async function saveAutoselect() { ...@@ -390,7 +390,7 @@ async function saveAutoselect() {
}); });
if (response.ok) { if (response.ok) {
window.location.href = '/dashboard/autoselect?success=1'; window.location.href = '{{ url_for(request, "/dashboard/autoselect?success=1") }}';
} else { } else {
alert('Error saving configuration'); alert('Error saving configuration');
} }
......
...@@ -1057,7 +1057,7 @@ async function uploadCodexFile(providerKey, file) { ...@@ -1057,7 +1057,7 @@ async function uploadCodexFile(providerKey, file) {
formData.append('file_type', 'credentials'); formData.append('file_type', 'credentials');
try { try {
const response = await fetch('/dashboard/providers/upload-auth-file', { const response = await fetch(''{{ url_for(request, "/dashboard/providers/upload-auth-file") }}'', {
method: 'POST', method: 'POST',
body: formData body: formData
}); });
...@@ -1083,7 +1083,7 @@ async function authenticateKilo(key) { ...@@ -1083,7 +1083,7 @@ async function authenticateKilo(key) {
statusEl.innerHTML = '<p style="margin: 0; color: #4a9eff;">🔄 Starting OAuth2 Device Authorization flow...</p>'; statusEl.innerHTML = '<p style="margin: 0; color: #4a9eff;">🔄 Starting OAuth2 Device Authorization flow...</p>';
try { try {
const response = await fetch('/dashboard/kilo/auth/start', { const response = await fetch('{{ url_for(request, "/dashboard/kilo/auth/start") }}', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
...@@ -1136,7 +1136,7 @@ async function authenticateKilo(key) { ...@@ -1136,7 +1136,7 @@ async function authenticateKilo(key) {
pollCount++; pollCount++;
try { try {
const pollResponse = await fetch('/dashboard/kilo/auth/poll', { const pollResponse = await fetch('{{ url_for(request, "/dashboard/kilo/auth/poll") }}', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
...@@ -1194,7 +1194,7 @@ async function checkKiloAuth(key) { ...@@ -1194,7 +1194,7 @@ async function checkKiloAuth(key) {
statusEl.innerHTML = '<p style="margin: 0; color: #4a9eff;">🔄 Checking authentication status...</p>'; statusEl.innerHTML = '<p style="margin: 0; color: #4a9eff;">🔄 Checking authentication status...</p>';
try { try {
const response = await fetch('/dashboard/kilo/auth/status', { const response = await fetch('{{ url_for(request, "/dashboard/kilo/auth/status") }}', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
...@@ -1236,7 +1236,7 @@ async function uploadKiloFile(providerKey, file) { ...@@ -1236,7 +1236,7 @@ async function uploadKiloFile(providerKey, file) {
formData.append('file_type', 'credentials'); formData.append('file_type', 'credentials');
try { try {
const response = await fetch('/dashboard/providers/upload-auth-file', { const response = await fetch('{{ url_for(request, "/dashboard/providers/upload-auth-file") }}', {
method: 'POST', method: 'POST',
body: formData body: formData
}); });
...@@ -1330,7 +1330,7 @@ async function authenticateClaude(key) { ...@@ -1330,7 +1330,7 @@ async function authenticateClaude(key) {
try { try {
// Get OAuth2 URL from server // Get OAuth2 URL from server
const response = await fetch('/dashboard/claude/auth/start', { const response = await fetch('{{ url_for(request, "/dashboard/claude/auth/start") }}', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
...@@ -1408,7 +1408,7 @@ async function authenticateClaude(key) { ...@@ -1408,7 +1408,7 @@ async function authenticateClaude(key) {
// For localhost flow, check callback status endpoint // For localhost flow, check callback status endpoint
if (!needsExtension) { if (!needsExtension) {
try { try {
const statusResponse = await fetch('/dashboard/claude/auth/callback-status'); const statusResponse = await fetch('{{ url_for(request, "/dashboard/claude/auth/callback-status") }}');
const statusData = await statusResponse.json(); const statusData = await statusResponse.json();
if (statusData.received) { if (statusData.received) {
...@@ -1437,7 +1437,7 @@ async function authenticateClaude(key) { ...@@ -1437,7 +1437,7 @@ async function authenticateClaude(key) {
try { try {
statusEl.innerHTML = '<p style="margin: 0; color: #4a9eff;">🔄 Completing authentication (this may take a moment if rate limited)...</p>'; statusEl.innerHTML = '<p style="margin: 0; color: #4a9eff;">🔄 Completing authentication (this may take a moment if rate limited)...</p>';
const completeResponse = await fetch('/dashboard/claude/auth/complete', { const completeResponse = await fetch('{{ url_for(request, "/dashboard/claude/auth/complete") }}', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
...@@ -1675,7 +1675,7 @@ async function checkClaudeAuth(key) { ...@@ -1675,7 +1675,7 @@ async function checkClaudeAuth(key) {
statusEl.innerHTML = '<p style="margin: 0; color: #4a9eff;">🔄 Checking authentication status...</p>'; statusEl.innerHTML = '<p style="margin: 0; color: #4a9eff;">🔄 Checking authentication status...</p>';
try { try {
const response = await fetch('/dashboard/claude/auth/status', { const response = await fetch('{{ url_for(request, "/dashboard/claude/auth/status") }}', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
...@@ -1793,7 +1793,7 @@ async function uploadKiroFile(providerKey, fileType, file) { ...@@ -1793,7 +1793,7 @@ async function uploadKiroFile(providerKey, fileType, file) {
formData.append('file_type', fileType); formData.append('file_type', fileType);
try { try {
const response = await fetch('/dashboard/providers/upload-auth-file', { const response = await fetch('{{ url_for(request, "/dashboard/providers/upload-auth-file") }}', {
method: 'POST', method: 'POST',
body: formData body: formData
}); });
...@@ -1828,7 +1828,7 @@ async function uploadClaudeFile(providerKey, file) { ...@@ -1828,7 +1828,7 @@ async function uploadClaudeFile(providerKey, file) {
formData.append('file_type', 'credentials'); formData.append('file_type', 'credentials');
try { try {
const response = await fetch('/dashboard/providers/upload-auth-file', { const response = await fetch('{{ url_for(request, "/dashboard/providers/upload-auth-file") }}', {
method: 'POST', method: 'POST',
body: formData body: formData
}); });
...@@ -1857,7 +1857,7 @@ async function getModelsFromProvider(providerKey) { ...@@ -1857,7 +1857,7 @@ async function getModelsFromProvider(providerKey) {
statusEl.innerHTML = '<p style="margin: 0; color: #4a9eff;">🔄 Fetching models from provider API...</p>'; statusEl.innerHTML = '<p style="margin: 0; color: #4a9eff;">🔄 Fetching models from provider API...</p>';
try { try {
const response = await fetch('/dashboard/providers/get-models', { const response = await fetch('{{ url_for(request, "/dashboard/providers/get-models") }}', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
......
...@@ -83,7 +83,7 @@ async function loadRateLimits() { ...@@ -83,7 +83,7 @@ async function loadRateLimits() {
content.innerHTML = '<p>Loading rate limit data...</p>'; content.innerHTML = '<p>Loading rate limit data...</p>';
try { try {
const response = await fetch('/dashboard/rate-limits/data'); const response = await fetch('{{ url_for(request, "/dashboard/rate-limits/data") }}');
const data = await response.json(); const data = await response.json();
if (Object.keys(data).length === 0) { if (Object.keys(data).length === 0) {
...@@ -181,7 +181,7 @@ async function clearAllRateLimiters() { ...@@ -181,7 +181,7 @@ async function clearAllRateLimiters() {
// First get the list of providers // First get the list of providers
try { try {
const response = await fetch('/dashboard/rate-limits/data'); const response = await fetch({{ url_for(request, "/dashboard/rate-limits/data") }});
const data = await response.json(); const data = await response.json();
for (const providerId of Object.keys(data)) { for (const providerId of Object.keys(data)) {
......
...@@ -231,7 +231,7 @@ async function authenticateClaudeUser(providerId) { ...@@ -231,7 +231,7 @@ async function authenticateClaudeUser(providerId) {
statusEl.innerHTML = '<p style="margin: 0; color: #4a9eff;">🔄 Starting OAuth2 authentication flow...</p>'; statusEl.innerHTML = '<p style="margin: 0; color: #4a9eff;">🔄 Starting OAuth2 authentication flow...</p>';
try { try {
const response = await fetch('/dashboard/claude/auth/start', { const response = await fetch('{{ url_for(request, "/dashboard/claude/auth/start") }}', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
...@@ -268,7 +268,7 @@ async function checkClaudeAuthUser(providerId) { ...@@ -268,7 +268,7 @@ async function checkClaudeAuthUser(providerId) {
statusEl.innerHTML = '<p style="margin: 0; color: #4a9eff;">🔄 Checking authentication status...</p>'; statusEl.innerHTML = '<p style="margin: 0; color: #4a9eff;">🔄 Checking authentication status...</p>';
try { try {
const response = await fetch('/dashboard/claude/auth/status', { const response = await fetch('{{ url_for(request, "/dashboard/claude/auth/status") }}', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
......
...@@ -150,7 +150,7 @@ function editUser(userId, username, role, isActive) { ...@@ -150,7 +150,7 @@ function editUser(userId, username, role, isActive) {
document.getElementById('edit-username').value = username; document.getElementById('edit-username').value = username;
document.getElementById('edit-role').value = role; document.getElementById('edit-role').value = role;
document.getElementById('edit-is-active').checked = isActive; document.getElementById('edit-is-active').checked = isActive;
document.getElementById('edit-form').action = '/dashboard/users/' + userId + '/edit'; document.getElementById('edit-form').action = '{{ url_for(request, "/dashboard/users/") }}' + userId + '/edit';
document.getElementById('edit-modal').style.display = 'block'; document.getElementById('edit-modal').style.display = 'block';
} }
...@@ -161,7 +161,7 @@ function closeEditModal() { ...@@ -161,7 +161,7 @@ function closeEditModal() {
function toggleUserStatus(userId, currentStatus) { function toggleUserStatus(userId, currentStatus) {
const action = currentStatus ? 'disable' : 'enable'; const action = currentStatus ? 'disable' : 'enable';
if (confirm('Are you sure you want to ' + action + ' this user?')) { if (confirm('Are you sure you want to ' + action + ' this user?')) {
fetch('/dashboard/users/' + userId + '/toggle', { fetch('{{ url_for(request, "/dashboard/users/") }}' + userId + '/toggle', {
method: 'POST', method: 'POST',
headers: {'Content-Type': 'application/json'} headers: {'Content-Type': 'application/json'}
}) })
...@@ -181,7 +181,7 @@ function toggleUserStatus(userId, currentStatus) { ...@@ -181,7 +181,7 @@ function toggleUserStatus(userId, currentStatus) {
function deleteUser(userId, username) { function deleteUser(userId, username) {
if (confirm('Are you sure you want to delete user "' + username + '"? This action cannot be undone.')) { if (confirm('Are you sure you want to delete user "' + username + '"? This action cannot be undone.')) {
fetch('/dashboard/users/' + userId + '/delete', { fetch('{{ url_for(request, "/dashboard/users/") }}' + userId + '/delete', {
method: 'POST', method: 'POST',
headers: {'Content-Type': 'application/json'} headers: {'Content-Type': 'application/json'}
}) })
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment