@@ -1,12 +1,13 @@
|
||||
import { RouterProvider } from "react-router";
|
||||
import { router } from "./routes";
|
||||
import { Toasts } from "@probo/ui";
|
||||
import { Toasts, ConfirmDialog } from "@probo/ui";
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<>
|
||||
<RouterProvider router={router} />
|
||||
<Toasts />
|
||||
<ConfirmDialog />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
Avatar,
|
||||
IconPeopleAdd,
|
||||
Badge,
|
||||
IconKey,
|
||||
IconLock,
|
||||
} from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
@@ -227,6 +228,11 @@ function UserDropdown({ organizationId }: { organizationId: string }) {
|
||||
|
||||
return (
|
||||
<UserDropdownRoot fullName={user.fullName} email={user.email}>
|
||||
<UserDropdownItem
|
||||
to="/api-keys"
|
||||
icon={IconKey}
|
||||
label={__("API Keys")}
|
||||
/>
|
||||
<UserDropdownItem
|
||||
to="mailto:support@getprobo.com"
|
||||
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")
|
||||
),
|
||||
},
|
||||
{
|
||||
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 { IconListStack } from "./IconListStack.tsx";
|
||||
export { IconLock } from "./IconLock.tsx";
|
||||
export { IconKey } from "./IconKey.tsx";
|
||||
export { IconCircleCheck1 } from "./IconCircleCheck1.tsx";
|
||||
export { IconClock } from "./IconClock.tsx";
|
||||
export { IconImport2 } from "./IconImport2.tsx";
|
||||
|
||||
@@ -54,7 +54,7 @@ export {
|
||||
useDialogRef,
|
||||
type DialogRef,
|
||||
} from "./Molecules/Dialog/Dialog";
|
||||
export { useConfirm } from "./Molecules/Dialog/ConfirmDialog";
|
||||
export { useConfirm, ConfirmDialog } from "./Molecules/Dialog/ConfirmDialog";
|
||||
export { RiskBadge } from "./Molecules/Badge/RiskBadge";
|
||||
export { SeverityBadge } from "./Molecules/Badge/SeverityBadge.tsx";
|
||||
export { DocumentVersionBadge } from "./Molecules/Badge/DocumentVersionBadge.tsx";
|
||||
|
||||
@@ -24,6 +24,8 @@ import (
|
||||
"net/mail"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/packages/emails"
|
||||
"go.probo.inc/probo/pkg/baseurl"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
@@ -31,8 +33,6 @@ import (
|
||||
"go.probo.inc/probo/pkg/crypto/passwdhash"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/statelesstoken"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.gearno.de/kit/pg"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -124,11 +124,32 @@ type (
|
||||
OrganizationID gid.GID
|
||||
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 (
|
||||
TokenTypeEmailConfirmation = "email_confirmation"
|
||||
TokenTypePasswordReset = "password_reset"
|
||||
TokenTypeAPIKey = "api_key"
|
||||
)
|
||||
|
||||
func (e ErrInvalidCredentials) Error() string {
|
||||
@@ -1235,3 +1256,388 @@ func verifyDomainOwnership(ctx context.Context, domain, expectedToken string) (b
|
||||
|
||||
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"
|
||||
"time"
|
||||
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/packages/emails"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"go.probo.inc/probo/pkg/statelesstoken"
|
||||
"go.gearno.de/kit/pg"
|
||||
)
|
||||
|
||||
type TenantAccessError struct {
|
||||
@@ -103,6 +103,26 @@ func (s *Service) GetAllUserOrganizations(
|
||||
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(
|
||||
ctx context.Context,
|
||||
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
|
||||
TrustCenterFileEntityType
|
||||
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"
|
||||
"time"
|
||||
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -161,7 +161,7 @@ FROM
|
||||
INNER JOIN
|
||||
user_org ON organizations.id = user_org.organization_id
|
||||
WHERE
|
||||
%S
|
||||
%s
|
||||
AND %s
|
||||
`
|
||||
|
||||
@@ -237,6 +237,60 @@ ORDER BY
|
||||
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(
|
||||
ctx context.Context,
|
||||
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"
|
||||
|
||||
"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/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/docgen"
|
||||
@@ -23,10 +27,6 @@ import (
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"go.probo.inc/probo/pkg/statelesstoken"
|
||||
"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 (
|
||||
|
||||
@@ -21,12 +21,12 @@ import (
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/packages/emails"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"go.probo.inc/probo/pkg/statelesstoken"
|
||||
"go.gearno.de/kit/pg"
|
||||
)
|
||||
|
||||
type (
|
||||
|
||||
@@ -20,16 +20,16 @@ call_argument_directives_with_null: true
|
||||
models:
|
||||
ID:
|
||||
model:
|
||||
- "github.com/getprobo/probo/pkg/server/graphql/types/gid.GIDScalar"
|
||||
- "go.probo.inc/probo/pkg/server/gqlutils/types/gid.GIDScalar"
|
||||
Datetime:
|
||||
model:
|
||||
- "github.com/99designs/gqlgen/graphql.Time"
|
||||
CursorKey:
|
||||
model:
|
||||
- "github.com/getprobo/probo/pkg/server/graphql/types/cursor.CursorKeyScalar"
|
||||
- "go.probo.inc/probo/pkg/server/gqlutils/types/cursor.CursorKeyScalar"
|
||||
Duration:
|
||||
model:
|
||||
- "github.com/99designs/gqlgen/graphql.Duration"
|
||||
BigInt:
|
||||
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/transport"
|
||||
"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/authz"
|
||||
"go.probo.inc/probo/pkg/connector"
|
||||
@@ -38,14 +43,9 @@ import (
|
||||
"go.probo.inc/probo/pkg/probo"
|
||||
"go.probo.inc/probo/pkg/saferedirect"
|
||||
"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/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 (
|
||||
@@ -77,6 +77,7 @@ var (
|
||||
sessionContextKey = &ctxKey{name: "session"}
|
||||
userContextKey = &ctxKey{name: "user"}
|
||||
userTenantContextKey = &ctxKey{name: "user_tenants"}
|
||||
userAPIKeyContextKey = &ctxKey{name: "user_api_key"}
|
||||
)
|
||||
|
||||
func SessionFromContext(ctx context.Context) *coredata.Session {
|
||||
@@ -89,6 +90,11 @@ func UserFromContext(ctx context.Context) *coredata.User {
|
||||
return user
|
||||
}
|
||||
|
||||
func UserAPIKeyFromContext(ctx context.Context) *coredata.UserAPIKey {
|
||||
userAPIKey, _ := ctx.Value(userAPIKeyContextKey).(*coredata.UserAPIKey)
|
||||
return userAPIKey
|
||||
}
|
||||
|
||||
func NewMux(
|
||||
logger *log.Logger,
|
||||
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) {
|
||||
ctx := r.Context()
|
||||
|
||||
if authCtx := tryAPIKeyAuth(ctx, r, authSvc, authzSvc); authCtx != nil {
|
||||
next(w, r.WithContext(authCtx))
|
||||
return
|
||||
}
|
||||
|
||||
sessionAuthCfg := session.AuthConfig{
|
||||
CookieName: authCfg.CookieName,
|
||||
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 {
|
||||
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 (
|
||||
"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](
|
||||
|
||||
@@ -16,7 +16,7 @@ package types
|
||||
|
||||
import (
|
||||
"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 {
|
||||
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
pgx "github.com/jackc/pgx/v5"
|
||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||
"go.probo.inc/probo/pkg/auth"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
@@ -20,9 +22,7 @@ import (
|
||||
"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/types"
|
||||
"go.probo.inc/probo/pkg/server/graphql"
|
||||
pgx "github.com/jackc/pgx/v5"
|
||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||
"go.probo.inc/probo/pkg/server/gqlutils"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
var errNotFound *coredata.ErrAssetNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrPeopleNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrOrganizationNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrAuditNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrOrganizationNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrAuditNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrFrameworkNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrAuditNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
panic(fmt.Errorf("cannot load audit: %w", err))
|
||||
}
|
||||
@@ -277,7 +277,7 @@ func (r *continualImprovementResolver) Organization(ctx context.Context, obj *ty
|
||||
if err != nil {
|
||||
var errNotFound *coredata.ErrOrganizationNotFound
|
||||
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))
|
||||
}
|
||||
@@ -298,7 +298,7 @@ func (r *continualImprovementResolver) Owner(ctx context.Context, obj *types.Con
|
||||
if err != nil {
|
||||
var errNotFound *coredata.ErrPeopleNotFound
|
||||
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))
|
||||
}
|
||||
@@ -335,7 +335,7 @@ func (r *controlResolver) Framework(ctx context.Context, obj *types.Control) (*t
|
||||
if err != nil {
|
||||
var errNotFound *coredata.ErrControlNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrFrameworkNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrPeopleNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrOrganizationNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrDocumentNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrPeopleNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrDocumentNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrOrganizationNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrDocumentNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrPeopleNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
panic(fmt.Errorf("cannot get owner: %w", err))
|
||||
}
|
||||
@@ -823,7 +823,7 @@ func (r *documentVersionSignatureResolver) SignedBy(ctx context.Context, obj *ty
|
||||
if err != nil {
|
||||
var errNotFound *coredata.ErrPeopleNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrFileNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrTaskNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrMeasureNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrFrameworkNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrOrganizationNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errAlreadyExists *coredata.ErrOrganizationAlreadyExists
|
||||
if errors.As(err, &errAlreadyExists) {
|
||||
return nil, graphql.Conflict(errAlreadyExists)
|
||||
return nil, gqlutils.Conflict(errAlreadyExists)
|
||||
}
|
||||
var errTrustCenterAlreadyExists *coredata.ErrTrustCenterAlreadyExists
|
||||
if errors.As(err, &errTrustCenterAlreadyExists) {
|
||||
return nil, graphql.Conflict(errTrustCenterAlreadyExists)
|
||||
return nil, gqlutils.Conflict(errTrustCenterAlreadyExists)
|
||||
}
|
||||
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 {
|
||||
var errAlreadyExists *coredata.ErrPeopleAlreadyExists
|
||||
if errors.As(err, &errAlreadyExists) {
|
||||
return nil, graphql.Conflict(errAlreadyExists)
|
||||
return nil, gqlutils.Conflict(errAlreadyExists)
|
||||
}
|
||||
panic(fmt.Errorf("cannot create people: %w", err))
|
||||
}
|
||||
@@ -1408,7 +1408,7 @@ func (r *mutationResolver) CreateTrustCenterAccess(ctx context.Context, input ty
|
||||
if err != nil {
|
||||
var errAlreadyExists *coredata.ErrTrustCenterAccessAlreadyExists
|
||||
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))
|
||||
}
|
||||
@@ -1632,7 +1632,7 @@ func (r *mutationResolver) InviteUser(ctx context.Context, input types.InviteUse
|
||||
if err != nil {
|
||||
var errAlreadyExists *coredata.ErrPeopleAlreadyExists
|
||||
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)
|
||||
}
|
||||
@@ -1697,7 +1697,7 @@ func (r *mutationResolver) CreatePeople(ctx context.Context, input types.CreateP
|
||||
if err != nil {
|
||||
var errAlreadyExists *coredata.ErrPeopleAlreadyExists
|
||||
if errors.As(err, &errAlreadyExists) {
|
||||
return nil, graphql.Conflict(errAlreadyExists)
|
||||
return nil, gqlutils.Conflict(errAlreadyExists)
|
||||
}
|
||||
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 {
|
||||
var errAlreadyExists *coredata.ErrVendorAlreadyExists
|
||||
if errors.As(err, &errAlreadyExists) {
|
||||
return nil, graphql.Conflict(errAlreadyExists)
|
||||
return nil, gqlutils.Conflict(errAlreadyExists)
|
||||
}
|
||||
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 {
|
||||
var errAlreadyExists *coredata.ErrControlAlreadyExists
|
||||
if errors.As(err, &errAlreadyExists) {
|
||||
return nil, graphql.Conflict(errAlreadyExists)
|
||||
return nil, gqlutils.Conflict(errAlreadyExists)
|
||||
}
|
||||
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 {
|
||||
var errAlreadyExists *coredata.ErrControlAlreadyExists
|
||||
if errors.As(err, &errAlreadyExists) {
|
||||
return nil, graphql.Conflict(errAlreadyExists)
|
||||
return nil, gqlutils.Conflict(errAlreadyExists)
|
||||
}
|
||||
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 {
|
||||
var errAlreadyExists *coredata.ErrMeasureAlreadyExists
|
||||
if errors.As(err, &errAlreadyExists) {
|
||||
return nil, graphql.Conflict(errAlreadyExists)
|
||||
return nil, gqlutils.Conflict(errAlreadyExists)
|
||||
}
|
||||
panic(fmt.Errorf("cannot create measure: %w", err))
|
||||
}
|
||||
@@ -2233,7 +2233,7 @@ func (r *mutationResolver) CreateControlDocumentMapping(ctx context.Context, inp
|
||||
if err != nil {
|
||||
var errMappingExists *coredata.ErrControlDocumentMappingAlreadyExists
|
||||
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))
|
||||
}
|
||||
@@ -2349,7 +2349,7 @@ func (r *mutationResolver) CreateTask(ctx context.Context, input types.CreateTas
|
||||
if err != nil {
|
||||
var errAlreadyExists *coredata.ErrTaskAlreadyExists
|
||||
if errors.As(err, &errAlreadyExists) {
|
||||
return nil, graphql.Conflict(errAlreadyExists)
|
||||
return nil, gqlutils.Conflict(errAlreadyExists)
|
||||
}
|
||||
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 {
|
||||
var errAlreadyExists *coredata.ErrRiskAlreadyExists
|
||||
if errors.As(err, &errAlreadyExists) {
|
||||
return nil, graphql.Conflict(errAlreadyExists)
|
||||
return nil, gqlutils.Conflict(errAlreadyExists)
|
||||
}
|
||||
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 {
|
||||
var errAlreadyExists *coredata.ErrDocumentAlreadyExists
|
||||
if errors.As(err, &errAlreadyExists) {
|
||||
return nil, graphql.Conflict(errAlreadyExists)
|
||||
return nil, gqlutils.Conflict(errAlreadyExists)
|
||||
}
|
||||
panic(fmt.Errorf("cannot create document: %w", err))
|
||||
}
|
||||
@@ -2858,7 +2858,7 @@ func (r *mutationResolver) PublishDocumentVersion(ctx context.Context, input typ
|
||||
if err != nil {
|
||||
var errNoChanges *coredata.ErrDocumentVersionNoChanges
|
||||
if errors.As(err, &errNoChanges) {
|
||||
return nil, graphql.Invalid(errNoChanges)
|
||||
return nil, gqlutils.Invalid(errNoChanges)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrOrganizationNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrAuditNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrPeopleNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrOrganizationNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrPeopleNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrOrganizationNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrOrganizationNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrPeopleNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrVendorNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrFrameworkNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrMeasureNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrTaskNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrDocumentNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrControlNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrRiskNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrAssetNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrAuditNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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) {
|
||||
user := UserFromContext(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{
|
||||
ID: session.ID,
|
||||
ID: viewerID,
|
||||
User: types.NewUser(user),
|
||||
}, nil
|
||||
}
|
||||
@@ -5060,7 +5070,7 @@ func (r *riskResolver) Owner(ctx context.Context, obj *types.Risk) (*types.Peopl
|
||||
if err != nil {
|
||||
var errNotFound *coredata.ErrRiskNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrPeopleNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrRiskNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrOrganizationNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrOrganizationNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrTaskNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrPeopleNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrTaskNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrOrganizationNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrTaskNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrMeasureNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrOrganizationNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
panic(fmt.Errorf("cannot get organization: %w", err))
|
||||
}
|
||||
@@ -5614,7 +5624,7 @@ func (r *trustCenterDocumentAccessResolver) Document(ctx context.Context, obj *t
|
||||
if err != nil {
|
||||
var errNotFound *coredata.ErrDocumentNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrOrganizationNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrVendorNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrOrganizationNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrVendorNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrPeopleNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrVendorNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrPeopleNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
panic(fmt.Errorf("cannot get security owner: %w", err))
|
||||
}
|
||||
@@ -5970,7 +5980,7 @@ func (r *vendorBusinessAssociateAgreementResolver) Vendor(ctx context.Context, o
|
||||
if err != nil {
|
||||
var errNotFound *coredata.ErrVendorNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrVendorNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrFileNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrVendorNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
panic(fmt.Errorf("cannot get vendor: %w", err))
|
||||
}
|
||||
@@ -6089,7 +6099,7 @@ func (r *vendorDataPrivacyAgreementResolver) Vendor(ctx context.Context, obj *ty
|
||||
if err != nil {
|
||||
var errNotFound *coredata.ErrVendorNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrVendorNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
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 {
|
||||
var errNotFound *coredata.ErrVendorNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, graphql.NotFound(errNotFound)
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
panic(fmt.Errorf("cannot get vendor: %w", err))
|
||||
}
|
||||
|
||||
@@ -20,10 +20,10 @@ call_argument_directives_with_null: true
|
||||
models:
|
||||
ID:
|
||||
model:
|
||||
- "github.com/getprobo/probo/pkg/server/graphql/types/gid.GIDScalar"
|
||||
- "go.probo.inc/probo/pkg/server/gqlutils/types/gid.GIDScalar"
|
||||
Datetime:
|
||||
model:
|
||||
- "github.com/99designs/gqlgen/graphql.Time"
|
||||
CursorKey:
|
||||
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/extension"
|
||||
"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/authz"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
@@ -34,12 +36,10 @@ import (
|
||||
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/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/statelesstoken"
|
||||
"go.probo.inc/probo/pkg/trust"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.gearno.de/kit/log"
|
||||
)
|
||||
|
||||
type (
|
||||
|
||||
@@ -45,14 +45,14 @@ type Organization implements Node {
|
||||
}
|
||||
|
||||
enum DocumentType
|
||||
@goModel(model: "github.com/getprobo/probo/pkg/coredata.DocumentType") {
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentType") {
|
||||
OTHER
|
||||
@goEnum(value: "github.com/getprobo/probo/pkg/coredata.DocumentTypeOther")
|
||||
ISMS @goEnum(value: "github.com/getprobo/probo/pkg/coredata.DocumentTypeISMS")
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeOther")
|
||||
ISMS @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeISMS")
|
||||
POLICY
|
||||
@goEnum(value: "github.com/getprobo/probo/pkg/coredata.DocumentTypePolicy")
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypePolicy")
|
||||
PROCEDURE
|
||||
@goEnum(value: "github.com/getprobo/probo/pkg/coredata.DocumentTypeProcedure")
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeProcedure")
|
||||
}
|
||||
|
||||
type Document implements Node {
|
||||
@@ -102,340 +102,340 @@ type AuditEdge {
|
||||
}
|
||||
|
||||
enum CountryCode
|
||||
@goModel(model: "github.com/getprobo/probo/pkg/coredata.CountryCode") {
|
||||
AD @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAD")
|
||||
AE @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAE")
|
||||
AF @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAF")
|
||||
AG @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAG")
|
||||
AI @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAI")
|
||||
AL @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAL")
|
||||
AM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAM")
|
||||
AO @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAO")
|
||||
AQ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAQ")
|
||||
AR @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAR")
|
||||
AS @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAS")
|
||||
AT @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAT")
|
||||
AU @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAU")
|
||||
AW @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAW")
|
||||
AX @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAX")
|
||||
AZ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeAZ")
|
||||
BA @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBA")
|
||||
BB @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBB")
|
||||
BD @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBD")
|
||||
BE @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBE")
|
||||
BF @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBF")
|
||||
BG @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBG")
|
||||
BH @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBH")
|
||||
BI @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBI")
|
||||
BJ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBJ")
|
||||
BL @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBL")
|
||||
BM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBM")
|
||||
BN @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBN")
|
||||
BO @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBO")
|
||||
BQ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBQ")
|
||||
BR @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBR")
|
||||
BS @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBS")
|
||||
BT @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBT")
|
||||
BV @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBV")
|
||||
BW @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBW")
|
||||
BY @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBY")
|
||||
BZ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeBZ")
|
||||
CA @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCA")
|
||||
CC @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCC")
|
||||
CD @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCD")
|
||||
CF @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCF")
|
||||
CG @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCG")
|
||||
CH @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCH")
|
||||
CI @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCI")
|
||||
CK @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCK")
|
||||
CL @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCL")
|
||||
CM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCM")
|
||||
CN @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCN")
|
||||
CO @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCO")
|
||||
CR @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCR")
|
||||
CU @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCU")
|
||||
CV @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCV")
|
||||
CW @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCW")
|
||||
CX @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCX")
|
||||
CY @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCY")
|
||||
CZ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeCZ")
|
||||
DE @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeDE")
|
||||
DJ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeDJ")
|
||||
DK @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeDK")
|
||||
DM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeDM")
|
||||
DO @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeDO")
|
||||
DZ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeDZ")
|
||||
EC @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeEC")
|
||||
EE @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeEE")
|
||||
EG @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeEG")
|
||||
EH @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeEH")
|
||||
ER @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeER")
|
||||
ES @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeES")
|
||||
ET @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeET")
|
||||
EU @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeEU")
|
||||
FI @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeFI")
|
||||
FJ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeFJ")
|
||||
FK @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeFK")
|
||||
FM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeFM")
|
||||
FO @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeFO")
|
||||
FR @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeFR")
|
||||
GA @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGA")
|
||||
GB @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGB")
|
||||
GD @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGD")
|
||||
GE @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGE")
|
||||
GF @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGF")
|
||||
GG @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGG")
|
||||
GH @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGH")
|
||||
GI @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGI")
|
||||
GL @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGL")
|
||||
GM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGM")
|
||||
GN @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGN")
|
||||
GP @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGP")
|
||||
GQ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGQ")
|
||||
GR @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGR")
|
||||
GT @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGT")
|
||||
GU @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGU")
|
||||
GW @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGW")
|
||||
GY @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeGY")
|
||||
HK @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeHK")
|
||||
HM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeHM")
|
||||
HN @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeHN")
|
||||
HR @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeHR")
|
||||
HT @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeHT")
|
||||
HU @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeHU")
|
||||
ID @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeID")
|
||||
IE @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeIE")
|
||||
IL @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeIL")
|
||||
IM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeIM")
|
||||
IN @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeIN")
|
||||
IO @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeIO")
|
||||
IQ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeIQ")
|
||||
IR @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeIR")
|
||||
IS @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeIS")
|
||||
IT @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeIT")
|
||||
JE @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeJE")
|
||||
JM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeJM")
|
||||
JO @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeJO")
|
||||
JP @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeJP")
|
||||
KE @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeKE")
|
||||
KG @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeKG")
|
||||
KH @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeKH")
|
||||
KI @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeKI")
|
||||
KM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeKM")
|
||||
KN @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeKN")
|
||||
KP @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeKP")
|
||||
KR @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeKR")
|
||||
KW @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeKW")
|
||||
KY @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeKY")
|
||||
KZ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeKZ")
|
||||
LA @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeLA")
|
||||
LB @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeLB")
|
||||
LC @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeLC")
|
||||
LI @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeLI")
|
||||
LK @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeLK")
|
||||
LR @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeLR")
|
||||
LS @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeLS")
|
||||
LT @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeLT")
|
||||
LU @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeLU")
|
||||
LV @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeLV")
|
||||
LY @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeLY")
|
||||
MA @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMA")
|
||||
MC @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMC")
|
||||
MD @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMD")
|
||||
ME @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeME")
|
||||
MF @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMF")
|
||||
MG @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMG")
|
||||
MH @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMH")
|
||||
MK @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMK")
|
||||
ML @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeML")
|
||||
MM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMM")
|
||||
MN @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMN")
|
||||
MO @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMO")
|
||||
MP @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMP")
|
||||
MQ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMQ")
|
||||
MR @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMR")
|
||||
MS @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMS")
|
||||
MT @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMT")
|
||||
MU @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMU")
|
||||
MV @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMV")
|
||||
MW @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMW")
|
||||
MX @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMX")
|
||||
MY @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMY")
|
||||
MZ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeMZ")
|
||||
NA @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeNA")
|
||||
NC @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeNC")
|
||||
NE @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeNE")
|
||||
NF @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeNF")
|
||||
NG @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeNG")
|
||||
NI @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeNI")
|
||||
NL @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeNL")
|
||||
NO @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeNO")
|
||||
NP @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeNP")
|
||||
NR @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeNR")
|
||||
NU @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeNU")
|
||||
NZ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeNZ")
|
||||
OM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeOM")
|
||||
PA @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodePA")
|
||||
PE @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodePE")
|
||||
PF @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodePF")
|
||||
PG @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodePG")
|
||||
PH @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodePH")
|
||||
PK @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodePK")
|
||||
PL @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodePL")
|
||||
PM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodePM")
|
||||
PN @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodePN")
|
||||
PR @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodePR")
|
||||
PS @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodePS")
|
||||
PT @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodePT")
|
||||
PW @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodePW")
|
||||
PY @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodePY")
|
||||
QA @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeQA")
|
||||
RE @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeRE")
|
||||
RO @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeRO")
|
||||
RS @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeRS")
|
||||
RU @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeRU")
|
||||
RW @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeRW")
|
||||
SA @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSA")
|
||||
SB @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSB")
|
||||
SC @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSC")
|
||||
SD @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSD")
|
||||
SE @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSE")
|
||||
SG @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSG")
|
||||
SH @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSH")
|
||||
SI @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSI")
|
||||
SJ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSJ")
|
||||
SK @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSK")
|
||||
SL @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSL")
|
||||
SM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSM")
|
||||
SN @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSN")
|
||||
SO @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSO")
|
||||
SR @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSR")
|
||||
SS @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSS")
|
||||
ST @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeST")
|
||||
SV @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSV")
|
||||
SX @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSX")
|
||||
SY @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSY")
|
||||
SZ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeSZ")
|
||||
TC @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTC")
|
||||
TD @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTD")
|
||||
TF @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTF")
|
||||
TG @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTG")
|
||||
TH @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTH")
|
||||
TJ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTJ")
|
||||
TK @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTK")
|
||||
TL @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTL")
|
||||
TM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTM")
|
||||
TN @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTN")
|
||||
TO @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTO")
|
||||
TR @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTR")
|
||||
TT @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTT")
|
||||
TV @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTV")
|
||||
TW @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTW")
|
||||
TZ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeTZ")
|
||||
UA @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeUA")
|
||||
UG @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeUG")
|
||||
UM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeUM")
|
||||
US @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeUS")
|
||||
UY @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeUY")
|
||||
UZ @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeUZ")
|
||||
VA @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeVA")
|
||||
VC @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeVC")
|
||||
VE @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeVE")
|
||||
VG @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeVG")
|
||||
VI @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeVI")
|
||||
VN @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeVN")
|
||||
VU @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeVU")
|
||||
WF @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeWF")
|
||||
WS @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeWS")
|
||||
YE @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeYE")
|
||||
YT @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeYT")
|
||||
ZA @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeZA")
|
||||
ZM @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeZM")
|
||||
ZW @goEnum(value: "github.com/getprobo/probo/pkg/coredata.CountryCodeZW")
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.CountryCode") {
|
||||
AD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAD")
|
||||
AE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAE")
|
||||
AF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAF")
|
||||
AG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAG")
|
||||
AI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAI")
|
||||
AL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAL")
|
||||
AM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAM")
|
||||
AO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAO")
|
||||
AQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAQ")
|
||||
AR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAR")
|
||||
AS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAS")
|
||||
AT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAT")
|
||||
AU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAU")
|
||||
AW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAW")
|
||||
AX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAX")
|
||||
AZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAZ")
|
||||
BA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBA")
|
||||
BB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBB")
|
||||
BD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBD")
|
||||
BE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBE")
|
||||
BF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBF")
|
||||
BG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBG")
|
||||
BH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBH")
|
||||
BI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBI")
|
||||
BJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBJ")
|
||||
BL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBL")
|
||||
BM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBM")
|
||||
BN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBN")
|
||||
BO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBO")
|
||||
BQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBQ")
|
||||
BR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBR")
|
||||
BS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBS")
|
||||
BT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBT")
|
||||
BV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBV")
|
||||
BW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBW")
|
||||
BY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBY")
|
||||
BZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBZ")
|
||||
CA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCA")
|
||||
CC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCC")
|
||||
CD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCD")
|
||||
CF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCF")
|
||||
CG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCG")
|
||||
CH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCH")
|
||||
CI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCI")
|
||||
CK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCK")
|
||||
CL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCL")
|
||||
CM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCM")
|
||||
CN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCN")
|
||||
CO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCO")
|
||||
CR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCR")
|
||||
CU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCU")
|
||||
CV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCV")
|
||||
CW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCW")
|
||||
CX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCX")
|
||||
CY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCY")
|
||||
CZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCZ")
|
||||
DE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDE")
|
||||
DJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDJ")
|
||||
DK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDK")
|
||||
DM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDM")
|
||||
DO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDO")
|
||||
DZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDZ")
|
||||
EC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEC")
|
||||
EE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEE")
|
||||
EG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEG")
|
||||
EH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEH")
|
||||
ER @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeER")
|
||||
ES @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeES")
|
||||
ET @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeET")
|
||||
EU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEU")
|
||||
FI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFI")
|
||||
FJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFJ")
|
||||
FK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFK")
|
||||
FM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFM")
|
||||
FO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFO")
|
||||
FR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFR")
|
||||
GA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGA")
|
||||
GB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGB")
|
||||
GD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGD")
|
||||
GE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGE")
|
||||
GF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGF")
|
||||
GG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGG")
|
||||
GH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGH")
|
||||
GI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGI")
|
||||
GL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGL")
|
||||
GM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGM")
|
||||
GN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGN")
|
||||
GP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGP")
|
||||
GQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGQ")
|
||||
GR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGR")
|
||||
GT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGT")
|
||||
GU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGU")
|
||||
GW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGW")
|
||||
GY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGY")
|
||||
HK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHK")
|
||||
HM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHM")
|
||||
HN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHN")
|
||||
HR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHR")
|
||||
HT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHT")
|
||||
HU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHU")
|
||||
ID @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeID")
|
||||
IE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIE")
|
||||
IL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIL")
|
||||
IM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIM")
|
||||
IN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIN")
|
||||
IO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIO")
|
||||
IQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIQ")
|
||||
IR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIR")
|
||||
IS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIS")
|
||||
IT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIT")
|
||||
JE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJE")
|
||||
JM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJM")
|
||||
JO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJO")
|
||||
JP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJP")
|
||||
KE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKE")
|
||||
KG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKG")
|
||||
KH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKH")
|
||||
KI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKI")
|
||||
KM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKM")
|
||||
KN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKN")
|
||||
KP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKP")
|
||||
KR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKR")
|
||||
KW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKW")
|
||||
KY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKY")
|
||||
KZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKZ")
|
||||
LA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLA")
|
||||
LB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLB")
|
||||
LC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLC")
|
||||
LI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLI")
|
||||
LK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLK")
|
||||
LR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLR")
|
||||
LS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLS")
|
||||
LT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLT")
|
||||
LU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLU")
|
||||
LV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLV")
|
||||
LY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLY")
|
||||
MA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMA")
|
||||
MC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMC")
|
||||
MD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMD")
|
||||
ME @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeME")
|
||||
MF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMF")
|
||||
MG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMG")
|
||||
MH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMH")
|
||||
MK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMK")
|
||||
ML @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeML")
|
||||
MM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMM")
|
||||
MN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMN")
|
||||
MO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMO")
|
||||
MP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMP")
|
||||
MQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMQ")
|
||||
MR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMR")
|
||||
MS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMS")
|
||||
MT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMT")
|
||||
MU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMU")
|
||||
MV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMV")
|
||||
MW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMW")
|
||||
MX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMX")
|
||||
MY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMY")
|
||||
MZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMZ")
|
||||
NA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNA")
|
||||
NC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNC")
|
||||
NE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNE")
|
||||
NF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNF")
|
||||
NG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNG")
|
||||
NI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNI")
|
||||
NL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNL")
|
||||
NO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNO")
|
||||
NP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNP")
|
||||
NR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNR")
|
||||
NU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNU")
|
||||
NZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNZ")
|
||||
OM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeOM")
|
||||
PA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePA")
|
||||
PE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePE")
|
||||
PF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePF")
|
||||
PG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePG")
|
||||
PH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePH")
|
||||
PK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePK")
|
||||
PL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePL")
|
||||
PM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePM")
|
||||
PN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePN")
|
||||
PR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePR")
|
||||
PS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePS")
|
||||
PT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePT")
|
||||
PW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePW")
|
||||
PY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePY")
|
||||
QA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeQA")
|
||||
RE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRE")
|
||||
RO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRO")
|
||||
RS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRS")
|
||||
RU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRU")
|
||||
RW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRW")
|
||||
SA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSA")
|
||||
SB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSB")
|
||||
SC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSC")
|
||||
SD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSD")
|
||||
SE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSE")
|
||||
SG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSG")
|
||||
SH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSH")
|
||||
SI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSI")
|
||||
SJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSJ")
|
||||
SK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSK")
|
||||
SL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSL")
|
||||
SM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSM")
|
||||
SN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSN")
|
||||
SO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSO")
|
||||
SR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSR")
|
||||
SS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSS")
|
||||
ST @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeST")
|
||||
SV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSV")
|
||||
SX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSX")
|
||||
SY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSY")
|
||||
SZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSZ")
|
||||
TC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTC")
|
||||
TD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTD")
|
||||
TF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTF")
|
||||
TG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTG")
|
||||
TH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTH")
|
||||
TJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTJ")
|
||||
TK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTK")
|
||||
TL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTL")
|
||||
TM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTM")
|
||||
TN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTN")
|
||||
TO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTO")
|
||||
TR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTR")
|
||||
TT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTT")
|
||||
TV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTV")
|
||||
TW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTW")
|
||||
TZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTZ")
|
||||
UA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUA")
|
||||
UG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUG")
|
||||
UM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUM")
|
||||
US @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUS")
|
||||
UY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUY")
|
||||
UZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUZ")
|
||||
VA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVA")
|
||||
VC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVC")
|
||||
VE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVE")
|
||||
VG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVG")
|
||||
VI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVI")
|
||||
VN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVN")
|
||||
VU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVU")
|
||||
WF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeWF")
|
||||
WS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeWS")
|
||||
YE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeYE")
|
||||
YT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeYT")
|
||||
ZA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeZA")
|
||||
ZM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeZM")
|
||||
ZW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeZW")
|
||||
}
|
||||
|
||||
enum VendorCategory
|
||||
@goModel(model: "github.com/getprobo/probo/pkg/coredata.VendorCategory") {
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.VendorCategory") {
|
||||
ANALYTICS
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryAnalytics"
|
||||
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryAnalytics"
|
||||
)
|
||||
CLOUD_MONITORING
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryCloudMonitoring"
|
||||
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryCloudMonitoring"
|
||||
)
|
||||
CLOUD_PROVIDER
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryCloudProvider"
|
||||
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryCloudProvider"
|
||||
)
|
||||
COLLABORATION
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryCollaboration"
|
||||
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryCollaboration"
|
||||
)
|
||||
CUSTOMER_SUPPORT
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryCustomerSupport"
|
||||
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryCustomerSupport"
|
||||
)
|
||||
DATA_STORAGE_AND_PROCESSING
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryDataStorageAndProcessing"
|
||||
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryDataStorageAndProcessing"
|
||||
)
|
||||
DOCUMENT_MANAGEMENT
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryDocumentManagement"
|
||||
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryDocumentManagement"
|
||||
)
|
||||
EMPLOYEE_MANAGEMENT
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryEmployeeManagement"
|
||||
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryEmployeeManagement"
|
||||
)
|
||||
ENGINEERING
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryEngineering"
|
||||
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryEngineering"
|
||||
)
|
||||
FINANCE
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryFinance"
|
||||
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryFinance"
|
||||
)
|
||||
IDENTITY_PROVIDER
|
||||
@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
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryMarketing"
|
||||
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryMarketing"
|
||||
)
|
||||
OFFICE_OPERATIONS
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryOfficeOperations"
|
||||
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryOfficeOperations"
|
||||
)
|
||||
OTHER
|
||||
@goEnum(value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryOther")
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryOther")
|
||||
PASSWORD_MANAGEMENT
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryPasswordManagement"
|
||||
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryPasswordManagement"
|
||||
)
|
||||
PRODUCT_AND_DESIGN
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryProductAndDesign"
|
||||
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryProductAndDesign"
|
||||
)
|
||||
PROFESSIONAL_SERVICES
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryProfessionalServices"
|
||||
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryProfessionalServices"
|
||||
)
|
||||
RECRUITING
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryRecruiting"
|
||||
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryRecruiting"
|
||||
)
|
||||
SALES
|
||||
@goEnum(value: "github.com/getprobo/probo/pkg/coredata.VendorCategorySales")
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategorySales")
|
||||
SECURITY
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategorySecurity"
|
||||
value: "go.probo.inc/probo/pkg/coredata.VendorCategorySecurity"
|
||||
)
|
||||
VERSION_CONTROL
|
||||
@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"
|
||||
"time"
|
||||
|
||||
"go.gearno.de/kit/httpserver"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/probo"
|
||||
console_v1 "go.probo.inc/probo/pkg/server/api/console/v1"
|
||||
"go.probo.inc/probo/pkg/server/session"
|
||||
"go.probo.inc/probo/pkg/statelesstoken"
|
||||
"go.probo.inc/probo/pkg/trust"
|
||||
"go.gearno.de/kit/httpserver"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -16,7 +16,7 @@ package types
|
||||
|
||||
import (
|
||||
"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](
|
||||
|
||||
@@ -16,7 +16,7 @@ package types
|
||||
|
||||
import (
|
||||
"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 {
|
||||
|
||||
@@ -10,13 +10,13 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"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/types"
|
||||
"go.probo.inc/probo/pkg/trust"
|
||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||
)
|
||||
|
||||
// Framework is the resolver for the framework field.
|
||||
|
||||
@@ -18,11 +18,11 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.gearno.de/kit/log"
|
||||
authsvc "go.probo.inc/probo/pkg/auth"
|
||||
"go.probo.inc/probo/pkg/authz"
|
||||
"go.probo.inc/probo/pkg/filemanager"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.gearno.de/kit/log"
|
||||
)
|
||||
|
||||
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.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.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))
|
||||
|
||||
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
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package graphql
|
||||
package gqlutils
|
||||
|
||||
import (
|
||||
"maps"
|
||||
@@ -12,18 +12,18 @@
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package graphql
|
||||
package gqlutils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"runtime/debug"
|
||||
|
||||
"go.probo.inc/probo/pkg/auth"
|
||||
"go.probo.inc/probo/pkg/authz"
|
||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||
"go.gearno.de/kit/httpserver"
|
||||
"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 {
|
||||
@@ -12,7 +12,7 @@
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package graphql
|
||||
package gqlutils
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -66,13 +66,15 @@ func (e ErrExpiredToken) Error() string {
|
||||
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) {
|
||||
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]{
|
||||
ExpiresAt: now.Add(expirationTime),
|
||||
IssuedAt: now,
|
||||
ExpiresAt: expiresAt,
|
||||
IssuedAt: issuedAt,
|
||||
Type: tokenType,
|
||||
Data: data,
|
||||
}
|
||||
|
||||
@@ -25,14 +25,14 @@ import (
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/packages/emails"
|
||||
"go.probo.inc/probo/pkg/auth"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/probo"
|
||||
"go.probo.inc/probo/pkg/statelesstoken"
|
||||
"go.gearno.de/kit/log"
|
||||
"go.gearno.de/kit/pg"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
Reference in New Issue
Block a user