user manager integration

This commit is contained in:
root
2025-12-23 18:19:25 -05:00
parent 42a5897d29
commit dcc9833338
4 changed files with 171 additions and 4 deletions

View File

@@ -4,9 +4,11 @@
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "build": "vite build --mode production --config vite.config.ts",
"build": "vue-tsc -b && vite build", "dev": "vite build --mode development --config vite.config.ts",
"preview": "vite preview" "watch": "vite build --mode development --watch --config vite.config.ts",
"typecheck": "vue-tsc --noEmit",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
}, },
"dependencies": { "dependencies": {
"vue": "^3.5.13" "vue": "^3.5.13"

View File

@@ -0,0 +1,148 @@
<script setup lang="ts">
import { ref } from 'vue';
interface User {
uid: string;
identity: string;
label: string;
provider?: string;
provider_subject?: string;
}
const props = defineProps<{
user: User;
}>();
const emit = defineEmits<{
update: [];
}>();
const loading = ref(false);
const error = ref<string | null>(null);
const success = ref<string | null>(null);
const unlinkDialog = ref(false);
// OIDC unlink is handled via the user_manager service since it modifies user record
const unlinkProvider = async () => {
loading.value = true;
error.value = null;
try {
const response = await fetch('/m/user_manager/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
body: JSON.stringify({
version: 1,
transaction: crypto.randomUUID(),
operation: 'user.provider.unlink',
data: {
uid: props.user.uid,
},
}),
});
if (response.ok) {
success.value = 'Provider unlinked successfully';
unlinkDialog.value = false;
emit('update');
} else {
const data = await response.json();
error.value = data.error || 'Failed to unlink provider';
}
} catch (err: any) {
error.value = err.message || 'Failed to unlink provider';
} finally {
loading.value = false;
}
};
</script>
<template>
<VCard v-if="user.provider" variant="outlined">
<VCardTitle class="d-flex align-center">
<VIcon icon="mdi-shield-lock" class="mr-2" />
External Identity Provider
</VCardTitle>
<VCardText>
<VAlert
v-if="error"
type="error"
closable
class="mb-4"
@click:close="error = null"
>
{{ error }}
</VAlert>
<VAlert
v-if="success"
type="success"
closable
class="mb-4"
@click:close="success = null"
>
{{ success }}
</VAlert>
<p class="mb-2">
<strong>Provider:</strong>
<VChip size="small" color="primary" class="ml-2">
{{ user.provider.toUpperCase() }}
</VChip>
</p>
<p class="mb-4">
<strong>Subject:</strong> {{ user.provider_subject || 'N/A' }}
</p>
<p class="mb-4 text-body-2">
This user is linked to an external identity provider. Unlinking will remove the association,
and the user will no longer be able to login via {{ user.provider.toUpperCase() }}.
</p>
<VBtn
color="error"
prepend-icon="mdi-link-off"
@click="unlinkDialog = true"
>
Unlink Provider
</VBtn>
</VCardText>
<!-- Unlink Provider Dialog -->
<VDialog v-model="unlinkDialog" max-width="500">
<VCard>
<VCardTitle class="d-flex align-center">
<VIcon icon="mdi-alert" color="warning" class="mr-2" />
Unlink External Provider
</VCardTitle>
<VCardText>
<VAlert type="warning" class="mb-4">
<strong>Warning:</strong> This will remove the association with the external identity provider.
The user will no longer be able to login via {{ user.provider?.toUpperCase() }}.
</VAlert>
<p>
Are you sure you want to unlink <strong>{{ user.provider?.toUpperCase() }}</strong> for <strong>{{ user.label }}</strong>?
</p>
<p class="mt-2 text-caption">
Make sure the user has alternative authentication methods configured before unlinking.
</p>
</VCardText>
<VCardActions>
<VSpacer />
<VBtn @click="unlinkDialog = false">Cancel</VBtn>
<VBtn
color="error"
:loading="loading"
@click="unlinkProvider"
>
Unlink Provider
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</VCard>
</template>

15
src/integrations.ts Normal file
View File

@@ -0,0 +1,15 @@
import type { ModuleIntegrations } from "@KTXC/types/moduleTypes";
const integrations: ModuleIntegrations = {
user_manager_security_panels: [
{
id: 'oidc-management',
label: 'External Provider',
icon: 'mdi-shield-lock',
priority: 30,
component: () => import('@/components/AdminSecurityPanel.vue'),
},
],
};
export default integrations;

View File

@@ -2,9 +2,11 @@
// Exports components for admin configuration UI // Exports components for admin configuration UI
import ConfigPanel from './components/ConfigPanel.vue' import ConfigPanel from './components/ConfigPanel.vue'
import integrations from './integrations'
export { export {
ConfigPanel ConfigPanel,
integrations
} }
export default { export default {