@@ -1,12 +1,13 @@
|
|||||||
import { RouterProvider } from "react-router";
|
import { RouterProvider } from "react-router";
|
||||||
import { router } from "./routes";
|
import { router } from "./routes";
|
||||||
import { Toasts } from "@probo/ui";
|
import { Toasts, ConfirmDialog } from "@probo/ui";
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<RouterProvider router={router} />
|
<RouterProvider router={router} />
|
||||||
<Toasts />
|
<Toasts />
|
||||||
|
<ConfirmDialog />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import {
|
|||||||
Avatar,
|
Avatar,
|
||||||
IconPeopleAdd,
|
IconPeopleAdd,
|
||||||
Badge,
|
Badge,
|
||||||
|
IconKey,
|
||||||
IconLock,
|
IconLock,
|
||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
import { useTranslate } from "@probo/i18n";
|
import { useTranslate } from "@probo/i18n";
|
||||||
@@ -227,6 +228,11 @@ function UserDropdown({ organizationId }: { organizationId: string }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<UserDropdownRoot fullName={user.fullName} email={user.email}>
|
<UserDropdownRoot fullName={user.fullName} email={user.email}>
|
||||||
|
<UserDropdownItem
|
||||||
|
to="/api-keys"
|
||||||
|
icon={IconKey}
|
||||||
|
label={__("API Keys")}
|
||||||
|
/>
|
||||||
<UserDropdownItem
|
<UserDropdownItem
|
||||||
to="mailto:support@getprobo.com"
|
to="mailto:support@getprobo.com"
|
||||||
icon={IconCircleQuestionmark}
|
icon={IconCircleQuestionmark}
|
||||||
|
|||||||
799
apps/console/src/pages/APIKeysPage.tsx
Normal file
799
apps/console/src/pages/APIKeysPage.tsx
Normal file
@@ -0,0 +1,799 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogFooter,
|
||||||
|
Field,
|
||||||
|
IconTrashCan,
|
||||||
|
IconPlusLarge,
|
||||||
|
IconPencil,
|
||||||
|
IconSquareBehindSquare2,
|
||||||
|
Label,
|
||||||
|
Select,
|
||||||
|
Badge,
|
||||||
|
useConfirm,
|
||||||
|
useDialogRef,
|
||||||
|
Option,
|
||||||
|
useToast,
|
||||||
|
Checkbox,
|
||||||
|
Table,
|
||||||
|
Thead,
|
||||||
|
Tbody,
|
||||||
|
Tr,
|
||||||
|
Th,
|
||||||
|
Td,
|
||||||
|
Input,
|
||||||
|
} from "@probo/ui";
|
||||||
|
import { useTranslate } from "@probo/i18n";
|
||||||
|
import { formatDate } from "@probo/helpers";
|
||||||
|
import { useFormWithSchema } from "/hooks/useFormWithSchema";
|
||||||
|
import { Controller } from "react-hook-form";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { UnAuthenticatedError } from "/providers/RelayProviders";
|
||||||
|
|
||||||
|
interface APIKey {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
expiresAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
organizations: APIKeyOrganization[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface APIKeyOrganization {
|
||||||
|
organizationId: string;
|
||||||
|
organizationName: string;
|
||||||
|
role: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Organization {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
authStatus: "authenticated" | "unauthenticated" | "expired";
|
||||||
|
}
|
||||||
|
|
||||||
|
const createSchema = z.object({
|
||||||
|
name: z.string().min(1, "Name is required"),
|
||||||
|
expiresIn: z.enum(["1month", "3months", "6months", "1year"]),
|
||||||
|
organizations: z.array(z.object({
|
||||||
|
organizationId: z.string(),
|
||||||
|
role: z.string(),
|
||||||
|
})).min(1, "At least one organization is required"),
|
||||||
|
});
|
||||||
|
|
||||||
|
type CreateFormData = z.infer<typeof createSchema>;
|
||||||
|
|
||||||
|
export default function APIKeysPage() {
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
const { toast } = useToast();
|
||||||
|
const confirm = useConfirm();
|
||||||
|
const dialogRef = useDialogRef();
|
||||||
|
const editDialogRef = useDialogRef();
|
||||||
|
const keyDialogRef = useDialogRef();
|
||||||
|
const [currentKey, setCurrentKey] = useState<string | null>(null);
|
||||||
|
const [isLoadingKey, setIsLoadingKey] = useState(false);
|
||||||
|
const [apiKeys, setApiKeys] = useState<APIKey[]>([]);
|
||||||
|
const [organizations, setOrganizations] = useState<Organization[]>([]);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [isCreating, setIsCreating] = useState(false);
|
||||||
|
const [isUpdating, setIsUpdating] = useState(false);
|
||||||
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
|
const [selectedOrganizations, setSelectedOrganizations] = useState<string[]>([]);
|
||||||
|
const [organizationRoles, setOrganizationRoles] = useState<Record<string, string>>({});
|
||||||
|
const [editingAPIKey, setEditingAPIKey] = useState<APIKey | null>(null);
|
||||||
|
const [editingName, setEditingName] = useState<string>("");
|
||||||
|
const [error, setError] = useState<Error | null>(null);
|
||||||
|
|
||||||
|
const { formState, handleSubmit, reset, control, setValue, register } = useFormWithSchema(createSchema, {
|
||||||
|
defaultValues: {
|
||||||
|
name: new Date().toISOString().split('T')[0],
|
||||||
|
expiresIn: "1month",
|
||||||
|
organizations: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchAPIKeys = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/connect/api-keys', { credentials: 'include' });
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch API keys');
|
||||||
|
}
|
||||||
|
const data: { apiKeys: APIKey[] } = await response.json();
|
||||||
|
setApiKeys(data.apiKeys);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to fetch API keys:', err);
|
||||||
|
toast({
|
||||||
|
title: __("Error"),
|
||||||
|
description: __("Failed to load API keys"),
|
||||||
|
variant: "error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchData = async () => {
|
||||||
|
try {
|
||||||
|
const [apiKeysResponse, organizationsResponse] = await Promise.all([
|
||||||
|
fetch('/connect/api-keys', { credentials: 'include' }),
|
||||||
|
fetch('/connect/organizations', { credentials: 'include' }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (apiKeysResponse.status === 401 || organizationsResponse.status === 401) {
|
||||||
|
setError(new UnAuthenticatedError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!apiKeysResponse.ok) {
|
||||||
|
throw new Error('Failed to fetch API keys');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!organizationsResponse.ok) {
|
||||||
|
throw new Error('Failed to fetch organizations');
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiKeysData: { apiKeys: APIKey[] } = await apiKeysResponse.json();
|
||||||
|
const orgsData: { organizations: Organization[] } = await organizationsResponse.json();
|
||||||
|
|
||||||
|
const authenticatedOrgs = orgsData.organizations.filter(org => org.authStatus === "authenticated");
|
||||||
|
|
||||||
|
setApiKeys(apiKeysData.apiKeys);
|
||||||
|
setOrganizations(authenticatedOrgs);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to fetch data:', err);
|
||||||
|
toast({
|
||||||
|
title: __("Error"),
|
||||||
|
description: __("Failed to load data"),
|
||||||
|
variant: "error",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchData();
|
||||||
|
}, [__, toast]);
|
||||||
|
|
||||||
|
const handleCreate = async (formData: CreateFormData) => {
|
||||||
|
const now = new Date();
|
||||||
|
const expiresAt = new Date(now);
|
||||||
|
|
||||||
|
switch (formData.expiresIn) {
|
||||||
|
case "1month":
|
||||||
|
expiresAt.setMonth(now.getMonth() + 1);
|
||||||
|
break;
|
||||||
|
case "3months":
|
||||||
|
expiresAt.setMonth(now.getMonth() + 3);
|
||||||
|
break;
|
||||||
|
case "6months":
|
||||||
|
expiresAt.setMonth(now.getMonth() + 6);
|
||||||
|
break;
|
||||||
|
case "1year":
|
||||||
|
expiresAt.setFullYear(now.getFullYear() + 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsCreating(true);
|
||||||
|
try {
|
||||||
|
const response = await fetch('/connect/api-keys', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: formData.name,
|
||||||
|
expiresAt: expiresAt.toISOString(),
|
||||||
|
organizations: formData.organizations,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to create API key');
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: { apiKey: APIKey; key: string } = await response.json();
|
||||||
|
|
||||||
|
await fetchAPIKeys();
|
||||||
|
dialogRef.current?.close();
|
||||||
|
reset();
|
||||||
|
setSelectedOrganizations([]);
|
||||||
|
setOrganizationRoles({});
|
||||||
|
setCurrentKey(data.key);
|
||||||
|
keyDialogRef.current?.open();
|
||||||
|
toast({
|
||||||
|
title: __("Success"),
|
||||||
|
description: __("API Key created successfully"),
|
||||||
|
variant: "success",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
toast({
|
||||||
|
title: __("Error"),
|
||||||
|
description: (error as Error).message,
|
||||||
|
variant: "error",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsCreating(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEdit = (apiKey: APIKey) => {
|
||||||
|
setEditingAPIKey(apiKey);
|
||||||
|
setEditingName(apiKey.name);
|
||||||
|
const orgIds = apiKey.organizations.map(org => org.organizationId);
|
||||||
|
const roles: Record<string, string> = {};
|
||||||
|
apiKey.organizations.forEach(org => {
|
||||||
|
roles[org.organizationId] = org.role;
|
||||||
|
});
|
||||||
|
setSelectedOrganizations(orgIds);
|
||||||
|
setOrganizationRoles(roles);
|
||||||
|
editDialogRef.current?.open();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdate = async () => {
|
||||||
|
if (!editingAPIKey) return;
|
||||||
|
|
||||||
|
setIsUpdating(true);
|
||||||
|
try {
|
||||||
|
const response = await fetch('/connect/api-keys', {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
body: JSON.stringify({
|
||||||
|
id: editingAPIKey.id,
|
||||||
|
name: editingName,
|
||||||
|
organizations: selectedOrganizations.map(id => ({
|
||||||
|
organizationId: id,
|
||||||
|
role: organizationRoles[id] || "FULL",
|
||||||
|
})),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to update API key');
|
||||||
|
}
|
||||||
|
|
||||||
|
await fetchAPIKeys();
|
||||||
|
editDialogRef.current?.close();
|
||||||
|
setEditingAPIKey(null);
|
||||||
|
setEditingName("");
|
||||||
|
setSelectedOrganizations([]);
|
||||||
|
setOrganizationRoles({});
|
||||||
|
toast({
|
||||||
|
title: __("Success"),
|
||||||
|
description: __("API Key updated successfully"),
|
||||||
|
variant: "success",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
toast({
|
||||||
|
title: __("Error"),
|
||||||
|
description: (error as Error).message,
|
||||||
|
variant: "error",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsUpdating(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = (id: string, name: string) => {
|
||||||
|
confirm(
|
||||||
|
async () => {
|
||||||
|
setIsDeleting(true);
|
||||||
|
try {
|
||||||
|
const response = await fetch('/connect/api-keys', {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
body: JSON.stringify({ id }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to delete API key');
|
||||||
|
}
|
||||||
|
|
||||||
|
setApiKeys(apiKeys.filter(key => key.id !== id));
|
||||||
|
toast({
|
||||||
|
title: __("Success"),
|
||||||
|
description: __("API Key deleted successfully"),
|
||||||
|
variant: "success",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
toast({
|
||||||
|
title: __("Error"),
|
||||||
|
description: (error as Error).message,
|
||||||
|
variant: "error",
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
setIsDeleting(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: __(`Are you sure you want to delete the API key "${name}"? This action cannot be undone.`),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleShowToken = async (id: string) => {
|
||||||
|
setIsLoadingKey(true);
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/connect/api-keys/${id}`, {
|
||||||
|
credentials: 'include',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to load API key');
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: { key: string } = await response.json();
|
||||||
|
setCurrentKey(data.key);
|
||||||
|
keyDialogRef.current?.open();
|
||||||
|
} catch (error) {
|
||||||
|
toast({
|
||||||
|
title: __("Error"),
|
||||||
|
description: __("Failed to load API key"),
|
||||||
|
variant: "error",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsLoadingKey(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const copyToClipboard = async (text: string) => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
toast({
|
||||||
|
title: __("Success"),
|
||||||
|
description: __("API key copied to clipboard"),
|
||||||
|
variant: "success",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
toast({
|
||||||
|
title: __("Error"),
|
||||||
|
description: __("Failed to copy to clipboard"),
|
||||||
|
variant: "error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isExpired = (expiresAt: string) => {
|
||||||
|
return new Date(expiresAt) < new Date();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="space-y-6 w-full py-6">
|
||||||
|
<h1 className="text-3xl font-bold text-center">{__("API Keys")}</h1>
|
||||||
|
<Card padded>
|
||||||
|
<div className="text-center py-8">
|
||||||
|
<p className="text-txt-tertiary">{__("Loading...")}</p>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6 w-full py-6">
|
||||||
|
<h1 className="text-3xl font-bold text-center">{__("API Keys")}</h1>
|
||||||
|
|
||||||
|
<div className="space-y-4 w-full">
|
||||||
|
{apiKeys.length === 0 ? (
|
||||||
|
<Card padded>
|
||||||
|
<div className="text-center py-8">
|
||||||
|
<p className="text-txt-tertiary mb-4">
|
||||||
|
{__("No API keys yet. Create one to get started.")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
) : (
|
||||||
|
apiKeys.map((apiKey) => {
|
||||||
|
const expired = isExpired(apiKey.expiresAt);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card key={apiKey.id} padded className="w-full">
|
||||||
|
<div className="flex items-start justify-between gap-4">
|
||||||
|
<div className="flex-1 space-y-2">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<h3 className="text-lg font-semibold">{apiKey.name}</h3>
|
||||||
|
{expired ? (
|
||||||
|
<Badge variant="danger">{__("Expired")}</Badge>
|
||||||
|
) : (
|
||||||
|
<Badge variant="success">{__("Active")}</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-4 text-sm text-txt-secondary">
|
||||||
|
<span>
|
||||||
|
{__("Created")}: {formatDate(apiKey.createdAt)}
|
||||||
|
</span>
|
||||||
|
<span>•</span>
|
||||||
|
<span>
|
||||||
|
{__("Expires")}: {formatDate(apiKey.expiresAt)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{apiKey.organizations.length > 0 && (
|
||||||
|
<div className="flex flex-wrap gap-2 mt-2">
|
||||||
|
{apiKey.organizations.map((org) => (
|
||||||
|
<Badge key={org.organizationId} variant="neutral">
|
||||||
|
{org.organizationName} ({org.role})
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => handleShowToken(apiKey.id)}
|
||||||
|
disabled={isLoadingKey}
|
||||||
|
title={__("Show Token")}
|
||||||
|
>
|
||||||
|
{__("Show")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => handleEdit(apiKey)}
|
||||||
|
disabled={isLoadingKey}
|
||||||
|
icon={IconPencil}
|
||||||
|
title={__("Edit")}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="danger"
|
||||||
|
onClick={() => handleDelete(apiKey.id, apiKey.name)}
|
||||||
|
disabled={isDeleting}
|
||||||
|
icon={IconTrashCan}
|
||||||
|
title={__("Delete")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Card padded>
|
||||||
|
<h2 className="text-xl font-semibold mb-1">
|
||||||
|
{__("Create an API key")}
|
||||||
|
</h2>
|
||||||
|
<p className="text-txt-tertiary mb-4">
|
||||||
|
{__("Generate a new API key for programmatic access to your organization")}
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
onClick={() => dialogRef.current?.open()}
|
||||||
|
variant="quaternary"
|
||||||
|
icon={IconPlusLarge}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
{__("Create API Key")}
|
||||||
|
</Button>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Dialog ref={dialogRef} title={__("Create API Key")}>
|
||||||
|
<form onSubmit={handleSubmit(handleCreate)}>
|
||||||
|
<DialogContent padded className="space-y-4">
|
||||||
|
<Field error={formState.errors.name?.message}>
|
||||||
|
<Label>{__("Name")}</Label>
|
||||||
|
<Input
|
||||||
|
{...register("name")}
|
||||||
|
placeholder={__("e.g., Production API Key")}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<Field error={formState.errors.expiresIn?.message}>
|
||||||
|
<Label>{__("Expires In")}</Label>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="expiresIn"
|
||||||
|
render={({ field }) => (
|
||||||
|
<Select
|
||||||
|
{...field}
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
value={field.value}
|
||||||
|
>
|
||||||
|
<Option value="1month">{__("1 Month")}</Option>
|
||||||
|
<Option value="3months">{__("3 Months")}</Option>
|
||||||
|
<Option value="6months">{__("6 Months")}</Option>
|
||||||
|
<Option value="1year">{__("1 Year")}</Option>
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<Field error={formState.errors.organizations?.message}>
|
||||||
|
<div className="flex justify-between items-center mb-4">
|
||||||
|
<h4 className="font-medium text-txt-primary">
|
||||||
|
{__("Organizations")}
|
||||||
|
</h4>
|
||||||
|
{organizations.length > 0 && (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="tertiary"
|
||||||
|
onClick={() => {
|
||||||
|
const allSelected = selectedOrganizations.length === organizations.length;
|
||||||
|
if (allSelected) {
|
||||||
|
setSelectedOrganizations([]);
|
||||||
|
setOrganizationRoles({});
|
||||||
|
setValue("organizations", []);
|
||||||
|
} else {
|
||||||
|
const allOrgIds = organizations.map(org => org.id);
|
||||||
|
const newRoles: Record<string, string> = {};
|
||||||
|
allOrgIds.forEach(id => {
|
||||||
|
newRoles[id] = organizationRoles[id] || "FULL";
|
||||||
|
});
|
||||||
|
setSelectedOrganizations(allOrgIds);
|
||||||
|
setOrganizationRoles(newRoles);
|
||||||
|
setValue(
|
||||||
|
"organizations",
|
||||||
|
allOrgIds.map(id => ({
|
||||||
|
organizationId: id,
|
||||||
|
role: newRoles[id]
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="text-xs h-7 min-h-7"
|
||||||
|
>
|
||||||
|
{selectedOrganizations.length === organizations.length ? __("Clear All") : __("Select All")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{organizations.length === 0 ? (
|
||||||
|
<div className="text-center text-txt-tertiary py-8">
|
||||||
|
{__("No organizations available")}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="bg-bg-secondary rounded-lg overflow-hidden">
|
||||||
|
<Table>
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<Th>{__("Name")}</Th>
|
||||||
|
<Th width={180}>{__("Role")}</Th>
|
||||||
|
<Th width={100}>
|
||||||
|
<div className="flex justify-end">
|
||||||
|
{__("Access")}
|
||||||
|
</div>
|
||||||
|
</Th>
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
{organizations.map((org) => (
|
||||||
|
<Tr key={org.id}>
|
||||||
|
<Td>
|
||||||
|
<div className="font-medium text-txt-primary">
|
||||||
|
{org.name}
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<div className="min-h-[36px] flex items-center">
|
||||||
|
{selectedOrganizations.includes(org.id) ? (
|
||||||
|
<Select
|
||||||
|
value={organizationRoles[org.id] || "FULL"}
|
||||||
|
onValueChange={(role) => {
|
||||||
|
const newRoles = { ...organizationRoles, [org.id]: role };
|
||||||
|
setOrganizationRoles(newRoles);
|
||||||
|
setValue(
|
||||||
|
"organizations",
|
||||||
|
selectedOrganizations.map(id => ({
|
||||||
|
organizationId: id,
|
||||||
|
role: newRoles[id] || "FULL"
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Option value="FULL">{__("Full")}</Option>
|
||||||
|
</Select>
|
||||||
|
) : (
|
||||||
|
<span className="text-txt-tertiary">—</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Checkbox
|
||||||
|
checked={selectedOrganizations.includes(org.id)}
|
||||||
|
onChange={(checked: boolean) => {
|
||||||
|
let newSelected: string[];
|
||||||
|
const newRoles = { ...organizationRoles };
|
||||||
|
if (checked) {
|
||||||
|
newSelected = [...selectedOrganizations, org.id];
|
||||||
|
if (!newRoles[org.id]) {
|
||||||
|
newRoles[org.id] = "FULL";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newSelected = selectedOrganizations.filter(id => id !== org.id);
|
||||||
|
delete newRoles[org.id];
|
||||||
|
}
|
||||||
|
setSelectedOrganizations(newSelected);
|
||||||
|
setOrganizationRoles(newRoles);
|
||||||
|
setValue(
|
||||||
|
"organizations",
|
||||||
|
newSelected.map(id => ({
|
||||||
|
organizationId: id,
|
||||||
|
role: newRoles[id] || "FULL"
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
))}
|
||||||
|
</Tbody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button type="submit" disabled={isCreating}>
|
||||||
|
{isCreating ? __("Creating...") : __("Create")}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</form>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<Dialog ref={editDialogRef} title={__("Edit API Key")}>
|
||||||
|
<DialogContent padded className="space-y-6">
|
||||||
|
<Field>
|
||||||
|
<Label>{__("Name")}</Label>
|
||||||
|
<Input
|
||||||
|
value={editingName}
|
||||||
|
onChange={(e) => setEditingName(e.target.value)}
|
||||||
|
placeholder={__("API Key Name")}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="flex justify-between items-center mb-4">
|
||||||
|
<h4 className="font-medium text-txt-primary">
|
||||||
|
{__("Organizations")}
|
||||||
|
</h4>
|
||||||
|
{organizations.length > 0 && (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="tertiary"
|
||||||
|
onClick={() => {
|
||||||
|
const allSelected = selectedOrganizations.length === organizations.length;
|
||||||
|
if (allSelected) {
|
||||||
|
setSelectedOrganizations([]);
|
||||||
|
setOrganizationRoles({});
|
||||||
|
} else {
|
||||||
|
const allOrgIds = organizations.map(org => org.id);
|
||||||
|
const newRoles: Record<string, string> = {};
|
||||||
|
allOrgIds.forEach(id => {
|
||||||
|
newRoles[id] = organizationRoles[id] || "FULL";
|
||||||
|
});
|
||||||
|
setSelectedOrganizations(allOrgIds);
|
||||||
|
setOrganizationRoles(newRoles);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="text-xs h-7 min-h-7"
|
||||||
|
>
|
||||||
|
{selectedOrganizations.length === organizations.length ? __("Clear All") : __("Select All")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{organizations.length === 0 ? (
|
||||||
|
<div className="text-center text-txt-tertiary py-8">
|
||||||
|
{__("No organizations available")}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="bg-bg-secondary rounded-lg overflow-hidden">
|
||||||
|
<Table>
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<Th>{__("Name")}</Th>
|
||||||
|
<Th width={180}>{__("Role")}</Th>
|
||||||
|
<Th width={100}>
|
||||||
|
<div className="flex justify-end">
|
||||||
|
{__("Access")}
|
||||||
|
</div>
|
||||||
|
</Th>
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
{organizations.map((org) => (
|
||||||
|
<Tr key={org.id}>
|
||||||
|
<Td>
|
||||||
|
<div className="font-medium text-txt-primary">
|
||||||
|
{org.name}
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<div className="min-h-[36px] flex items-center">
|
||||||
|
{selectedOrganizations.includes(org.id) ? (
|
||||||
|
<Select
|
||||||
|
value={organizationRoles[org.id] || "FULL"}
|
||||||
|
onValueChange={(role) => {
|
||||||
|
const newRoles = { ...organizationRoles, [org.id]: role };
|
||||||
|
setOrganizationRoles(newRoles);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Option value="FULL">{__("Full")}</Option>
|
||||||
|
</Select>
|
||||||
|
) : (
|
||||||
|
<span className="text-txt-tertiary">—</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Checkbox
|
||||||
|
checked={selectedOrganizations.includes(org.id)}
|
||||||
|
onChange={(checked: boolean) => {
|
||||||
|
let newSelected: string[];
|
||||||
|
const newRoles = { ...organizationRoles };
|
||||||
|
if (checked) {
|
||||||
|
newSelected = [...selectedOrganizations, org.id];
|
||||||
|
if (!newRoles[org.id]) {
|
||||||
|
newRoles[org.id] = "FULL";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newSelected = selectedOrganizations.filter(id => id !== org.id);
|
||||||
|
delete newRoles[org.id];
|
||||||
|
}
|
||||||
|
setSelectedOrganizations(newSelected);
|
||||||
|
setOrganizationRoles(newRoles);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
))}
|
||||||
|
</Tbody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button onClick={handleUpdate} disabled={isUpdating || (selectedOrganizations.length === 0 && !editingName.trim())}>
|
||||||
|
{isUpdating ? __("Updating...") : __("Update")}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<Dialog ref={keyDialogRef} title={__("API Key")}>
|
||||||
|
<DialogContent padded className="space-y-4">
|
||||||
|
<p className="text-sm text-txt-tertiary">
|
||||||
|
{__("Please save this API key securely.")}
|
||||||
|
</p>
|
||||||
|
<div className="bg-gray-100 p-4 rounded-lg flex items-center gap-2">
|
||||||
|
<code className="text-sm font-mono break-all flex-1">
|
||||||
|
{currentKey || ""}
|
||||||
|
</code>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (currentKey) {
|
||||||
|
copyToClipboard(currentKey);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
variant="secondary"
|
||||||
|
disabled={!currentKey}
|
||||||
|
title={__("Copy to Clipboard")}
|
||||||
|
>
|
||||||
|
<IconSquareBehindSquare2 size={16} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
keyDialogRef.current?.close();
|
||||||
|
setCurrentKey(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{__("Done")}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -114,6 +114,10 @@ const routes = [
|
|||||||
() => import("./pages/DocumentSigningRequestsPage.tsx")
|
() => import("./pages/DocumentSigningRequestsPage.tsx")
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "api-keys",
|
||||||
|
Component: lazy(() => import("./pages/APIKeysPage")),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
22
packages/ui/src/Atoms/Icons/IconKey.tsx
Normal file
22
packages/ui/src/Atoms/Icons/IconKey.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import type { IconProps } from "./type.ts";
|
||||||
|
|
||||||
|
// https://lucide.dev/icons/key-round
|
||||||
|
export function IconKey({ size = 24, className }: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
className={className}
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path d="M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z" />
|
||||||
|
<circle cx="16.5" cy="7.5" r=".5" fill="currentColor" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ export { IconCrossLargeX } from "./IconCrossLargeX.tsx";
|
|||||||
export { IconUpload } from "./IconUpload.tsx";
|
export { IconUpload } from "./IconUpload.tsx";
|
||||||
export { IconListStack } from "./IconListStack.tsx";
|
export { IconListStack } from "./IconListStack.tsx";
|
||||||
export { IconLock } from "./IconLock.tsx";
|
export { IconLock } from "./IconLock.tsx";
|
||||||
|
export { IconKey } from "./IconKey.tsx";
|
||||||
export { IconCircleCheck1 } from "./IconCircleCheck1.tsx";
|
export { IconCircleCheck1 } from "./IconCircleCheck1.tsx";
|
||||||
export { IconClock } from "./IconClock.tsx";
|
export { IconClock } from "./IconClock.tsx";
|
||||||
export { IconImport2 } from "./IconImport2.tsx";
|
export { IconImport2 } from "./IconImport2.tsx";
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export {
|
|||||||
useDialogRef,
|
useDialogRef,
|
||||||
type DialogRef,
|
type DialogRef,
|
||||||
} from "./Molecules/Dialog/Dialog";
|
} from "./Molecules/Dialog/Dialog";
|
||||||
export { useConfirm } from "./Molecules/Dialog/ConfirmDialog";
|
export { useConfirm, ConfirmDialog } from "./Molecules/Dialog/ConfirmDialog";
|
||||||
export { RiskBadge } from "./Molecules/Badge/RiskBadge";
|
export { RiskBadge } from "./Molecules/Badge/RiskBadge";
|
||||||
export { SeverityBadge } from "./Molecules/Badge/SeverityBadge.tsx";
|
export { SeverityBadge } from "./Molecules/Badge/SeverityBadge.tsx";
|
||||||
export { DocumentVersionBadge } from "./Molecules/Badge/DocumentVersionBadge.tsx";
|
export { DocumentVersionBadge } from "./Molecules/Badge/DocumentVersionBadge.tsx";
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ import (
|
|||||||
"net/mail"
|
"net/mail"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
"go.probo.inc/probo/packages/emails"
|
"go.probo.inc/probo/packages/emails"
|
||||||
"go.probo.inc/probo/pkg/baseurl"
|
"go.probo.inc/probo/pkg/baseurl"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
@@ -31,8 +33,6 @@ import (
|
|||||||
"go.probo.inc/probo/pkg/crypto/passwdhash"
|
"go.probo.inc/probo/pkg/crypto/passwdhash"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/statelesstoken"
|
"go.probo.inc/probo/pkg/statelesstoken"
|
||||||
"github.com/jackc/pgx/v5"
|
|
||||||
"go.gearno.de/kit/pg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -124,11 +124,32 @@ type (
|
|||||||
OrganizationID gid.GID
|
OrganizationID gid.GID
|
||||||
RedirectURL string
|
RedirectURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UserAPIKeyMembershipRequest struct {
|
||||||
|
MembershipID gid.GID
|
||||||
|
Role coredata.APIRole
|
||||||
|
}
|
||||||
|
|
||||||
|
UserAPIKeyOrganizationRequest struct {
|
||||||
|
OrganizationID gid.GID
|
||||||
|
Role coredata.APIRole
|
||||||
|
}
|
||||||
|
|
||||||
|
UserAPIKeyWithMembershipsResponse struct {
|
||||||
|
UserAPIKey *coredata.UserAPIKey
|
||||||
|
Memberships []*coredata.UserAPIKeyMembership
|
||||||
|
}
|
||||||
|
|
||||||
|
UserAPIKeyTokenData struct {
|
||||||
|
ID gid.GID `json:"id"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TokenTypeEmailConfirmation = "email_confirmation"
|
TokenTypeEmailConfirmation = "email_confirmation"
|
||||||
TokenTypePasswordReset = "password_reset"
|
TokenTypePasswordReset = "password_reset"
|
||||||
|
TokenTypeAPIKey = "api_key"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (e ErrInvalidCredentials) Error() string {
|
func (e ErrInvalidCredentials) Error() string {
|
||||||
@@ -1235,3 +1256,388 @@ func verifyDomainOwnership(ctx context.Context, domain, expectedToken string) (b
|
|||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) ValidateAndBuildUserAPIKeyMemberships(
|
||||||
|
ctx context.Context,
|
||||||
|
userID gid.GID,
|
||||||
|
organizationRequests []UserAPIKeyOrganizationRequest,
|
||||||
|
) ([]UserAPIKeyMembershipRequest, error) {
|
||||||
|
memberships := make([]UserAPIKeyMembershipRequest, 0, len(organizationRequests))
|
||||||
|
|
||||||
|
err := s.pg.WithConn(
|
||||||
|
ctx,
|
||||||
|
func(conn pg.Conn) error {
|
||||||
|
for _, org := range organizationRequests {
|
||||||
|
tenantID := org.OrganizationID.TenantID()
|
||||||
|
|
||||||
|
var membership coredata.Membership
|
||||||
|
if err := membership.LoadByUserAndOrg(ctx, conn, coredata.NewScope(tenantID), userID, org.OrganizationID); err != nil {
|
||||||
|
return fmt.Errorf("you do not have access to organization %s", org.OrganizationID)
|
||||||
|
}
|
||||||
|
|
||||||
|
var role coredata.APIRole
|
||||||
|
switch org.Role {
|
||||||
|
case coredata.APIRoleFull:
|
||||||
|
role = coredata.APIRoleFull
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid role: %s", org.Role)
|
||||||
|
}
|
||||||
|
|
||||||
|
memberships = append(memberships, UserAPIKeyMembershipRequest{
|
||||||
|
MembershipID: membership.ID,
|
||||||
|
Role: role,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return memberships, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) CreateUserAPIKey(
|
||||||
|
ctx context.Context,
|
||||||
|
userID gid.GID,
|
||||||
|
name string,
|
||||||
|
expiresAt time.Time,
|
||||||
|
memberships []UserAPIKeyMembershipRequest,
|
||||||
|
) (*coredata.UserAPIKey, string, error) {
|
||||||
|
var userAPIKey *coredata.UserAPIKey
|
||||||
|
var token string
|
||||||
|
|
||||||
|
err := s.pg.WithTx(
|
||||||
|
ctx,
|
||||||
|
func(tx pg.Conn) error {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
userAPIKey = &coredata.UserAPIKey{
|
||||||
|
ID: gid.New(gid.NilTenant, coredata.UserAPIKeyEntityType),
|
||||||
|
UserID: userID,
|
||||||
|
Name: name,
|
||||||
|
ExpiresAt: expiresAt,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := userAPIKey.Insert(ctx, tx); err != nil {
|
||||||
|
return fmt.Errorf("cannot insert user api key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := userAPIKey.LoadByID(ctx, tx, userAPIKey.ID); err != nil {
|
||||||
|
return fmt.Errorf("cannot load user api key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenData := UserAPIKeyTokenData{
|
||||||
|
ID: userAPIKey.ID,
|
||||||
|
CreatedAt: userAPIKey.CreatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
generatedToken, err := statelesstoken.NewDeterministicToken(
|
||||||
|
s.tokenSecret,
|
||||||
|
TokenTypeAPIKey,
|
||||||
|
userAPIKey.ExpiresAt,
|
||||||
|
userAPIKey.CreatedAt,
|
||||||
|
tokenData,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot generate user api key token: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
token = generatedToken
|
||||||
|
|
||||||
|
for _, membership := range memberships {
|
||||||
|
scope := coredata.NewScope(membership.MembershipID.TenantID())
|
||||||
|
userAPIKeyMembership := &coredata.UserAPIKeyMembership{
|
||||||
|
ID: gid.New(membership.MembershipID.TenantID(), coredata.UserAPIKeyMembershipEntityType),
|
||||||
|
UserAPIKeyID: userAPIKey.ID,
|
||||||
|
MembershipID: membership.MembershipID,
|
||||||
|
Role: membership.Role,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := userAPIKeyMembership.Insert(ctx, tx, scope); err != nil {
|
||||||
|
return fmt.Errorf("cannot insert user api key membership: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return userAPIKey, token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ListUserAPIKeys(
|
||||||
|
ctx context.Context,
|
||||||
|
userID gid.GID,
|
||||||
|
) ([]*coredata.UserAPIKey, error) {
|
||||||
|
var userAPIKeys coredata.UserAPIKeys
|
||||||
|
|
||||||
|
err := s.pg.WithConn(
|
||||||
|
ctx,
|
||||||
|
func(conn pg.Conn) error {
|
||||||
|
if err := userAPIKeys.LoadByUserID(ctx, conn, userID); err != nil {
|
||||||
|
return fmt.Errorf("cannot load user api keys: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return userAPIKeys, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ListUserAPIKeysWithMemberships(
|
||||||
|
ctx context.Context,
|
||||||
|
userID gid.GID,
|
||||||
|
tenantIDs []gid.TenantID,
|
||||||
|
) ([]UserAPIKeyWithMembershipsResponse, error) {
|
||||||
|
var userAPIKeys coredata.UserAPIKeys
|
||||||
|
var result []UserAPIKeyWithMembershipsResponse
|
||||||
|
|
||||||
|
err := s.pg.WithConn(
|
||||||
|
ctx,
|
||||||
|
func(conn pg.Conn) error {
|
||||||
|
if err := userAPIKeys.LoadByUserID(ctx, conn, userID); err != nil {
|
||||||
|
return fmt.Errorf("cannot load user api keys: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result = make([]UserAPIKeyWithMembershipsResponse, 0, len(userAPIKeys))
|
||||||
|
for _, userAPIKey := range userAPIKeys {
|
||||||
|
keyWithMemberships := UserAPIKeyWithMembershipsResponse{
|
||||||
|
UserAPIKey: userAPIKey,
|
||||||
|
Memberships: make([]*coredata.UserAPIKeyMembership, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tenantID := range tenantIDs {
|
||||||
|
scope := coredata.NewScope(tenantID)
|
||||||
|
var memberships coredata.UserAPIKeyMemberships
|
||||||
|
if err := memberships.LoadByUserAPIKeyID(ctx, conn, scope, userAPIKey.ID); err != nil {
|
||||||
|
return fmt.Errorf("cannot load user api key memberships: %w", err)
|
||||||
|
}
|
||||||
|
keyWithMemberships.Memberships = append(keyWithMemberships.Memberships, memberships...)
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, keyWithMemberships)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetUserAPIKey(
|
||||||
|
ctx context.Context,
|
||||||
|
userAPIKeyID gid.GID,
|
||||||
|
userID gid.GID,
|
||||||
|
) (string, error) {
|
||||||
|
var token string
|
||||||
|
|
||||||
|
err := s.pg.WithConn(
|
||||||
|
ctx,
|
||||||
|
func(conn pg.Conn) error {
|
||||||
|
userAPIKey := &coredata.UserAPIKey{}
|
||||||
|
if err := userAPIKey.LoadByID(ctx, conn, userAPIKeyID); err != nil {
|
||||||
|
return fmt.Errorf("cannot load user api key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if userAPIKey.UserID != userID {
|
||||||
|
return fmt.Errorf("user api key does not belong to user")
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenData := UserAPIKeyTokenData{
|
||||||
|
ID: userAPIKey.ID,
|
||||||
|
CreatedAt: userAPIKey.CreatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
generatedToken, err := statelesstoken.NewDeterministicToken(
|
||||||
|
s.tokenSecret,
|
||||||
|
TokenTypeAPIKey,
|
||||||
|
userAPIKey.ExpiresAt,
|
||||||
|
userAPIKey.CreatedAt,
|
||||||
|
tokenData,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot generate user api key token: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
token = generatedToken
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateUserAPIKeyMemberships(
|
||||||
|
ctx context.Context,
|
||||||
|
userAPIKeyID gid.GID,
|
||||||
|
userID gid.GID,
|
||||||
|
memberships []UserAPIKeyMembershipRequest,
|
||||||
|
) error {
|
||||||
|
return s.pg.WithTx(
|
||||||
|
ctx,
|
||||||
|
func(tx pg.Conn) error {
|
||||||
|
userAPIKey := &coredata.UserAPIKey{}
|
||||||
|
if err := userAPIKey.LoadByID(ctx, tx, userAPIKeyID); err != nil {
|
||||||
|
return fmt.Errorf("cannot load user api key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if userAPIKey.UserID != userID {
|
||||||
|
return fmt.Errorf("user api key does not belong to user")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := coredata.DeleteAllUserAPIKeyMembershipsByUserAPIKeyID(ctx, tx, userAPIKeyID); err != nil {
|
||||||
|
return fmt.Errorf("cannot delete existing memberships: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
for _, membership := range memberships {
|
||||||
|
scope := coredata.NewScope(membership.MembershipID.TenantID())
|
||||||
|
userAPIKeyMembership := &coredata.UserAPIKeyMembership{
|
||||||
|
ID: gid.New(membership.MembershipID.TenantID(), coredata.UserAPIKeyMembershipEntityType),
|
||||||
|
UserAPIKeyID: userAPIKey.ID,
|
||||||
|
MembershipID: membership.MembershipID,
|
||||||
|
Role: membership.Role,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := userAPIKeyMembership.Insert(ctx, tx, scope); err != nil {
|
||||||
|
return fmt.Errorf("cannot insert user api key membership: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateUserAPIKeyName(
|
||||||
|
ctx context.Context,
|
||||||
|
userAPIKeyID gid.GID,
|
||||||
|
userID gid.GID,
|
||||||
|
name string,
|
||||||
|
) error {
|
||||||
|
return s.pg.WithTx(
|
||||||
|
ctx,
|
||||||
|
func(tx pg.Conn) error {
|
||||||
|
userAPIKey := &coredata.UserAPIKey{}
|
||||||
|
if err := userAPIKey.LoadByID(ctx, tx, userAPIKeyID); err != nil {
|
||||||
|
return fmt.Errorf("cannot load user api key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if userAPIKey.UserID != userID {
|
||||||
|
return fmt.Errorf("user api key does not belong to user")
|
||||||
|
}
|
||||||
|
|
||||||
|
userAPIKey.Name = name
|
||||||
|
userAPIKey.UpdatedAt = time.Now()
|
||||||
|
|
||||||
|
if err := userAPIKey.Update(ctx, tx); err != nil {
|
||||||
|
return fmt.Errorf("cannot update user api key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) DeleteUserAPIKey(
|
||||||
|
ctx context.Context,
|
||||||
|
userAPIKeyID gid.GID,
|
||||||
|
userID gid.GID,
|
||||||
|
) error {
|
||||||
|
return s.pg.WithTx(
|
||||||
|
ctx,
|
||||||
|
func(tx pg.Conn) error {
|
||||||
|
userAPIKey := &coredata.UserAPIKey{}
|
||||||
|
if err := userAPIKey.LoadByID(ctx, tx, userAPIKeyID); err != nil {
|
||||||
|
return fmt.Errorf("cannot load user api key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if userAPIKey.UserID != userID {
|
||||||
|
return fmt.Errorf("user api key does not belong to user")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := userAPIKey.Delete(ctx, tx); err != nil {
|
||||||
|
return fmt.Errorf("cannot delete user api key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ValidateUserAPIKey(
|
||||||
|
ctx context.Context,
|
||||||
|
token string,
|
||||||
|
) (*coredata.User, *coredata.UserAPIKey, error) {
|
||||||
|
payload, err := statelesstoken.ValidateToken[UserAPIKeyTokenData](
|
||||||
|
s.tokenSecret,
|
||||||
|
TokenTypeAPIKey,
|
||||||
|
token,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, &ErrInvalidCredentials{message: "invalid user api key"}
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenData := payload.Data
|
||||||
|
|
||||||
|
var user *coredata.User
|
||||||
|
var userAPIKey *coredata.UserAPIKey
|
||||||
|
|
||||||
|
err = s.pg.WithConn(
|
||||||
|
ctx,
|
||||||
|
func(conn pg.Conn) error {
|
||||||
|
userAPIKey = &coredata.UserAPIKey{}
|
||||||
|
if err := userAPIKey.LoadByID(ctx, conn, tokenData.ID); err != nil {
|
||||||
|
var errNotFound *coredata.ErrUserAPIKeyNotFound
|
||||||
|
if errors.As(err, &errNotFound) {
|
||||||
|
return &ErrInvalidCredentials{message: "invalid user api key"}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("cannot load user api key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !userAPIKey.CreatedAt.Equal(tokenData.CreatedAt) {
|
||||||
|
return &ErrInvalidCredentials{message: "invalid user api key"}
|
||||||
|
}
|
||||||
|
|
||||||
|
if time.Now().After(userAPIKey.ExpiresAt) {
|
||||||
|
return &ErrInvalidCredentials{message: "user api key expired"}
|
||||||
|
}
|
||||||
|
|
||||||
|
user = &coredata.User{}
|
||||||
|
if err := user.LoadByID(ctx, conn, userAPIKey.UserID); err != nil {
|
||||||
|
return fmt.Errorf("cannot load user: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, userAPIKey, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
"go.probo.inc/probo/packages/emails"
|
"go.probo.inc/probo/packages/emails"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
"go.probo.inc/probo/pkg/statelesstoken"
|
"go.probo.inc/probo/pkg/statelesstoken"
|
||||||
"go.gearno.de/kit/pg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type TenantAccessError struct {
|
type TenantAccessError struct {
|
||||||
@@ -103,6 +103,26 @@ func (s *Service) GetAllUserOrganizations(
|
|||||||
return organizations, err
|
return organizations, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetAllOrganizationsForUserAPIKeyId(
|
||||||
|
ctx context.Context,
|
||||||
|
userAPIKeyID gid.GID,
|
||||||
|
) (coredata.Organizations, error) {
|
||||||
|
organizations := coredata.Organizations{}
|
||||||
|
|
||||||
|
err := s.pg.WithConn(
|
||||||
|
ctx,
|
||||||
|
func(conn pg.Conn) error {
|
||||||
|
if err := organizations.LoadAllByUserAPIKeyID(ctx, conn, userAPIKeyID); err != nil {
|
||||||
|
return fmt.Errorf("cannot load user api key organizations: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return organizations, err
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) GetUserOrganizations(
|
func (s *Service) GetUserOrganizations(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
userID gid.GID,
|
userID gid.GID,
|
||||||
|
|||||||
180
pkg/coredata/api_key_membership.go
Normal file
180
pkg/coredata/api_key_membership.go
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package coredata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"maps"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
|
"go.probo.inc/probo/pkg/gid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
UserAPIKeyMembership struct {
|
||||||
|
ID gid.GID `db:"id"`
|
||||||
|
UserAPIKeyID gid.GID `db:"auth_user_api_key_id"`
|
||||||
|
MembershipID gid.GID `db:"membership_id"`
|
||||||
|
Role APIRole `db:"role"`
|
||||||
|
OrganizationID gid.GID `db:"organization_id"`
|
||||||
|
OrganizationName string `db:"organization_name"`
|
||||||
|
CreatedAt time.Time `db:"created_at"`
|
||||||
|
UpdatedAt time.Time `db:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
UserAPIKeyMemberships []*UserAPIKeyMembership
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *UserAPIKeyMembership) Insert(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Conn,
|
||||||
|
scope Scoper,
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
INSERT INTO
|
||||||
|
authz_api_keys_memberships (id, tenant_id, auth_user_api_key_id, membership_id, role, created_at, updated_at)
|
||||||
|
VALUES (
|
||||||
|
@id,
|
||||||
|
@tenant_id,
|
||||||
|
@auth_user_api_key_id,
|
||||||
|
@membership_id,
|
||||||
|
@role,
|
||||||
|
@created_at,
|
||||||
|
@updated_at
|
||||||
|
)
|
||||||
|
`
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{
|
||||||
|
"id": a.ID,
|
||||||
|
"tenant_id": scope.GetTenantID(),
|
||||||
|
"auth_user_api_key_id": a.UserAPIKeyID,
|
||||||
|
"membership_id": a.MembershipID,
|
||||||
|
"role": a.Role,
|
||||||
|
"created_at": a.CreatedAt,
|
||||||
|
"updated_at": a.UpdatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := conn.Exec(ctx, q, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot insert user api key membership: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *UserAPIKeyMemberships) LoadByUserAPIKeyID(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Conn,
|
||||||
|
scope Scoper,
|
||||||
|
userAPIKeyID gid.GID,
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
SELECT
|
||||||
|
akm.id,
|
||||||
|
akm.auth_user_api_key_id,
|
||||||
|
akm.membership_id,
|
||||||
|
akm.role,
|
||||||
|
akm.created_at,
|
||||||
|
akm.updated_at,
|
||||||
|
m.organization_id,
|
||||||
|
o.name as organization_name
|
||||||
|
FROM
|
||||||
|
authz_api_keys_memberships akm
|
||||||
|
JOIN
|
||||||
|
authz_memberships m ON akm.membership_id = m.id
|
||||||
|
JOIN
|
||||||
|
organizations o ON m.organization_id = o.id
|
||||||
|
WHERE
|
||||||
|
akm.auth_user_api_key_id = @auth_user_api_key_id
|
||||||
|
AND m.%s
|
||||||
|
ORDER BY akm.created_at DESC
|
||||||
|
`
|
||||||
|
|
||||||
|
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{
|
||||||
|
"auth_user_api_key_id": userAPIKeyID,
|
||||||
|
}
|
||||||
|
maps.Copy(args, scope.SQLArguments())
|
||||||
|
|
||||||
|
rows, err := conn.Query(ctx, q, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot query user api key memberships: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
memberships, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[UserAPIKeyMembership])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot collect user api key memberships: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*a = memberships
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *UserAPIKeyMembership) Delete(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Conn,
|
||||||
|
scope Scoper,
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
DELETE FROM
|
||||||
|
authz_api_keys_memberships
|
||||||
|
WHERE
|
||||||
|
id = @id
|
||||||
|
AND %s
|
||||||
|
`
|
||||||
|
|
||||||
|
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{
|
||||||
|
"id": a.ID,
|
||||||
|
}
|
||||||
|
maps.Copy(args, scope.SQLArguments())
|
||||||
|
|
||||||
|
_, err := conn.Exec(ctx, q, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot delete user api key membership: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteAllUserAPIKeyMembershipsByUserAPIKeyID(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Conn,
|
||||||
|
userAPIKeyID gid.GID,
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
DELETE FROM
|
||||||
|
authz_api_keys_memberships
|
||||||
|
WHERE
|
||||||
|
auth_user_api_key_id = @auth_user_api_key_id
|
||||||
|
`
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{
|
||||||
|
"auth_user_api_key_id": userAPIKeyID,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := conn.Exec(ctx, q, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot delete user api key memberships: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
54
pkg/coredata/api_role.go
Normal file
54
pkg/coredata/api_role.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package coredata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type APIRole string
|
||||||
|
|
||||||
|
const (
|
||||||
|
APIRoleFull APIRole = "FULL"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r APIRole) String() string {
|
||||||
|
return string(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *APIRole) Scan(value any) error {
|
||||||
|
var s string
|
||||||
|
switch v := value.(type) {
|
||||||
|
case string:
|
||||||
|
s = v
|
||||||
|
case []byte:
|
||||||
|
s = string(v)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported type for APIRole: %T", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch s {
|
||||||
|
case "FULL":
|
||||||
|
*r = APIRoleFull
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid APIRole value: %q", s)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r APIRole) Value() (driver.Value, error) {
|
||||||
|
return r.String(), nil
|
||||||
|
}
|
||||||
@@ -64,4 +64,6 @@ const (
|
|||||||
SlackMessageEntityType
|
SlackMessageEntityType
|
||||||
TrustCenterFileEntityType
|
TrustCenterFileEntityType
|
||||||
SAMLConfigurationEntityType
|
SAMLConfigurationEntityType
|
||||||
|
UserAPIKeyEntityType
|
||||||
|
UserAPIKeyMembershipEntityType
|
||||||
)
|
)
|
||||||
|
|||||||
26
pkg/coredata/migrations/20251031T152957Z.sql
Normal file
26
pkg/coredata/migrations/20251031T152957Z.sql
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
CREATE TYPE authz_api_role AS ENUM ('FULL');
|
||||||
|
|
||||||
|
CREATE TABLE auth_user_api_keys (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
user_id TEXT NOT NULL REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX auth_user_api_keys_user_id_idx ON auth_user_api_keys(user_id);
|
||||||
|
|
||||||
|
CREATE TABLE authz_api_keys_memberships (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
tenant_id TEXT NOT NULL,
|
||||||
|
auth_user_api_key_id TEXT NOT NULL REFERENCES auth_user_api_keys(id) ON UPDATE CASCADE ON DELETE CASCADE,
|
||||||
|
membership_id TEXT NOT NULL REFERENCES authz_memberships(id) ON UPDATE CASCADE ON DELETE CASCADE,
|
||||||
|
role authz_api_role NOT NULL,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||||
|
UNIQUE(auth_user_api_key_id, membership_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX authz_api_keys_memberships_auth_user_api_key_id_idx ON authz_api_keys_memberships(auth_user_api_key_id);
|
||||||
|
CREATE INDEX authz_api_keys_memberships_membership_id_idx ON authz_api_keys_memberships(membership_id);
|
||||||
@@ -21,10 +21,10 @@ import (
|
|||||||
"maps"
|
"maps"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.probo.inc/probo/pkg/gid"
|
|
||||||
"go.probo.inc/probo/pkg/page"
|
|
||||||
"github.com/jackc/pgx/v5"
|
"github.com/jackc/pgx/v5"
|
||||||
"go.gearno.de/kit/pg"
|
"go.gearno.de/kit/pg"
|
||||||
|
"go.probo.inc/probo/pkg/gid"
|
||||||
|
"go.probo.inc/probo/pkg/page"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -161,7 +161,7 @@ FROM
|
|||||||
INNER JOIN
|
INNER JOIN
|
||||||
user_org ON organizations.id = user_org.organization_id
|
user_org ON organizations.id = user_org.organization_id
|
||||||
WHERE
|
WHERE
|
||||||
%S
|
%s
|
||||||
AND %s
|
AND %s
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -237,6 +237,60 @@ ORDER BY
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *Organizations) LoadAllByUserAPIKeyID(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Conn,
|
||||||
|
userAPIKeyID gid.GID,
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
WITH user_api_key_org AS (
|
||||||
|
SELECT
|
||||||
|
am.organization_id
|
||||||
|
FROM
|
||||||
|
authz_api_keys_memberships akm
|
||||||
|
INNER JOIN
|
||||||
|
authz_memberships am ON akm.membership_id = am.id
|
||||||
|
WHERE
|
||||||
|
akm.auth_user_api_key_id = @auth_user_api_key_id
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
tenant_id,
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
website_url,
|
||||||
|
email,
|
||||||
|
headquarter_address,
|
||||||
|
custom_domain_id,
|
||||||
|
logo_file_id,
|
||||||
|
horizontal_logo_file_id,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM
|
||||||
|
organizations
|
||||||
|
INNER JOIN
|
||||||
|
user_api_key_org ON organizations.id = user_api_key_org.organization_id
|
||||||
|
ORDER BY
|
||||||
|
name ASC
|
||||||
|
`
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{"auth_user_api_key_id": userAPIKeyID}
|
||||||
|
|
||||||
|
rows, err := conn.Query(ctx, q, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot query organizations: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
organizations, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[Organization])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot collect organizations: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*o = organizations
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (o *Organization) Insert(
|
func (o *Organization) Insert(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
conn pg.Conn,
|
conn pg.Conn,
|
||||||
|
|||||||
210
pkg/coredata/user_api_key.go
Normal file
210
pkg/coredata/user_api_key.go
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package coredata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
|
"go.probo.inc/probo/pkg/gid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
UserAPIKey struct {
|
||||||
|
ID gid.GID `db:"id"`
|
||||||
|
UserID gid.GID `db:"user_id"`
|
||||||
|
Name string `db:"name"`
|
||||||
|
ExpiresAt time.Time `db:"expires_at"`
|
||||||
|
CreatedAt time.Time `db:"created_at"`
|
||||||
|
UpdatedAt time.Time `db:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
UserAPIKeys []*UserAPIKey
|
||||||
|
|
||||||
|
ErrUserAPIKeyNotFound struct {
|
||||||
|
Identifier string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e ErrUserAPIKeyNotFound) Error() string {
|
||||||
|
return fmt.Sprintf("user api key not found: %q", e.Identifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *UserAPIKey) LoadByID(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Conn,
|
||||||
|
apiKeyID gid.GID,
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
user_id,
|
||||||
|
name,
|
||||||
|
expires_at,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM
|
||||||
|
auth_user_api_keys
|
||||||
|
WHERE
|
||||||
|
id = @api_key_id
|
||||||
|
LIMIT 1;
|
||||||
|
`
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{"api_key_id": apiKeyID}
|
||||||
|
|
||||||
|
rows, err := conn.Query(ctx, q, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot query user api key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
apiKey, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[UserAPIKey])
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
return &ErrUserAPIKeyNotFound{Identifier: apiKeyID.String()}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("cannot collect user api key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*a = apiKey
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *UserAPIKeys) LoadByUserID(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Conn,
|
||||||
|
userID gid.GID,
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
user_id,
|
||||||
|
name,
|
||||||
|
expires_at,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM
|
||||||
|
auth_user_api_keys
|
||||||
|
WHERE
|
||||||
|
user_id = @user_id
|
||||||
|
ORDER BY created_at DESC;
|
||||||
|
`
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{"user_id": userID}
|
||||||
|
|
||||||
|
rows, err := conn.Query(ctx, q, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot query user api keys: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
apiKeys, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[UserAPIKey])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot collect user api keys: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*a = apiKeys
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *UserAPIKey) Insert(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Conn,
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
INSERT INTO
|
||||||
|
auth_user_api_keys (id, user_id, name, expires_at, created_at, updated_at)
|
||||||
|
VALUES (
|
||||||
|
@api_key_id,
|
||||||
|
@user_id,
|
||||||
|
@name,
|
||||||
|
@expires_at,
|
||||||
|
@created_at,
|
||||||
|
@updated_at
|
||||||
|
)
|
||||||
|
`
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{
|
||||||
|
"api_key_id": a.ID,
|
||||||
|
"user_id": a.UserID,
|
||||||
|
"name": a.Name,
|
||||||
|
"expires_at": a.ExpiresAt,
|
||||||
|
"created_at": a.CreatedAt,
|
||||||
|
"updated_at": a.UpdatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := conn.Exec(ctx, q, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot insert user api key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *UserAPIKey) Update(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Conn,
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
UPDATE
|
||||||
|
auth_user_api_keys
|
||||||
|
SET
|
||||||
|
name = @name,
|
||||||
|
expires_at = @expires_at,
|
||||||
|
updated_at = @updated_at
|
||||||
|
WHERE
|
||||||
|
id = @api_key_id
|
||||||
|
`
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{
|
||||||
|
"api_key_id": a.ID,
|
||||||
|
"name": a.Name,
|
||||||
|
"expires_at": a.ExpiresAt,
|
||||||
|
"updated_at": a.UpdatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := conn.Exec(ctx, q, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot update user api key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *UserAPIKey) Delete(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Conn,
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
DELETE FROM
|
||||||
|
auth_user_api_keys
|
||||||
|
WHERE
|
||||||
|
id = @api_key_id
|
||||||
|
`
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{"api_key_id": a.ID}
|
||||||
|
|
||||||
|
_, err := conn.Exec(ctx, q, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot delete user api key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -15,6 +15,10 @@ import (
|
|||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"go.gearno.de/crypto/uuid"
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
|
"go.gearno.de/x/ref"
|
||||||
"go.probo.inc/probo/packages/emails"
|
"go.probo.inc/probo/packages/emails"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/docgen"
|
"go.probo.inc/probo/pkg/docgen"
|
||||||
@@ -23,10 +27,6 @@ import (
|
|||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
"go.probo.inc/probo/pkg/statelesstoken"
|
"go.probo.inc/probo/pkg/statelesstoken"
|
||||||
"go.probo.inc/probo/pkg/watermarkpdf"
|
"go.probo.inc/probo/pkg/watermarkpdf"
|
||||||
"github.com/jackc/pgx/v5"
|
|
||||||
"go.gearno.de/crypto/uuid"
|
|
||||||
"go.gearno.de/kit/pg"
|
|
||||||
"go.gearno.de/x/ref"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
"go.probo.inc/probo/packages/emails"
|
"go.probo.inc/probo/packages/emails"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
"go.probo.inc/probo/pkg/statelesstoken"
|
"go.probo.inc/probo/pkg/statelesstoken"
|
||||||
"go.gearno.de/kit/pg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
|||||||
@@ -20,16 +20,16 @@ call_argument_directives_with_null: true
|
|||||||
models:
|
models:
|
||||||
ID:
|
ID:
|
||||||
model:
|
model:
|
||||||
- "github.com/getprobo/probo/pkg/server/graphql/types/gid.GIDScalar"
|
- "go.probo.inc/probo/pkg/server/gqlutils/types/gid.GIDScalar"
|
||||||
Datetime:
|
Datetime:
|
||||||
model:
|
model:
|
||||||
- "github.com/99designs/gqlgen/graphql.Time"
|
- "github.com/99designs/gqlgen/graphql.Time"
|
||||||
CursorKey:
|
CursorKey:
|
||||||
model:
|
model:
|
||||||
- "github.com/getprobo/probo/pkg/server/graphql/types/cursor.CursorKeyScalar"
|
- "go.probo.inc/probo/pkg/server/gqlutils/types/cursor.CursorKeyScalar"
|
||||||
Duration:
|
Duration:
|
||||||
model:
|
model:
|
||||||
- "github.com/99designs/gqlgen/graphql.Duration"
|
- "github.com/99designs/gqlgen/graphql.Duration"
|
||||||
BigInt:
|
BigInt:
|
||||||
model:
|
model:
|
||||||
- "github.com/getprobo/probo/pkg/server/graphql/types/bigint.BigIntScalar"
|
- "go.probo.inc/probo/pkg/server/gqlutils/types/bigint.BigIntScalar"
|
||||||
|
|||||||
@@ -30,6 +30,11 @@ import (
|
|||||||
"github.com/99designs/gqlgen/graphql/handler/extension"
|
"github.com/99designs/gqlgen/graphql/handler/extension"
|
||||||
"github.com/99designs/gqlgen/graphql/handler/transport"
|
"github.com/99designs/gqlgen/graphql/handler/transport"
|
||||||
"github.com/99designs/gqlgen/graphql/playground"
|
"github.com/99designs/gqlgen/graphql/playground"
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||||
|
"go.gearno.de/crypto/uuid"
|
||||||
|
"go.gearno.de/kit/httpserver"
|
||||||
|
"go.gearno.de/kit/log"
|
||||||
"go.probo.inc/probo/pkg/auth"
|
"go.probo.inc/probo/pkg/auth"
|
||||||
"go.probo.inc/probo/pkg/authz"
|
"go.probo.inc/probo/pkg/authz"
|
||||||
"go.probo.inc/probo/pkg/connector"
|
"go.probo.inc/probo/pkg/connector"
|
||||||
@@ -38,14 +43,9 @@ import (
|
|||||||
"go.probo.inc/probo/pkg/probo"
|
"go.probo.inc/probo/pkg/probo"
|
||||||
"go.probo.inc/probo/pkg/saferedirect"
|
"go.probo.inc/probo/pkg/saferedirect"
|
||||||
"go.probo.inc/probo/pkg/server/api/console/v1/schema"
|
"go.probo.inc/probo/pkg/server/api/console/v1/schema"
|
||||||
gqlutils "go.probo.inc/probo/pkg/server/graphql"
|
"go.probo.inc/probo/pkg/server/gqlutils"
|
||||||
"go.probo.inc/probo/pkg/server/session"
|
"go.probo.inc/probo/pkg/server/session"
|
||||||
"go.probo.inc/probo/pkg/statelesstoken"
|
"go.probo.inc/probo/pkg/statelesstoken"
|
||||||
"github.com/go-chi/chi/v5"
|
|
||||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
|
||||||
"go.gearno.de/crypto/uuid"
|
|
||||||
"go.gearno.de/kit/httpserver"
|
|
||||||
"go.gearno.de/kit/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -77,6 +77,7 @@ var (
|
|||||||
sessionContextKey = &ctxKey{name: "session"}
|
sessionContextKey = &ctxKey{name: "session"}
|
||||||
userContextKey = &ctxKey{name: "user"}
|
userContextKey = &ctxKey{name: "user"}
|
||||||
userTenantContextKey = &ctxKey{name: "user_tenants"}
|
userTenantContextKey = &ctxKey{name: "user_tenants"}
|
||||||
|
userAPIKeyContextKey = &ctxKey{name: "user_api_key"}
|
||||||
)
|
)
|
||||||
|
|
||||||
func SessionFromContext(ctx context.Context) *coredata.Session {
|
func SessionFromContext(ctx context.Context) *coredata.Session {
|
||||||
@@ -89,6 +90,11 @@ func UserFromContext(ctx context.Context) *coredata.User {
|
|||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UserAPIKeyFromContext(ctx context.Context) *coredata.UserAPIKey {
|
||||||
|
userAPIKey, _ := ctx.Value(userAPIKeyContextKey).(*coredata.UserAPIKey)
|
||||||
|
return userAPIKey
|
||||||
|
}
|
||||||
|
|
||||||
func NewMux(
|
func NewMux(
|
||||||
logger *log.Logger,
|
logger *log.Logger,
|
||||||
proboSvc *probo.Service,
|
proboSvc *probo.Service,
|
||||||
@@ -357,6 +363,11 @@ func WithSession(authSvc *auth.Service, authzSvc *authz.Service, authCfg AuthCon
|
|||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
|
if authCtx := tryAPIKeyAuth(ctx, r, authSvc, authzSvc); authCtx != nil {
|
||||||
|
next(w, r.WithContext(authCtx))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
sessionAuthCfg := session.AuthConfig{
|
sessionAuthCfg := session.AuthConfig{
|
||||||
CookieName: authCfg.CookieName,
|
CookieName: authCfg.CookieName,
|
||||||
CookieSecret: authCfg.CookieSecret,
|
CookieSecret: authCfg.CookieSecret,
|
||||||
@@ -402,6 +413,43 @@ func WithSession(authSvc *auth.Service, authzSvc *authz.Service, authCfg AuthCon
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tryAPIKeyAuth(ctx context.Context, r *http.Request, authSvc *auth.Service, authzSvc *authz.Service) context.Context {
|
||||||
|
authHeader := r.Header.Get("Authorization")
|
||||||
|
if authHeader == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(authHeader, "Bearer ") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
apiKeyString := strings.TrimPrefix(authHeader, "Bearer ")
|
||||||
|
|
||||||
|
user, userAPIKey, err := authSvc.ValidateUserAPIKey(ctx, apiKeyString)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
organizations, err := authzSvc.GetAllOrganizationsForUserAPIKeyId(ctx, userAPIKey.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tenantIDs := make([]gid.TenantID, 0, len(organizations))
|
||||||
|
for _, org := range organizations {
|
||||||
|
tenantIDs = append(tenantIDs, org.ID.TenantID())
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = context.WithValue(ctx, userContextKey, user)
|
||||||
|
ctx = context.WithValue(ctx, userAPIKeyContextKey, userAPIKey)
|
||||||
|
ctx = context.WithValue(ctx, userTenantContextKey, &userTenantAccess{
|
||||||
|
tenantIDs: tenantIDs,
|
||||||
|
authErrors: make(map[gid.TenantID]error),
|
||||||
|
})
|
||||||
|
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Resolver) ProboService(ctx context.Context, tenantID gid.TenantID) *probo.TenantService {
|
func (r *Resolver) ProboService(ctx context.Context, tenantID gid.TenantID) *probo.TenantService {
|
||||||
return GetTenantService(ctx, r.proboSvc, tenantID)
|
return GetTenantService(ctx, r.proboSvc, tenantID)
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,7 @@ package types
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
"go.probo.inc/probo/pkg/server/graphql/types/cursor"
|
"go.probo.inc/probo/pkg/server/gqlutils/types/cursor"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewCursor[O page.OrderField](
|
func NewCursor[O page.OrderField](
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ package types
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
"go.probo.inc/probo/pkg/server/graphql/types/pageinfo"
|
"go.probo.inc/probo/pkg/server/gqlutils/types/pageinfo"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewPageInfo[T page.Paginable[O], O page.OrderField](p *page.Page[T, O]) *PageInfo {
|
func NewPageInfo[T page.Paginable[O], O page.OrderField](p *page.Page[T, O]) *PageInfo {
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
pgx "github.com/jackc/pgx/v5"
|
||||||
|
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||||
"go.probo.inc/probo/pkg/auth"
|
"go.probo.inc/probo/pkg/auth"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
@@ -20,9 +22,7 @@ import (
|
|||||||
"go.probo.inc/probo/pkg/probo"
|
"go.probo.inc/probo/pkg/probo"
|
||||||
"go.probo.inc/probo/pkg/server/api/console/v1/schema"
|
"go.probo.inc/probo/pkg/server/api/console/v1/schema"
|
||||||
"go.probo.inc/probo/pkg/server/api/console/v1/types"
|
"go.probo.inc/probo/pkg/server/api/console/v1/types"
|
||||||
"go.probo.inc/probo/pkg/server/graphql"
|
"go.probo.inc/probo/pkg/server/gqlutils"
|
||||||
pgx "github.com/jackc/pgx/v5"
|
|
||||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Owner is the resolver for the owner field.
|
// Owner is the resolver for the owner field.
|
||||||
@@ -33,7 +33,7 @@ func (r *assetResolver) Owner(ctx context.Context, obj *types.Asset) (*types.Peo
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrAssetNotFound
|
var errNotFound *coredata.ErrAssetNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get asset: %w", err))
|
panic(fmt.Errorf("cannot get asset: %w", err))
|
||||||
}
|
}
|
||||||
@@ -42,7 +42,7 @@ func (r *assetResolver) Owner(ctx context.Context, obj *types.Asset) (*types.Peo
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrPeopleNotFound
|
var errNotFound *coredata.ErrPeopleNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get owner: %w", err))
|
panic(fmt.Errorf("cannot get owner: %w", err))
|
||||||
}
|
}
|
||||||
@@ -103,7 +103,7 @@ func (r *assetResolver) Organization(ctx context.Context, obj *types.Asset) (*ty
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrOrganizationNotFound
|
var errNotFound *coredata.ErrOrganizationNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get organization: %w", err))
|
panic(fmt.Errorf("cannot get organization: %w", err))
|
||||||
}
|
}
|
||||||
@@ -140,7 +140,7 @@ func (r *auditResolver) Organization(ctx context.Context, obj *types.Audit) (*ty
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrAuditNotFound
|
var errNotFound *coredata.ErrAuditNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot load audit: %w", err))
|
panic(fmt.Errorf("cannot load audit: %w", err))
|
||||||
}
|
}
|
||||||
@@ -149,7 +149,7 @@ func (r *auditResolver) Organization(ctx context.Context, obj *types.Audit) (*ty
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrOrganizationNotFound
|
var errNotFound *coredata.ErrOrganizationNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot load organization: %w", err))
|
panic(fmt.Errorf("cannot load organization: %w", err))
|
||||||
}
|
}
|
||||||
@@ -165,7 +165,7 @@ func (r *auditResolver) Framework(ctx context.Context, obj *types.Audit) (*types
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrAuditNotFound
|
var errNotFound *coredata.ErrAuditNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot load audit: %w", err))
|
panic(fmt.Errorf("cannot load audit: %w", err))
|
||||||
}
|
}
|
||||||
@@ -174,7 +174,7 @@ func (r *auditResolver) Framework(ctx context.Context, obj *types.Audit) (*types
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrFrameworkNotFound
|
var errNotFound *coredata.ErrFrameworkNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot load framework: %w", err))
|
panic(fmt.Errorf("cannot load framework: %w", err))
|
||||||
}
|
}
|
||||||
@@ -190,7 +190,7 @@ func (r *auditResolver) Report(ctx context.Context, obj *types.Audit) (*types.Re
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrAuditNotFound
|
var errNotFound *coredata.ErrAuditNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot load audit: %w", err))
|
panic(fmt.Errorf("cannot load audit: %w", err))
|
||||||
}
|
}
|
||||||
@@ -277,7 +277,7 @@ func (r *continualImprovementResolver) Organization(ctx context.Context, obj *ty
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrOrganizationNotFound
|
var errNotFound *coredata.ErrOrganizationNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get continual improvement organization: %w", err))
|
panic(fmt.Errorf("cannot get continual improvement organization: %w", err))
|
||||||
}
|
}
|
||||||
@@ -298,7 +298,7 @@ func (r *continualImprovementResolver) Owner(ctx context.Context, obj *types.Con
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrPeopleNotFound
|
var errNotFound *coredata.ErrPeopleNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get continual improvement owner: %w", err))
|
panic(fmt.Errorf("cannot get continual improvement owner: %w", err))
|
||||||
}
|
}
|
||||||
@@ -335,7 +335,7 @@ func (r *controlResolver) Framework(ctx context.Context, obj *types.Control) (*t
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrControlNotFound
|
var errNotFound *coredata.ErrControlNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get control: %w", err))
|
panic(fmt.Errorf("cannot get control: %w", err))
|
||||||
}
|
}
|
||||||
@@ -344,7 +344,7 @@ func (r *controlResolver) Framework(ctx context.Context, obj *types.Control) (*t
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrFrameworkNotFound
|
var errNotFound *coredata.ErrFrameworkNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get framework: %w", err))
|
panic(fmt.Errorf("cannot get framework: %w", err))
|
||||||
}
|
}
|
||||||
@@ -516,7 +516,7 @@ func (r *datumResolver) Owner(ctx context.Context, obj *types.Datum) (*types.Peo
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrPeopleNotFound
|
var errNotFound *coredata.ErrPeopleNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("cannot get owner: %w", err)
|
return nil, fmt.Errorf("cannot get owner: %w", err)
|
||||||
}
|
}
|
||||||
@@ -557,7 +557,7 @@ func (r *datumResolver) Organization(ctx context.Context, obj *types.Datum) (*ty
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrOrganizationNotFound
|
var errNotFound *coredata.ErrOrganizationNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get organization: %w", err))
|
panic(fmt.Errorf("cannot get organization: %w", err))
|
||||||
}
|
}
|
||||||
@@ -594,7 +594,7 @@ func (r *documentResolver) Owner(ctx context.Context, obj *types.Document) (*typ
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrDocumentNotFound
|
var errNotFound *coredata.ErrDocumentNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get document: %w", err))
|
panic(fmt.Errorf("cannot get document: %w", err))
|
||||||
}
|
}
|
||||||
@@ -604,7 +604,7 @@ func (r *documentResolver) Owner(ctx context.Context, obj *types.Document) (*typ
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrPeopleNotFound
|
var errNotFound *coredata.ErrPeopleNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get owner: %w", err))
|
panic(fmt.Errorf("cannot get owner: %w", err))
|
||||||
}
|
}
|
||||||
@@ -620,7 +620,7 @@ func (r *documentResolver) Organization(ctx context.Context, obj *types.Document
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrDocumentNotFound
|
var errNotFound *coredata.ErrDocumentNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get document: %w", err))
|
panic(fmt.Errorf("cannot get document: %w", err))
|
||||||
}
|
}
|
||||||
@@ -629,7 +629,7 @@ func (r *documentResolver) Organization(ctx context.Context, obj *types.Document
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrOrganizationNotFound
|
var errNotFound *coredata.ErrOrganizationNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get organization: %w", err))
|
panic(fmt.Errorf("cannot get organization: %w", err))
|
||||||
}
|
}
|
||||||
@@ -733,7 +733,7 @@ func (r *documentVersionResolver) Document(ctx context.Context, obj *types.Docum
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrDocumentNotFound
|
var errNotFound *coredata.ErrDocumentNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get document: %w", err))
|
panic(fmt.Errorf("cannot get document: %w", err))
|
||||||
}
|
}
|
||||||
@@ -754,7 +754,7 @@ func (r *documentVersionResolver) Owner(ctx context.Context, obj *types.Document
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrPeopleNotFound
|
var errNotFound *coredata.ErrPeopleNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get owner: %w", err))
|
panic(fmt.Errorf("cannot get owner: %w", err))
|
||||||
}
|
}
|
||||||
@@ -823,7 +823,7 @@ func (r *documentVersionSignatureResolver) SignedBy(ctx context.Context, obj *ty
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrPeopleNotFound
|
var errNotFound *coredata.ErrPeopleNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get people: %w", err))
|
panic(fmt.Errorf("cannot get people: %w", err))
|
||||||
}
|
}
|
||||||
@@ -848,7 +848,7 @@ func (r *evidenceResolver) File(ctx context.Context, obj *types.Evidence) (*type
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrFileNotFound
|
var errNotFound *coredata.ErrFileNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot load evidence file: %w", err))
|
panic(fmt.Errorf("cannot load evidence file: %w", err))
|
||||||
}
|
}
|
||||||
@@ -873,7 +873,7 @@ func (r *evidenceResolver) Task(ctx context.Context, obj *types.Evidence) (*type
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrTaskNotFound
|
var errNotFound *coredata.ErrTaskNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot load task: %w", err))
|
panic(fmt.Errorf("cannot load task: %w", err))
|
||||||
}
|
}
|
||||||
@@ -894,7 +894,7 @@ func (r *evidenceResolver) Measure(ctx context.Context, obj *types.Evidence) (*t
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrMeasureNotFound
|
var errNotFound *coredata.ErrMeasureNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot load measure: %w", err))
|
panic(fmt.Errorf("cannot load measure: %w", err))
|
||||||
}
|
}
|
||||||
@@ -944,7 +944,7 @@ func (r *frameworkResolver) Organization(ctx context.Context, obj *types.Framewo
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrFrameworkNotFound
|
var errNotFound *coredata.ErrFrameworkNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot load framework: %w", err))
|
panic(fmt.Errorf("cannot load framework: %w", err))
|
||||||
}
|
}
|
||||||
@@ -953,7 +953,7 @@ func (r *frameworkResolver) Organization(ctx context.Context, obj *types.Framewo
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrOrganizationNotFound
|
var errNotFound *coredata.ErrOrganizationNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot load organization: %w", err))
|
panic(fmt.Errorf("cannot load organization: %w", err))
|
||||||
}
|
}
|
||||||
@@ -1227,11 +1227,11 @@ func (r *mutationResolver) CreateOrganization(ctx context.Context, input types.C
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errAlreadyExists *coredata.ErrOrganizationAlreadyExists
|
var errAlreadyExists *coredata.ErrOrganizationAlreadyExists
|
||||||
if errors.As(err, &errAlreadyExists) {
|
if errors.As(err, &errAlreadyExists) {
|
||||||
return nil, graphql.Conflict(errAlreadyExists)
|
return nil, gqlutils.Conflict(errAlreadyExists)
|
||||||
}
|
}
|
||||||
var errTrustCenterAlreadyExists *coredata.ErrTrustCenterAlreadyExists
|
var errTrustCenterAlreadyExists *coredata.ErrTrustCenterAlreadyExists
|
||||||
if errors.As(err, &errTrustCenterAlreadyExists) {
|
if errors.As(err, &errTrustCenterAlreadyExists) {
|
||||||
return nil, graphql.Conflict(errTrustCenterAlreadyExists)
|
return nil, gqlutils.Conflict(errTrustCenterAlreadyExists)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot create organization: %w", err))
|
panic(fmt.Errorf("cannot create organization: %w", err))
|
||||||
}
|
}
|
||||||
@@ -1260,7 +1260,7 @@ func (r *mutationResolver) CreateOrganization(ctx context.Context, input types.C
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errAlreadyExists *coredata.ErrPeopleAlreadyExists
|
var errAlreadyExists *coredata.ErrPeopleAlreadyExists
|
||||||
if errors.As(err, &errAlreadyExists) {
|
if errors.As(err, &errAlreadyExists) {
|
||||||
return nil, graphql.Conflict(errAlreadyExists)
|
return nil, gqlutils.Conflict(errAlreadyExists)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot create people: %w", err))
|
panic(fmt.Errorf("cannot create people: %w", err))
|
||||||
}
|
}
|
||||||
@@ -1408,7 +1408,7 @@ func (r *mutationResolver) CreateTrustCenterAccess(ctx context.Context, input ty
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errAlreadyExists *coredata.ErrTrustCenterAccessAlreadyExists
|
var errAlreadyExists *coredata.ErrTrustCenterAccessAlreadyExists
|
||||||
if errors.As(err, &errAlreadyExists) {
|
if errors.As(err, &errAlreadyExists) {
|
||||||
return nil, graphql.Conflict(errAlreadyExists)
|
return nil, gqlutils.Conflict(errAlreadyExists)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot create trust center access: %w", err))
|
panic(fmt.Errorf("cannot create trust center access: %w", err))
|
||||||
}
|
}
|
||||||
@@ -1632,7 +1632,7 @@ func (r *mutationResolver) InviteUser(ctx context.Context, input types.InviteUse
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errAlreadyExists *coredata.ErrPeopleAlreadyExists
|
var errAlreadyExists *coredata.ErrPeopleAlreadyExists
|
||||||
if errors.As(err, &errAlreadyExists) {
|
if errors.As(err, &errAlreadyExists) {
|
||||||
return nil, graphql.Conflict(errAlreadyExists)
|
return nil, gqlutils.Conflict(errAlreadyExists)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("cannot create people record: %w", err)
|
return nil, fmt.Errorf("cannot create people record: %w", err)
|
||||||
}
|
}
|
||||||
@@ -1697,7 +1697,7 @@ func (r *mutationResolver) CreatePeople(ctx context.Context, input types.CreateP
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errAlreadyExists *coredata.ErrPeopleAlreadyExists
|
var errAlreadyExists *coredata.ErrPeopleAlreadyExists
|
||||||
if errors.As(err, &errAlreadyExists) {
|
if errors.As(err, &errAlreadyExists) {
|
||||||
return nil, graphql.Conflict(errAlreadyExists)
|
return nil, gqlutils.Conflict(errAlreadyExists)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot create people: %w", err))
|
panic(fmt.Errorf("cannot create people: %w", err))
|
||||||
}
|
}
|
||||||
@@ -1776,7 +1776,7 @@ func (r *mutationResolver) CreateVendor(ctx context.Context, input types.CreateV
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errAlreadyExists *coredata.ErrVendorAlreadyExists
|
var errAlreadyExists *coredata.ErrVendorAlreadyExists
|
||||||
if errors.As(err, &errAlreadyExists) {
|
if errors.As(err, &errAlreadyExists) {
|
||||||
return nil, graphql.Conflict(errAlreadyExists)
|
return nil, gqlutils.Conflict(errAlreadyExists)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("cannot create vendor: %w", err)
|
return nil, fmt.Errorf("cannot create vendor: %w", err)
|
||||||
}
|
}
|
||||||
@@ -2079,7 +2079,7 @@ func (r *mutationResolver) CreateControl(ctx context.Context, input types.Create
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errAlreadyExists *coredata.ErrControlAlreadyExists
|
var errAlreadyExists *coredata.ErrControlAlreadyExists
|
||||||
if errors.As(err, &errAlreadyExists) {
|
if errors.As(err, &errAlreadyExists) {
|
||||||
return nil, graphql.Conflict(errAlreadyExists)
|
return nil, gqlutils.Conflict(errAlreadyExists)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot create control: %w", err))
|
panic(fmt.Errorf("cannot create control: %w", err))
|
||||||
}
|
}
|
||||||
@@ -2105,7 +2105,7 @@ func (r *mutationResolver) UpdateControl(ctx context.Context, input types.Update
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errAlreadyExists *coredata.ErrControlAlreadyExists
|
var errAlreadyExists *coredata.ErrControlAlreadyExists
|
||||||
if errors.As(err, &errAlreadyExists) {
|
if errors.As(err, &errAlreadyExists) {
|
||||||
return nil, graphql.Conflict(errAlreadyExists)
|
return nil, gqlutils.Conflict(errAlreadyExists)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot update control: %w", err))
|
panic(fmt.Errorf("cannot update control: %w", err))
|
||||||
}
|
}
|
||||||
@@ -2142,7 +2142,7 @@ func (r *mutationResolver) CreateMeasure(ctx context.Context, input types.Create
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errAlreadyExists *coredata.ErrMeasureAlreadyExists
|
var errAlreadyExists *coredata.ErrMeasureAlreadyExists
|
||||||
if errors.As(err, &errAlreadyExists) {
|
if errors.As(err, &errAlreadyExists) {
|
||||||
return nil, graphql.Conflict(errAlreadyExists)
|
return nil, gqlutils.Conflict(errAlreadyExists)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot create measure: %w", err))
|
panic(fmt.Errorf("cannot create measure: %w", err))
|
||||||
}
|
}
|
||||||
@@ -2233,7 +2233,7 @@ func (r *mutationResolver) CreateControlDocumentMapping(ctx context.Context, inp
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errMappingExists *coredata.ErrControlDocumentMappingAlreadyExists
|
var errMappingExists *coredata.ErrControlDocumentMappingAlreadyExists
|
||||||
if errors.As(err, &errMappingExists) {
|
if errors.As(err, &errMappingExists) {
|
||||||
return nil, graphql.Conflict(errMappingExists)
|
return nil, gqlutils.Conflict(errMappingExists)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot create control document mapping: %w", err))
|
panic(fmt.Errorf("cannot create control document mapping: %w", err))
|
||||||
}
|
}
|
||||||
@@ -2349,7 +2349,7 @@ func (r *mutationResolver) CreateTask(ctx context.Context, input types.CreateTas
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errAlreadyExists *coredata.ErrTaskAlreadyExists
|
var errAlreadyExists *coredata.ErrTaskAlreadyExists
|
||||||
if errors.As(err, &errAlreadyExists) {
|
if errors.As(err, &errAlreadyExists) {
|
||||||
return nil, graphql.Conflict(errAlreadyExists)
|
return nil, gqlutils.Conflict(errAlreadyExists)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot create task: %w", err))
|
panic(fmt.Errorf("cannot create task: %w", err))
|
||||||
}
|
}
|
||||||
@@ -2445,7 +2445,7 @@ func (r *mutationResolver) CreateRisk(ctx context.Context, input types.CreateRis
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errAlreadyExists *coredata.ErrRiskAlreadyExists
|
var errAlreadyExists *coredata.ErrRiskAlreadyExists
|
||||||
if errors.As(err, &errAlreadyExists) {
|
if errors.As(err, &errAlreadyExists) {
|
||||||
return nil, graphql.Conflict(errAlreadyExists)
|
return nil, gqlutils.Conflict(errAlreadyExists)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot create risk: %w", err))
|
panic(fmt.Errorf("cannot create risk: %w", err))
|
||||||
}
|
}
|
||||||
@@ -2799,7 +2799,7 @@ func (r *mutationResolver) CreateDocument(ctx context.Context, input types.Creat
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errAlreadyExists *coredata.ErrDocumentAlreadyExists
|
var errAlreadyExists *coredata.ErrDocumentAlreadyExists
|
||||||
if errors.As(err, &errAlreadyExists) {
|
if errors.As(err, &errAlreadyExists) {
|
||||||
return nil, graphql.Conflict(errAlreadyExists)
|
return nil, gqlutils.Conflict(errAlreadyExists)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot create document: %w", err))
|
panic(fmt.Errorf("cannot create document: %w", err))
|
||||||
}
|
}
|
||||||
@@ -2858,7 +2858,7 @@ func (r *mutationResolver) PublishDocumentVersion(ctx context.Context, input typ
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNoChanges *coredata.ErrDocumentVersionNoChanges
|
var errNoChanges *coredata.ErrDocumentVersionNoChanges
|
||||||
if errors.As(err, &errNoChanges) {
|
if errors.As(err, &errNoChanges) {
|
||||||
return nil, graphql.Invalid(errNoChanges)
|
return nil, gqlutils.Invalid(errNoChanges)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot publish document version: %w", err))
|
panic(fmt.Errorf("cannot publish document version: %w", err))
|
||||||
}
|
}
|
||||||
@@ -3939,7 +3939,7 @@ func (r *nonconformityResolver) Organization(ctx context.Context, obj *types.Non
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrOrganizationNotFound
|
var errNotFound *coredata.ErrOrganizationNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get nonconformity organization: %w", err))
|
panic(fmt.Errorf("cannot get nonconformity organization: %w", err))
|
||||||
}
|
}
|
||||||
@@ -3960,7 +3960,7 @@ func (r *nonconformityResolver) Audit(ctx context.Context, obj *types.Nonconform
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrAuditNotFound
|
var errNotFound *coredata.ErrAuditNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get nonconformity audit: %w", err))
|
panic(fmt.Errorf("cannot get nonconformity audit: %w", err))
|
||||||
}
|
}
|
||||||
@@ -3981,7 +3981,7 @@ func (r *nonconformityResolver) Owner(ctx context.Context, obj *types.Nonconform
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrPeopleNotFound
|
var errNotFound *coredata.ErrPeopleNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get nonconformity owner: %w", err))
|
panic(fmt.Errorf("cannot get nonconformity owner: %w", err))
|
||||||
}
|
}
|
||||||
@@ -4023,7 +4023,7 @@ func (r *obligationResolver) Organization(ctx context.Context, obj *types.Obliga
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrOrganizationNotFound
|
var errNotFound *coredata.ErrOrganizationNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get obligation organization: %w", err))
|
panic(fmt.Errorf("cannot get obligation organization: %w", err))
|
||||||
}
|
}
|
||||||
@@ -4044,7 +4044,7 @@ func (r *obligationResolver) Owner(ctx context.Context, obj *types.Obligation) (
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrPeopleNotFound
|
var errNotFound *coredata.ErrPeopleNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get obligation owner: %w", err))
|
panic(fmt.Errorf("cannot get obligation owner: %w", err))
|
||||||
}
|
}
|
||||||
@@ -4742,7 +4742,7 @@ func (r *processingActivityResolver) Organization(ctx context.Context, obj *type
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrOrganizationNotFound
|
var errNotFound *coredata.ErrOrganizationNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get organization: %w", err))
|
panic(fmt.Errorf("cannot get organization: %w", err))
|
||||||
}
|
}
|
||||||
@@ -4806,7 +4806,7 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrOrganizationNotFound
|
var errNotFound *coredata.ErrOrganizationNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get organization: %w", err))
|
panic(fmt.Errorf("cannot get organization: %w", err))
|
||||||
}
|
}
|
||||||
@@ -4817,7 +4817,7 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrPeopleNotFound
|
var errNotFound *coredata.ErrPeopleNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get people: %w", err))
|
panic(fmt.Errorf("cannot get people: %w", err))
|
||||||
}
|
}
|
||||||
@@ -4828,7 +4828,7 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrVendorNotFound
|
var errNotFound *coredata.ErrVendorNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get vendor: %w", err))
|
panic(fmt.Errorf("cannot get vendor: %w", err))
|
||||||
}
|
}
|
||||||
@@ -4839,7 +4839,7 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrFrameworkNotFound
|
var errNotFound *coredata.ErrFrameworkNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get framework: %w", err))
|
panic(fmt.Errorf("cannot get framework: %w", err))
|
||||||
}
|
}
|
||||||
@@ -4850,7 +4850,7 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrMeasureNotFound
|
var errNotFound *coredata.ErrMeasureNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get measure: %w", err))
|
panic(fmt.Errorf("cannot get measure: %w", err))
|
||||||
}
|
}
|
||||||
@@ -4861,7 +4861,7 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrTaskNotFound
|
var errNotFound *coredata.ErrTaskNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get task: %w", err))
|
panic(fmt.Errorf("cannot get task: %w", err))
|
||||||
}
|
}
|
||||||
@@ -4879,7 +4879,7 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrDocumentNotFound
|
var errNotFound *coredata.ErrDocumentNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get document: %w", err))
|
panic(fmt.Errorf("cannot get document: %w", err))
|
||||||
}
|
}
|
||||||
@@ -4889,7 +4889,7 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrControlNotFound
|
var errNotFound *coredata.ErrControlNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get control: %w", err))
|
panic(fmt.Errorf("cannot get control: %w", err))
|
||||||
}
|
}
|
||||||
@@ -4900,7 +4900,7 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrRiskNotFound
|
var errNotFound *coredata.ErrRiskNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get risk: %w", err))
|
panic(fmt.Errorf("cannot get risk: %w", err))
|
||||||
}
|
}
|
||||||
@@ -4940,7 +4940,7 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrAssetNotFound
|
var errNotFound *coredata.ErrAssetNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get asset: %w", err))
|
panic(fmt.Errorf("cannot get asset: %w", err))
|
||||||
}
|
}
|
||||||
@@ -4956,7 +4956,7 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrAuditNotFound
|
var errNotFound *coredata.ErrAuditNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get audit: %w", err))
|
panic(fmt.Errorf("cannot get audit: %w", err))
|
||||||
}
|
}
|
||||||
@@ -5021,9 +5021,19 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
|
|||||||
func (r *queryResolver) Viewer(ctx context.Context) (*types.Viewer, error) {
|
func (r *queryResolver) Viewer(ctx context.Context) (*types.Viewer, error) {
|
||||||
user := UserFromContext(ctx)
|
user := UserFromContext(ctx)
|
||||||
session := SessionFromContext(ctx)
|
session := SessionFromContext(ctx)
|
||||||
|
apiKey := UserAPIKeyFromContext(ctx)
|
||||||
|
|
||||||
|
var viewerID gid.GID
|
||||||
|
if session != nil {
|
||||||
|
viewerID = session.ID
|
||||||
|
} else if apiKey != nil {
|
||||||
|
viewerID = apiKey.ID
|
||||||
|
} else {
|
||||||
|
viewerID = user.ID
|
||||||
|
}
|
||||||
|
|
||||||
return &types.Viewer{
|
return &types.Viewer{
|
||||||
ID: session.ID,
|
ID: viewerID,
|
||||||
User: types.NewUser(user),
|
User: types.NewUser(user),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -5060,7 +5070,7 @@ func (r *riskResolver) Owner(ctx context.Context, obj *types.Risk) (*types.Peopl
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrRiskNotFound
|
var errNotFound *coredata.ErrRiskNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get risk: %w", err))
|
panic(fmt.Errorf("cannot get risk: %w", err))
|
||||||
}
|
}
|
||||||
@@ -5073,7 +5083,7 @@ func (r *riskResolver) Owner(ctx context.Context, obj *types.Risk) (*types.Peopl
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrPeopleNotFound
|
var errNotFound *coredata.ErrPeopleNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get owner: %w", err))
|
panic(fmt.Errorf("cannot get owner: %w", err))
|
||||||
}
|
}
|
||||||
@@ -5089,7 +5099,7 @@ func (r *riskResolver) Organization(ctx context.Context, obj *types.Risk) (*type
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrRiskNotFound
|
var errNotFound *coredata.ErrRiskNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get risk: %w", err))
|
panic(fmt.Errorf("cannot get risk: %w", err))
|
||||||
}
|
}
|
||||||
@@ -5098,7 +5108,7 @@ func (r *riskResolver) Organization(ctx context.Context, obj *types.Risk) (*type
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrOrganizationNotFound
|
var errNotFound *coredata.ErrOrganizationNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get organization: %w", err))
|
panic(fmt.Errorf("cannot get organization: %w", err))
|
||||||
}
|
}
|
||||||
@@ -5296,7 +5306,7 @@ func (r *snapshotResolver) Organization(ctx context.Context, obj *types.Snapshot
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrOrganizationNotFound
|
var errNotFound *coredata.ErrOrganizationNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get organization: %w", err))
|
panic(fmt.Errorf("cannot get organization: %w", err))
|
||||||
}
|
}
|
||||||
@@ -5358,7 +5368,7 @@ func (r *taskResolver) AssignedTo(ctx context.Context, obj *types.Task) (*types.
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrTaskNotFound
|
var errNotFound *coredata.ErrTaskNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get task: %w", err))
|
panic(fmt.Errorf("cannot get task: %w", err))
|
||||||
}
|
}
|
||||||
@@ -5371,7 +5381,7 @@ func (r *taskResolver) AssignedTo(ctx context.Context, obj *types.Task) (*types.
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrPeopleNotFound
|
var errNotFound *coredata.ErrPeopleNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get assigned to: %w", err))
|
panic(fmt.Errorf("cannot get assigned to: %w", err))
|
||||||
}
|
}
|
||||||
@@ -5387,7 +5397,7 @@ func (r *taskResolver) Organization(ctx context.Context, obj *types.Task) (*type
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrTaskNotFound
|
var errNotFound *coredata.ErrTaskNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get task: %w", err))
|
panic(fmt.Errorf("cannot get task: %w", err))
|
||||||
}
|
}
|
||||||
@@ -5396,7 +5406,7 @@ func (r *taskResolver) Organization(ctx context.Context, obj *types.Task) (*type
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrOrganizationNotFound
|
var errNotFound *coredata.ErrOrganizationNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get organization: %w", err))
|
panic(fmt.Errorf("cannot get organization: %w", err))
|
||||||
}
|
}
|
||||||
@@ -5412,7 +5422,7 @@ func (r *taskResolver) Measure(ctx context.Context, obj *types.Task) (*types.Mea
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrTaskNotFound
|
var errNotFound *coredata.ErrTaskNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get task: %w", err))
|
panic(fmt.Errorf("cannot get task: %w", err))
|
||||||
}
|
}
|
||||||
@@ -5421,7 +5431,7 @@ func (r *taskResolver) Measure(ctx context.Context, obj *types.Task) (*types.Mea
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrMeasureNotFound
|
var errNotFound *coredata.ErrMeasureNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get measure: %w", err))
|
panic(fmt.Errorf("cannot get measure: %w", err))
|
||||||
}
|
}
|
||||||
@@ -5495,7 +5505,7 @@ func (r *trustCenterResolver) Organization(ctx context.Context, obj *types.Trust
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrOrganizationNotFound
|
var errNotFound *coredata.ErrOrganizationNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get organization: %w", err))
|
panic(fmt.Errorf("cannot get organization: %w", err))
|
||||||
}
|
}
|
||||||
@@ -5614,7 +5624,7 @@ func (r *trustCenterDocumentAccessResolver) Document(ctx context.Context, obj *t
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrDocumentNotFound
|
var errNotFound *coredata.ErrDocumentNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("cannot load document: %w", err)
|
return nil, fmt.Errorf("cannot load document: %w", err)
|
||||||
}
|
}
|
||||||
@@ -5691,7 +5701,7 @@ func (r *trustCenterFileResolver) Organization(ctx context.Context, obj *types.T
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrOrganizationNotFound
|
var errNotFound *coredata.ErrOrganizationNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get organization: %w", err))
|
panic(fmt.Errorf("cannot get organization: %w", err))
|
||||||
}
|
}
|
||||||
@@ -5756,7 +5766,7 @@ func (r *vendorResolver) Organization(ctx context.Context, obj *types.Vendor) (*
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrVendorNotFound
|
var errNotFound *coredata.ErrVendorNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get vendor: %w", err))
|
panic(fmt.Errorf("cannot get vendor: %w", err))
|
||||||
}
|
}
|
||||||
@@ -5765,7 +5775,7 @@ func (r *vendorResolver) Organization(ctx context.Context, obj *types.Vendor) (*
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrOrganizationNotFound
|
var errNotFound *coredata.ErrOrganizationNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get organization: %w", err))
|
panic(fmt.Errorf("cannot get organization: %w", err))
|
||||||
}
|
}
|
||||||
@@ -5913,7 +5923,7 @@ func (r *vendorResolver) BusinessOwner(ctx context.Context, obj *types.Vendor) (
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrVendorNotFound
|
var errNotFound *coredata.ErrVendorNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get vendor: %w", err))
|
panic(fmt.Errorf("cannot get vendor: %w", err))
|
||||||
}
|
}
|
||||||
@@ -5926,7 +5936,7 @@ func (r *vendorResolver) BusinessOwner(ctx context.Context, obj *types.Vendor) (
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrPeopleNotFound
|
var errNotFound *coredata.ErrPeopleNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get business owner: %w", err))
|
panic(fmt.Errorf("cannot get business owner: %w", err))
|
||||||
}
|
}
|
||||||
@@ -5941,7 +5951,7 @@ func (r *vendorResolver) SecurityOwner(ctx context.Context, obj *types.Vendor) (
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrVendorNotFound
|
var errNotFound *coredata.ErrVendorNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get vendor: %w", err))
|
panic(fmt.Errorf("cannot get vendor: %w", err))
|
||||||
}
|
}
|
||||||
@@ -5954,7 +5964,7 @@ func (r *vendorResolver) SecurityOwner(ctx context.Context, obj *types.Vendor) (
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrPeopleNotFound
|
var errNotFound *coredata.ErrPeopleNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get security owner: %w", err))
|
panic(fmt.Errorf("cannot get security owner: %w", err))
|
||||||
}
|
}
|
||||||
@@ -5970,7 +5980,7 @@ func (r *vendorBusinessAssociateAgreementResolver) Vendor(ctx context.Context, o
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrVendorNotFound
|
var errNotFound *coredata.ErrVendorNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("cannot get vendor: %w", err)
|
return nil, fmt.Errorf("cannot get vendor: %w", err)
|
||||||
}
|
}
|
||||||
@@ -5998,7 +6008,7 @@ func (r *vendorComplianceReportResolver) Vendor(ctx context.Context, obj *types.
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrVendorNotFound
|
var errNotFound *coredata.ErrVendorNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get vendor: %w", err))
|
panic(fmt.Errorf("cannot get vendor: %w", err))
|
||||||
}
|
}
|
||||||
@@ -6023,7 +6033,7 @@ func (r *vendorComplianceReportResolver) File(ctx context.Context, obj *types.Ve
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrFileNotFound
|
var errNotFound *coredata.ErrFileNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot load evidence file: %w", err))
|
panic(fmt.Errorf("cannot load evidence file: %w", err))
|
||||||
}
|
}
|
||||||
@@ -6073,7 +6083,7 @@ func (r *vendorContactResolver) Vendor(ctx context.Context, obj *types.VendorCon
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrVendorNotFound
|
var errNotFound *coredata.ErrVendorNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get vendor: %w", err))
|
panic(fmt.Errorf("cannot get vendor: %w", err))
|
||||||
}
|
}
|
||||||
@@ -6089,7 +6099,7 @@ func (r *vendorDataPrivacyAgreementResolver) Vendor(ctx context.Context, obj *ty
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrVendorNotFound
|
var errNotFound *coredata.ErrVendorNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get vendor: %w", err))
|
panic(fmt.Errorf("cannot get vendor: %w", err))
|
||||||
}
|
}
|
||||||
@@ -6117,7 +6127,7 @@ func (r *vendorRiskAssessmentResolver) Vendor(ctx context.Context, obj *types.Ve
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrVendorNotFound
|
var errNotFound *coredata.ErrVendorNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get vendor: %w", err))
|
panic(fmt.Errorf("cannot get vendor: %w", err))
|
||||||
}
|
}
|
||||||
@@ -6139,7 +6149,7 @@ func (r *vendorServiceResolver) Vendor(ctx context.Context, obj *types.VendorSer
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *coredata.ErrVendorNotFound
|
var errNotFound *coredata.ErrVendorNotFound
|
||||||
if errors.As(err, &errNotFound) {
|
if errors.As(err, &errNotFound) {
|
||||||
return nil, graphql.NotFound(errNotFound)
|
return nil, gqlutils.NotFound(errNotFound)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot get vendor: %w", err))
|
panic(fmt.Errorf("cannot get vendor: %w", err))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ call_argument_directives_with_null: true
|
|||||||
models:
|
models:
|
||||||
ID:
|
ID:
|
||||||
model:
|
model:
|
||||||
- "github.com/getprobo/probo/pkg/server/graphql/types/gid.GIDScalar"
|
- "go.probo.inc/probo/pkg/server/gqlutils/types/gid.GIDScalar"
|
||||||
Datetime:
|
Datetime:
|
||||||
model:
|
model:
|
||||||
- "github.com/99designs/gqlgen/graphql.Time"
|
- "github.com/99designs/gqlgen/graphql.Time"
|
||||||
CursorKey:
|
CursorKey:
|
||||||
model:
|
model:
|
||||||
- "github.com/getprobo/probo/pkg/server/graphql/types/cursor.CursorKeyScalar"
|
- "go.probo.inc/probo/pkg/server/gqlutils/types/cursor.CursorKeyScalar"
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ import (
|
|||||||
"github.com/99designs/gqlgen/graphql/handler"
|
"github.com/99designs/gqlgen/graphql/handler"
|
||||||
"github.com/99designs/gqlgen/graphql/handler/extension"
|
"github.com/99designs/gqlgen/graphql/handler/extension"
|
||||||
"github.com/99designs/gqlgen/graphql/handler/transport"
|
"github.com/99designs/gqlgen/graphql/handler/transport"
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"go.gearno.de/kit/log"
|
||||||
"go.probo.inc/probo/pkg/auth"
|
"go.probo.inc/probo/pkg/auth"
|
||||||
"go.probo.inc/probo/pkg/authz"
|
"go.probo.inc/probo/pkg/authz"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
@@ -34,12 +36,10 @@ import (
|
|||||||
console_v1 "go.probo.inc/probo/pkg/server/api/console/v1"
|
console_v1 "go.probo.inc/probo/pkg/server/api/console/v1"
|
||||||
"go.probo.inc/probo/pkg/server/api/trust/v1/schema"
|
"go.probo.inc/probo/pkg/server/api/trust/v1/schema"
|
||||||
"go.probo.inc/probo/pkg/server/api/trust/v1/trustauth"
|
"go.probo.inc/probo/pkg/server/api/trust/v1/trustauth"
|
||||||
gqlutils "go.probo.inc/probo/pkg/server/graphql"
|
"go.probo.inc/probo/pkg/server/gqlutils"
|
||||||
"go.probo.inc/probo/pkg/server/session"
|
"go.probo.inc/probo/pkg/server/session"
|
||||||
"go.probo.inc/probo/pkg/statelesstoken"
|
"go.probo.inc/probo/pkg/statelesstoken"
|
||||||
"go.probo.inc/probo/pkg/trust"
|
"go.probo.inc/probo/pkg/trust"
|
||||||
"github.com/go-chi/chi/v5"
|
|
||||||
"go.gearno.de/kit/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
|||||||
@@ -45,14 +45,14 @@ type Organization implements Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum DocumentType
|
enum DocumentType
|
||||||
@goModel(model: "github.com/getprobo/probo/pkg/coredata.DocumentType") {
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentType") {
|
||||||
OTHER
|
OTHER
|
||||||
@goEnum(value: "github.com/getprobo/probo/pkg/coredata.DocumentTypeOther")
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeOther")
|
||||||
ISMS @goEnum(value: "github.com/getprobo/probo/pkg/coredata.DocumentTypeISMS")
|
ISMS @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeISMS")
|
||||||
POLICY
|
POLICY
|
||||||
@goEnum(value: "github.com/getprobo/probo/pkg/coredata.DocumentTypePolicy")
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypePolicy")
|
||||||
PROCEDURE
|
PROCEDURE
|
||||||
@goEnum(value: "github.com/getprobo/probo/pkg/coredata.DocumentTypeProcedure")
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeProcedure")
|
||||||
}
|
}
|
||||||
|
|
||||||
type Document implements Node {
|
type Document implements Node {
|
||||||
@@ -102,340 +102,340 @@ type AuditEdge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum CountryCode
|
enum CountryCode
|
||||||
@goModel(model: "github.com/getprobo/probo/pkg/coredata.CountryCode") {
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.CountryCode") {
|
||||||
AD @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAD")
|
AD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAD")
|
||||||
AE @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAE")
|
AE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAE")
|
||||||
AF @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAF")
|
AF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAF")
|
||||||
AG @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAG")
|
AG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAG")
|
||||||
AI @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAI")
|
AI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAI")
|
||||||
AL @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAL")
|
AL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAL")
|
||||||
AM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAM")
|
AM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAM")
|
||||||
AO @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAO")
|
AO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAO")
|
||||||
AQ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAQ")
|
AQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAQ")
|
||||||
AR @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAR")
|
AR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAR")
|
||||||
AS @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAS")
|
AS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAS")
|
||||||
AT @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAT")
|
AT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAT")
|
||||||
AU @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAU")
|
AU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAU")
|
||||||
AW @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAW")
|
AW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAW")
|
||||||
AX @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAX")
|
AX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAX")
|
||||||
AZ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAZ")
|
AZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAZ")
|
||||||
BA @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBA")
|
BA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBA")
|
||||||
BB @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBB")
|
BB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBB")
|
||||||
BD @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBD")
|
BD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBD")
|
||||||
BE @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBE")
|
BE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBE")
|
||||||
BF @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBF")
|
BF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBF")
|
||||||
BG @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBG")
|
BG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBG")
|
||||||
BH @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBH")
|
BH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBH")
|
||||||
BI @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBI")
|
BI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBI")
|
||||||
BJ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBJ")
|
BJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBJ")
|
||||||
BL @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBL")
|
BL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBL")
|
||||||
BM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBM")
|
BM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBM")
|
||||||
BN @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBN")
|
BN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBN")
|
||||||
BO @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBO")
|
BO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBO")
|
||||||
BQ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBQ")
|
BQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBQ")
|
||||||
BR @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBR")
|
BR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBR")
|
||||||
BS @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBS")
|
BS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBS")
|
||||||
BT @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBT")
|
BT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBT")
|
||||||
BV @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBV")
|
BV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBV")
|
||||||
BW @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBW")
|
BW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBW")
|
||||||
BY @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBY")
|
BY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBY")
|
||||||
BZ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBZ")
|
BZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBZ")
|
||||||
CA @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCA")
|
CA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCA")
|
||||||
CC @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCC")
|
CC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCC")
|
||||||
CD @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCD")
|
CD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCD")
|
||||||
CF @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCF")
|
CF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCF")
|
||||||
CG @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCG")
|
CG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCG")
|
||||||
CH @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCH")
|
CH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCH")
|
||||||
CI @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCI")
|
CI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCI")
|
||||||
CK @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCK")
|
CK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCK")
|
||||||
CL @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCL")
|
CL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCL")
|
||||||
CM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCM")
|
CM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCM")
|
||||||
CN @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCN")
|
CN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCN")
|
||||||
CO @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCO")
|
CO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCO")
|
||||||
CR @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCR")
|
CR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCR")
|
||||||
CU @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCU")
|
CU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCU")
|
||||||
CV @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCV")
|
CV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCV")
|
||||||
CW @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCW")
|
CW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCW")
|
||||||
CX @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCX")
|
CX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCX")
|
||||||
CY @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCY")
|
CY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCY")
|
||||||
CZ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCZ")
|
CZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCZ")
|
||||||
DE @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeDE")
|
DE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDE")
|
||||||
DJ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeDJ")
|
DJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDJ")
|
||||||
DK @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeDK")
|
DK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDK")
|
||||||
DM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeDM")
|
DM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDM")
|
||||||
DO @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeDO")
|
DO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDO")
|
||||||
DZ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeDZ")
|
DZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDZ")
|
||||||
EC @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeEC")
|
EC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEC")
|
||||||
EE @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeEE")
|
EE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEE")
|
||||||
EG @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeEG")
|
EG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEG")
|
||||||
EH @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeEH")
|
EH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEH")
|
||||||
ER @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeER")
|
ER @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeER")
|
||||||
ES @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeES")
|
ES @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeES")
|
||||||
ET @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeET")
|
ET @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeET")
|
||||||
EU @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeEU")
|
EU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEU")
|
||||||
FI @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeFI")
|
FI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFI")
|
||||||
FJ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeFJ")
|
FJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFJ")
|
||||||
FK @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeFK")
|
FK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFK")
|
||||||
FM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeFM")
|
FM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFM")
|
||||||
FO @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeFO")
|
FO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFO")
|
||||||
FR @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeFR")
|
FR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFR")
|
||||||
GA @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGA")
|
GA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGA")
|
||||||
GB @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGB")
|
GB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGB")
|
||||||
GD @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGD")
|
GD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGD")
|
||||||
GE @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGE")
|
GE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGE")
|
||||||
GF @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGF")
|
GF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGF")
|
||||||
GG @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGG")
|
GG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGG")
|
||||||
GH @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGH")
|
GH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGH")
|
||||||
GI @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGI")
|
GI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGI")
|
||||||
GL @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGL")
|
GL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGL")
|
||||||
GM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGM")
|
GM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGM")
|
||||||
GN @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGN")
|
GN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGN")
|
||||||
GP @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGP")
|
GP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGP")
|
||||||
GQ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGQ")
|
GQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGQ")
|
||||||
GR @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGR")
|
GR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGR")
|
||||||
GT @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGT")
|
GT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGT")
|
||||||
GU @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGU")
|
GU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGU")
|
||||||
GW @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGW")
|
GW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGW")
|
||||||
GY @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGY")
|
GY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGY")
|
||||||
HK @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeHK")
|
HK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHK")
|
||||||
HM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeHM")
|
HM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHM")
|
||||||
HN @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeHN")
|
HN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHN")
|
||||||
HR @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeHR")
|
HR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHR")
|
||||||
HT @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeHT")
|
HT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHT")
|
||||||
HU @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeHU")
|
HU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHU")
|
||||||
ID @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeID")
|
ID @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeID")
|
||||||
IE @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeIE")
|
IE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIE")
|
||||||
IL @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeIL")
|
IL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIL")
|
||||||
IM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeIM")
|
IM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIM")
|
||||||
IN @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeIN")
|
IN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIN")
|
||||||
IO @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeIO")
|
IO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIO")
|
||||||
IQ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeIQ")
|
IQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIQ")
|
||||||
IR @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeIR")
|
IR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIR")
|
||||||
IS @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeIS")
|
IS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIS")
|
||||||
IT @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeIT")
|
IT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIT")
|
||||||
JE @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeJE")
|
JE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJE")
|
||||||
JM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeJM")
|
JM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJM")
|
||||||
JO @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeJO")
|
JO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJO")
|
||||||
JP @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeJP")
|
JP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJP")
|
||||||
KE @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeKE")
|
KE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKE")
|
||||||
KG @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeKG")
|
KG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKG")
|
||||||
KH @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeKH")
|
KH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKH")
|
||||||
KI @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeKI")
|
KI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKI")
|
||||||
KM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeKM")
|
KM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKM")
|
||||||
KN @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeKN")
|
KN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKN")
|
||||||
KP @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeKP")
|
KP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKP")
|
||||||
KR @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeKR")
|
KR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKR")
|
||||||
KW @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeKW")
|
KW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKW")
|
||||||
KY @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeKY")
|
KY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKY")
|
||||||
KZ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeKZ")
|
KZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKZ")
|
||||||
LA @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeLA")
|
LA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLA")
|
||||||
LB @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeLB")
|
LB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLB")
|
||||||
LC @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeLC")
|
LC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLC")
|
||||||
LI @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeLI")
|
LI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLI")
|
||||||
LK @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeLK")
|
LK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLK")
|
||||||
LR @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeLR")
|
LR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLR")
|
||||||
LS @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeLS")
|
LS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLS")
|
||||||
LT @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeLT")
|
LT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLT")
|
||||||
LU @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeLU")
|
LU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLU")
|
||||||
LV @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeLV")
|
LV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLV")
|
||||||
LY @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeLY")
|
LY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLY")
|
||||||
MA @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMA")
|
MA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMA")
|
||||||
MC @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMC")
|
MC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMC")
|
||||||
MD @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMD")
|
MD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMD")
|
||||||
ME @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeME")
|
ME @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeME")
|
||||||
MF @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMF")
|
MF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMF")
|
||||||
MG @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMG")
|
MG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMG")
|
||||||
MH @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMH")
|
MH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMH")
|
||||||
MK @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMK")
|
MK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMK")
|
||||||
ML @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeML")
|
ML @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeML")
|
||||||
MM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMM")
|
MM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMM")
|
||||||
MN @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMN")
|
MN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMN")
|
||||||
MO @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMO")
|
MO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMO")
|
||||||
MP @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMP")
|
MP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMP")
|
||||||
MQ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMQ")
|
MQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMQ")
|
||||||
MR @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMR")
|
MR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMR")
|
||||||
MS @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMS")
|
MS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMS")
|
||||||
MT @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMT")
|
MT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMT")
|
||||||
MU @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMU")
|
MU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMU")
|
||||||
MV @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMV")
|
MV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMV")
|
||||||
MW @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMW")
|
MW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMW")
|
||||||
MX @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMX")
|
MX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMX")
|
||||||
MY @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMY")
|
MY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMY")
|
||||||
MZ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMZ")
|
MZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMZ")
|
||||||
NA @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeNA")
|
NA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNA")
|
||||||
NC @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeNC")
|
NC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNC")
|
||||||
NE @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeNE")
|
NE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNE")
|
||||||
NF @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeNF")
|
NF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNF")
|
||||||
NG @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeNG")
|
NG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNG")
|
||||||
NI @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeNI")
|
NI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNI")
|
||||||
NL @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeNL")
|
NL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNL")
|
||||||
NO @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeNO")
|
NO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNO")
|
||||||
NP @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeNP")
|
NP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNP")
|
||||||
NR @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeNR")
|
NR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNR")
|
||||||
NU @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeNU")
|
NU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNU")
|
||||||
NZ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeNZ")
|
NZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNZ")
|
||||||
OM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeOM")
|
OM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeOM")
|
||||||
PA @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodePA")
|
PA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePA")
|
||||||
PE @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodePE")
|
PE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePE")
|
||||||
PF @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodePF")
|
PF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePF")
|
||||||
PG @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodePG")
|
PG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePG")
|
||||||
PH @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodePH")
|
PH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePH")
|
||||||
PK @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodePK")
|
PK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePK")
|
||||||
PL @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodePL")
|
PL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePL")
|
||||||
PM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodePM")
|
PM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePM")
|
||||||
PN @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodePN")
|
PN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePN")
|
||||||
PR @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodePR")
|
PR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePR")
|
||||||
PS @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodePS")
|
PS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePS")
|
||||||
PT @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodePT")
|
PT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePT")
|
||||||
PW @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodePW")
|
PW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePW")
|
||||||
PY @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodePY")
|
PY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePY")
|
||||||
QA @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeQA")
|
QA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeQA")
|
||||||
RE @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeRE")
|
RE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRE")
|
||||||
RO @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeRO")
|
RO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRO")
|
||||||
RS @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeRS")
|
RS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRS")
|
||||||
RU @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeRU")
|
RU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRU")
|
||||||
RW @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeRW")
|
RW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRW")
|
||||||
SA @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSA")
|
SA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSA")
|
||||||
SB @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSB")
|
SB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSB")
|
||||||
SC @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSC")
|
SC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSC")
|
||||||
SD @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSD")
|
SD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSD")
|
||||||
SE @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSE")
|
SE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSE")
|
||||||
SG @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSG")
|
SG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSG")
|
||||||
SH @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSH")
|
SH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSH")
|
||||||
SI @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSI")
|
SI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSI")
|
||||||
SJ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSJ")
|
SJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSJ")
|
||||||
SK @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSK")
|
SK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSK")
|
||||||
SL @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSL")
|
SL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSL")
|
||||||
SM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSM")
|
SM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSM")
|
||||||
SN @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSN")
|
SN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSN")
|
||||||
SO @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSO")
|
SO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSO")
|
||||||
SR @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSR")
|
SR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSR")
|
||||||
SS @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSS")
|
SS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSS")
|
||||||
ST @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeST")
|
ST @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeST")
|
||||||
SV @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSV")
|
SV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSV")
|
||||||
SX @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSX")
|
SX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSX")
|
||||||
SY @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSY")
|
SY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSY")
|
||||||
SZ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSZ")
|
SZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSZ")
|
||||||
TC @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTC")
|
TC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTC")
|
||||||
TD @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTD")
|
TD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTD")
|
||||||
TF @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTF")
|
TF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTF")
|
||||||
TG @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTG")
|
TG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTG")
|
||||||
TH @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTH")
|
TH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTH")
|
||||||
TJ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTJ")
|
TJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTJ")
|
||||||
TK @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTK")
|
TK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTK")
|
||||||
TL @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTL")
|
TL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTL")
|
||||||
TM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTM")
|
TM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTM")
|
||||||
TN @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTN")
|
TN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTN")
|
||||||
TO @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTO")
|
TO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTO")
|
||||||
TR @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTR")
|
TR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTR")
|
||||||
TT @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTT")
|
TT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTT")
|
||||||
TV @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTV")
|
TV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTV")
|
||||||
TW @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTW")
|
TW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTW")
|
||||||
TZ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTZ")
|
TZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTZ")
|
||||||
UA @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeUA")
|
UA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUA")
|
||||||
UG @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeUG")
|
UG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUG")
|
||||||
UM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeUM")
|
UM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUM")
|
||||||
US @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeUS")
|
US @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUS")
|
||||||
UY @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeUY")
|
UY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUY")
|
||||||
UZ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeUZ")
|
UZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUZ")
|
||||||
VA @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeVA")
|
VA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVA")
|
||||||
VC @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeVC")
|
VC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVC")
|
||||||
VE @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeVE")
|
VE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVE")
|
||||||
VG @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeVG")
|
VG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVG")
|
||||||
VI @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeVI")
|
VI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVI")
|
||||||
VN @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeVN")
|
VN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVN")
|
||||||
VU @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeVU")
|
VU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVU")
|
||||||
WF @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeWF")
|
WF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeWF")
|
||||||
WS @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeWS")
|
WS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeWS")
|
||||||
YE @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeYE")
|
YE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeYE")
|
||||||
YT @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeYT")
|
YT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeYT")
|
||||||
ZA @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeZA")
|
ZA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeZA")
|
||||||
ZM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeZM")
|
ZM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeZM")
|
||||||
ZW @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeZW")
|
ZW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeZW")
|
||||||
}
|
}
|
||||||
|
|
||||||
enum VendorCategory
|
enum VendorCategory
|
||||||
@goModel(model: "github.com/getprobo/probo/pkg/coredata.VendorCategory") {
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.VendorCategory") {
|
||||||
ANALYTICS
|
ANALYTICS
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryAnalytics"
|
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryAnalytics"
|
||||||
)
|
)
|
||||||
CLOUD_MONITORING
|
CLOUD_MONITORING
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryCloudMonitoring"
|
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryCloudMonitoring"
|
||||||
)
|
)
|
||||||
CLOUD_PROVIDER
|
CLOUD_PROVIDER
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryCloudProvider"
|
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryCloudProvider"
|
||||||
)
|
)
|
||||||
COLLABORATION
|
COLLABORATION
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryCollaboration"
|
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryCollaboration"
|
||||||
)
|
)
|
||||||
CUSTOMER_SUPPORT
|
CUSTOMER_SUPPORT
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryCustomerSupport"
|
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryCustomerSupport"
|
||||||
)
|
)
|
||||||
DATA_STORAGE_AND_PROCESSING
|
DATA_STORAGE_AND_PROCESSING
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryDataStorageAndProcessing"
|
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryDataStorageAndProcessing"
|
||||||
)
|
)
|
||||||
DOCUMENT_MANAGEMENT
|
DOCUMENT_MANAGEMENT
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryDocumentManagement"
|
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryDocumentManagement"
|
||||||
)
|
)
|
||||||
EMPLOYEE_MANAGEMENT
|
EMPLOYEE_MANAGEMENT
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryEmployeeManagement"
|
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryEmployeeManagement"
|
||||||
)
|
)
|
||||||
ENGINEERING
|
ENGINEERING
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryEngineering"
|
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryEngineering"
|
||||||
)
|
)
|
||||||
FINANCE
|
FINANCE
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryFinance"
|
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryFinance"
|
||||||
)
|
)
|
||||||
IDENTITY_PROVIDER
|
IDENTITY_PROVIDER
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryIdentityProvider"
|
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryIdentityProvider"
|
||||||
)
|
)
|
||||||
IT @goEnum(value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryIT")
|
IT @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryIT")
|
||||||
MARKETING
|
MARKETING
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryMarketing"
|
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryMarketing"
|
||||||
)
|
)
|
||||||
OFFICE_OPERATIONS
|
OFFICE_OPERATIONS
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryOfficeOperations"
|
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryOfficeOperations"
|
||||||
)
|
)
|
||||||
OTHER
|
OTHER
|
||||||
@goEnum(value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryOther")
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryOther")
|
||||||
PASSWORD_MANAGEMENT
|
PASSWORD_MANAGEMENT
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryPasswordManagement"
|
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryPasswordManagement"
|
||||||
)
|
)
|
||||||
PRODUCT_AND_DESIGN
|
PRODUCT_AND_DESIGN
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryProductAndDesign"
|
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryProductAndDesign"
|
||||||
)
|
)
|
||||||
PROFESSIONAL_SERVICES
|
PROFESSIONAL_SERVICES
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryProfessionalServices"
|
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryProfessionalServices"
|
||||||
)
|
)
|
||||||
RECRUITING
|
RECRUITING
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryRecruiting"
|
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryRecruiting"
|
||||||
)
|
)
|
||||||
SALES
|
SALES
|
||||||
@goEnum(value: "github.com/getprobo/probo/pkg/coredata.VendorCategorySales")
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategorySales")
|
||||||
SECURITY
|
SECURITY
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategorySecurity"
|
value: "go.probo.inc/probo/pkg/coredata.VendorCategorySecurity"
|
||||||
)
|
)
|
||||||
VERSION_CONTROL
|
VERSION_CONTROL
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryVersionControl"
|
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryVersionControl"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -21,13 +21,13 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.gearno.de/kit/httpserver"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/probo"
|
"go.probo.inc/probo/pkg/probo"
|
||||||
console_v1 "go.probo.inc/probo/pkg/server/api/console/v1"
|
console_v1 "go.probo.inc/probo/pkg/server/api/console/v1"
|
||||||
"go.probo.inc/probo/pkg/server/session"
|
"go.probo.inc/probo/pkg/server/session"
|
||||||
"go.probo.inc/probo/pkg/statelesstoken"
|
"go.probo.inc/probo/pkg/statelesstoken"
|
||||||
"go.probo.inc/probo/pkg/trust"
|
"go.probo.inc/probo/pkg/trust"
|
||||||
"go.gearno.de/kit/httpserver"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ package types
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
"go.probo.inc/probo/pkg/server/graphql/types/cursor"
|
"go.probo.inc/probo/pkg/server/gqlutils/types/cursor"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewCursor[O page.OrderField](
|
func NewCursor[O page.OrderField](
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ package types
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
"go.probo.inc/probo/pkg/server/graphql/types/pageinfo"
|
"go.probo.inc/probo/pkg/server/gqlutils/types/pageinfo"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewPageInfo[T page.Paginable[O], O page.OrderField](p *page.Page[T, O]) *PageInfo {
|
func NewPageInfo[T page.Paginable[O], O page.OrderField](p *page.Page[T, O]) *PageInfo {
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
"go.probo.inc/probo/pkg/server/api/trust/v1/schema"
|
"go.probo.inc/probo/pkg/server/api/trust/v1/schema"
|
||||||
"go.probo.inc/probo/pkg/server/api/trust/v1/types"
|
"go.probo.inc/probo/pkg/server/api/trust/v1/types"
|
||||||
"go.probo.inc/probo/pkg/trust"
|
"go.probo.inc/probo/pkg/trust"
|
||||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Framework is the resolver for the framework field.
|
// Framework is the resolver for the framework field.
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"go.gearno.de/kit/log"
|
||||||
authsvc "go.probo.inc/probo/pkg/auth"
|
authsvc "go.probo.inc/probo/pkg/auth"
|
||||||
"go.probo.inc/probo/pkg/authz"
|
"go.probo.inc/probo/pkg/authz"
|
||||||
"go.probo.inc/probo/pkg/filemanager"
|
"go.probo.inc/probo/pkg/filemanager"
|
||||||
"github.com/go-chi/chi/v5"
|
|
||||||
"go.gearno.de/kit/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@@ -56,6 +56,12 @@ func NewServer(cfg Config) (*Server, error) {
|
|||||||
router.Get("/invitations", RequireAuth(cfg.Auth, cfg.Authz, cfg.CookieName, cfg.CookieSecret, ListInvitationsHandler(cfg.Authz)))
|
router.Get("/invitations", RequireAuth(cfg.Auth, cfg.Authz, cfg.CookieName, cfg.CookieSecret, ListInvitationsHandler(cfg.Authz)))
|
||||||
router.Post("/invitations/accept", AcceptInvitationHandler(cfg.Auth, cfg.Authz, cfg.CookieName, cfg.CookieSecret))
|
router.Post("/invitations/accept", AcceptInvitationHandler(cfg.Auth, cfg.Authz, cfg.CookieName, cfg.CookieSecret))
|
||||||
|
|
||||||
|
router.Get("/api-keys", RequireAuth(cfg.Auth, cfg.Authz, cfg.CookieName, cfg.CookieSecret, ListUserAPIKeysHandler(cfg.Auth, cfg.Authz)))
|
||||||
|
router.Post("/api-keys", RequireAuth(cfg.Auth, cfg.Authz, cfg.CookieName, cfg.CookieSecret, CreateUserAPIKeyHandler(cfg.Auth)))
|
||||||
|
router.Get("/api-keys/{id}", RequireAuth(cfg.Auth, cfg.Authz, cfg.CookieName, cfg.CookieSecret, GetUserAPIKeyHandler(cfg.Auth)))
|
||||||
|
router.Put("/api-keys", RequireAuth(cfg.Auth, cfg.Authz, cfg.CookieName, cfg.CookieSecret, UpdateUserAPIKeyHandler(cfg.Auth)))
|
||||||
|
router.Delete("/api-keys", RequireAuth(cfg.Auth, cfg.Authz, cfg.CookieName, cfg.CookieSecret, DeleteUserAPIKeyHandler(cfg.Auth)))
|
||||||
|
|
||||||
router.Get("/saml/login/{samlConfigID}", SAMLLoginHandler(cfg.SAML, cfg.Auth, cfg.Logger))
|
router.Get("/saml/login/{samlConfigID}", SAMLLoginHandler(cfg.SAML, cfg.Auth, cfg.Logger))
|
||||||
router.Post("/saml/consume", SAMLACSHandler(cfg.SAML, cfg.Auth, cfg.Authz, cfg.CookieName, cfg.CookieSecret, cfg.SessionDuration, cfg.Logger))
|
router.Post("/saml/consume", SAMLACSHandler(cfg.SAML, cfg.Auth, cfg.Authz, cfg.CookieName, cfg.CookieSecret, cfg.SessionDuration, cfg.Logger))
|
||||||
router.Get("/saml/metadata", SAMLMetadataHandler(cfg.SAML))
|
router.Get("/saml/metadata", SAMLMetadataHandler(cfg.SAML))
|
||||||
|
|||||||
126
pkg/server/auth/create_api_key_handler.go
Normal file
126
pkg/server/auth/create_api_key_handler.go
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.gearno.de/kit/httpserver"
|
||||||
|
authsvc "go.probo.inc/probo/pkg/auth"
|
||||||
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
|
"go.probo.inc/probo/pkg/gid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
CreateUserAPIKeyRequest struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
ExpiresAt time.Time `json:"expiresAt"`
|
||||||
|
Organizations []UserAPIKeyOrganizationMembershipRequest `json:"organizations"`
|
||||||
|
}
|
||||||
|
|
||||||
|
UserAPIKeyOrganizationMembershipRequest struct {
|
||||||
|
OrganizationID string `json:"organizationId"`
|
||||||
|
Role string `json:"role"`
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateUserAPIKeyResponse struct {
|
||||||
|
UserAPIKey UserAPIKeyResponse `json:"apiKey"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateUserAPIKeyHandler(authSvc *authsvc.Service) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
user := UserFromContext(ctx)
|
||||||
|
|
||||||
|
var req CreateUserAPIKeyRequest
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
httpserver.RenderJSON(w, http.StatusBadRequest, map[string]string{
|
||||||
|
"error": "invalid request body",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.ExpiresAt.IsZero() {
|
||||||
|
httpserver.RenderJSON(w, http.StatusBadRequest, map[string]string{
|
||||||
|
"error": "expiresAt is required",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.ExpiresAt.Before(time.Now()) {
|
||||||
|
httpserver.RenderJSON(w, http.StatusBadRequest, map[string]string{
|
||||||
|
"error": "expiration date must be in the future",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Organizations) == 0 {
|
||||||
|
httpserver.RenderJSON(w, http.StatusBadRequest, map[string]string{
|
||||||
|
"error": "at least one organization is required",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
name := req.Name
|
||||||
|
if name == "" {
|
||||||
|
name = time.Now().Format("2006-01-02")
|
||||||
|
}
|
||||||
|
|
||||||
|
orgInputs := make([]authsvc.UserAPIKeyOrganizationRequest, len(req.Organizations))
|
||||||
|
for i, org := range req.Organizations {
|
||||||
|
orgID, err := gid.ParseGID(org.OrganizationID)
|
||||||
|
if err != nil {
|
||||||
|
httpserver.RenderJSON(w, http.StatusBadRequest, map[string]string{
|
||||||
|
"error": "invalid organization id",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
orgInputs[i] = authsvc.UserAPIKeyOrganizationRequest{
|
||||||
|
OrganizationID: orgID,
|
||||||
|
Role: coredata.APIRole(org.Role),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
memberships, err := authSvc.ValidateAndBuildUserAPIKeyMemberships(ctx, user.ID, orgInputs)
|
||||||
|
if err != nil {
|
||||||
|
httpserver.RenderJSON(w, http.StatusBadRequest, map[string]string{
|
||||||
|
"error": "invalid organizations",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userAPIKey, key, err := authSvc.CreateUserAPIKey(ctx, user.ID, name, req.ExpiresAt, memberships)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("cannot create user api key: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
response := CreateUserAPIKeyResponse{
|
||||||
|
UserAPIKey: UserAPIKeyResponse{
|
||||||
|
ID: userAPIKey.ID,
|
||||||
|
Name: userAPIKey.Name,
|
||||||
|
ExpiresAt: userAPIKey.ExpiresAt,
|
||||||
|
CreatedAt: userAPIKey.CreatedAt,
|
||||||
|
},
|
||||||
|
Key: key,
|
||||||
|
}
|
||||||
|
|
||||||
|
httpserver.RenderJSON(w, http.StatusCreated, response)
|
||||||
|
}
|
||||||
|
}
|
||||||
84
pkg/server/auth/delete_api_key_handler.go
Normal file
84
pkg/server/auth/delete_api_key_handler.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"go.gearno.de/kit/httpserver"
|
||||||
|
authsvc "go.probo.inc/probo/pkg/auth"
|
||||||
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
|
"go.probo.inc/probo/pkg/gid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DeleteUserAPIKeyRequest struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteUserAPIKeyResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteUserAPIKeyHandler(authSvc *authsvc.Service) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
user := UserFromContext(ctx)
|
||||||
|
|
||||||
|
var req DeleteUserAPIKeyRequest
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
httpserver.RenderJSON(w, http.StatusBadRequest, map[string]string{
|
||||||
|
"error": "invalid request body",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.ID == "" {
|
||||||
|
httpserver.RenderJSON(w, http.StatusBadRequest, map[string]string{
|
||||||
|
"error": "user api key id is required",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userAPIKeyID, err := gid.ParseGID(req.ID)
|
||||||
|
if err != nil {
|
||||||
|
httpserver.RenderJSON(w, http.StatusBadRequest, map[string]string{
|
||||||
|
"error": "invalid user api key id",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := authSvc.DeleteUserAPIKey(ctx, userAPIKeyID, user.ID); err != nil {
|
||||||
|
var errNotFound *coredata.ErrUserAPIKeyNotFound
|
||||||
|
if errors.As(err, &errNotFound) {
|
||||||
|
httpserver.RenderJSON(w, http.StatusNotFound, map[string]string{
|
||||||
|
"error": "user api key not found",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
httpserver.RenderJSON(w, http.StatusInternalServerError, map[string]string{
|
||||||
|
"error": "failed to delete user api key",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := DeleteUserAPIKeyResponse{
|
||||||
|
ID: req.ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
httpserver.RenderJSON(w, http.StatusOK, response)
|
||||||
|
}
|
||||||
|
}
|
||||||
65
pkg/server/auth/get_api_key_handler.go
Normal file
65
pkg/server/auth/get_api_key_handler.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"go.gearno.de/kit/httpserver"
|
||||||
|
authsvc "go.probo.inc/probo/pkg/auth"
|
||||||
|
"go.probo.inc/probo/pkg/gid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetUserAPIKeyResponse struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserAPIKeyHandler(authSvc *authsvc.Service) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
user := UserFromContext(ctx)
|
||||||
|
|
||||||
|
userAPIKeyIDStr := chi.URLParam(r, "id")
|
||||||
|
if userAPIKeyIDStr == "" {
|
||||||
|
httpserver.RenderJSON(w, http.StatusBadRequest, map[string]string{
|
||||||
|
"error": "user api key id is required",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userAPIKeyID, err := gid.ParseGID(userAPIKeyIDStr)
|
||||||
|
if err != nil {
|
||||||
|
httpserver.RenderJSON(w, http.StatusBadRequest, map[string]string{
|
||||||
|
"error": "invalid user api key id",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := authSvc.GetUserAPIKey(ctx, userAPIKeyID, user.ID)
|
||||||
|
if err != nil {
|
||||||
|
httpserver.RenderJSON(w, http.StatusNotFound, map[string]string{
|
||||||
|
"error": "user api key not found",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := GetUserAPIKeyResponse{
|
||||||
|
Key: key,
|
||||||
|
}
|
||||||
|
|
||||||
|
httpserver.RenderJSON(w, http.StatusOK, response)
|
||||||
|
}
|
||||||
|
}
|
||||||
93
pkg/server/auth/list_api_keys_handler.go
Normal file
93
pkg/server/auth/list_api_keys_handler.go
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.gearno.de/kit/httpserver"
|
||||||
|
authsvc "go.probo.inc/probo/pkg/auth"
|
||||||
|
"go.probo.inc/probo/pkg/authz"
|
||||||
|
"go.probo.inc/probo/pkg/gid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
ListUserAPIKeysResponse struct {
|
||||||
|
UserAPIKeys []UserAPIKeyResponse `json:"apiKeys"`
|
||||||
|
}
|
||||||
|
|
||||||
|
UserAPIKeyResponse struct {
|
||||||
|
ID gid.GID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
ExpiresAt time.Time `json:"expiresAt"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
Organizations []UserAPIKeyOrganizationMembership `json:"organizations"`
|
||||||
|
}
|
||||||
|
|
||||||
|
UserAPIKeyOrganizationMembership struct {
|
||||||
|
OrganizationID gid.GID `json:"organizationId"`
|
||||||
|
OrganizationName string `json:"organizationName"`
|
||||||
|
Role string `json:"role"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func ListUserAPIKeysHandler(authSvc *authsvc.Service, authzSvc *authz.Service) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
user := UserFromContext(ctx)
|
||||||
|
|
||||||
|
organizations, err := authzSvc.GetAllUserOrganizations(ctx, user.ID)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("cannot list organizations for user: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
tenantIDs := make([]gid.TenantID, 0, len(organizations))
|
||||||
|
for _, org := range organizations {
|
||||||
|
tenantIDs = append(tenantIDs, org.ID.TenantID())
|
||||||
|
}
|
||||||
|
|
||||||
|
userAPIKeysWithMemberships, err := authSvc.ListUserAPIKeysWithMemberships(ctx, user.ID, tenantIDs)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("cannot list user api keys: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
response := ListUserAPIKeysResponse{
|
||||||
|
UserAPIKeys: make([]UserAPIKeyResponse, 0, len(userAPIKeysWithMemberships)),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, keyWithMemberships := range userAPIKeysWithMemberships {
|
||||||
|
organizations := make([]UserAPIKeyOrganizationMembership, 0, len(keyWithMemberships.Memberships))
|
||||||
|
for _, membership := range keyWithMemberships.Memberships {
|
||||||
|
organizations = append(organizations, UserAPIKeyOrganizationMembership{
|
||||||
|
OrganizationID: membership.OrganizationID,
|
||||||
|
OrganizationName: membership.OrganizationName,
|
||||||
|
Role: membership.Role.String(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
response.UserAPIKeys = append(response.UserAPIKeys, UserAPIKeyResponse{
|
||||||
|
ID: keyWithMemberships.UserAPIKey.ID,
|
||||||
|
Name: keyWithMemberships.UserAPIKey.Name,
|
||||||
|
ExpiresAt: keyWithMemberships.UserAPIKey.ExpiresAt,
|
||||||
|
CreatedAt: keyWithMemberships.UserAPIKey.CreatedAt,
|
||||||
|
Organizations: organizations,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
httpserver.RenderJSON(w, http.StatusOK, response)
|
||||||
|
}
|
||||||
|
}
|
||||||
142
pkg/server/auth/update_api_key_handler.go
Normal file
142
pkg/server/auth/update_api_key_handler.go
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"go.gearno.de/kit/httpserver"
|
||||||
|
authsvc "go.probo.inc/probo/pkg/auth"
|
||||||
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
|
"go.probo.inc/probo/pkg/gid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UpdateUserAPIKeyRequest struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
Organizations []UserAPIKeyOrganizationMembershipRequest `json:"organizations"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateUserAPIKeyHandler(authSvc *authsvc.Service) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
user := UserFromContext(ctx)
|
||||||
|
|
||||||
|
var req UpdateUserAPIKeyRequest
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
httpserver.RenderJSON(w, http.StatusBadRequest, map[string]string{
|
||||||
|
"error": "invalid request body",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.ID == "" {
|
||||||
|
httpserver.RenderJSON(w, http.StatusBadRequest, map[string]string{
|
||||||
|
"error": "user api key id is required",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userAPIKeyID, err := gid.ParseGID(req.ID)
|
||||||
|
if err != nil {
|
||||||
|
httpserver.RenderJSON(w, http.StatusBadRequest, map[string]string{
|
||||||
|
"error": "invalid user api key id",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Name == nil && len(req.Organizations) == 0 {
|
||||||
|
httpserver.RenderJSON(w, http.StatusBadRequest, map[string]string{
|
||||||
|
"error": "at least one field must be provided for update",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Name != nil && *req.Name != "" {
|
||||||
|
if err := authSvc.UpdateUserAPIKeyName(ctx, userAPIKeyID, user.ID, *req.Name); err != nil {
|
||||||
|
var errNotFound *coredata.ErrUserAPIKeyNotFound
|
||||||
|
if errors.As(err, &errNotFound) {
|
||||||
|
httpserver.RenderJSON(w, http.StatusNotFound, map[string]string{
|
||||||
|
"error": "user api key not found",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strings.Contains(err.Error(), "does not belong to user") {
|
||||||
|
httpserver.RenderJSON(w, http.StatusForbidden, map[string]string{
|
||||||
|
"error": "access denied",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
httpserver.RenderJSON(w, http.StatusInternalServerError, map[string]string{
|
||||||
|
"error": "failed to update user api key name",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Organizations) > 0 {
|
||||||
|
orgInputs := make([]authsvc.UserAPIKeyOrganizationRequest, len(req.Organizations))
|
||||||
|
for i, org := range req.Organizations {
|
||||||
|
orgID, err := gid.ParseGID(org.OrganizationID)
|
||||||
|
if err != nil {
|
||||||
|
httpserver.RenderJSON(w, http.StatusBadRequest, map[string]string{
|
||||||
|
"error": "invalid organization id",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
orgInputs[i] = authsvc.UserAPIKeyOrganizationRequest{
|
||||||
|
OrganizationID: orgID,
|
||||||
|
Role: coredata.APIRole(org.Role),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
memberships, err := authSvc.ValidateAndBuildUserAPIKeyMemberships(ctx, user.ID, orgInputs)
|
||||||
|
if err != nil {
|
||||||
|
httpserver.RenderJSON(w, http.StatusBadRequest, map[string]string{
|
||||||
|
"error": "invalid organizations",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := authSvc.UpdateUserAPIKeyMemberships(ctx, userAPIKeyID, user.ID, memberships); err != nil {
|
||||||
|
var errNotFound *coredata.ErrUserAPIKeyNotFound
|
||||||
|
if errors.As(err, &errNotFound) {
|
||||||
|
httpserver.RenderJSON(w, http.StatusNotFound, map[string]string{
|
||||||
|
"error": "user api key not found",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strings.Contains(err.Error(), "does not belong to user") {
|
||||||
|
httpserver.RenderJSON(w, http.StatusForbidden, map[string]string{
|
||||||
|
"error": "access denied",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
httpserver.RenderJSON(w, http.StatusInternalServerError, map[string]string{
|
||||||
|
"error": "failed to update user api key",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
httpserver.RenderJSON(w, http.StatusOK, map[string]string{
|
||||||
|
"message": "User API key updated successfully",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
// PERFORMANCE OF THIS SOFTWARE.
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
package graphql
|
package gqlutils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"maps"
|
"maps"
|
||||||
@@ -12,18 +12,18 @@
|
|||||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
// PERFORMANCE OF THIS SOFTWARE.
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
package graphql
|
package gqlutils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
|
||||||
"go.probo.inc/probo/pkg/auth"
|
|
||||||
"go.probo.inc/probo/pkg/authz"
|
|
||||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||||
"go.gearno.de/kit/httpserver"
|
"go.gearno.de/kit/httpserver"
|
||||||
"go.gearno.de/kit/log"
|
"go.gearno.de/kit/log"
|
||||||
|
"go.probo.inc/probo/pkg/auth"
|
||||||
|
"go.probo.inc/probo/pkg/authz"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RecoverFunc(ctx context.Context, err any) error {
|
func RecoverFunc(ctx context.Context, err any) error {
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
// PERFORMANCE OF THIS SOFTWARE.
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
package graphql
|
package gqlutils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -66,13 +66,15 @@ func (e ErrExpiredToken) Error() string {
|
|||||||
return e.message
|
return e.message
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewToken creates a new token with the specified type, data, and expiration time
|
|
||||||
func NewToken[T any](secret string, tokenType string, expirationTime time.Duration, data T) (string, error) {
|
func NewToken[T any](secret string, tokenType string, expirationTime time.Duration, data T) (string, error) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
return NewDeterministicToken(secret, tokenType, now.Add(expirationTime), now, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDeterministicToken[T any](secret string, tokenType string, expiresAt time.Time, issuedAt time.Time, data T) (string, error) {
|
||||||
payload := Payload[T]{
|
payload := Payload[T]{
|
||||||
ExpiresAt: now.Add(expirationTime),
|
ExpiresAt: expiresAt,
|
||||||
IssuedAt: now,
|
IssuedAt: issuedAt,
|
||||||
Type: tokenType,
|
Type: tokenType,
|
||||||
Data: data,
|
Data: data,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,14 +25,14 @@ import (
|
|||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.gearno.de/kit/log"
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
"go.probo.inc/probo/packages/emails"
|
"go.probo.inc/probo/packages/emails"
|
||||||
"go.probo.inc/probo/pkg/auth"
|
"go.probo.inc/probo/pkg/auth"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/probo"
|
"go.probo.inc/probo/pkg/probo"
|
||||||
"go.probo.inc/probo/pkg/statelesstoken"
|
"go.probo.inc/probo/pkg/statelesstoken"
|
||||||
"go.gearno.de/kit/log"
|
|
||||||
"go.gearno.de/kit/pg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
Reference in New Issue
Block a user