194 lines
8.2 KiB
Vue
194 lines
8.2 KiB
Vue
<template>
|
|
<teleport to="body">
|
|
<div class="modal-backdrop-custom" @click.self="$emit('close')">
|
|
<div class="modal-panel">
|
|
<div class="provider-modal-header">
|
|
<div>
|
|
<h5 class="provider-modal-title mb-1">Identity Providers</h5>
|
|
<p class="text-muted mb-0">Configure OAuth2 and OIDC buttons for the login page.</p>
|
|
</div>
|
|
<button type="button" class="btn-close provider-modal-close" @click="$emit('close')"></button>
|
|
</div>
|
|
|
|
<div class="provider-modal-body">
|
|
<div v-if="error" class="alert alert-danger">{{ error }}</div>
|
|
<div v-if="successMessage" class="alert alert-success">{{ successMessage }}</div>
|
|
|
|
<section class="provider-section">
|
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
<h6 class="mb-0">Configured Providers</h6>
|
|
<button class="btn btn-sm btn-outline-secondary" :disabled="loading" @click="loadProviders">Refresh</button>
|
|
</div>
|
|
|
|
<div v-if="loading" class="text-muted small">Loading providers...</div>
|
|
<div v-else-if="providers.length === 0" class="border rounded p-3 text-muted">No providers configured yet.</div>
|
|
<div v-else class="list-group">
|
|
<div v-for="provider in providers" :key="provider.id" class="list-group-item d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<div class="fw-semibold">{{ provider.name }}</div>
|
|
<div class="small text-muted">{{ provider.type.toUpperCase() }} · {{ provider.scopes.join(", ") }}</div>
|
|
<div class="small text-muted">Callback: {{ buildCallbackUrl(provider.id) }}</div>
|
|
</div>
|
|
<span class="badge" :class="provider.is_active ? 'text-bg-success' : 'text-bg-secondary'">
|
|
{{ provider.is_active ? "Active" : "Disabled" }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="provider-section">
|
|
<h6 class="mb-3">Add Provider</h6>
|
|
<form class="row g-3" @submit.prevent="createProvider">
|
|
<div class="col-md-6">
|
|
<label class="form-label">Display Name</label>
|
|
<input v-model="form.name" type="text" class="form-control" required />
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<label class="form-label">Provider Type</label>
|
|
<select v-model="form.type" class="form-select">
|
|
<option value="oidc">OIDC</option>
|
|
<option value="oauth2">OAuth2</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<label class="form-label">Client ID</label>
|
|
<input v-model="form.client_id" type="text" class="form-control" required />
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<label class="form-label">Client Secret</label>
|
|
<input v-model="form.client_secret" type="password" class="form-control" required />
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<label class="form-label">Authorization URL</label>
|
|
<input v-model="form.authorization_url" type="url" class="form-control" required />
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<label class="form-label">Token URL</label>
|
|
<input v-model="form.token_url" type="url" class="form-control" required />
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<label class="form-label">UserInfo URL</label>
|
|
<input v-model="form.userinfo_url" type="url" class="form-control" placeholder="Optional when id_token contains profile claims" />
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<label class="form-label">ID Token Field</label>
|
|
<input v-model="form.id_token_claim" type="text" class="form-control" placeholder="id_token" />
|
|
</div>
|
|
|
|
<div class="col-12">
|
|
<label class="form-label">Scopes</label>
|
|
<input v-model="form.scopes" type="text" class="form-control" placeholder="openid, profile, email" />
|
|
</div>
|
|
|
|
<div class="col-12 form-check ms-2">
|
|
<input id="provider-active" v-model="form.is_active" type="checkbox" class="form-check-input" />
|
|
<label for="provider-active" class="form-check-label">Provider is active</label>
|
|
</div>
|
|
|
|
<div class="col-12 d-flex justify-content-end gap-2">
|
|
<button type="button" class="btn btn-outline-secondary" @click="$emit('close')">Close</button>
|
|
<button type="submit" class="btn btn-primary" :disabled="submitting">
|
|
{{ submitting ? "Saving..." : "Add Provider" }}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</teleport>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { onMounted, ref } from "vue";
|
|
import apiClient from "../services/apiClient";
|
|
|
|
defineEmits(["close"]);
|
|
|
|
const loading = ref(false);
|
|
const submitting = ref(false);
|
|
const error = ref("");
|
|
const successMessage = ref("");
|
|
const providers = ref([]);
|
|
const form = ref({
|
|
name: "",
|
|
type: "oidc",
|
|
client_id: "",
|
|
client_secret: "",
|
|
authorization_url: "",
|
|
token_url: "",
|
|
userinfo_url: "",
|
|
id_token_claim: "id_token",
|
|
scopes: "openid, profile, email",
|
|
is_active: true,
|
|
});
|
|
|
|
const loadProviders = async () => {
|
|
loading.value = true;
|
|
error.value = "";
|
|
|
|
try {
|
|
const response = await apiClient.get("/api/v1/auth/providers");
|
|
providers.value = response.data.providers || [];
|
|
} catch (err) {
|
|
error.value = err.response?.data || err.message;
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
};
|
|
|
|
const resetForm = () => {
|
|
form.value = {
|
|
name: "",
|
|
type: "oidc",
|
|
client_id: "",
|
|
client_secret: "",
|
|
authorization_url: "",
|
|
token_url: "",
|
|
userinfo_url: "",
|
|
id_token_claim: "id_token",
|
|
scopes: "openid, profile, email",
|
|
is_active: true,
|
|
};
|
|
};
|
|
|
|
const buildCallbackUrl = (providerId) => `${apiClient.defaults.baseURL}/api/v1/auth/providers/${providerId}/callback`;
|
|
|
|
const createProvider = async () => {
|
|
submitting.value = true;
|
|
error.value = "";
|
|
successMessage.value = "";
|
|
|
|
try {
|
|
await apiClient.post("/api/v1/auth/providers", {
|
|
...form.value,
|
|
scopes: form.value.scopes
|
|
.split(",")
|
|
.map((scope) => scope.trim())
|
|
.filter(Boolean),
|
|
});
|
|
successMessage.value = "Provider added.";
|
|
resetForm();
|
|
await loadProviders();
|
|
} catch (err) {
|
|
error.value = err.response?.data || err.message;
|
|
} finally {
|
|
submitting.value = false;
|
|
}
|
|
};
|
|
|
|
onMounted(loadProviders);
|
|
</script>
|
|
|
|
<style scoped src="../assets/styles/scoped/components/ManageAuthProvidersModal.css"></style>
|
|
|
|
|
|
|