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
## [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
### Fixed
......
......@@ -41,8 +41,8 @@ python -m build
```
This creates:
- `dist/aisbf-0.99.3.tar.gz` - Source distribution
- `dist/aisbf-0.99.3-py3-none-any.whl` - Wheel distribution
- `dist/aisbf-0.99.4.tar.gz` - Source distribution
- `dist/aisbf-0.99.4-py3-none-any.whl` - Wheel distribution
## Testing the Package
......@@ -50,7 +50,7 @@ This creates:
```bash
# 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
aisbf status
......
......@@ -54,7 +54,7 @@ from .auth.qwen import QwenOAuth2
from .handlers import RequestHandler, RotationHandler, AutoselectHandler
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__ = [
# Config
"config",
......
......@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
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"
readme = "README.md"
license = "GPL-3.0-or-later"
......
......@@ -49,7 +49,7 @@ class InstallCommand(_install):
setup(
name="aisbf",
version="0.99.3",
version="0.99.4",
author="AISBF Contributors",
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",
......
......@@ -381,7 +381,7 @@ function updateAutoselectModel(autoselectKey, index, field, value) {
async function saveAutoselect() {
try {
const response = await fetch('/dashboard/autoselect', {
const response = await fetch('{{ url_for(request, "/dashboard/autoselect") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
......@@ -390,7 +390,7 @@ async function saveAutoselect() {
});
if (response.ok) {
window.location.href = '/dashboard/autoselect?success=1';
window.location.href = '{{ url_for(request, "/dashboard/autoselect?success=1") }}';
} else {
alert('Error saving configuration');
}
......
......@@ -1057,7 +1057,7 @@ async function uploadCodexFile(providerKey, file) {
formData.append('file_type', 'credentials');
try {
const response = await fetch('/dashboard/providers/upload-auth-file', {
const response = await fetch(''{{ url_for(request, "/dashboard/providers/upload-auth-file") }}'', {
method: 'POST',
body: formData
});
......@@ -1083,7 +1083,7 @@ async function authenticateKilo(key) {
statusEl.innerHTML = '<p style="margin: 0; color: #4a9eff;">🔄 Starting OAuth2 Device Authorization flow...</p>';
try {
const response = await fetch('/dashboard/kilo/auth/start', {
const response = await fetch('{{ url_for(request, "/dashboard/kilo/auth/start") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
......@@ -1136,7 +1136,7 @@ async function authenticateKilo(key) {
pollCount++;
try {
const pollResponse = await fetch('/dashboard/kilo/auth/poll', {
const pollResponse = await fetch('{{ url_for(request, "/dashboard/kilo/auth/poll") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
......@@ -1194,7 +1194,7 @@ async function checkKiloAuth(key) {
statusEl.innerHTML = '<p style="margin: 0; color: #4a9eff;">🔄 Checking authentication status...</p>';
try {
const response = await fetch('/dashboard/kilo/auth/status', {
const response = await fetch('{{ url_for(request, "/dashboard/kilo/auth/status") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
......@@ -1236,7 +1236,7 @@ async function uploadKiloFile(providerKey, file) {
formData.append('file_type', 'credentials');
try {
const response = await fetch('/dashboard/providers/upload-auth-file', {
const response = await fetch('{{ url_for(request, "/dashboard/providers/upload-auth-file") }}', {
method: 'POST',
body: formData
});
......@@ -1330,7 +1330,7 @@ async function authenticateClaude(key) {
try {
// 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',
headers: {
'Content-Type': 'application/json',
......@@ -1408,7 +1408,7 @@ async function authenticateClaude(key) {
// For localhost flow, check callback status endpoint
if (!needsExtension) {
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();
if (statusData.received) {
......@@ -1437,7 +1437,7 @@ async function authenticateClaude(key) {
try {
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',
headers: {
'Content-Type': 'application/json',
......@@ -1675,7 +1675,7 @@ async function checkClaudeAuth(key) {
statusEl.innerHTML = '<p style="margin: 0; color: #4a9eff;">🔄 Checking authentication status...</p>';
try {
const response = await fetch('/dashboard/claude/auth/status', {
const response = await fetch('{{ url_for(request, "/dashboard/claude/auth/status") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
......@@ -1793,7 +1793,7 @@ async function uploadKiroFile(providerKey, fileType, file) {
formData.append('file_type', fileType);
try {
const response = await fetch('/dashboard/providers/upload-auth-file', {
const response = await fetch('{{ url_for(request, "/dashboard/providers/upload-auth-file") }}', {
method: 'POST',
body: formData
});
......@@ -1828,7 +1828,7 @@ async function uploadClaudeFile(providerKey, file) {
formData.append('file_type', 'credentials');
try {
const response = await fetch('/dashboard/providers/upload-auth-file', {
const response = await fetch('{{ url_for(request, "/dashboard/providers/upload-auth-file") }}', {
method: 'POST',
body: formData
});
......@@ -1857,7 +1857,7 @@ async function getModelsFromProvider(providerKey) {
statusEl.innerHTML = '<p style="margin: 0; color: #4a9eff;">🔄 Fetching models from provider API...</p>';
try {
const response = await fetch('/dashboard/providers/get-models', {
const response = await fetch('{{ url_for(request, "/dashboard/providers/get-models") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
......
......@@ -83,7 +83,7 @@ async function loadRateLimits() {
content.innerHTML = '<p>Loading rate limit data...</p>';
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();
if (Object.keys(data).length === 0) {
......@@ -181,7 +181,7 @@ async function clearAllRateLimiters() {
// First get the list of providers
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();
for (const providerId of Object.keys(data)) {
......
......@@ -231,7 +231,7 @@ async function authenticateClaudeUser(providerId) {
statusEl.innerHTML = '<p style="margin: 0; color: #4a9eff;">🔄 Starting OAuth2 authentication flow...</p>';
try {
const response = await fetch('/dashboard/claude/auth/start', {
const response = await fetch('{{ url_for(request, "/dashboard/claude/auth/start") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
......@@ -268,7 +268,7 @@ async function checkClaudeAuthUser(providerId) {
statusEl.innerHTML = '<p style="margin: 0; color: #4a9eff;">🔄 Checking authentication status...</p>';
try {
const response = await fetch('/dashboard/claude/auth/status', {
const response = await fetch('{{ url_for(request, "/dashboard/claude/auth/status") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
......
......@@ -150,7 +150,7 @@ function editUser(userId, username, role, isActive) {
document.getElementById('edit-username').value = username;
document.getElementById('edit-role').value = role;
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';
}
......@@ -161,7 +161,7 @@ function closeEditModal() {
function toggleUserStatus(userId, currentStatus) {
const action = currentStatus ? 'disable' : 'enable';
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',
headers: {'Content-Type': 'application/json'}
})
......@@ -181,7 +181,7 @@ function toggleUserStatus(userId, currentStatus) {
function deleteUser(userId, username) {
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',
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