feat: tenant CA certificate management with staging
Some checks failed
CI / build (push) Successful in 1m7s
CI / docker (push) Has been cancelled

Tenants can upload multiple CA certificates for enterprise SSO providers
that use private certificate authorities.

- New tenant_ca_certs table (V013) with PEM storage in DB
- Stage/activate/delete lifecycle per CA cert
- Aggregated ca.pem rebuild on activate/delete (atomic .wip swap)
- REST API: GET/POST/DELETE on /api/tenant/ca
- UI: CA Certificates section on SSO page with upload, activate, remove

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-10 19:35:04 +02:00
parent a3a6f99958
commit dd30ee77d4
8 changed files with 650 additions and 3 deletions

45
ui/src/api/ca-hooks.ts Normal file
View File

@@ -0,0 +1,45 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { api } from './client';
export interface CaCertResponse {
id: string;
status: string;
label: string | null;
subject: string;
issuer: string;
fingerprint: string;
notBefore: string;
notAfter: string;
createdAt: string;
}
export function useTenantCaCerts() {
return useQuery<CaCertResponse[]>({
queryKey: ['tenant', 'ca'],
queryFn: () => api.get('/tenant/ca'),
});
}
export function useStageCaCert() {
const qc = useQueryClient();
return useMutation<CaCertResponse, Error, FormData>({
mutationFn: (formData) => api.post('/tenant/ca', formData),
onSuccess: () => qc.invalidateQueries({ queryKey: ['tenant', 'ca'] }),
});
}
export function useActivateCaCert() {
const qc = useQueryClient();
return useMutation<CaCertResponse, Error, string>({
mutationFn: (id) => api.post(`/tenant/ca/${id}/activate`),
onSuccess: () => qc.invalidateQueries({ queryKey: ['tenant', 'ca'] }),
});
}
export function useDeleteCaCert() {
const qc = useQueryClient();
return useMutation<void, Error, string>({
mutationFn: (id) => api.delete(`/tenant/ca/${id}`),
onSuccess: () => qc.invalidateQueries({ queryKey: ['tenant', 'ca'] }),
});
}