user manager integration
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
148
src/components/AdminSecurityPanel.vue
Normal file
148
src/components/AdminSecurityPanel.vue
Normal 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
15
src/integrations.ts
Normal 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;
|
||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user