Merge branch 'main' of github.com:CoryHawkless/gatehouse-ui
This commit is contained in:
+1
-1
@@ -3,7 +3,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
args:
|
args:
|
||||||
- VITE_API_BASE_URL=https://secuird.tech/api/v1
|
- VITE_API_BASE_URL=https://secuird.tech/
|
||||||
container_name: gatehouse-ui
|
container_name: gatehouse-ui
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
|
|||||||
+5
-4
@@ -1184,10 +1184,10 @@ export const api = {
|
|||||||
request<{ clients: OIDCClient[]; count: number }>(`/organizations/${orgId}/clients`, {}, true, requestConfig),
|
request<{ clients: OIDCClient[]; count: number }>(`/organizations/${orgId}/clients`, {}, true, requestConfig),
|
||||||
|
|
||||||
// Create OIDC client
|
// Create OIDC client
|
||||||
createClient: (orgId: string, name: string, redirect_uris: string[], requestConfig?: RequestConfig) =>
|
createClient: (orgId: string, name: string, redirect_uris: string[], allowed_cors_origins?: string[] | null, requestConfig?: RequestConfig) =>
|
||||||
request<{ client: OIDCClientWithSecret }>(`/organizations/${orgId}/clients`, {
|
request<{ client: OIDCClientWithSecret }>(`/organizations/${orgId}/clients`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({ name, redirect_uris }),
|
body: JSON.stringify({ name, redirect_uris, allowed_cors_origins }),
|
||||||
}, true, requestConfig),
|
}, true, requestConfig),
|
||||||
|
|
||||||
// Delete OIDC client
|
// Delete OIDC client
|
||||||
@@ -1196,8 +1196,8 @@ export const api = {
|
|||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
}, true, requestConfig),
|
}, true, requestConfig),
|
||||||
|
|
||||||
// Update OIDC client (name and/or redirect_uris)
|
// Update OIDC client (name, redirect_uris, and/or allowed_cors_origins)
|
||||||
updateClient: (orgId: string, clientId: string, data: { name?: string; redirect_uris?: string[] }, requestConfig?: RequestConfig) =>
|
updateClient: (orgId: string, clientId: string, data: { name?: string; redirect_uris?: string[]; allowed_cors_origins?: string[] | null }, requestConfig?: RequestConfig) =>
|
||||||
request<{ client: OIDCClient }>(`/organizations/${orgId}/clients/${clientId}`, {
|
request<{ client: OIDCClient }>(`/organizations/${orgId}/clients/${clientId}`, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
@@ -1854,6 +1854,7 @@ export interface OIDCClient {
|
|||||||
redirect_uris: string[];
|
redirect_uris: string[];
|
||||||
scopes: string[];
|
scopes: string[];
|
||||||
grant_types: string[];
|
grant_types: string[];
|
||||||
|
allowed_cors_origins: string[] | null;
|
||||||
is_active: boolean;
|
is_active: boolean;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ export default function OIDCClientsPage() {
|
|||||||
// Generic form
|
// Generic form
|
||||||
const nameRef = useRef<HTMLInputElement>(null);
|
const nameRef = useRef<HTMLInputElement>(null);
|
||||||
const urisRef = useRef<HTMLTextAreaElement>(null);
|
const urisRef = useRef<HTMLTextAreaElement>(null);
|
||||||
|
const corsRef = useRef<HTMLTextAreaElement>(null);
|
||||||
|
|
||||||
// Proxy form
|
// Proxy form
|
||||||
const proxyNameRef = useRef<HTMLInputElement>(null);
|
const proxyNameRef = useRef<HTMLInputElement>(null);
|
||||||
@@ -131,6 +132,7 @@ export default function OIDCClientsPage() {
|
|||||||
const [editingClient, setEditingClient] = useState<OIDCClient | null>(null);
|
const [editingClient, setEditingClient] = useState<OIDCClient | null>(null);
|
||||||
const [editName, setEditName] = useState("");
|
const [editName, setEditName] = useState("");
|
||||||
const [editUris, setEditUris] = useState("");
|
const [editUris, setEditUris] = useState("");
|
||||||
|
const [editCors, setEditCors] = useState("");
|
||||||
const [isSavingEdit, setIsSavingEdit] = useState(false);
|
const [isSavingEdit, setIsSavingEdit] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -149,10 +151,16 @@ export default function OIDCClientsPage() {
|
|||||||
let uris: string[];
|
let uris: string[];
|
||||||
let proxyHost: string | undefined;
|
let proxyHost: string | undefined;
|
||||||
|
|
||||||
|
let corsOrigins: string[] | null = null;
|
||||||
|
|
||||||
if (dialogMode === "generic") {
|
if (dialogMode === "generic") {
|
||||||
name = nameRef.current?.value.trim() ?? "";
|
name = nameRef.current?.value.trim() ?? "";
|
||||||
uris = (urisRef.current?.value ?? "").split(/[\n,]+/).map((u) => u.trim()).filter(Boolean);
|
uris = (urisRef.current?.value ?? "").split(/[\n,]+/).map((u) => u.trim()).filter(Boolean);
|
||||||
if (!name || !uris.length) return;
|
if (!name || !uris.length) return;
|
||||||
|
const corsRaw = (corsRef.current?.value ?? "").trim();
|
||||||
|
if (corsRaw) {
|
||||||
|
corsOrigins = corsRaw.split(/[\n,]+/).map((o) => o.trim()).filter(Boolean);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
name = proxyNameRef.current?.value.trim() ?? "";
|
name = proxyNameRef.current?.value.trim() ?? "";
|
||||||
proxyHost = proxyHostRef.current?.value.trim() ?? "";
|
proxyHost = proxyHostRef.current?.value.trim() ?? "";
|
||||||
@@ -166,7 +174,7 @@ export default function OIDCClientsPage() {
|
|||||||
|
|
||||||
setIsCreating(true);
|
setIsCreating(true);
|
||||||
try {
|
try {
|
||||||
const result = await api.organizations.createClient(orgId, name, uris);
|
const result = await api.organizations.createClient(orgId, name, uris, corsOrigins);
|
||||||
const created = result.client as OIDCClientWithSecret;
|
const created = result.client as OIDCClientWithSecret;
|
||||||
setClients((prev) => [...prev, created]);
|
setClients((prev) => [...prev, created]);
|
||||||
setNewSecret({
|
setNewSecret({
|
||||||
@@ -202,6 +210,7 @@ export default function OIDCClientsPage() {
|
|||||||
setEditingClient(client);
|
setEditingClient(client);
|
||||||
setEditName(client.name);
|
setEditName(client.name);
|
||||||
setEditUris((client.redirect_uris ?? []).join("\n"));
|
setEditUris((client.redirect_uris ?? []).join("\n"));
|
||||||
|
setEditCors((client.allowed_cors_origins ?? []).join("\n"));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSaveEdit = async () => {
|
const handleSaveEdit = async () => {
|
||||||
@@ -210,9 +219,14 @@ export default function OIDCClientsPage() {
|
|||||||
const uris = editUris.split(/[\n,]+/).map((u) => u.trim()).filter(Boolean);
|
const uris = editUris.split(/[\n,]+/).map((u) => u.trim()).filter(Boolean);
|
||||||
if (!name || !uris.length) return;
|
if (!name || !uris.length) return;
|
||||||
|
|
||||||
|
const corsRaw = editCors.trim();
|
||||||
|
const corsOrigins: string[] | null = corsRaw
|
||||||
|
? corsRaw.split(/[\n,]+/).map((o) => o.trim()).filter(Boolean)
|
||||||
|
: null;
|
||||||
|
|
||||||
setIsSavingEdit(true);
|
setIsSavingEdit(true);
|
||||||
try {
|
try {
|
||||||
const result = await api.organizations.updateClient(orgId, editingClient.id, { name, redirect_uris: uris });
|
const result = await api.organizations.updateClient(orgId, editingClient.id, { name, redirect_uris: uris, allowed_cors_origins: corsOrigins });
|
||||||
setClients((prev) =>
|
setClients((prev) =>
|
||||||
prev.map((c) => (c.id === editingClient.id ? result.client : c))
|
prev.map((c) => (c.id === editingClient.id ? result.client : c))
|
||||||
);
|
);
|
||||||
@@ -390,6 +404,16 @@ export default function OIDCClientsPage() {
|
|||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
|
{client.allowed_cors_origins && client.allowed_cors_origins.length > 0 && (
|
||||||
|
<div className="flex flex-wrap gap-1 mt-2">
|
||||||
|
<span className="text-xs text-muted-foreground mr-1">CORS:</span>
|
||||||
|
{client.allowed_cors_origins.map((origin) => (
|
||||||
|
<Badge key={origin} variant="outline" className="text-xs font-mono">
|
||||||
|
{origin}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="mt-3 pt-3 border-t flex items-center justify-between text-xs text-muted-foreground">
|
<div className="mt-3 pt-3 border-t flex items-center justify-between text-xs text-muted-foreground">
|
||||||
<span>Created {new Date(client.created_at).toLocaleDateString()}</span>
|
<span>Created {new Date(client.created_at).toLocaleDateString()}</span>
|
||||||
<span>
|
<span>
|
||||||
@@ -439,6 +463,19 @@ export default function OIDCClientsPage() {
|
|||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground">One URI per line</p>
|
<p className="text-xs text-muted-foreground">One URI per line</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="corsOrigins">Allowed CORS origins</Label>
|
||||||
|
<Textarea
|
||||||
|
id="corsOrigins"
|
||||||
|
placeholder={"https://myapp.example.com\nhttps://staging.myapp.example.com"}
|
||||||
|
className="min-h-[60px] font-mono text-sm"
|
||||||
|
ref={corsRef}
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
One origin per line (scheme + host + optional port, no path). Leave empty to use the server default.
|
||||||
|
Use <code className="bg-muted px-1 rounded">+</code> to auto-derive from redirect URIs, or <code className="bg-muted px-1 rounded">*</code> to allow any origin.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
{/* oauth2-proxy tab */}
|
{/* oauth2-proxy tab */}
|
||||||
@@ -521,7 +558,7 @@ export default function OIDCClientsPage() {
|
|||||||
<DialogContent className="sm:max-w-lg">
|
<DialogContent className="sm:max-w-lg">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Edit OIDC Client</DialogTitle>
|
<DialogTitle>Edit OIDC Client</DialogTitle>
|
||||||
<DialogDescription>Update the client name and redirect URIs.</DialogDescription>
|
<DialogDescription>Update the client name, redirect URIs, and CORS origins.</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="space-y-4 pt-2">
|
<div className="space-y-4 pt-2">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -544,6 +581,20 @@ export default function OIDCClientsPage() {
|
|||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground">One URI per line</p>
|
<p className="text-xs text-muted-foreground">One URI per line</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="editCors">Allowed CORS origins</Label>
|
||||||
|
<Textarea
|
||||||
|
id="editCors"
|
||||||
|
value={editCors}
|
||||||
|
onChange={(e) => setEditCors(e.target.value)}
|
||||||
|
placeholder={"https://myapp.example.com\nhttps://staging.myapp.example.com"}
|
||||||
|
className="min-h-[60px] font-mono text-sm"
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
One origin per line. Leave empty to use the server default.
|
||||||
|
Use <code className="bg-muted px-1 rounded">+</code> to auto-derive from redirect URIs, or <code className="bg-muted px-1 rounded">*</code> to allow any origin.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
{editingClient && (
|
{editingClient && (
|
||||||
<div className="rounded-md bg-muted/50 border px-3 py-2 space-y-1">
|
<div className="rounded-md bg-muted/50 border px-3 py-2 space-y-1">
|
||||||
<p className="text-xs text-muted-foreground font-medium">Client ID (read-only)</p>
|
<p className="text-xs text-muted-foreground font-medium">Client ID (read-only)</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user