mirror of
https://github.com/unraid/api.git
synced 2026-02-04 06:59:01 -06:00
- 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>
124 lines
3.2 KiB
TypeScript
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,
|
|
};
|
|
}
|