Files
api/web/composables/useAuthorizationLink.ts
Eli Bosley 4e945f5f56 feat(api): enhance OIDC redirect URI handling in service and tests (#1618)
- Updated `getRedirectUri` method in `OidcAuthService` to handle various
edge cases for redirect URIs, including full URIs, malformed URLs, and
default ports.
- Added comprehensive tests for `OidcAuthService` to validate redirect
URI construction and error handling.
- Modified `RestController` to utilize `redirect_uri` query parameter
for authorization requests.
- Updated frontend components to include `redirect_uri` in authorization
URLs, ensuring correct handling of different protocols and ports.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Stronger OIDC redirect_uri validation and an admin GraphQL endpoint to
view full OIDC configuration.
* OIDC Debug Logs UI (panel, button, modal), enhanced log viewer with
presets/filters, ANSI-colored rendering, and a File Viewer component.
* New GraphQL queries to list and fetch config files; API Config
Download page.

* **Refactor**
* Centralized, modular OIDC flows and safer redirect handling;
topic-based log subscriptions with a watcher manager for scalable live
logs.

* **Documentation**
  * Cache TTL guidance clarified to use milliseconds.

* **Chores**
* Added ansi_up and escape-html deps; improved log formatting; added
root codegen script.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-09-02 10:40:20 -04:00

124 lines
3.2 KiB
TypeScript

import { computed, ref } from 'vue';
import {
decodeScopesToPermissions,
scopesToFormData,
buildCallbackUrl as buildUrl,
generateAuthorizationUrl as generateUrl
} from '~/utils/authorizationScopes';
export interface ApiKeyAuthorizationParams {
name: string;
description: string;
scopes: string[];
redirectUri: string;
state: string;
}
/**
* Composable for authorization link handling with reactive state
*/
export function useAuthorizationLink(urlSearchParams?: URLSearchParams) {
// Parse query parameters with SSR safety
const params = urlSearchParams || (
typeof window !== 'undefined'
? new URLSearchParams(window.location.search)
: new URLSearchParams()
);
// Parse authorization parameters from URL
const authParams = ref<ApiKeyAuthorizationParams>({
name: params.get('name') || 'Unknown Application',
description: params.get('description') || '',
scopes: (params.get('scopes') || '').split(',').filter(Boolean),
redirectUri: params.get('redirect_uri') || '',
state: params.get('state') || '',
});
// Convert to form data structure with grouped permissions
const formData = computed(() => {
return scopesToFormData(
authParams.value.scopes,
authParams.value.name,
authParams.value.description
);
});
// Decode scopes to permissions and roles
const decodedPermissions = computed(() => {
return decodeScopesToPermissions(authParams.value.scopes);
});
// Validate redirect URI
const hasValidRedirectUri = computed(() => {
const uri = authParams.value.redirectUri;
if (!uri) return false;
try {
new URL(uri);
return true;
} catch {
return false;
}
});
// Get display name (remove " API Key" suffix if present)
const displayAppName = computed(() => {
const name = authParams.value.name;
if (name.endsWith(' API Key')) {
return name.slice(0, -8);
}
return name;
});
// Check if there are any permissions
const hasPermissions = computed(() => {
return authParams.value.scopes && authParams.value.scopes.length > 0;
});
// Get permissions summary for display
const permissionsSummary = computed(() => {
const scopes = authParams.value.scopes;
if (!scopes || scopes.length === 0) {
return '';
}
const roleCount = scopes.filter(scope => scope.startsWith('role:')).length;
const permissionCount = scopes.filter(scope => !scope.startsWith('role:')).length;
const summary: string[] = [];
if (roleCount > 0) {
summary.push(`${roleCount} role(s)`);
}
if (permissionCount > 0) {
summary.push(`${permissionCount} permission(s)`);
}
return summary.join(', ');
});
// Wrapper functions that use the reactive state
const buildCallbackUrl = (apiKey?: string, error?: string) => {
return buildUrl(authParams.value.redirectUri, apiKey, error, authParams.value.state);
};
const generateAuthorizationUrl = generateUrl;
return {
// Parsed params
authParams,
// Decoded data
decodedPermissions,
formData,
// Display helpers
displayAppName,
hasPermissions,
permissionsSummary,
hasValidRedirectUri,
// URL generation functions
generateAuthorizationUrl,
buildCallbackUrl,
};
}